Luogu P6478(NOI Online #2 T3游戏)详细题解

这样子的神仙题,我当然是做不出来的。辛亏有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)=kstdp(st,jnotdraw(k,i))

这里的 s t ′ st' st表示在集合 s t st st与{i}的差集, k ∈ s t k∈st kst表示 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 m1 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=imCji fj

为什么呢?因为对于一个 f x f_x fx,如果 y ≤ x y≤x yx,那么 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=im(1)ji gj Cji

所以,只需要求出所有满足 0 ≤ i ≤ m 0≤i≤m 0im 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;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值