【GDOI2020模拟01.16】树上的鼠 (博弈+长链剖分优化dp)

https://gmoj.net/senior/#contest/show/2989/2

思考什么时候先手会赢。

一开始双方都不会希望走到直径的端点上,因为那样对方就可以走直径而使自己输掉。

删掉直径的端点,考虑剩下的树的子问题。

如果又走到端点去了,对面就走到另外一个端点,那我就走到下一层的直径端点去了。

所以大家一直都不想走到直径端点。

一直删,如果最后只剩1一个点,说明先手必败,否则先手必胜。

如果是一条链,就是链的两边的长度不等先手就必胜。

如果是一棵树,考虑随便找一条直径,每次删去它的两个端点。

1.这条直径不经过1,1会在中间被删掉,先手必胜;

2.这条直径经过1,则只有1是直径的中点时先手必败。

然后变成了一个dp:
\(f[i][j]\)表示\(i\)为根的子树里,选了含根联通块,到\(i\)的距离最大的距离是\(j\) 的方案数。

设\(md[x]\)表示\(x\)子树里的点到\(x\)的最大距离。

显然\(0<=j<=md[x]\)。

对树长链剖分,每次把一个短的子树合并过来。

假设长链的长度是p,合并来的链的长度是q,当前点是x。

对\(f[x][0-q+1]\)暴力求个前缀和来转移。

\(f[x][>q+1]\)是整体乘上一个数,再用一个数组来打lazytag。

最后合并时还需要一些细节,不过比较简单,前后缀扫一下即可。

pty教的实现\(f\)时用指针会很方便,见代码。

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

const int mo = 998244353;

const int N = 1e6 + 5;

int n, x, y;
int fi[N], nt[N * 2], to[N * 2], tot;

void link(int x, int y) {
    nt[++ tot] = fi[x], to[tot] = y, fi[x] = tot;
}

int t[N], t0;

void Init() {
    scanf("%d", &n);
    fo(i, 1, n - 1) {
        scanf("%d %d", &x, &y);
        link(x, y); link(y, x);
    }
}

int md[N], fa[N], son[N];

void bfs() {
    t[t0 = 1] = 1;
    for(int i = 1; i <= t0; i ++) {
        int x = t[i];
        for(int j = fi[x]; j; j = nt[j]) if(to[j] != fa[x]) {
            fa[to[j]] = x;
            t[++ t0] = to[j];
        }
    }
    fd(i, t0, 1) {
        int x = t[i];
        for(int j = fi[x]; j; j = nt[j]) if(to[j] != fa[x]) {
            int y = to[j];
            md[x] = max(md[x], md[y] + 1);
            if(md[y] > md[son[x]]) son[x] = y;
        }
    }
}

ll fv[N * 2], *f[N], gv[N * 2], *g[N];

int d[N], d0, us;

void build() {
    fo(x, 1, n) if(son[fa[x]] != x) {
        d[d0 = 1] = x;
        for(int i = 1; i <= d0; i ++)
            if(son[d[i]]) d[++ d0] = son[d[i]];
        fo(i, 1, d0) f[d[i]] = fv + (us + i), g[d[i]] = gv + (us + i);
        us += d0 + 1;
    }
}

void xc(int x, int d) {
    fo(i, 0, d) {
        g[x][i + 1] = g[x][i + 1] * g[x][i] % mo;
        f[x][i] = f[x][i] * g[x][i] % mo;
        g[x][i] = 1;
    }
}

ll sa[N], sb[N];

