Skip to content

泉州一中信息学Blog

信息学奥赛技术分享博客

  • 登录
  • 小学oj
  • 中学oj
  • 测试页面1
  • Toggle search form

GDNOJ – DAY 3

Posted on 2025年7月28日2025年7月28日 By 张, 高畅 GDNOJ – DAY 3无评论

前言

题单整理好了,算是留下遗产,给自己将来用了(

反正今天就是一堆学过但是忘了的算法与数据结构复健。

正文

最小斯坦纳树

简介

就是一个在 n 个点的图里面保证让其中给定的 k 个点联通的神秘算法。

具体做法也很简单,定义 f_{u, S} 表示以 u 为根,目前 k 个点的覆盖情况是 S 的最小代价。

转移有:

$f{u,S} = \min{T \subset S}f{u, T}+f{u,S – T}(这个是显然的,就是以u$ 为根把两个集合并起来)

还有:

$f{u,S} = \min f{v,S} + w_{v,u}$(这个其实就是最短路的 “松弛” 操作,大部分 dijkstra / spfa 二选一,少部分用 floyd 提前处理)

然后就没了。

题目

[P4294]

有意思,但主要难点集中在对方案的查询,还有对板子一个递推式的修改。

由于这道题从边权变成了点权,所以合并的时候上述的第一个式子大概长成这样:

$f{u,S} = \min{T \subset S}f{u, T}+f{u,S – T} – w_u, 其中w_u表示u点点权,很好理解,就是合并时候不要合出两个u$ 点点权。

至于方案记录,考虑无数记录最短路方案里的做法,即记录前驱节点 pre,用 pre 不断递归往前。但这里注意有可能从一个点分出两条路,类似二叉树结构,详情见代码。

#include
using namespace std;
typedef pair PII;
const int N = 101, K = 11, INF = 0x3f3f3f3f;
const int dx[4] = {0, -1, 0, 1};
const int dy[4] = {-1, 0, 1, 0};
int n, m, p, k, root;
int w[N], f[N][1 << K], st[N], res[N];
PII pre[N][1 << K];
priority_queue<PII, vector, greater >q;
inline int get(int x, int y){return x * m + y;}
void dijkstra(int S)
{
    memset(st, 0, sizeof st);
    while (q.size())
    {
        PII u = q.top();
        q.pop();
        int ver = u.second;
        if (st[ver])continue;
        st[ver] = 1;
        int x = ver / m, y = ver % m;
        for (int i = 0; i < 4; i ++)
        {
            int nx = x + dx[i], ny = y + dy[i];
            if (nx = n || ny = m)continue;
            int j = get(nx, ny);
            if (f[j][S] > f[ver][S] + w[j])
            {
                f[j][S] = f[ver][S] + w[j];
                pre[j][S] = {ver, S};
                q.push({f[j][S], j});
            }
        }
    }
}
void dfs(int u, int S)
{
    if (!pre[u][S].second)return;
    res[u] = 1;
    dfs(pre[u][S].first, pre[u][S].second);
    if(pre[u][S].first == u)dfs(pre[u][S].first, S ^ pre[u][S].second);
}
int main()
{
    scanf("%d%d", &n, &m);
    p = n * m;
    for (int i = 0; i < p; i ++)
    {
        for (int j = 0; j < (1 << K); j ++)f[i][j] = INF;
    }
    for (int i = 0; i < n; i ++)
    {
        for (int j = 0; j < m; j ++)
        {
            int id = get(i, j);
            scanf("%d", &w[id]);
            if (!w[id])f[root = id][(1 << (k ++))] = 0;
        }
    }
    for (int S = 1; S < (1 << k); S ++)
    {
        for (int i = 0; i 

f[i][T] + f[i][S ^ T] - w[i]) { f[i][S] = f[i][T] + f[i][S ^ T] - w[i]; pre[i][S] = {i, T}; } } if(f[i][S] != INF)q.push({f[i][S], i}); } dijkstra(S); } printf("%d\n", f[root][(1 << k) - 1] == INF ? 0 : f[root][(1 << k) - 1]); dfs(root, (1 << k) - 1); for (int i = 0; i < n; i ++) { for (int j = 0; j < m; j ++) { int id = get(i, j); if (!w[id])putchar('x'); else if(res[id])putchar('o'); else putchar('_'); } puts(""); } return 0; }

[ABC395G]

当你有了一个模板的时候,你大多数时候并不会对模板做根本性的改进,对于很多算法都是这个道理。

可惜,这题需要改进。

我们考虑我们最初设了个什么东西。

以 u 为根的覆盖情况。

也就是在如果这个题只有新加一个点,原来的做法是天然可行的。

