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;
}