博弈策略
首先考虑一条链的情况。
如果链长是奇数,而开始鼠在中点,则无论往哪里跳,对手都可以跳到对称点,因为距离限制,只会越来越靠外,最后到达边界先手就输了。否则只要第一手跳到中点即可。
如果链长是偶数,则策略与奇数相同,只是没有中点了,无论开始在哪里,只要跳到中间的两个点,先手就赢了。
树上方法是一样的,只要在直径上跳就可以了,不跳直径只会增加长度限制,所以每次都跳回到直径上就可以了。
因此先手必败的条件就是\(1\)号在长度为奇数的直径中点上,可以转化为赢的条件就是\(1\)子树中最深的深度只出现一次。
DP与优化
记\(x\)子树,最大深度\(\leq i\),方案数为\(f(x, i)\)
\(v\)是\(x\)的孩子,\(len(v)\)表示\(v\)的最大深度
\[ f(x, i)= \begin{cases} f'(x,i) \cdot (1+f(v, i-1)) & i-1
\(+1\)就是不选这棵子树。然后与深度有关,用长链剖分优化。
注意方程中更新时要更新到\(len(x)-1\),但实际上第二种情况就是一个后缀乘法,因此可以打上标记。
同时对于每个点,新增时会增加一个\(dep=0\)的情况,此时要把数组整体\(+1\),也打标记(因为\(f\)是个前缀和,方便转移),这样可以保证复杂度\(\mathcal O(n)\)
如果不加限制,这样的两种操作差分标记是维护不了的,但这题因为是DP,每次都是按顺序连续进行的,可以在DP更新当前位置前把标记推到下一个位置上,这样就可以保证所有标记都被按时推走了。
统计答案
算出所有最深子树出现\(1\)次的方案相加就可以了。
这里做法是枚举最大深度,然后维护一个std::list
\(active\)表示深度达到当前深度的元素,先做出前后缀积,然后每次枚举一个元素,它的深度必须是\(dep\),其他的深度小于\(dep\),乘起来。最后把\(len
Code
#include
#include
#include
#include
#include
using namespace std;
#define File(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
typedef long long ll;
const int inv2 = 499122177;
namespace io {
const int SIZE = (1 << 21) + 1;
char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
char getc () {return gc();}
inline void flush () {fwrite (obuf, 1, oS - obuf, stdout); oS = obuf;}
inline void putc (char x) {*oS ++ = x; if (oS == oT) flush ();}
template inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
template inline void print (I x) {if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while (x) qu[++ qr] = x % 10 + '0', x /= 10;while (qr) putc (qu[qr --]);}
struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io :: gi; using io :: putc; using io :: print; using io :: getc;
template void upmax(T &x, T y){x = x>y ? x : y;}
template void upmin(T &x, T y){x = x=p ? x+y-p : x+y;}
inline int sub(int x, int y){return x-y<0 ? x-y+p : x-y;}
inline int mul(int x, int y){return 1LL * x * y % p;}
inline int power(int x, int y){
int res = 1;
for(; y; y>>=1, x = mul(x, x)) if(y & 1) res = mul(res, x);
return res;
}
int hd[N], to[E], nt[E], ec = 0;
inline void link(int x, int y){
++ec;
to[ec] = y; nt[ec] = hd[x]; hd[x] = ec;
}
int son[N], len[N];
struct Dp{
int val, tmul, tadd;
Dp():val(0), tmul(1), tadd(0){}
};
Dp tp[N], *pt = tp, *f[N];
void build(int x, int fa){
for(int i=hd[x]; i; i=nt[i]){
if(to[i] == fa) continue;
build(to[i], x);
if(len[to[i]] > len[son[x]]) son[x] = to[i];
}
len[x] = len[son[x]] + 1;
}
void push(int x, int i){
f[x][i].val = mul(f[x][i].val, f[x][i].tmul);
f[x][i].val = add(f[x][i].val, f[x][i].tadd);
if(i + 1 < len[x]){
f[x][i+1].tmul = mul(f[x][i+1].tmul, f[x][i].tmul);
f[x][i+1].tadd = add(mul(f[x][i+1].tadd, f[x][i].tmul), f[x][i].tadd);
}
f[x][i].tadd = 0;
f[x][i].tmul = 1;
}
void dfs(int x, int fa){
f[x][0].val = 1;
if(!son[x]) return ;
f[son[x]] = f[x] + 1;
dfs(son[x], x);
f[x][1].tadd = add(f[x][1].tadd, 1);
for(int i=hd[x]; i; i=nt[i]){
if(to[i] == fa || to[i] == son[x]) continue;
int v = to[i];
f[v] = pt; pt += len[v];
dfs(v, x);
push(v, 0);
for(int i=1; i active;
int inactive = 1;
for(int i=hd[1]; i; i=nt[i]){
int v = to[i];
f[v] = pt; pt += len[v];
dfs(v, 1);
++cnt0;
for(int i=0; i 1) active.push_back(v);
else inactive = mul(inactive, f[v][len[v] - 1].val);
}
static int pre[N], suf[N];
for(int i=1; i::iterator it = active.begin(); p < sz; p++, it++)
pre[p] = mul(pre[p - 1], f[*it][i-1].val);
p = sz;
suf[sz + 1] = 1;
for(list::reverse_iterator it = active.rbegin(); p >= 1; p--, it++){
suf[p] = mul(suf[p + 1], f[*it][i-1].val);
res = add(res, mul(mul(sub(f[*it][i].val, f[*it][i-1].val), mul(suf[p+1], pre[p-1])), inactive));
}
for(list::iterator it = active.begin(); it != active.end(); ){
list::iterator last = it; ++it;
if(len[*last] - 1 == i){
inactive = mul(inactive, f[*last][i].val);
active.erase(last);
}
}
}
printf("%d\n", add(res, cnt0));
return 0;
}