如何处理两个点呢?

再加一个维度。

其实就相当于把板子复制一遍并在外面套上一层循环。(bushi

注意把控数组大小。

#include
using namespace std;
typedef long long LL;
const int N = 85, K = 10;
const LL INF = 1e14;
int n, k;
LL f[N][N][1 << K];
LL G[N][N];
int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i ++)
    {
        for (int j = 1; j <= n; j ++)
        {
            scanf("%lld", &G[i][j]);
            for (int S = 0; S < (1 << (k + 1)); S ++)f[i][j][S] = INF;
        }
    }
    for (int k = 1; k <= n; k ++)
    {
        for (int i = 1; i <= n; i ++)
        {
            for (int j = 1; j <= n; j ++)G[i][j] = min(G[i][j], G[i][k] + G[k][j]);
        }
    }
    for (int i = 1; i <= n; i ++)
    {
        for (int j = 0; j < k; j ++)f[i][j + 1][1 << j] = 0;
        f[i][i][1 << k] = 0;
    }
    for (int S = 1; S < (1 << (k + 1)); S ++)
    {
        for (int i = 1; i <= n; i ++)
        {
            for (int j = 1; j <= n; j ++)
            {
                for (int T = S & (S - 1); T; T = S & (T - 1))f[i][j][S] = min(f[i][j][S], f[i][j][T] + f[i][j][S ^ T]);
            }
            for (int j = 1; j <= n; j ++)
            {
                for (int k = 1; k <= n; k ++)f[i][j][S] = min(f[i][j][S], f[i][k][S] + G[k][j]);
            }
        }
    }
    int q;
    scanf("%d", &q);
    while(q --)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        printf("%lld\n", f[x][y][(1 << (k + 1)) - 1]);
    }
    return 0;
}

树链剖分

简介

具体地,其实本质上只是一种处理树形结构的方法,即把一棵树断成若干条链,变成线性结构,这样才好使用数据结构处理。

