AtCoder Regular Contest 124

这场题质量真的高,我愿称之为 ( t Atcoder Regular Counting 124)

E.Pass to Next

题目描述

点此看题

人和人是不能一概而论的,因为 ( t zxy) 不知道想了多久的问题被小减一语道破天机。

(n) 个人排成一个环玩传球游戏,第 (i) 个人初始有的球数是 (a_i),每个人同时把球传给下一个人(第 (n) 个人传给第 (1) 个人),对所有最后可能形成的不同局面,设第 (i) 个人最终的球数是 (b_i),我们想求出:

[sum_{b}prod_{i=1}^nb_i ]

(3leq nleq 10^5,a_ileq 10^9)

解法

首先考虑如何不算重,因为每个人乱传球最后形成的局面可能相同。设 (x_i) 表示第 (i) 个人传出去的球数,那么如果所有 (x) 都为正,我们把所有 (x)(1) 得到的局面相同,那么说明只要保证至少有一个人不传球就可以不算重。

发现这个限制是很宽松的,先不管这个限制,我们来思考 (prod b_i) 这东西怎么算。考虑它的组合意义是在每个局面中,从每个人最终的球中拿走一个的方案数,注意这个计数问题有两个要素:确定局面和选球,我们考虑用 (dp) 来解决这个问题。

球数肯定不能直接塞状态中,那么我们记录什么呢?考虑对于第 (i) 个人所选的球只有两种来源:自己原来就有的,别人传过来的。那么我们用拆分的方法把这两种情况分开来讨论,因为原来有的只和下一个人有关,传过来的只和上一个人有关,那么就达到了简化问题的目的。

(dp[i][0]) 表示第 (i) 个人选原有的球,考虑前 (i-1) 个人选球的方案数,(dp[i][1]) 表示第 (i) 个人选传过来的球,考虑前 (i) 个人选球的方案数,这样设计状态有利于 (dp[i][0])(dp[i+1][1]) 的转移,(i) 原有的球和传给 (i+1) 的球是紧密相关的,所以 (dp[i][0]) 就暂时不能算 (i) 选球的方案数,然后我们考虑状态之间的 (4) 种转移:

  • (s(n)=sum_{i=1}^ni , g(n)=sum_{i=1}^ni^2)

  • (dp[i+1][0]leftarrow dp[i][0]),因为第 (i) 个人的选择还没有算进去:(dp[i+1][0]leftarrow dp[i][0]cdot s(a_i))

  • (dp[i+1][0]leftarrow dp[i][1]),不用考虑决策,直接算传球的方案即可:(dp[i+1][0]leftarrow dp[i][1]cdot(a_i+1))

  • (dp[i+1][1]leftarrow dp[i][0]),考虑第 (i) 个人如果递送 (x) 个,那么第 (i+1) 个人有 (x) 种选择方案,那么方案是 (sum (a_i-x)x)(dp[i+1][1]leftarrow dp[i][0]cdot(a_icdot s(a_i)-g(a_i)))

  • (dp[i+1][1]leftarrow dp[i][1]),考虑递送过去的数量:(dp[i+1][1]leftarrow dp[i][1]cdot s(a_i))

那么我们先算出没有限制的答案,然后减去每个位置都递送球的答案,对于第一二种转移将 (a_i)(1) 计算即可。

考虑有环怎么初始化,我们先让 (dp[1][0]=1),跑环之后得到 (dp[1][0]-1) 就是第一个人选原有球的答案。再让 (dp[1][1]=1),得到 (dp[1][1]-1) 就是第一个人选传过来球的答案,两种情况要分开讨论避免互相转移(第一个人又选原有球又选递送球)

总结

寻找答案式的组合意义可以用于计数。

学会用拆分的方法来解决计数问题,可能的适用情况:情况数有限,拆分之后易于考虑;拆分之后的状态不会互相影响,满足乘法原理;记录某种状态另一状态就没用了,可以拆分来设计状态。

