[十二省联考 2019] 皮配
一、题目
二、解法
暴力 \(dp\) 直接 \(O(nm^3)\) ,也就是表示出每个导师下面的人数,剩下的一位老师可以被算出来。
但出题人是个阴间玩意,如果这道题你按照:给导师分配学生
这种思路来做的话就永远做不出来。因为你的 限制是在阵营 \(/\) 派系上面的 ,那我们就让导师滚蛋,我们用 给学生分配阵容和派系
的思路来完成这道题。
更形象的描述我推荐这位大佬的孟德尔性状法,把题目转化成 \(c\) 个豆荚,\(n\) 的重量不同的豆子,每个豆荚里的豆子都是黄色或者都是绿色,每个豆子还可以选择成为圆豆或者皱豆。黄圆\(/\)黄皱\(/\)绿圆\(/\)绿皱的总重量分别不能超过 \(C_0/C_1/D_0/D_1\) 。同时有 \(k\) 颗垃圾豆子拒绝成为某个形状(上述四个的其中之一)。
那么我们设 \(F[i][j]\) 表示黄豆的总重量为 \(i\) ,圆豆的总重量为 \(j\) 的方案数。暴力跑背包是 \(O(nm^2)\) 的,现在应该可以得到 \(50\) 分。
好像是优化不动了。但是仔细观察数据范围,还有 \(k=0\) 的部分分没有尝试啊。写出 \(50\) 分的转移之后发现这种情况下黄\(/\)绿 和 圆\(/\)皱 这两种形状是独立的,这提示我们可以分开算最后再用乘法原理合并算答案。设 \(f[i]\) 为黄豆的重量和,\(g[i]\) 为圆豆的质量和,转移似乎更简单了。那么应该可以得到 \(70\) 分。
发现 \(k\leq 30\) 是特别小的,我们是否可以把这种垃圾豆子单独提出来讨论呢?那我们就先把正常的豆子的 \(dp\) 跑完再说,\(f[i],g[i]\) 就用正常豆荚(没有垃圾豆子) \(/\) 正常豆子去算(垃圾豆子不影响正常豆子的圆皱)。设 \(F[i][j]\) 为黄色的垃圾豆荚质量和为 \(i\),圆的垃圾豆子质量和为 \(j\) 的方案数,最后可以花 \(O(m^2)\) 的时间枚举 \(i,j\) 来合并答案(\(f,g\) 要做前缀和)
\(F[i][j]\) 就可以用 \(50\) 分的算法来做,因为 \(s\leq10\),所以 \(j\) 这一维会特别小,那么时间复杂度实际上是 \(O(k^2sm)\)。具体实现的时候可以再定义一个 \(G[i][j]\) 表示这个垃圾豆荚要选绿色,\(F[i][j]\) 就表示这个垃圾豆荚要选黄色,这样以豆荚为单位来算就很方便了。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 2505;
const int MOD = 998244353;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,c,k,ans,sum,c0,c1,d0,d1,C,D,f[M],g[M];
int b[M],s[M],h[M],sc[M],hc[M],F[M][M],G[M][M];
void init()
{
//数据千万条,清空第一条
//多测不清空,爆零两行泪
C=D=ans=sum=0;
for(int i=1;i<=n;i++)
{
h[i]=-1;
sc[i]=hc[i]=0;
}
memset(f,0,sizeof f);memset(g,0,sizeof g);
memset(F,0,sizeof F);memset(G,0,sizeof G);
}
void work()
{
n=read();c=read();c0=read();
c1=read();d0=read();d1=read();
init();
for(int i=1;i<=n;i++)
{
b[i]=read();s[i]=read();
sc[b[i]]+=s[i];sum+=s[i];
}
k=read();
for(int i=1;i<=k;i++)
{
int x=read();h[x]=read();
hc[b[x]]=1;
}
f[0]=1;
for(int i=1;i<=c;i++)//考虑无毒豆荚
if(!hc[i] && sc[i])
{
for(int j=c0;j>=sc[i];j--)
f[j]=(f[j]+f[j-sc[i]])%MOD;
}
for(int i=1;i<=c0;i++) f[i]=(f[i-1]+f[i])%MOD;
g[0]=1;
for(int i=1;i<=n;i++)//考虑豆子
if(h[i]==-1)
{
for(int j=d0;j>=s[i];j--)
g[j]=(g[j]+g[j-s[i]])%MOD;
}
for(int i=1;i<=d0;i++) g[i]=(g[i-1]+g[i])%MOD;
F[0][0]=1;
for(int ct=1;ct<=c;ct++)
if(hc[ct])//有毒的豆荚
{
C+=sc[ct];C=min(C,c0);
for(int i=0;i<=C;i++)
for(int j=0;j<=D;j++)
G[i][j]=F[i][j];
//F表示都选黄,G表示都选绿
//0黄圆 1黄皱 2绿圆 3绿皱
for(int x=1;x<=n;x++)
if(h[x]!=-1 && b[x]==ct)//有毒的豆子
{
int t=s[x];
D+=t;D=min(D,d0);
if(h[x]==1)//那么只能选皱
{
for(int i=0;i<=C;i++)
{
for(int j=D;j>=t;j--)
F[i][j]=F[i][j-t];
for(int j=0;j<t;j++)
F[i][j]=0;
}
}
if(h[x]>=2)//那么随便选
{
for(int i=0;i<=C;i++)
for(int j=D;j>=t;j--)
F[i][j]=(F[i][j]+F[i][j-t])%MOD;
}
if(h[x]==3)//那么只能选皱
{
for(int i=0;i<=C;i++)
{
for(int j=D;j>=t;j--)
G[i][j]=G[i][j-t];
for(int j=0;j<t;j++)
G[i][j]=0;
}
}
if(h[x]<=1)//那么随便选
{
for(int i=0;i<=C;i++)
for(int j=D;j>=t;j--)
G[i][j]=(G[i][j]+G[i][j-t])%MOD;
}
}
for(int j=0,t=sc[ct];j<=D;j++)//F是需要全部选的
{
for(int i=C;i>=t;i--)
F[i][j]=F[i-t][j];
for(int i=0;i<t;i++)
F[i][j]=0;
}
for(int i=0;i<=C;i++)//combine them
for(int j=0;j<=D;j++)
F[i][j]=(F[i][j]+G[i][j])%MOD;
}
for(int i=0;i<=C;i++)
for(int j=0;j<=D;j++)
{
int l1=max(0,sum-c1-i),r1=c0-i;if(l1>r1) continue;
int l2=max(0,sum-d1-j),r2=d0-j;if(l2>r2) continue;
int t1=f[r1];if(l1) t1=(t1-f[l1-1])%MOD;
int t2=g[r2];if(l2) t2=(t2-g[l2-1])%MOD;
ans=(ans+1ll*t1*t2%MOD*F[i][j])%MOD;
}
printf("%d\n",(ans+MOD)%MOD);
}
signed main()
{
T=read();
while(T--) work();
}