• 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);
    }
    
  • 相关阅读:
    JavaScript数组迭代方法
    Ant Design Mobile RN中Toast不起作用的原因【坑篇】
    解决vsCode终端不能运行yarn脚本
    k8s——Service和Ingress
    Prometheus学习
    k8s——pod控制器
    k8s——管理pod资源对象
    k8s——资源管理基础
    docker学习
    k8s学习——Helm入门及使用
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15067652.html
Copyright © 2020-2023  润新知