void dp() {
    fd(i2, t0, 2) {
        int x = t[i2];
        f[x][0] = 1;
        for(int ii = fi[x]; ii; ii = nt[ii]) if(to[ii] != fa[x] && to[ii] != son[x]) {
            int y = to[ii];
            xc(x, md[y] + 1); xc(y, md[y]);
            sa[0] = sb[0] = 1;
            fo(i, 1, md[y] + 1) {
                sa[i] = (sa[i - 1] + f[x][i]) % mo;
                sb[i] = (sb[i - 1] + f[y][i - 1]) % mo;
            }
            f[x][0] = 1;
            fo(i, 1, md[y] + 1) f[x][i] = (sa[i] * sb[i] - sa[i - 1] * sb[i - 1] % mo + mo) % mo;
            g[x][md[y] + 2] = g[x][md[y] + 2] * sb[md[y] + 1] % mo;
        }
    }
}

ll p[N], q[N];
int ky[N];

int main() {
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    Init();
    md[0] = -1;
    bfs();
    build();
    fo(i, 0, 2 * n) gv[i] = 1;
    dp();
    ll ans = 0;
    d0 = 0;
    for(int i = fi[1]; i; i = nt[i]) {
        int y = to[i];
        d[++ d0] = y;
        xc(y, md[y]);
    }
    ll s1 = 1;
    fo(w, 0, md[1] - 1) {
        p[0] = 1; q[d0 + 1] = 1;
        fo(i, 1, d0) {
            int x = d[i];
            p[i] = p[i - 1] * (w == 0 ? 1 : f[x][w - 1]) % mo;
        }
        fd(i, d0, 1) {
            int x = d[i];
            q[i] = q[i + 1] * (w == 0 ? 1 : f[x][w - 1]) % mo;
            ans = (ans + f[x][w] * p[i - 1] % mo * q[i + 1] % mo * s1) % mo;

            f[x][w] = ((w ? f[x][w - 1] : 1) + f[x][w]) % mo;
            ky[i] = md[x] > w;
        }
        int D = d0; d0 = 0;
        fo(i, 1, D) if(ky[i])
            d[++ d0] = d[i]; else s1 = s1 * f[d[i]][md[d[i]]] % mo;
    }
    ans = (ans % mo + mo) % mo;
    pp("%lld\n", ans);
}

原文地址:https://www.cnblogs.com/coldchair/p/12203297.html

时间: 2024-11-06 20:49:09

【GDOI2020模拟01.16】树上的鼠 (博弈+长链剖分优化dp)的相关文章

6438. 【GDOI2020模拟01.16】树上的鼠

题目 由于时间过于久远,而且题面本身也很清晰,所以就懒得另外叙述题目大意了(还有思考历程). 正解 先考虑一条链的情况(长度为奇数,这里的长度是指点的数量): 如果根在中点,先手无论移到哪里,后手都可以移到它的对称点去. 此时先手必败: 如果根不在中点,先手只要一开始移到中点,先手就赢了. 若长度为偶数,就将中间的两个点都看成中点. 先手第一步先移到离根比较远的那个中点上,以后就用一样的策略,每次到达对方的对称点.所以偶数时先手必胜. 然后这就可以推广到一棵树的情况. 可以发现先手必败的情况当且

【GDOI2020模拟01.16】划愤(nim积+行列式)

https://gmoj.net/senior/#contest/show/2989/1 先考虑n=2时怎么做,打表找规律找了半天找不出来. 赛后才知道这是nim积. 定义\(x?y\)为\(sg(x,y)\). 有一坨性质: \(x,y<2^{2^k},x?y<2^{2^k}\) \(2^{2^k}?2^{2^k}={3 \over 2}2^{2^k}\) 可以把?看做乘法,\(⊕\)(异或)看做加法,所以还有分配律. 求\(x?y(x>y)\),设\(k\)为最大的\(k\)满足\(

csp-s模拟测试50(9.22)「施工(单调栈优化DP)」&#183;「蔬菜(二维莫队???)」&#183;「联盟(树上直径)」

