12月12日计数作业

\[\]

计数题

BZOJ 4360 括号序列再战猪猪侠

区间 \(dp\) ,枚举区间内第一个左括号匹配的位置,前缀和差分 \(O(1)\) 判断合法性

点击查看代码
#include<bits stdc++.h>
using namespace std;
int T;
int n,m;
long long dp[305][305];
const long long md=998244353;
int lim[305][305],sum[305][305];
inline int Get(int l1,int r1,int l2,int r2){
	if(l1>r1||l2>r2)return 0;
	return sum[r1][r2]-sum[l1-1][r2]-sum[r1][l2-1]+sum[l1-1][l2-1];
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		memset(lim,0,sizeof(lim));
		bool flag=0;
		for(int i=1;i<=m;i++){
			int x,y;scanf("%d%d",&x,&y);
			lim[x][y]++;if(x==y)flag=1;
		}
		if(flag){puts("0");continue;}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++)sum[i][j]=sum[i][j-1]+lim[i][j];
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++)sum[i][j]=sum[i-1][j]+sum[i][j];
		}
		for(int i=0;i<=n;i++)dp[i+1][i]=1;
		for(int i=1;i<=n;i++)dp[i][i]=1;
		for(int len=2;len<=n;len++){
			for(int l=1;l+len-1<=n;l++){
				int r=l+len-1;
				dp[l][r]=0;
				for(int i=l;i<=r;i++){
					if(!Get(l,l,l+1,i)&&!Get(i+1,r,l,i)){
						dp[l][r]=(dp[l][r]+dp[l+1][i]*dp[i+1][r])%md;
					}
				}
			}
		}printf("%lld\n",dp[1][n]);
	}
	return 0;
}

[NOI2009]管道取珠

对于所有可能的结果,设可以产生第 \(i\) 种结果的方案数为 \(a_i\),求 \(\sum a_i^2\),等价于通过两次操作得到统一结果的方案数

点击查看代码
#include<bits stdc++.h="">
using namespace std;
int n,m;
long long dp[2][505][505];
const long long md=1024523;
char a[505],b[505];
int main(){
	scanf("%d%d",&n,&m);
	scanf("%s%s",a+1,b+1);
	dp[0][0][0]=1;
	for(int len=1;len<=n+m;len++){
		for(int l1=0;l1<=n&&l1<=len;l1++){
			int r1=len-l1;
			for(int l2=0;l2<=n&&l2<=len;l2++){
				int r2=len-l2;dp[len&1][l1][l2]=0;
				if(l1&&l2&&a[l1]==a[l2])dp[len&1][l1][l2]=(dp[len&1][l1][l2]+dp[(len&1)^1][l1-1][l2-1])%md;
				if(l1&&r2&&a[l1]==b[r2])dp[len&1][l1][l2]=(dp[len&1][l1][l2]+dp[(len&1)^1][l1-1][l2])%md;
				if(r1&&r2&&b[r1]==b[r2])dp[len&1][l1][l2]=(dp[len&1][l1][l2]+dp[(len&1)^1][l1][l2])%md;
				if(r1&&l2&&b[r1]==a[l2])dp[len&1][l1][l2]=(dp[len&1][l1][l2]+dp[(len&1)^1][l1][l2-1])%md;
			}
		}
	}printf("%lld",dp[(n+m)&1][n][n]);

    return 0;
}

51nod 1327 棋盘游戏

从左边向右 \(dp\)\(dp[i][j]\) 表示处理到第 \(i\) 列,而有 \(j\) 列填了右区间

有三种转移,一是向后走但不填数,直接加上 \(f[i-1][j]\)

二是在填一个属于右区间的数,\(f[i-1][j-1]*(r[i]-j+1)\) ;这个很容易理解,前面已经填了 \(j\) 个,我新增的这一个就是端点位于 \(i\) 左侧除去填过的 \(j\) 个后的任意一个

三是要满足 \(i\) 自身以内左端点要搞完,要填的数就是 \(l[i]-l[i-1]\) 而总共有 \(i-j-l[i-1]\) 个位置,要乘上排列数

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int l[3005],r[3005];
long long inv[3005],jc[3005];
long long md=998244353;
inline void init(){
	inv[0]=inv[1]=jc[0]=jc[1]=1;
	for(int i=2;i<=m;i++)inv[i]=(md-md/i)*inv[md%i]%md,jc[i]=jc[i-1]*i%md;
	for(int i=2;i<=m;i++)inv[i]=inv[i]*inv[i-1]%md;
	for(int i=1;i<=m;i++)l[i]+=l[i-1],r[i]+=r[i-1];
}
long long dp[3005][3005];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		l[x]++;r[y]++;
	}
	init();
	dp[0][0]=1;
	for(int i=1;i<=m;i++){
		dp[i][0]=dp[i-1][0]*(jc[i-l[i-1]]*inv[i-l[i]]%md)%md;
		for(int j=1;j<=i;j++){
			dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]*(r[i]-j+1>0?r[i]-j+1:0)%md)%md;
			dp[i][j]=dp[i][j]*(jc[i-j-l[i-1]]*inv[i-j-l[i]]%md)%md;
		}
	}
	printf("%lld",dp[m][n]);


	return 0;
}

