这样子的神仙题,我当然是做不出来的。辛亏有dy巨佬在,他帮我彻底理解了本题,这是他写的题解。
为了把自己的思路再理一遍,我决定再写这一篇详细的题解。
Task 1: 状压 d p dp dp, 20-40pts
令 m = n 2 m=\frac n 2 m=2n。
我们可以首先确定下小 A A A选的节点的顺序,由于小 A A A选的节点的顺序已确定,所以此时两种方案不同当且仅当小 B B B选的节点的顺序不同。
考虑状压
d
p
dp
dp。
d
p
s
t
,
i
dp_{st,i}
dpst,i表示目前已选的
1
1
1类节点(即被小
B
B
B拥有了节点)的集合为
s
t
st
st,且此时的非平局数量为
i
i
i。状态转移:
d
p
(
s
t
,
i
)
=
∑
k
∈
s
t
d
p
(
s
t
′
,
j
−
n
o
t
d
r
a
w
(
k
,
i
)
)
dp(st,i)=\sum_{k∈st} dp(st',j-notdraw(k,i))
dp(st,i)=k∈st∑dp(st′,j−notdraw(k,i))
这里的 s t ′ st' st′表示在集合 s t st st与{i}的差集, k ∈ s t k∈st k∈st表示 s t st st包含了 k k k, n o t d r a w ( k , i ) notdraw(k,i) notdraw(k,i)为真当且仅当 k k k是 i i i的祖先或 i i i是 k k k的祖先。
时间复杂度为 O ( 2 m × m 2 ) O(2^m×m^2) O(2m×m2),空间复杂度为 O ( 2 m × m ) O(2^m×m) O(2m×m),能拿到 20 20 20的保底分;另外的 20 20 20分取决于常数的大小。例如,枚举属于集合 s t st st的 k k k可以采用 l o w b i t lowbit lowbit来优化;或者更牛逼一点,直接使用FMT来搞。
但是,由于本题的数据并不水,无论如何优化也绝对不可能达到 40 40 40分。
Task 2: 链, 20 20 20 pts
对于链上的任意两个节点,由于一个重要的性质: 它们中的一个一定是另一个的祖先。
所以,每一轮都不可能是平局;我们只需要输出 m − 1 m-1 m−1个 0 0 0与一个 m ! m! m!即可。
Task 3: 迈向正解之路的转化
考虑做一个转化。
假设 g i g_i gi表示钦定选了 i i i对有祖先关系的方案数, f i f_i fi表示恰好存在 i i i对有祖先关系的答案。可以发现, g i = ∑ j = i m C j i f j g_i=\sum_{j=i}^m C_{j}^i\ f_j gi=j=i∑mCji fj
为什么呢?因为对于一个 f x f_x fx,如果 y ≤ x y≤x y≤x,那么 g y g_y gy就算了 f x C x y f_x\ C_{x}^y fx Cxy次,所以得到了上面那个式子。
通过二项式反演,可以得到 f i = ∑ j = i m ( − 1 ) j − i g j C j i f_i=\sum_{j=i}^m (-1)^{j-i}\ g_j\ C_{j}^i fi=j=i∑m(−1)j−i gj Cji
所以,只需要求出所有满足 0 ≤ i ≤ m 0≤i≤m 0≤i≤m的 g i g_i gi即可求出每个答案。如何求出这个值呢?我们考虑使用树上背包。
d p i , j dp_{i,j} dpi,j表示在以 i i i为根的子树中,钦定了 j j j对有祖先关系的对(即在这个子树内至少有 j j j对满足要求的)的方案数。每次我们将 i i i与其子树中的一个节点配对,将这个子树的各种状态与 i i i的状态合并即可。
更详细地说,
①对于合并,将
d
p
i
,
j
dp_{i,j}
dpi,j与
d
p
s
o
n
,
k
dp_{son,k}
dpson,k(
s
o
n
son
son为
i
i
i的儿子节点之一)合并成
d
p
i
,
j
+
k
dp_{i,j+k}
dpi,j+k,系数为
1
1
1;
②如果我们想要让
i
i
i号节点参与这个配对,那么
(1)如果
a
i
=
0
a_i=0
ai=0,那么一定是
0
0
0与
1
1
1配对;即将
d
p
i
,
j
dp_{i,j}
dpi,j与
d
p
s
o
n
,
k
dp_{son,k}
dpson,k合并成
d
p
i
,
j
+
k
+
1
dp_{i,j+k+1}
dpi,j+k+1,注意这里系数是以
s
o
n
son
son为根的子树中点权为
1
1
1的点数。
(2)
a
i
=
1
a_i=1
ai=1同理。
注意,我们每次合并的大小是看到这个孩子之前的 s i z e size size 与这个孩子节点的 s i z e size size,单次合并的复杂度是这两个值的乘积。
这个复杂度乍一看是 O ( n 3 ) O(n^3) O(n3)的,实际上是 O ( n 2 ) O(n^2) O(n2)的;因为,任何两个节点当且仅当会在它们的 L C A LCA LCA处算一次贡献。
总时间复杂度 O ( n 2 ) O(n^2) O(n2)。
Code
#include <bits/stdc++.h>
#define int long long
#define getmod hdgkfajia
using namespace std;
const int mod=998244353,maxl=5005;
int getmod(int p){return (p%mod+mod)%mod;}
int pro(int x,int y){return (x*y)%mod;}
void Add(int &x,int y){x=getmod(x+y);}
void sub(int &x,int y){x=getmod(x-y);}
int n,m,cnt=0;
int dp[maxl][maxl],cur[maxl][maxl][2],tmp[maxl][2],f[maxl][2];
int head[maxl],a[maxl],g[maxl],siz[maxl],jc[maxl],inv[maxl];
struct edge{int nxt,to;}e[2*maxl];
void add_edge(int u,int v){cnt++,e[cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
int quick_power(int x,int y)
{
int res=1;
for (;y;y=y>>1,x=pro(x,x))
{
if (y&1) res=pro(res,x);
}
return res;
}
void init()
{
jc[0]=1;
for (int i=1;i<=n;i++) jc[i]=(jc[i-1]*i)%mod;
inv[n]=quick_power(jc[n],mod-2);
for (int i=n-1;i>=1;i--) inv[i]=(inv[i+1]*(i+1))%mod;
}
int C(int x,int y)
{
if (x<0||x==0||y>x) return 0;
if (x==0||x==y) return 1;
else return pro(pro(jc[x],inv[y]),inv[x-y]);
}
void dfs(int now,int fath)
{
cur[now][0][0]=1;
siz[now]=1;
if (a[now]==0) f[now][0]=1,f[now][1]=0;
else f[now][0]=0,f[now][1]=1;
for (int i=head[now];i;i=e[i].nxt)
{
int y=e[i].to;
if (y==fath) continue;
dfs(y,now);
int su=min(f[now][0],f[now][1]),sv=min(f[y][0],f[y][1]);
int max_p=(siz[now]+siz[y])/2;
for (int k=0;k<=max_p;k++) tmp[k][0]=tmp[k][1]=0;
for (int j=0;j<=su;j++)
{
for (int k=0;k<=sv;k++)
{
Add(tmp[j+k][0],pro(cur[now][j][0],dp[y][k]));
Add(tmp[j+k][1],pro(cur[now][j][1],dp[y][k]));
int free1=f[y][1]-k,free0=f[y][0]-k;
if (a[now]==0)//与cnt[y][1]配对
Add(tmp[j+k+1][1],pro(pro(cur[now][j][0],dp[y][k]),free1));
else
Add(tmp[j+k+1][1],pro(pro(cur[now][j][0],dp[y][k]),free0));
}
}
for (int j=0;j<=max_p;j++)
cur[now][j][0]=tmp[j][0],cur[now][j][1]=tmp[j][1];
f[now][0]+=f[y][0],f[now][1]+=f[y][1];
siz[now]+=siz[y];
}
for (int j=0;j<=(siz[now])/2;j++)
dp[now][j]=(cur[now][j][0]+cur[now][j][1])%mod;
}
signed main()
{
cin>>n;m=n/2;
init();
for (int i=1;i<=n;i++)
{
char x;cin>>x;
a[i]=x-'0';
}
for (int i=1;i<n;i++)
{
int tmpx,tmpy;
cin>>tmpx>>tmpy;
add_edge(tmpx,tmpy),add_edge(tmpy,tmpx);
}
dfs(1,0);
for (int i=0;i<=m;i++) g[i]=(dp[1][i]*jc[m-i])%mod;
for ( int i=0;i<=m;i++)
{
int ans=0;
for (int j=i;j<=m;j++)
{
if ((j-i)%2==1) sub(ans,pro(g[j],C(j,i)));
else Add(ans,pro(g[j],C(j,i)));
}
printf("%d\n",ans);
}
return 0;
}

被折叠的 条评论
为什么被折叠?



