【背包DP】LOJ3051 [十二省联考 2019]皮配

版权声明:这是蒟蒻的BLOG,神犇转载也要吱一声哦~ https://blog.csdn.net/Dream_Lolita/article/details/89260864

【题目】
LOJ
2 2 个阵营红蓝和两个派系鸭R,四位导师分别属于不同的阵营+派系搭配,有一些人来参加比赛:
一共有 c c 个城市的 n n 所不同学校,第 i i 所学校所属城市为 b i b_i ,有 s i s_i 名选手。
现在要求来自同一城市的选手必须加入属于相同阵营的导师,来自同一学校的选手必须加入相同的导师。
另外有限制某 k k 所学校不会加入某一位导师。
还有限制 C 0 , C 1 , D 0 , D 1 C_0,C_1,D_0,D_1 表示每个阵营或派系选手上限(设它们最大值为 m m )。
求合法的分配选手方案数模 998244353 998244353

c , n 1000 , k 30 , m 2500 , s i 10 c,n\leq 1000,k\leq 30,m\leq 2500,s_i\leq 10

【解题思路】
考虑这样一个暴力:
先设蓝阵鸭派为 0 0 ,红阵R派为 1 1
将学校按所属城市派系,然后设 f i , j , k f_{i,j,k}表示前 i 个学校,有 j$个人在 0 0 阵, k k 个在 1 1 派的方案数,暴力背包一下就可以做到 O ( n m 2 ) O(nm^2)

观察到部分分有 k = 0 k=0 ,我们分析一下这个限制,发现实际上选阵和选派可以分开算,然后两个相乘就是答案。
于是记 g 1 i , j g1_{i,j} 表示前 i i 个城市,有 j j 个在 0 0 阵的方案数, g 2 i , j g2_{i,j} 表示前 i i 个学校,有 j j 个在 0 0 派的方案数,转移就枚举当前城市或学校有多少人即可。
这是一个背包的形式,可以写成一维的。
那么对于 K = 0 K=0 的情况的复杂度就是 O ( ( c + n ) m ) O((c+n)m) 的了。

现在考虑有这些限制的时候,实际上影响的范围很少。于是我们把没有被限制的学校背包一下选派,没有学校被限制的城市背包一下阵营。
对于有被限制的城市,选择一个阵营后将这个城市所有学校加入该阵营,然后要对有限制的城市和学校进行转移,我们用那个暴力来做。由于对于城市,不同阵营的学校对派系的影响是不同的,因此还要用一个辅助数组记录不同派的转移。
这部分可以做到 O ( k m 2 ) O(km^2) ,如果卡一下上界就可以做到 O ( k m s k ) O(km\sum s_k)

于是总的复杂度就是 O ( ( c + n ) m + k m s k ) O((c+n)m+km\sum s_k) 的了。

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
using namespace std;

const int N=2505,mod=998244353;

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

namespace Math
{
	int upm(int x){return x>=mod?x-mod:(x<0?x+mod:x);}
	void up(int &x,int y){x=upm(x+y);}
	int mul(int x,int y){return 1ll*x*y%mod;}
}
using namespace Math;

namespace DreamLolita
{
	int n,c,K,ans,sm,C0,C1,D0,D1,S0,S1;
	int b[N],s[N],city[N],ban[N],Ban[N];
	int g1[N],g2[N],f[N][N],g[N][N];
	vector<int>vec[N];
	void clear()
	{
		memset(g1,0,sizeof(g1));memset(g2,0,sizeof(g2));memset(f,0,sizeof(f));memset(g,0,sizeof(g));
		memset(city,0,sizeof(city));for(int i=0;i<N;++i) vec[i].clear();
	}
	void solve1()
	{
		int lim=0;g1[0]=1;
		for(int i=1;i<=c;++i) if(!~Ban[i] && city[i])
		{
			lim+=city[i];
			for(int j=min(lim,C0);j>=city[i];--j) up(g1[j],g1[j-city[i]]);
		}
		for(int i=1;i<=C0;++i) up(g1[i],g1[i-1]);
		//for(int i=0;i<=C0;++i) printf("%d ",g1[i]); puts("");
	}
	void solve2()
	{
		int lim=0;g2[0]=1;
		for(int i=1;i<=n;++i) if(!~ban[i])
		{
			lim+=s[i];
			for(int j=min(lim,D0);j>=s[i];--j) up(g2[j],g2[j-s[i]]);
		}
		for(int i=1;i<=D0;++i) up(g2[i],g2[i-1]);
		//for(int i=0;i<=D0;++i) printf("%d ",g2[i]); puts("");
	}
	void solvef()
	{
		S0=S1=0;f[0][0]=1;
		for(int i=1;i<=c;++i) if(~Ban[i])
		{
			for(int j=0;j<=S0 && j<=C0;++j) for(int k=0;k<=S1 && k<=D0;++k) g[j][k]=f[j][k];
			for(auto v:vec[i])
			{
				if(!~ban[v]) break; S1+=s[v];
				for(int j=min(S0,C0);~j;--j) for(int k=min(S1,D0);~k;--k)
				{
					if(ban[v]==1) f[j][k]=0;
					if(ban[v] && k>=s[v]) up(f[j][k],f[j][k-s[v]]);
					if(ban[v]==3) g[j][k]=0;
					if(ban[v]!=2 && k>=s[v]) up(g[j][k],g[j][k-s[v]]);
				}
			}
			S0+=city[i];
			for(int j=min(S0,C0);~j;--j) for(int k=min(S1,D0);~k;--k)
				f[j][k]=g[j][k],up(f[j][k],j>=city[i]?f[j-city[i]][k]:0);
		}
	}
	int calc(int x,int y)
	{
		int xl=max(0,sm-x-C1),xr=C0-x,yl=max(0,sm-y-D1),yr=D0-y;
		return mul(upm(g1[xr]-(xl?g1[xl-1]:0)),upm(g2[yr]-(yl?g2[yl-1]:0)));
	}
	bool cmp(int x,int y){return ban[x]>ban[y];}
	void solution()
	{
		n=read();c=read();C0=read();C1=read();D0=read();D1=read();sm=ans=0;
		for(int i=1;i<=n;++i)
		{
			b[i]=read();s[i]=read();
			city[b[i]]+=s[i];sm+=s[i];
			ban[i]=Ban[i]=-1;vec[b[i]].pb(i);
		}
		K=read();
		for(int x,y,i=1;i<=K;++i) x=read(),y=read(),ban[x]=Ban[b[x]]=y;
		for(int i=1;i<=c;++i) sort(vec[i].begin(),vec[i].end(),cmp);
		solve1();solve2();solvef();
		for(int i=0;i<=S0 && i<=C0;++i) for(int j=0;j<=S1 && j<=D0;++j) up(ans,mul(f[i][j],calc(i,j)));
		printf("%d\n",ans); 
		clear();
	}
}

int main()
{
#ifdef Durant_Lee
	freopen("LOJ3051.in","r",stdin);
	freopen("LOJ3051.out","w",stdout);
#endif
	int T=read();
	while(T--) DreamLolita::solution();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Dream_Lolita/article/details/89260864