HDU 6416 Rikka with Seam

杭电 OJ 好像上不去了,以后再写吧

BZOJ 2169 连边

看第一眼以为是个水题,实际上并不难

\(dp[i][j]\) 表示连了 \(i\) 条边,还有 \(j\) 个点度数是奇数的方案数,枚举第 \(i\) 条边连了两个奇点,两个偶点,一奇一偶三种情况,再减去重边的情况即可

由于边无标号,所以每一步要除以 \(i\) 去重

点击查看代码
#include<bits stdc++.h>
using namespace std;
int n,m,k;
int deg[1005],cnt,dp[1005][1005],inv[1005];
const int md=1e4+7;
inline long long C(int x){
	return x*(x-1)/2%md;
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;i++){
		int x,y;scanf("%d%d",&x,&y);
		deg[x]^=1;deg[y]^=1;
	}inv[0]=inv[1]=1;
	for(int i=2;i<=k;i++)inv[i]=(md-md/i)*inv[md%i]%md;
	for(int i=1;i<=n;i++)if(deg[i])cnt++;
	dp[0][cnt]=1;
	for(int i=1;i<=k;i++){
		for(int j=0;j<=n;j++){
			dp[i][j]=(dp[i-1][j+2]*C(j+2)%md+1ll*dp[i-1][j]*j*(n-j)%md+(j>=2?1ll*dp[i-1][j-2]*C(n-j+2)%md:0)-(i>=2?1ll*dp[i-2][j]*(C(n)-i+2)%md:0))*inv[i]%md;
		}
	}printf("%d",(dp[k][0]+md)%md);

    return 0;
}

\[\]


有点难写的计数题

Project Euler 452 Lattice Quadrilaterals

好像上不去?

51nod 1303 交叉矩阵

\[\]


数学题

[SHOI2015]超能粒子炮·改

求:

\[f(n,k)=\sum_i^k C_n^i \% \ p \]

由卢卡斯定理:

\[\sum_{i=0}^k C_n^i \% \ p= \sum_{i=0}^k C_{n/p}^{i/p} \times C_{n\%p}^{i\%p}\ \% \ p \]

\[=\sum _{r=0}^{p-1}C_{n\%p}^{r} \times \sum_{i=0}^k[i\%p=r] C_{n/p}^{i/p} \]

\[=\sum _{r=0}^{k\%p}C_{n\%p}^{r} \times \sum_{i=0}^{k/p} C_{n/p}^{(i*p+r)/p}+\sum _{r=k\%p+1}^{p-1}C_{n\%p}^{r} \times \sum_{i=0}^{k/p-1} C_{n/p}^{(i*p+r)/p} \]

由于 \(r<p\),有 \((i*p+r)/p=i*p/p=i\),进而:

\[\sum _{r=0}^{k\%p}C_{n\%p}^{r} \times \sum_{i=0}^{k/p} C_{n/p}^{(i*p+r)/p}=\sum _{r=0}^{k\%p}C_{n\%p}^{r} \times \sum_{i=0}^{k/p} C_{n/p}^{i} \\ \sum _{r=k\%p+1}^{p-1}C_{n\%p}^{r} \times \sum_{i=0}^{k/p-1} C_{n/p}^{(i*p+r)/p}=\sum _{r=k\%p+1}^{p-1}C_{n\%p}^{r} \times \sum_{i=0}^{k/p-1} C_{n/p}^{i} \]

因此:

\[f(n,k)=\sum _{r=0}^{k\%p}C_{n\%p}^{r} \times f(n/p,k/p)+\sum _{r=k\%p+1}^{p-1}C_{n\%p}^{r} \times f(n/p,k/p-1) \]

点击查看代码
#include<bits/stdc++.h>
using namespace std;
long long fac[3005],inv[3005];
const long long md=2333;
long long sum[3005][3005];
inline long long C(int x,int y){
	if(x<y)return 0;
	return fac[x]*inv[y]%md*inv[x-y]%md;
}
inline void init(){
	fac[0]=fac[1]=inv[0]=inv[1]=1;
	for(int i=2;i<md;i++)fac[i]=fac[i-1]*i%md;
	for(int i=2;i<md;i++)inv[i]=(md-md/i)*inv[md%i]%md;
	for(int i=2;i<md;i++)inv[i]=inv[i]*inv[i-1]%md;
	for(int i=0;i<md;i++){
		sum[i][0]=1;
		for(int j=1;j<md;j++)sum[i][j]=(sum[i][j-1]+C(i,j))%md;
	}
}
long long solve(long long n,long long k){
	if(k<0)return 0;
	if(n<md&&k<md)return sum[n][k];
	return (sum[n%md][k%md]*solve(n/md,k/md)%md+(sum[n%md][md-1]-sum[n%md][k%md])*solve(n/md,k/md-1)%md)%md;
}
int T;
int main(){
	scanf("%d",&T);init();
	while(T--){
		long long n,k;
		scanf("%lld%lld",&n,&k);
		printf("%lld\n",(solve(n,k)+md)%md);
	}

    return 0;
}
posted @ 2021-12-14 16:38  一粒夸克  阅读(65)  评论(0编辑  收藏  举报