杂题选做(Fate三部曲)
在一个珂朵莉泛滥的世界里找到三道有关\(fate\)的题多么不容易
Emiya家今天的饭
非常不错的一道容斥+\(dp\)题
考虑第三个限制,发现最多只有一列不合法,那么我们可以枚举不合法的一列是哪一列
那么接下来的任务就是求出总方案数-不合法的方案数
那么首先考虑如何计算不合法的方案数
令\(s_i\)表示每一行的\(\sum a_{i,j}\),\(col\)表示枚举到的不合法的那一列
设\(f_{i,j,k}\)表示考虑到了第\(i\)行,不合法的那一列选了\(j\)行,剩下的列总共选了\(k\)行的方案数
那么可以得到转移
\(f_{i,j,k}=f_{i-1,j,k}+a_{i,col}\times f_{i-1,j-1,k}+(s_i-a_{i,col})\times f_{i-1,j,k-1}\)
那么对于一种不合法的列\(col\),他的方案数就是\(\sum\limits_{j>k} f_{n,j,k}\)
接下来考虑如何计算总方案数
设\(g_{i,j}\)表示考虑到第\(i\)列选择了\(j\)行的方案数,那么转移更简单一些
\(g_{i,j}=g_{i-1,j}+s_i\times g_{i-1,j-1}\)
总方案数就是\(\sum\limits_{j=1}^{n}g_{n,j}\)
这样的话复杂度是\(O(n^3m)\)的,预计得分\(84\),考虑优化枚举次数
发现\(f_{i,j,k}\)中知道\(j,k\)的具体数值并没有什么意义,
那么考虑去掉一维变成\(f_{i,j}\)表示考虑到第\(i\)行,不合法的那一列选的行数和其他列选的行数差值为\(j\),也就是原来的\(j-k\)
这样转移就变成了\(O(n^2m)\),预计得分\(100\)
code
#include<cstdio>
#include<cstring>
#include<iostream>
#define LL long long
const int NN=105,MM=2005;
const LL mod=998244353;
namespace AE86{
auto read=[](){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
};
auto write=[](LL x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
};
}using namespace AE86;
int n,m,a[NN][MM];
LL ans,s[NN],f[2][NN<<1],g[2][NN],tot,r[NN][MM];
namespace WSN{
inline short main(){
// freopen("meal.in","r",stdin);
// freopen("meal.out","w",stdout);
n=read();m=read();
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j)
a[i][j]=read(),s[i]=(s[i]+a[i][j])%mod;
for(int j=1;j<=m;++j) r[i][j]=(s[i]-a[i][j]+mod)%mod;
}
for(int col=1;col<=m;++col){
memset(f,0,sizeof(f));f[0][n]=1;
for(int i=1;i<=n;++i)
for(int j=n-i;j<=n+i;++j)
f[i&1][j]=(f[(i-1)&1][j]+(j>0?f[(i-1)&1][j-1]*a[i][col]%mod:0)+(j<n*2?f[(i-1)&1][j+1]*r[i][col]%mod:0))%mod;
int tmp=0;
for(int j=1;j<=n;++j) tmp=(tmp+f[n&1][n+j])%mod;
ans=(ans+tmp)%mod;
}
g[0][0]=1;
for(int i=1;i<=n;++i)
for(int j=0;j<=n;++j)
g[i&1][j]=(g[(i-1)&1][j]+(j>0?s[i]*g[(i-1)&1][j-1]%mod:0))%mod;
for(int j=1;j<=n;++j) tot=(tot+g[n&1][j])%mod;
write((tot-ans+mod)%mod);
return 0;
}
}
signed main(){return WSN::main();}
\(tips\):
容斥,动态规划
王之财宝(Gate of Babylon)
比较经典的容斥题目,考虑题意的转化
有一个不定方程\(x_1+x_2+...+x_n\leq m\),其中一些\(x_i\)有\(x_i\leq b_i\)的限制,求合法的不定方程的非负整数解有几个。
注意:以下说的解均为是非负整数解
先来考虑不定方程\(x+y+z=10\)的解有几个,用插板法可知其解有\(C_{12}^2\)个
那么可知有\(n\)个未知数的不定方程\(x_1+...+x_n=m\)解有\(C_{m+n-1}^{n-1}\)个
考虑\(x+y+z\leq 10\)的解,亦使用插板法得到有\(C_{13}^3\)个,因为可以选出一堆来空出空间
那么在没有限制的情况下\(x_1+...+x_n\leq m\)的解有\(C_{m+n}^{n}\)个
继续考虑有限制的情况下\(x+y+z\leq 10\)的解,假设\(x\leq 6\)
这个问题可以考虑容斥,用总的解减去超越限制的解就是有限制的解,考虑计算超越限制的解
我们可以整一个新的\(xx=x+7\),那么对于\(x\leq 6\),都有\(xx<0\),也就是说\(xx+7+y+z\leq 10\)的解个数为\(0\),
然后继续使用插板法可以得到超限制的解的个数是\(C_6^3\),那么最后\(x+y+z\leq 10\)且\(x\leq 6\)的解的个数就是\(C_{13}^3-C_6^3\)
题目中有多组限制,就考虑使用容斥,奇减偶加,\(2^T\)枚举所有可能状态,然后计算合法解的个数即可
code
#include<cstdio>
#include<cstring>
#include<iostream>
#define int long long
const int NN=1e5+5;
using namespace std;
namespace AE86{
auto read=[](){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
};
auto write=[](int x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
};
}using namespace AE86;
int n,m,t,mod,b[16],ans;
namespace Math{
int h[NN],v[NN];
auto qmo=[](int a,int b,int ans=1){
int c=mod;for(;b;b>>=1,a=a*a%c)if(b&1)ans=ans*a%c;
return ans;
};
auto inv=[](int x){return qmo(x,mod-2);};
auto prework=[](){
h[0]=h[1]=1; v[0]=v[1]=1;
for(int i=2;i<mod;++i) h[i]=h[i-1]*i%mod;
for(int i=2;i<mod;++i) v[i]=v[i-1]*inv(i)%mod;
};
auto C=[](int n,int m){return (n<m||n<0||m<0)?0:h[n]*v[n-m]%mod*v[m]%mod;};
inline int lucas(int n,int m){return m?lucas(n/mod,m/mod)*C(n%mod,m%mod)%mod:1;}
}using namespace Math;
namespace WSN{
inline short main(){
n=read(); t=read(); m=read(); mod=read(); prework();
for(int i=1;i<=t;i++) b[i]=read();
for(int i=0;i<(1<<t);i++){
int tmp=m,bs=(__builtin_popcount(i)&1)?-1:1;
for(int j=1;j<=t;j++)if(i&(1<<j-1))tmp-=(b[j]+1);
ans=(ans+bs*lucas(tmp+n,n)+mod)%mod;
}
write(ans);
return 0;
}
}
signed main(){return WSN::main();}
\(tips\):
组合数学,插板法,容斥,卢卡斯
王者之剑(Excalibar)
直接二分图最大权独立集,跑网络流就行了,至于为什么我是看别人的详细证明才会的,所以想要知其所以然的右转就好了
code(三道题里最具特色的代码,因为题面主人公是呆毛,所以还是忍不住中二了一下)
#include<cstdio>
#include<cstring>
#include<iostream>
#define int long long
const int NN=5e5+5,inf=1e18;
using namespace std;
namespace Lancer{
auto Excalibar=[](){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
};
auto Enuma_Elish=[](int x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
};
}using namespace Lancer;
int n,m,w[101][101],ans;
int dx[4]={0,0,-1,1},
dy[4]={-1,1,0,0};
struct Caster{int to,val,next;}e[NN<<1];int head[NN],rp=1;
auto add=[](int x,int y,int z){
e[++rp]=Caster{y,z,head[x]};head[x]=rp;
e[++rp]=Caster{x,0,head[y]};head[y]=rp;
};
namespace Archer{
int HD[NN],q[NN],h,t,dis[NN],S,T;
auto bfs=[](){
memset(dis,0x3f,sizeof(dis));
memcpy(head,HD,sizeof(head));
q[h=t=1]=S; dis[S]=0;
while(h<=t){
int x=q[h++];
for(int i=head[x];i;i=e[i].next)if(e[i].val)
if(dis[e[i].to]>dis[x]+1)
dis[e[i].to]=dis[x]+1,q[++t]=e[i].to;
if(x==T) return 1;
} return 0;
};
inline int dfs(int x,int in){
if(x==T) return in;
int rest=in,go;
for(int i=head[x];i;head[x]=i=e[i].next)if(e[i].val){
int y=e[i].to,v=e[i].val;
if(dis[y]==dis[x]+1){
go=dfs(y,min(rest,v));
if(go) e[i].val-=go,e[i^1].val+=go,rest-=go;
else dis[y]=0;
}if(!rest) break;
} return in-rest;
}
auto dinic=[](int ans=0){
memcpy(HD,head,sizeof(HD));
while(bfs()) ans+=dfs(S,inf);
return ans;
};
auto id=[](int x,int y){return (x-1)*m+y;};
}using namespace Archer;
namespace Saber{
inline short main(){
n=Excalibar(); m=Excalibar(); S=n*m+1; T=S+1;
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++) w[i][j]=Excalibar(),ans+=w[i][j];
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
if((i+j)&1){
add(S,id(i,j),w[i][j]);
for(int k=0;k<4;k++){
int x=i+dx[k],y=j+dy[k];
if(x>0&&x<=n&&y>0&&y<=m)
add(id(i,j),id(x,y),inf);
}
}
else add(id(i,j),T,w[i][j]);
Enuma_Elish(ans-dinic());
return 0;
}
}
signed main(){return Saber::main();}
\(tips\):
网络流,二分图最大权独立集
在此记录发现的同为\(fate\)忠实粉丝的大佬的博客