传送门
我zxyoi学OI两年,什么场面没见过。
这场面我真没见过。
真正的DP好题,想清楚之后完全不难写。
虽然写完再去看觉得窒息得一批。
准确来说,这道题的考察内容就是背包计数。
是的,只有背包计数,除了乘法原理甚至可以说没有任何组合数学内容。
纯粹到极致的背包DP神仙题。
题解:
设 S S S为 s s s之和,即总人数。
首先考虑低分暴力(因为正解里面会用到)。
直接维护三位导师旗下的学员人数进行转移,显然第四位导师可以直接算出来,转移复杂度 O ( n M 3 ) O(nM^3) O(nM3)
发现其实可以直接分阵营和派系维护,即我们只维护蓝阵营和R派系的人数即可,复杂度 O ( n M 2 ) O(nM^2) O(nM2)
考虑 k = 0 k=0 k=0的情况。
我们重新理一遍题意,发现这种情况下阵营和派系的选择互不影响。
分别背包DP,设 f [ i ] f[i] f[i]表示蓝阵营中人数为 i i i的方案数, g [ i ] g[i] g[i]表示鸭派系中人数为 j j j的方案数。
则由乘法原理,答案为 ∑ i = S − C 1 C 0 ∑ j = S − D 1 D 0 f [ i ] g [ j ] \sum\limits_{i=S-C_1}^{C_0}\sum\limits_{j=S-D_1}^{D_0}f[i]g[j] i=S−C1∑C0j=S−D1∑D0f[i]g[j]
接下来考虑存在某些学校讨厌某个导师。
发现对于没有任何一个学校讨厌某个导师的城市,和没有讨厌的导师的学校,这部分计数可以直接套用上面的做法。
然后对于有限制的,可以直接套用暴力解法。
枚举有限制的学校分在蓝阵营,鸭派系的有多少人,剩下的部分直接乘法原理计算即可,需要维护一下上面背包结果的前缀和。
背包过程中需要比较精细的上界优化,不然复杂度假的。
单组数据复杂度 O ( ( c + n ) m + k 2 s M ) O((c+n)m+k^2sM) O((c+n)m+k2sM)
代码:
#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const
namespace IO{
inline char get_char(){
static cs int Rlen=1<<22|1;
static char buf[Rlen],*p1,*p2;
return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
}
template<typename T>
inline T get(){
char c;T num;
while(!isdigit(c=gc()));num=c^48;
while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
return num;
}
inline int gi(){return get<int>();}
}
using namespace IO;
using std::cerr;
using std::cout;
cs int mod=998244353;
inline int add(int a,int b){a+=b-mod;return a+(a>>31&mod);}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
cs int N=1e3+7,M=2.5e3+7;
bool ban[N];
int sum[N],hate[N],b[N],s[N];
int f[M],F[M][M];
int g[M],G[M][M];
std::vector<int> C[N];
int n,c,C0,C1,D0,D1,tot,K;
inline void solve(){
n=gi(),c=gi();
C0=gi(),C1=gi(),D0=gi(),D1=gi();tot=0;
for(int re i=1;i<=c;++i)ban[i]=false,sum[i]=0,C[i].clear();
for(int re i=1;i<=n;++i){
b[i]=gi(),s[i]=gi();
sum[b[i]]+=s[i];hate[i]=-1;
tot+=s[i];C[b[i]].push_back(i);
}
K=gi();while(K--){int x=gi();hate[x]=gi();ban[b[x]]=true;}
int lim=0,tmp=0;
memset(f,0,sizeof(int)*(C0+1));f[0]=1;lim=0;
for(int re i=1;i<=c;++i)if(!ban[i]&&sum[i])
for(int re j=lim=std::min(lim+(tmp=sum[i]),C0);j>=tmp;--j)Inc(f[j],f[j-tmp]);
for(int re i=1;i<=C0;++i)Inc(f[i],f[i-1]);
memset(g,0,sizeof(int)*(D0+1));g[0]=1;lim=0;
for(int re i=1;i<=n;++i)if(!~hate[i])
for(int re j=lim=std::min(lim+(tmp=s[i]),D0);j>=tmp;--j)Inc(g[j],g[j-tmp]);
for(int re i=1;i<=D0;++i)Inc(g[i],g[i-1]);
int CS=0,SS=0;F[0][0]=1;
for(int re ct=1;ct<=c;++ct)if(ban[ct]){
CS=std::min(CS+sum[ct],C0);
for(int re i=0;i<=CS;++i)
memcpy(G[i],F[i],sizeof(int)*(SS+1));
for(int re a:C[ct])if(~hate[a]){
int t=s[a];SS=std::min(SS+t,D0);
if(hate[a]==1)
for(int re i=0;i<=CS;++i){
for(int re j=SS;j>=t;--j)F[i][j]=F[i][j-t];
memset(F[i],0,sizeof(int)*t);
}
if(hate[a]>=2)
for(int re i=0;i<=CS;++i)
for(int re j=SS;j>=t;--j)Inc(F[i][j],F[i][j-t]);
if(hate[a]==3)
for(int re i=0;i<=CS;++i){
for(int re j=SS;j>=t;--j)G[i][j]=G[i][j-t];
memset(G[i],0,sizeof(int)*t);
}
if(hate[a]<=1)
for(int re i=0;i<=CS;++i)
for(int re j=SS;j>=t;--j)Inc(G[i][j],G[i][j-t]);
}
for(int re j=0,tmp=sum[ct];j<=SS;++j){
for(int re i=CS;i>=tmp;--i)F[i][j]=F[i-tmp][j];
for(int re i=tmp-1;~i;--i)F[i][j]=0;
}
for(int re i=0;i<=CS;++i)
for(int re j=0;j<=SS;++j)Inc(F[i][j],G[i][j]);
}
int res=0;
for(int re i=0;i<=CS;++i)
for(int re j=0;j<=SS;++j){
int l1=std::max(0,tot-C1-i),r1=C0-i;
int l2=std::max(0,tot-D1-j),r2=D0-j;
if(l1>r1)std::swap(l1,r1);
if(l2>r2)std::swap(l2,r2);
int v1=f[r1];if(l1)Inc(v1,mod-f[l1-1]);
int v2=g[r2];if(l2)Inc(v2,mod-g[l2-1]);
Inc(res,(ll)v1*v2%mod*F[i][j]%mod);
}
cout<<res<<"\n";
for(int re i=0;i<=CS;++i)
memset(F[i],0,sizeof(int)*(SS+1)),
memset(G[i],0,sizeof(int)*(SS+1));
}
signed main(){
#ifdef zxyoi
freopen("match.in","r",stdin);
#endif
int T=gi();while(T--)solve();
return 0;
}