CWOI 计数专题

非常好题目,爱来自坤皇。

A

原:CF498E. Stairs and Lines

考虑状压,\(f_{i,s}\) 表示前 \(i\) 列,最后一列状态为 \(s\) 的方案数,其中 0/1 表示选/不选。可以枚举下一列的填法来转移。用矩阵是 \(\mathcal{O}(7(2^7)^3\log w)\) 的。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7; 
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct mat{
	int a[130][130];
	mat(){memset(a,0,sizeof(a));};
	void init(){for(int i=0;i<(1ll<<7);i++)a[i][i]=1;}
	mat operator *(const mat &b)const{
		mat c;
		for(int i=0;i<(1ll<<7);i++){
			for(int k=0;k<(1ll<<7);k++){
				if(a[i][k]==0)continue;
				for(int j=0;j<(1ll<<7);j++){
					c.a[i][j]=(c.a[i][j]+a[i][k]*b.a[k][j]%mod)%mod;
				}
			}
		}
		return c;
	}
};
mat qpow(mat b,int p){
	mat res;res.init();
	for(;p;p>>=1,b=b*b)if(p&1ll)res=res*b;
	return res;
}
mat init(int n){
	mat res;
	for(int i=0;i<(1ll<<n);i++){
		for(int j=0;j<(1ll<<n);j++){
			for(int k=0;k<(1ll<<(n-1));k++){
				if((i|j|k|(k<<1))==(1ll<<n)-1)res.a[i][j]++;
			}
		}
	}
	return res;
}
int w[10];
signed main(){
	for(int i=1;i<=7;i++)w[i]=read();
	mat res;res.init();
	for(int i=1;i<=7;i++)if(w[i])res=res*qpow(init(i),w[i]);
	printf("%lld\n",res.a[0][0]);
	return 0;
}

B

套路的求前缀和,然后单调栈求每个点右边第一个小于它的位置 \(p_i\),即每个点作为左端点左侧点的右端点的合法范围。

