P5289 [十二省联考 2019] 皮配 题解
$\large\textbf{Statement.}$
本题的重点在于题面。
有 $n$ 个学校和 $c$ 个城市,每个学校属于一个城市,学校人数给定。
你需要将每个学校和城市染成黑色或白色,其中有 $k$ 个学校不希望自己和所在城市的颜色同时分别为给定的两种颜色,黑/白城市和学校的总人数也有限制。求染色方案数。
$\large\textbf{Solution.}$
令 $dp_{i,j,k}$ 表示考虑了前 $i$ 个有限制的城市,黑色城市和学校的人数和分别为 $j,k$,染色方案数。
对于没有限制的学校,最后一起做一个背包即可。
对于有限制的城市,枚举其颜色,用它的所有有限制的学校更新 $dp$ 值。
最后有限制的和没限制的两个背包合并一下就做完了。
参考代码:
#include<bits/stdc++.h>
#define ll long long
#define mxn 2503
#define md 998244353
#define pb push_back
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
int T,n,m,k,ans,c[2],d[2],s[mxn],a[mxn],f[mxn],dp[2][mxn][mxn],f1[mxn][mxn],f2[mxn][mxn],d1[mxn],d2[mxn];
bool v[mxn];
vector<int>g[mxn];
signed main(){
scanf("%d",&T);
while(T--){
scanf("%d%d%d%d%d%d",&n,&m,&c[0],&c[1],&d[0],&d[1]);
rep(i,1,m)v[i]=0,g[i].clear();
rep(i,1,n)scanf("%d%d",&a[i],&s[i]),f[i]=-1,g[a[i]].pb(i);
scanf("%d",&k);
for(int i=0,x,y;i<k;++i){
scanf("%d%d",&x,&y);
f[x]=y,v[a[x]]=1;
}
int sum=0;
bool fl=0;
memset(dp,0,sizeof(dp));
dp[0][0][0]=1;
rep(i,1,m)if(v[i]){
int sm=0;
for(int j:g[i])sm+=s[j];
fl^=1;
int s1=sm;
drep(j,min(sum+sm,c[0]),0)drep(k,min(sum+sm,d[0]),0){
dp[fl][j][k]=0;
f1[j][k]=dp[fl^1][j][k];
}
for(int j:g[i])if(f[j]!=-1){
drep(x,min(sum+sm,c[0]),0)drep(y,min(sum+sm,d[0]),0)f2[x][y]=0;
drep(x,min(sum,c[0]-s[j]),0){
drep(y,min(sum,d[0]),0)if(f1[x][y]){
if((f[j]>>1)==1){
if(y+s[j]<=d[0])f2[x+s[j]][y+s[j]]=(f2[x+s[j]][y+s[j]]+f1[x][y])%md;
f2[x+s[j]][y]=(f2[x+s[j]][y]+f1[x][y])%md;
}else if(f[j]&1){
if(y+s[j]<=d[0])f2[x+s[j]][y+s[j]]=(f2[x+s[j]][y+s[j]]+f1[x][y])%md;
}else f2[x+s[j]][y]=(f2[x+s[j]][y]+f1[x][y])%md;
}
}
sum+=s[j],sm-=s[j];
drep(x,min(sum+sm,c[0]),0)drep(y,min(sum+sm,d[0]),0)f1[x][y]=f2[x][y];
}
drep(x,min(sum,c[0]-sm),0)drep(y,min(sum,d[0]),0)if(f1[x][y]){
dp[fl][x+sm][y]=(dp[fl][x+sm][y]+f1[x][y])%md;
}
drep(j,min(sum+sm,c[0]),0)drep(k,min(sum+sm,d[0]),0)f1[j][k]=dp[fl^1][j][k];
sum-=s1-sm,sm=s1;
for(int j:g[i])if(f[j]!=-1){
drep(x,min(sum+sm,c[0]),0)drep(y,min(sum+sm,d[0]),0)f2[x][y]=0;
drep(x,min(sum,c[0]),0){
drep(y,min(sum,d[0]),0)if(f1[x][y]){
if((f[j]>>1)==0){
if(y+s[j]<=d[0])f2[x][y+s[j]]=(f2[x][y+s[j]]+f1[x][y])%md;
f2[x][y]=(f2[x][y]+f1[x][y])%md;
}else if(f[j]&1){
if(y+s[j]<=d[0])f2[x][y+s[j]]=(f2[x][y+s[j]]+f1[x][y])%md;
}else f2[x][y]=(f2[x][y]+f1[x][y])%md;
}
}
sum+=s[j],sm-=s[j];
drep(x,min(sum+sm,c[0]),0)drep(y,min(sum+sm,d[0]),0)f1[x][y]=f2[x][y];
}
drep(x,min(sum,c[0]),0)drep(y,min(sum,d[0]),0)if(f1[x][y]){
dp[fl][x][y]=(dp[fl][x][y]+f1[x][y])%md;
}
sum+=sm;
}
memset(d1,0,sizeof(d1));
memset(d2,0,sizeof(d2));
d1[0]=d2[0]=1;
rep(i,1,m)if(!v[i]&&g[i].size()){
int sm=0;
for(int j:g[i])sm+=s[j];
drep(j,c[0],sm)d1[j]=(d1[j]+d1[j-sm])%md;
for(int j:g[i])drep(k,d[0],s[j])d2[k]=(d2[k]+d2[k-s[j]])%md;
sum+=sm;
}
rep(i,1,n)if(v[a[i]]&&f[i]==-1){
drep(k,d[0],s[i])d2[k]=(d2[k]+d2[k-s[i]])%md;
}
rep(i,1,c[0])d1[i]=(d1[i]+d1[i-1])%md;
rep(i,1,d[0])d2[i]=(d2[i]+d2[i-1])%md;
ans=0;
rep(i,0,c[0])rep(j,0,d[0]){
if(c[0]-i<sum-i-c[1]||d[0]-j<sum-j-d[1])continue;
ans=(ans+(ll)dp[fl][i][j]*(d1[c[0]-i]-(sum-i-c[1]-1>=0?d1[sum-i-c[1]-1]:0))%md*(d2[d[0]-j]-(sum-j-d[1]-1>=0?d2[sum-j-d[1]-1]:0)))%md;
}
printf("%d\n",(ans+md)%md);
}
return 0;
}