[十二省联考 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();
}
posted @ 2020-12-30 15:18  C202044zxy  阅读(331)  评论(0编辑  收藏  举报