#include <cstdio>
#include <cstring>
const int MOD = 998244353;
const int M = 100005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,i2,i6,a[M],dp[M][2];
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
int get(int C,int D)
{
	memset(dp,0,sizeof dp);
	dp[1][0]=C;dp[1][1]=C^1;
	for(int i=1;i<=n;i++)
	{
		int t=i%n+1,b=a[i]-D;
		int c=b*(b+1)%MOD*i2%MOD,d=0,e=0;
		dp[t][0]=(dp[t][0]+dp[i][0]*c)%MOD;
		dp[t][0]=(dp[t][0]+dp[i][1]*(b+1))%MOD;
		if(D) b++;
		c=b*(b+1)%MOD*i2%MOD;
		d=b*(b+1)%MOD*(2*b+1)%MOD*i6%MOD;
		e=(c*b-d)%MOD;
		dp[t][1]=(dp[t][1]+dp[i][0]*e)%MOD;
		dp[t][1]=(dp[t][1]+dp[i][1]*c)%MOD;
	}
	return (C?dp[1][0]:dp[1][1])-1;
}
signed main()
{
	n=read();
	i2=qkpow(2,MOD-2);
	i6=qkpow(6,MOD-2);
	for(int i=1;i<=n;i++)
		a[i]=read();
	int ans=get(1,0)+get(0,0)-get(1,1)-get(0,1);
	printf("%lld
",(ans%MOD+MOD)%MOD);
}

F.Chance Meeting

题目描述

点此看题

人和人是不能一概而论的,因为 ( t zxy) 不知道想了多久的问题被标杆一语道破天机。

给你一个 (n imes m) 的矩阵,( t zxy)((1,1)),标杆在 ((n,1)),因为校长的要求,( t zxy) 需要通过下移和右移走到 ((n,m)),而标杆需要通过上移和右移走到 ((1,m));但是 ( t zxy) 因为害怕被报告给校长,所以 ( t zxy) 只希望和标杆碰面恰好一次。

设四种移动操作互不相同,如果我们把它写成序列有多少种本质不同的方案?答案对 (998244353) 取模。

(2leq n,mleq 2 imes 10^5)

解法

先搞一个 ( t observation):因为两人总是会走到同一行,所以限制出现在是否能在此时走到同一列,发现行对列没有任何限制,所以我们只需要考虑 ( t zxy) 走到 (n) 再和标杆恰好相遇一次的情况,然后使用乘法原理考虑走到其他行的情况。

为了方便表示我们把 (n,m) 都减 (1),设 (f(x)) 表示 ( t zxy) 往下走 (n) 步之后和标杆在 ((n,x))第一次相遇的方案。我们把整个过程拆分两部分,首先需要从 (2n) 步纵向移动选出 (n) 步,第二部分本来是两人纵向分开到终点的过程,但是可以看成从终点走到汇合的的过程,而且这个反转过后的过程也满足需要第一次在 ((n,x)) 处相遇,所以答案是:

[{2nchoose n}sum_{i=0}^m f(i)cdot f(m-i) ]

现在的问题是计算 (f(x)),设 (g(x)) 表示 ( t zxy) 向下走 (n) 步之后和标杆在 ((n,x)) 相遇的方案,计算时需要插入纵向的移动:

[g(x)={2x+nchoose n}{2xchoose x} ]

(f(x)) 时考虑容斥,也就是枚举 (i) 作为 (x) 前面第一个相遇的位置,那么 ((i,x)) 这一段就不能再相遇了,需要考虑谁在这个过程中先走了一步,那么另一个人就会走最后一步,中间的过程不能越过 (y=x),所以就转化为了卡特兰数 (C(x-i-1))

[f(x)=g(x)-2sum_{i=0}^{x-1} g(i)cdot C(x-i-1) ]

然后用 ( t NTT) 卷一下就做出来了,时间复杂度 (O(nlog n))

总结

首先排除无关变量很重要,如果把行混在一起就很难搞。

然后就是考虑方案是什么样子的,比如这道题有多次相遇的情况,我们就只在第一次相遇的时候统计。

还有一点是设计函数来计数,比如此题觉得第一次相遇这个方案难以表示的时候就设计了函数 (f(x)),再考虑计算 (f(x)) 即可,如果不设计函数直接列式的话就会很麻烦。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1000005;
const int MOD = 998244353;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,fac[M],inv[M],rev[M],g[M],c[M],f[M];
void init(int n)
{
	fac[0]=inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
}
int C(int n,int m)
{
	if(n<m || m<0) return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int cat(int n)
{
	return (C(2*n,n)-C(2*n,n-1)+MOD)%MOD;
}
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
void NTT(int *a,int len,int op)
{
	for(int i=0;i<len;i++)
	{
		rev[i]=(rev[i>>1]>>1)|((i&1)*(len/2));
		if(i<rev[i]) swap(a[i],a[rev[i]]);
	}
	for(int s=2;s<=len;s<<=1)
	{
		int w=(op==1)?qkpow(3,(MOD-1)/s):qkpow(3,(MOD-1)-(MOD-1)/s);
		for(int i=0,t=s/2;i<len;i+=s)
			for(int j=0,x=1;j<t;j++,x=x*w%MOD)
			{
				int fo=a[i+j],fe=a[i+j+t];
				a[i+j]=(fo+x*fe)%MOD;
				a[i+j+t]=((fo-x*fe)%MOD+MOD)%MOD;
			}
	}
	if(op==1) return ;
	int inv=qkpow(len,MOD-2);
	for(int i=0;i<len;i++)
		a[i]=a[i]*inv%MOD;
}
signed main()
{
	n=read();m=read();init(1e6);
	n--;m--;int len=1,ans=0; 
	for(int i=0;i<=m;i++)
		g[i]=f[i]=C(2*i,i)*C(2*i+n,n)%MOD;
	for(int i=0;i<=m;i++)
		c[i]=cat(i);
	while(len<=2*m+2) len<<=1;
	NTT(g,len,1);NTT(c,len,1);
	for(int i=0;i<len;i++) c[i]=g[i]*c[i]%MOD;
	NTT(c,len,-1);
	for(int i=1;i<=m;i++)
		f[i]=((f[i]-2*c[i-1])%MOD+MOD)%MOD;
	for(int i=0;i<=m;i++)
		ans=(ans+f[i]*f[m-i])%MOD;
	printf("%lld
",C(2*n,n)*ans%MOD);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15067652.html