做法不想写了,这玩意比最小斯坦纳树常见多了。(bushi

题目

[LOJ6669]

What an amazing interactive problem!

首先看到题的第一眼没有任何证据指向剖分。

好吧其实关系和剖分不是很大,只是借鉴了重儿子这一特性。

首先把每一个点都和 1 进行询问,得到每一个点的深度 d_u

得到之后枚举层数 dep 和当前层的点 u。

①在以下操作开始前,先 DFS 一遍本树已有的部分,得到每个点沿重儿子往下走的最深点。

② 令 v 最开始为 1,设 v 沿重儿子往下走的最深点 w,询问 (u, w),得到的结果称为 dist

显然地,LCA 有一个特性:$d_u + dw – 2 \times d{lca(u, w)} = dist$,

根据本式得到 d_{lca(u, w)} 。

显然地,lca(u,w) 一定在 v \rightarrow w 的这一条重链上,

让 v 移动到 lca(u, w) 后,由于本树是二叉树,选择另一条链(即轻链)往下走一步,重复 ②,直到 d_u – d_v = 1。

然后做完了,跑了 10s。

#include 
using namespace std;
const int N = 3005;
int n, d[N], son[N][2], Size[N], lds[N], par[N];
vector P[N];
void add(int u, int v)
{
    par[v] = u;
    if(son[u][0])son[u][1] = v;
    else son[u][0] = v;
}
void dfs(int u)
{
    Size[u] = 1;
    lds[u] = u;
    if (son[u][0])
    {
        dfs(son[u][0]);
        Size[u] += Size[son[u][0]];
    }
    if(son[u][1])
    {
        dfs(son[u][1]);
        Size[u] += Size[son[u][1]];
        if(Size[son[u][1]] > Size[son[u][0]])swap(son[u][0], son[u][1]);
    }
    if (son[u][0])lds[u] = lds[son[u][0]];
}
int ASK(int a, int b)
{
    int x;
    printf("? %d %d\n", a, b);
    fflush(stdout);
    scanf("%d", &x);
    return x;
}
void work(int u)
{
    int v = 1;
    while (d[u] - d[v] > 1)
    {
        int dist = ASK(u, lds[v]);
        // d[u] + d[lds[v]] - 2 * d[lca(u, lds[v])] = dist
        // => d[lca(u, lds[v])] = (d[u] + d[lds[v]] - dist) / 2
        int gd = (d[u] + d[lds[v]] - dist) / 2;
        while (d[u] - d[v] > 1 && d[v] < gd)v = son[v][0];
        if (d[u] - d[v] == 1)break;
        v = son[v][1];
    }
    add(v, u);
}
int main()
{
    scanf("%d", &n);
    for (int i = 2; i <= n; i ++)P[d[i] = ASK(1, i)].push_back(i);
    for (int dep = 1; dep <= n; dep ++)
    {
        dfs(1);
        for (int u : P[dep])work(u);
    }
    printf("! ");
    for (int i = 2; i <= n; i ++)printf("%d ", par[i]);
    puts("");
    fflush(stdout);
    return 0;
}

原本应该还有CDQ/整体二分和带花树的题目,但是没法,就不写了。

复健了一些,自我感觉好多了,狠狠弥补了昨天只获得了 3pts 的现实(逃

训练日志

文章导航

Previous Post: 中山纪念中学 Day 3
Next Post: 2025暑假中山集训Day3——7.28

发表回复 取消回复

要发表评论,您必须先登录。

2025年 12月
一 二 三 四 五 六 日
1234567
891011121314
15161718192021
22232425262728
293031  
« 8月    

2024常州 Class Classic OI Problems Contest cqr的长乐集训2023 CZYZ LOC New Game NOI NOIP Password Protected PM_PK Preview Problems Retrospect Selfmade Qusetion STL The end Training Uneasy Problem 蒟蒻 通报

  • 训练日志
  • 链表
  • 入门
  • 模拟
  • dfs序
  • 并查集
  • spfa
  • 最小割
  • 矩阵树定理
  • 仙人掌
  • BSGS
  • 凸包
  • 回文自动机
  • 递推与动归
  • 堆
  • 莫队算法
  • ST表
  • Treap
  • 树套树
  • 可持久化线段树
  • 初赛
  • 搜索
  • 贪心
  • 深度优先搜索
  • 欧拉图
  • dijkstra
  • 费用流
  • 哈夫曼树
  • kruskual
  • 置换
  • 旋转卡壳
  • KMP
  • 区间动归
  • STL
  • 链表
  • 可并堆
  • sply
  • 主席树
  • 可持久化字典树
  • 算法
  • 动态规划
  • 构造
  • 广度优先搜索
  • 最短路
  • floyd
  • 最大流
  • 虚树
  • prim
  • 筛法
  • 半平面交
  • 字典树
  • 背包动归
  • 基础数据结构
  • 分块
  • 线段树
  • 替罪羊树
  • K-DTree
  • 图论
  • 二分法
  • 迭代搜索
  • 拓扑排序
  • 有上下界网络流
  • 生成树
  • 快速幂
  • 后缀数组
  • 树形动归
  • 哈希表
  • 中级数据结构
  • 平衡树
  • 可持久化数据结构
  • 数据结构
  • 三分法
  • 启发式搜索
  • 图的连通
  • 点分治
  • 博弈论
  • AC自动机
  • 状压动归
  • 单调栈
  • 树状数组
  • 高级数据结构
  • OI资料
  • 数学
  • 高精度
  • 差分约束
  • 树上倍增
  • 素数测试
  • 后缀自动机
  • 数位动归
  • 单调队列
  • 新闻
  • 几何
  • 随机化
  • 二分图染色
  • 树链剖分
  • 欧拉函数
  • manacher
  • 斜率优化
  • 离线处理
  • 信息学奥赛学长风采
  • 字符串
  • 二分图匹配
  • prufer编码
  • 卡特兰数
  • 密码学
  • 决策单调
  • 赛后总结
  • 其他
  • 2-SAT
  • 最近公共祖先
  • 矩阵乘法
  • 记忆化搜索
  • 网络流
  • Link cut tree
  • 排列组合
  • 树
  • 高斯消元
  • 乘法逆元
  • 容斥原理
  • 调和级数
  • 概率与期望
  • 模线性方程组
  • 莫比乌斯反演
  • 快速傅里叶变换
  • 扩展欧几里德
  • 最大公约数与最小公倍数

近期文章

  • 中山纪念中学 Day21
  • 中山集训8.15 LAST DAY+集训小结
  • GDNOJ – DAY 18
  • 中山8.14
  • 2025暑假中山集训Day20——8.14

近期评论

归档

  • 2025年8月
  • 2025年7月
  • 2025年2月
  • 2025年1月
  • 2024年11月
  • 2024年10月
  • 2024年9月
  • 2024年8月
  • 2024年7月
  • 2024年3月
  • 2024年2月
  • 2024年1月
  • 2023年12月
  • 2023年11月
  • 2023年10月
  • 2023年9月
  • 2023年8月
  • 2023年7月
  • 2023年3月
  • 2023年2月
  • 2023年1月
  • 2022年12月

Copyright © 2025 泉州一中信息学Blog.

Powered by PressBook WordPress theme