对于前缀和相同的位置,我们一起拿出来看。从左往右扫过去,显然 \(p\) 不降,于是可以找到每个点能和哪些点组成区间。随便用查分维护一下即可。时间复杂度 \(\mathcal{O}(n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7; 
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int a[1000005],p[1000005],q[1000005],c[1000005];char s[1000005];vector<int>v[2000005];
void solve(){
	scanf("%s",s+1);int n=strlen(s+1),top=0; 
	for(int i=1;i<=n;i++)a[i]=a[i-1]+((s[i]=='(')?1:-1);
	a[n+1]=-inf,q[++top]=n+1;
	for(int i=n;i>=0;i--){
		while(top&&a[q[top]]>=a[i])top--;
		p[i]=q[top],q[++top]=i;
	}
	for(int i=0;i<=n;i++)v[a[i]+n].push_back(i);
	for(int i=0;i<=n+n;i++){
		if((int)v[i].size()<2){v[i].clear();continue;}
		int siz=(int)v[i].size();vector<int>d1,d2;d1.resize(siz+5),d2.resize(siz+5);
		for(int j=0,k=0;j<siz;j++){
			while(k<siz&&v[i][k]<=p[v[i][j]]-1)k++;
			if(j+1!=k)d1[k-1]+=k,d1[j]-=k,d2[k-1]-=1,d2[j]+=1;
		}
		for(int j=siz-2;j>=0;j--)d1[j]+=d1[j+1],d2[j]+=d2[j+1];
		for(int j=siz-1;j>=1;j--)c[v[i][j]]+=d1[j]+d2[j]*j,c[v[i][j-1]]-=d1[j]+d2[j]*j;
		v[i].clear();
	}
	for(int i=n-1;i>=1;i--)c[i]+=c[i+1];
	int ans=0;
	for(int i=1;i<=n;i++)ans+=c[i]*i%mod;
	memset(a,0,sizeof(a));
	memset(p,0,sizeof(p));
	memset(q,0,sizeof(q));
	memset(c,0,sizeof(c));
	printf("%lld\n",ans);
}
signed main(){
	int T=read();
	while(T--)solve(); 
	return 0;
}

C

考虑状压,\(\mathcal{O}(2^5n)\),可以压成矩阵,\(\mathcal{O}((2^5)^3\log n)\)。可以进一步优化,但没必要。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct mat{
	int a[33][33];
	mat(){memset(a,0,sizeof(a));};
	mat operator *(const mat &b)const{
		mat c;
		for(int i=0;i<32;i++){
			for(int k=0;k<32;k++){
				if(a[i][k]==0)continue;
				for(int j=0;j<32;j++){
					c.a[i][j]=(c.a[i][j]+a[i][k]*b.a[k][j]%mod)%mod;
				}
			}
		}
		return c;
	}
};
mat qpow(mat b,int p){
	mat res=b;p--;
	for(;p;p>>=1,b=b*b)if(p&1ll)res=res*b;
	return res;
} 
signed main(){
	int n=read();mat f;
	for(int j=0;j<32;j++)for(int k=0;k<5;k++){
		if(!(((j>>1)>>k)&1ll))f.a[j][(j>>1)|(1ll<<k)]=1;
	}
	mat tmp;tmp.a[0][7]=1;tmp=tmp*qpow(f,n);
	printf("%lld\n",tmp.a[0][7]);
	return 0;
}

D

读错题了()

首先需要注意的一点是,被你选的矩形围住的格子不一定全都要用上,所以显然是选最大的矩形更优。

枚举两个矩形的左上角坐标,算围住了多少个 RGB,求二维后缀最大值算出在选 \(r\) 个 R,\(g\) 个 G 拼链时,最多可以用多少个 B。假如各选了 \(r,g,b\) 个,那么在考虑方向性的情况下可以拼出 \(\dfrac{(r+g+b)!}{r!\times g!\times b!}\) 条链。

去掉方向性,就是减去回文链除以 2 再加上回文链。当 \(r,g,b\) 全为偶数时,回文链数量是 \(\dfrac{(\frac{r}{2}+\frac{g}{2}+\frac{b}{2})!}{(\frac{r}{2})!\times(\frac{g}{2})!\times(\frac{b}{2})!}\),因为安排了左边右边是对应的。同理,当 \(r,g,b\) 中只有一个奇数时,不妨设 \(b\) 为奇数,此时回文链数量是 \(\dfrac{(\frac{r}{2}+\frac{g}{2}+\frac{b-1}{2})!}{(\frac{r}{2})!\times(\frac{g}{2})!\times(\frac{b-1}{2})!}\)

在进行计算时,我们可以先枚举 \(r+g\),预处理出 \(b\) 在任意取值时上式的分子除最右边的分母的值,然后再枚举 \(r,g\) 进行计算。时间复杂度 \(\mathcal{O}(n^2m^2)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+9,i2=(mod+1)/2;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int r[85][85],g[85][85],b[85][85],c[6405][6405],mx[6405][6405];char s[85][85];
int askr(int x1,int y1,int x2,int y2){
	return r[x2][y2]-r[x1-1][y2]-r[x2][y1-1]+r[x1-1][y1-1];
}
int askg(int x1,int y1,int x2,int y2){
	return g[x2][y2]-g[x1-1][y2]-g[x2][y1-1]+g[x1-1][y1-1];
}
int askb(int x1,int y1,int x2,int y2){
	return b[x2][y2]-b[x1-1][y2]-b[x2][y1-1]+b[x1-1][y1-1];
}
int andr(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4){
	if((x2<x3||x4<x1)||(y2<y3||y4<y1))return askr(x1,y1,x2,y2)+askr(x3,y3,x4,y4);
	return askr(x1,y1,x2,y2)+askr(x3,y3,x4,y4)-askr(max(x1,x3),max(y1,y3),min(x2,x4),min(y2,y4));
}
int andg(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4){
	if((x2<x3||x4<x1)||(y2<y3||y4<y1))return askg(x1,y1,x2,y2)+askg(x3,y3,x4,y4);
	return askg(x1,y1,x2,y2)+askg(x3,y3,x4,y4)-askg(max(x1,x3),max(y1,y3),min(x2,x4),min(y2,y4));
}
int andb(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4){
	if((x2<x3||x4<x1)||(y2<y3||y4<y1))return askb(x1,y1,x2,y2)+askb(x3,y3,x4,y4);
	return askb(x1,y1,x2,y2)+askb(x3,y3,x4,y4)-askb(max(x1,x3),max(y1,y3),min(x2,x4),min(y2,y4));
}
int jc[6405],iv[6405],ij[6405],s1[6405],s2[6405],s3[6405]; 
signed main(){
	int n=read(),m=read(),k=read();
	jc[0]=1;for(int i=1;i<=n*m;i++)jc[i]=1ll*jc[i-1]*i%mod;
	iv[1]=1;for(int i=2;i<=n*m;i++)iv[i]=mod-1ll*(mod/i)*iv[mod%i]%mod;
	ij[0]=1;for(int i=1;i<=n*m;i++)ij[i]=1ll*ij[i-1]*iv[i]%mod;
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		r[i][j]=r[i-1][j]+r[i][j-1]-r[i-1][j-1]+(s[i][j]=='R');
		g[i][j]=g[i-1][j]+g[i][j-1]-g[i-1][j-1]+(s[i][j]=='G');
		b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+(s[i][j]=='B');
	}
	for(int i=0;i<=n*m+1;i++)for(int j=0;j<=n*m+1;j++)c[i][j]=mx[i][j]=-1;
	for(int x=1;x<=n;x++)for(int y=1;y<=m;y++){
		for(int z=1;z<=n;z++)for(int w=1;w<=m;w++){
			int cr=andr(x,y,min(x+k-1,n),min(y+k-1,m),z,w,min(z+k-1,n),min(w+k-1,m));
			int cg=andg(x,y,min(x+k-1,n),min(y+k-1,m),z,w,min(z+k-1,n),min(w+k-1,m));
			int cb=andb(x,y,min(x+k-1,n),min(y+k-1,m),z,w,min(z+k-1,n),min(w+k-1,m));
			c[cr][cg]=max(c[cr][cg],cb);
		}
	}
	for(int i=n*m;i>=0;i--)for(int j=n*m;j>=0;j--){
		mx[i][j]=max({mx[i][j+1],mx[i+1][j],mx[i+1][j+1],c[i][j]});
	}
	int ans=0,tmp=0;
	for(int rg=0;rg<=n*m;rg++){
		s1[0]=(rg?1ll*jc[rg+0]*ij[0]%mod:0ll);
		for(int b=1;b<=n*m-rg;b++)s1[b]=(s1[b-1]+1ll*jc[rg+b]*ij[b]%mod)%mod;
		s2[0]=(rg?1ll*jc[rg/2+0/2]*ij[0/2]%mod:0ll);
		for(int b=1;b<=n*m-rg;b++)s2[b]=(s2[b-1]+1ll*(1ll-(b&1ll))*jc[rg/2+b/2]*ij[b/2]%mod)%mod;
		s3[0]=(rg?1ll*jc[rg/2+0/2]*ij[0/2]%mod:0ll);
		for(int b=1;b<=n*m-rg;b++)s3[b]=(s3[b-1]+1ll*jc[rg/2+b/2]*ij[b/2]%mod)%mod;
		for(int r=0,g=rg;r<=rg;r++,g--){
			int o=mx[r][g];if(o==-1)continue;
			ans=(ans+1ll*ij[r]*ij[g]%mod*s1[o]%mod)%mod;
			if((r&1ll)&&(g&1ll))continue;
			if((r&1ll)||(g&1ll))tmp=(tmp+1ll*ij[r/2]*ij[g/2]%mod*s2[o]%mod)%mod;
			else tmp=(tmp+1ll*ij[r/2]*ij[g/2]%mod*s3[o]%mod)%mod;
		}
	}
	printf("%lld\n",(1ll*(ans-tmp+mod)%mod*i2%mod+tmp)%mod);
	return 0;
}

E

原:ARC124E - Pass to Next

又又又是 zjk 讲过的题。sto zjk orz

\(x_i\) 是第 \(i\) 个人给第 \(i\bmod n+1\) 个人的球数,首先如果 \(\forall i\in[1,n],x_i\ge 1\),那么我们可以让所有人都少给一个球,得到的结果是一样的。所以我们只用统计 \(\min\{x_i\}=0\) 的操作序列对应的答案就行了,也就是用 \(\min\{x_i\}\ge 0\) 的减去 \(\min\{x_i\}\ge 1\) 的,容易发现这是不重不漏的。

现在需要对 \(\prod b_i\) 进行求和,考虑其组合意义:每个人在自己最终有的球中选一个的方案数。每个人的球可以根据来源分为两个部分:原有的球和上一个人给的球。定义 \(f_{i,0}\) 表示考虑前 \(i\) 个人,第 \(i\) 个人选自己的球,前 \(i-1\) 个人的方案数;\(f_{i,1}\) 表示考虑前 \(i\) 个人,第 \(i\) 个人选上一个人给自己的球,前 \(i\) 个人的方案数。这样定义是为了方便考虑第 \(i\) 个人向下一个人传球的情况。以 \(\min\{x_i\}\ge 0\) 时为例,令 \(s_k(n)=\sum\limits_{i=1}^ni^k\),有转移式

\[f_{i\bmod n+1,0}=s_1(a_i)f_{i,0}+(a_i+1)f_{i,1} \]

\[f_{i\bmod n+1,1}=(a_is_1(a_i)-s_2(a_i))f_{i,0}+s_1(a_i)f_{i,1} \]

解释一下系数:第一个式子前半部分是 \(\sum\limits_{c\ge 0}(a_i-c)=s_1(a_i)\),后半部分是 \(\sum\limits_{c\ge 0}1=(a_i+1)\);第二个式子前半部分是 \(\sum\limits_{c>0}c(a_i-c)=a_is_1(a_i)-s_2(a_i)\),后半部分是 \(\sum\limits_{c>0}c=s_1(a_i)\),因为下一个人一定要拿到 \(i\) 的球都有所以 \(c>0\)

于是断环成链,枚举第一个人的选择,对应的吧 \(f_{1,0/1}\) 分别赋成 1 跑 dp,答案就是初始位置 dp 值减 1(因为我们初始化的时候赋为 1 了)。时间复杂度 \(\mathcal{O}(n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=998244353,i2=499122177,i6=166374059;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int s(int ty,int n){
	if(ty==0)return (n+1)%mod;
	else if(ty==1)return n*(n+1)%mod*i2%mod;
	else return n*(n+1)%mod*(2*n+1)%mod*i6%mod;
}
int n,a[100005],f[2][100005];
inline void add(int &x,int y){
	x+=y;if(x>=mod)x-=mod;
}
int calc(int o1,int o2){
	for(int i=1;i<=n;i++)f[0][i]=f[1][i]=0;
	f[o1][1]=1;
	for(int i=1;i<=n;i++){
		add(f[0][i%n+1],f[0][i]*s(1,a[i]-o2)%mod);
		add(f[0][i%n+1],f[1][i]*s(0,a[i]-o2)%mod);
		add(f[1][i%n+1],f[0][i]*(a[i]*s(1,a[i])%mod-s(2,a[i])+mod)%mod%mod);
		add(f[1][i%n+1],f[1][i]*s(1,a[i])%mod);		
	}
	return (f[o1][1]-1+mod)%mod;
}
signed main(){
	n=read();for(int i=1;i<=n;i++)a[i]=read();
	printf("%lld\n",((calc(0,0)+calc(1,0))%mod-(calc(0,1)+calc(1,1))%mod+mod)%mod);
	return 0;
}

F

原:CF722E. Research Rover。算是很经典。

定义 \(f_{i,j}\) 表示从 \((1,1)\) 出发到 \((x_i,y_i)\) 恰好经过了 \(j\) 个障碍物的方案数。这个东西并不好求,考虑转化,定义 \(g_{i,j}\) 表示从 \((1,1)\) 出发到 \((x_i,y_i)\) 至少经过了 \(j\) 个障碍物的方案数,容易发现 \(f_{i,j}=g_{i,j}-g_{i,j+1}\)。对于 \(g\),我们可以单步容斥得到递推式

\[g_{i,j}=\sum\limits_{x_k\le x_i\land y_k\le y_i}\dbinom{(x_i-x_k)+(y_i-y_k)}{x_i-x_k}f_{k,j-1} \]

同时在经过不多的障碍物后 \(s\) 会变成 1,故 \(j\) 的取值范围为 \(\mathcal{O}(\log s)\) 级别。总复杂度 \(\mathcal{O}(k^2\log s)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int jc[200005],iv[200005],ij[200005]; 
int C(int n,int m){
	if(n<0||m<0||n-m<0)return 0;
	return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
struct Node{
	int x,y;
}p[2005];
int cmp(Node x,Node y){
	if(x.x^y.x)return x.x<y.x;
	return x.y<y.y;
}
int f[2005][25],g[2005][25];
int qpow(int b,int p){
	int res=1;
	for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
	return res;
} 
signed main(){
	int n=read(),m=read(),k=read(),s0=read(),flag=0;
	jc[0]=1;for(int i=1;i<=n+m;i++)jc[i]=jc[i-1]*i%mod;
	iv[1]=1;for(int i=2;i<=n+m;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
	ij[0]=1;for(int i=1;i<=n+m;i++)ij[i]=ij[i-1]*iv[i]%mod;
	for(int i=1;i<=k;i++){
		p[i].x=read(),p[i].y=read();
		if(p[i].x==n&&p[i].y==m)flag=1;
	}
	if(!flag)p[++k]=(Node){n,m},s0*=2;
	sort(p+1,p+k+1,cmp);int lim=0;
	for(int j=1,s=s0;s!=1;j++)lim=j,s=(s+1)/2;
	lim=min(lim+1,k)+1;
	for(int i=1;i<=k;i++){
		g[i][1]=C(p[i].x+p[i].y-2,p[i].x-1);
		for(int j=2;j<=lim;j++){
			for(int t=1;t<=i-1;t++){
				if(p[t].x<=p[i].x&&p[t].y<=p[i].y){
					g[i][j]=(g[i][j]+f[t][j-1]*C(p[i].x-p[t].x+p[i].y-p[t].y,p[i].x-p[t].x)%mod)%mod;
				}
			}
		}
		for(int j=1;j<=lim;j++)f[i][j]=(g[i][j]-g[i][j+1]+mod)%mod;
	}
	int ans=0;
	for(int i=0;i<=lim;i++)ans=(ans+f[k][i]*s0%mod)%mod,s0=(s0+1)/2;
	printf("%lld\n",ans*qpow(C(n+m-2,n-1),mod-2)%mod);
	return 0;
}

G

思考有没有办法能把每一个点上一个编号使得距离与编号相关。容易发现以下对应关系:有一个 01 串,还有一个人初始在根。从前往后看 01 串的每一位,如果当前位是 0 人就不动,否则向这个点新加的叶子走,最后到达的点就是 01 串对应的点。显然这是一一对应的,且两个点的距离是刨去它们的 LCP 后剩下的 1 之和(其实这个对应关系跟这道题相同)。

问题转化为有多少对长为 \(n\) 的 01 串距离为 \(d\)。枚举去掉 LCP 后的长度 \(i\),答案就是 \(\sum\limits_{i=1}^n2^{n-i}\dbinom{2i-2}{d-1}\)。需要减一是因为刨去 LCP 后第一位必须不同。令 \(n\gets n-1\)\(d\gets d-1\)\(i\gets i-1\)\(f(d)=\sum\limits_{i=1}^n2^{n-i}\dbinom{2i-2}{d-1}\),开始推式子:

\[\begin{aligned} f(d)&=\sum\limits_{i=0}^n2^{n-i}\dbinom{2i}{d}\\ &=\sum\limits_{i=0}^n2^{n-i}\left(\dbinom{2i-1}{d}+\dbinom{2i-1}{d-1}\right)\\ &=\sum\limits_{i=0}^n2^{n-i}\left(\dbinom{2i-2}{d}+2\dbinom{2i-2}{d-1}+\dbinom{2i-2}{d-2}\right)\\ &=\sum\limits_{i=1}^n2^{n-i}\dbinom{2i-2}{d}+2\sum\limits_{i=1}^n2^{n-i}\dbinom{2i-2}{d-1}+\sum\limits_{i=1}^n2^{n-i}\dbinom{2i-2}{d-2}\\ &=\frac{1}{2}\sum\limits_{i=0}^{n-1}2^{n-i}\dbinom{2i}{d}+\sum\limits_{i=0}^{n-1}2^{n-i}\dbinom{2i}{d-1}+\frac{1}{2}\sum\limits_{i=0}^{n-1}2^{n-i}\dbinom{2i}{d-2}\\ &=\frac{1}{2}\left(f(d)-\binom{2n}{d}\right)+\left(f(d-1)-\binom{2n}{d-1}\right)+\frac{1}{2}\left(f(d-2)-\binom{2n}{d-2}\right)\\ \end{aligned} \]

解释一下为什么会这样推:往上推两次是因为这样才能化为相似的形式,第四行到第五行 \(i\gets i-1\) 了。

再化简一下,可以得到递推式:\(f(d)=f(d-2)+2f(d-1)-\dbinom{2n}{d-2}-2\dbinom{2n}{d-1}-\dbinom{2n}{d}\)。可以推出 \(f(0)=2^{n+1}-1\)\(f(1)=2^{n+2}-2(n+2)\),预处理一下 \(\dbinom{2n}{i}\) 就可以递推了。时间复杂度 \(\mathcal{O}(\log n+d)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int qpow(int b,int p,int mod){
	int res=1;
	for(;p;p>>=1,b=1ll*b*b%mod)if(p&1ll)res=1ll*res*b%mod;
	return res;
}
int iv[10000005],C[10000005],f[10000005];
signed main(){
	int n=read()-1,d=read()-1,mod=read();
	iv[1]=1;for(int i=2;i<=d;i++)iv[i]=mod-1ll*(mod/i)*iv[mod%i]%mod;
	C[0]=1;for(int i=0;i<d;i++)C[i+1]=1ll*C[i]*(2*n-i)%mod*iv[i+1]%mod;
	f[0]=(qpow(2,n+1,mod)-1+mod)%mod,f[1]=(qpow(2,n+2,mod)-2ll*(n+2)%mod+mod)%mod;
	for(int i=2;i<=d;i++)f[i]=((f[i-2]+2ll*f[i-1]%mod)%mod-((C[i-2]+2ll*C[i-1])%mod+C[i])%mod+mod)%mod;
	printf("%d\n",f[d]);
	return 0;
}

I

原:P4528 [CTSC2008] 图腾

神仙题。

我们使用 1234 的排列来表示子序列每个位置的相对大小关系,x 表示可以填任何数。比如 1234 表示 \(t_1<t_2<t_3<t_4\),1x3x 表示 \(t_1<t_2<t_3<t_4\)\(t_1<t_4<t_3<t_2\)

直接计算题目中的偏序关系是困难的,不妨对原式进行一定的变形:

\[\begin{aligned} ans&=1324-1432-1243\\ &=(1324+1423)-(1432+1423)-(1243+1234)+1234\\ &=1x2x-14xx-12xx+1234\\ &=1x2x-(1xxx-12xx-13xx)-12xx+1234\\ &=1x2x-1xxx+13xx+1234 \end{aligned} \]

下面依次进行计算。记 \(l_i\) 表示 \(p_i\) 左边有多少个比它小的,\(r_i\) 表示 \(p_i\) 右边有多少个比它大的。

  • 1x2x:枚举 2 的位置 \(i\),右边有一个比它大的,剩下的要满足 \(j<k<i\)\(p_j<p_i<p_k\)。如果只考虑 \(p_j<p_i\)\(j<i,k<i\),情况有 \(l_i(i-1)\) 种。不合法的情况有 \(j<k\)\(p_k<p_i\)\(j\ge k\)。前者相当于在 \(l_i\) 个数中选两个,后者对 \(p_k\) 没有限制,\(k\) 可以取遍 \([1,j]\),相当于求 \(\sum\limits_{j<i\land p_j<p_i}j\),树状数组维护;

  • 1xxx:枚举 1 的位置 \(i\),相当于在 \(r_i\) 个数中选三个;

  • 13xx:枚举 3 的位置 \(i\),右边有一个比它大的,剩下的要满足 \(j<i<k\)\(p_j<p_k<p_i\)。如果只考虑 \(j<i<k\)
    \(p_j<p_i,p_k<p_i\),情况有 \(l_i(p_i-1-l_i)\) 种。不合法的情况是 \(j<i<k\)\(p_j>p_k\)。如果认为所有满足 \(j<i,j<k\)\(p_j>p_k\)\(\sum\limits_{j<i\land p_j<p_i}(n-j-r_j)\) 种情况都不合法,会多减 \(j<k<i\)\(p_j>p_k\)\(\dbinom{l_i}{2}-\sum\limits_{j<i\land p_j<p_i}l_j\) 种情况,树状数组维护;

  • 1234:求长度为 4 的上升子序列,树状数组维护。

时间复杂度 \(\mathcal{O}(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=(1ll<<24);
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int C2(int n){
	return (n*(n-1)/2)%mod;
}
int C3(int n){
	return (n*(n-1)*(n-2)/6)%mod;
}
int n,a[200005];
struct BIT{
	int c[200005];
	void clear(){
		for(int i=1;i<=n;i++)c[i]=0;
	}
	void add(int x,int y){
		for(;x<=n;x+=x&-x)c[x]=(c[x]+y)%mod;
	}
	int ask(int x){
		int res=0;
		for(;x;x-=x&-x)res=(res+c[x])%mod;
		return res;
	}
}Tr;
int ans,f[5][200005],L[200005],R[200005];
signed main(){
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	Tr.clear();for(int i=1;i<=n;i++)L[i]=Tr.ask(a[i]),Tr.add(a[i],1);
	Tr.clear();for(int i=n;i>=1;i--)R[i]=(n-i)-Tr.ask(a[i]),Tr.add(a[i],1);
	//1234
	for(int i=1;i<=n;i++)f[1][i]=1;
	for(int j=2;j<=4;j++){Tr.clear();for(int i=1;i<=n;i++)f[j][i]=Tr.ask(a[i]),Tr.add(a[i],f[j-1][i]);}
	for(int i=1;i<=n;i++)ans=(ans+f[4][i])%mod;
	//1xxx
	for(int i=1;i<=n;i++)ans=(ans-C3(R[i])+mod)%mod;
	//1x2x
	Tr.clear();
	for(int i=1;i<=n;i++)ans=(ans+((L[i]*(i-1)%mod-C2(L[i])-Tr.ask(a[i]))%mod+mod)%mod*R[i]%mod)%mod,Tr.add(a[i],i);
	//13xx
	Tr.clear();
	for(int i=1;i<=n;i++){
		ans=(ans+(L[i]*(n-i-R[i])%mod+C2(L[i])-Tr.ask(a[i])+mod)%mod*R[i]%mod)%mod;
		Tr.add(a[i],a[i]-1);
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2023-09-08 19:39  xx019  阅读(28)  评论(2编辑  收藏  举报