改了两天,终于将T1,T3毒瘤题改完了... T1 施工(单调栈优化DP) 考场上只想到了n*hmaxn*hmaxn的DP,用线段树优化一下变成n*hmaxn*log但显然不是正解 正解是很**的单调栈 可以想象到最优情况一定是将两端高于中间的一段平原填成一段平的坑,不然如果坑内存在高度差那么我们即使只将一部分抬升也肯定没有用处,并且如果中间的坑已经高于了两端,再向上升也肯定不优,然后就中间的坑可以很很小,也可以很长,对于这个模型我们首先想到n^2*h的DP 设当前表示的f[i]表示当前到了i节

6441. 【GDOI2020模拟01.17】小 ω 维护序列

Description Input Output 输出到标准输出流中. 若干行,对于每个操作 1 和操作 5,输出一个数表示答案. Sample Input Sample Input1 5 8 1 2 3 2 1 1 1 3 5 1 5 2 2 4 1 2 4 3 3 4 0 5 1 1 2 1 1 5 Sample Input2 10 15 5 4 3 5 4 1 5 4 3 1 2 8 580347 4 6 503576 1 2 5 5 8 11 1 2 6 4 7 565239 3 6 3

6442. 【GDOI2020模拟01.18】钩子

题目描述 Description Input Output Sample Input Sample Input1 3 1000000007 Sample Input2 4 1000000007 Sample Output Sample Output1 0 1 0 500000004 0 500000004 500000004 0 500000004 Sample Output2 0 500000004 500000004 0 333333336 166666668 166666668 33333

6447. 【GDOI2020模拟01.19】sort

题目 正解 这题一看也是不能写的题目-- 用个平衡树来维护. 平衡树维护的是一个\(Trie\)的序列.每个\(Trie\)表示这段区间中所有数都会排序一遍. 进行各种操作时,首先会将至多两个节点的\(Trie\)分裂.分裂\(Trie\)会新增\(O(\lg n)\)个节点. 然后将整段区间旋到一棵子树内,然后打标记.平衡树和\(Trie\)上都要打标记. 排序是时候将若干个\(Trie\)合并成一个. 由于这些\(Trie\)是带标记的,所以要将标记下传.\(Trie\)树上标记下传时,如果

【GDOI2020模拟01.17】小 ω 玩游戏 (容斥+EGF)

小 ω 正在玩一个游戏. 小 ω 有一个 n 行 m 列的网格,初始每个方格中都有数字 0.她需要执行 q 次操作,每次操作可以选择其中一个方格 (x, y),然后先将第 x 行的数全部 +1,接着将第 y 列的数全部 +1. 小 ω 想知道有多少种执行操作的方式能使最后的网格中有不超过 k 个奇数. 两种方式不同当且仅当存在某一步中选择的方格坐标不同. \(1<=n,m<=2e5,q<=10^{18}\) 考虑行列分开,对行,算出\(f(x)\)表示恰好x行奇数的方案数,对列同理,算出

6439. 【GDOI2020模拟01.17】小 ω 数排列

题目描述 Description Input Output Sample Input Sample Input1 4 10 3 6 2 9 Sample Input2 8 35 3 7 1 5 10 2 11 6 Sample Output Sample Output1 6 [样例 1 解释] 共有 6 个排列符合条件,它们是 (1, 3, 2, 4),(2, 4, 1, 3),(3, 1, 2, 4),(3, 1, 4, 2),(4, 2, 1, 3),(4, 2, 3, 1). Sample

6445. 【GDOI2020模拟01.19】String

题目 正解 一听到正解是NTT,我心态崩了. 我特么知道是NTT都不知道该怎么做!哪里像个卷积了? 好吧,是我孤陋寡闻-- 设两个字符串分别为\(A\)和\(B\) 可以考虑试着计算出\(A\)每个子串和\(B\)的相似度(就是位置相同.字母相同的个数),直接统计就完事了. 看到字符集这么小,就可以对于每个字母分开考虑. 假如\(A_i=B_j\),那么以\(A_{i-j+1}\)开头的子串就有\(1\)的贡献. 这样一来,看起来就很像是个卷积了. 搞完之后将贡献加起来,统计即可. 总结 还是没