zoukankan      html  css  js  c++  java
  • 【洛谷5289】[十二省联考2019] 皮配(背包)

    点此看题面

    大致题意: 让你把(m)组共(n)个物品放入(蓝/红)(鸭/R)这(4)个背包,每组物品放入的背包阵营必须相同,且对于每种阵营和每种派系的背包各有一个容量总限制。另有(k)个限制规定某个物品不能放入某个背包,求总方案数。

    考虑(k=0)

    首先我们来考虑(k=0)的部分分。

    则我们可以发现,城市选择的阵营学校选择的派系两者是没有关系的,可以分开来计算。

    如果你对这句话看得一脸懵逼,那么接下来就是对它的具体解释。


    我们记每个学校的人数为(s_i),统计每个城市的总人数为(t_i)

    则由于同一个城市必须选择同一个阵营,而每个阵营又各有容量限制,因此就相当于把城市不重不漏分入两个有容量限制的背包中。

    这是典型的背包问题,我们可以用(k1_{i,j})表示(i)个城市蓝阵营有(j)人的方案数,则转移为:

    [k1_{i,j}=k1_{i-1,j}+k1_{i-1,j-t_i} ]

    而考虑在城市确定阵营后,每个学校其实只能选择派系,因此同样相当于把学校不重不漏分入两个有容量限制的背包中。

    与前面类似,我们用(k2_{i,j})表示(i)个学校鸭派系有(j)人的方案数,则转移为:

    [k2_{i,j}=k2_{i-1,j}+k2_{i-1,j-s_i} ]


    计算完之后,我们把它们相乘,就可以得到答案了。

    考虑推广

    虽然上面的方法有很大的局限性,但它是十分值得借鉴的。

    所以,现在我们来考虑此方法的推广。

    看数据范围可以发现,(k)其实很小,只有(30)

    则我们可以考虑,将不含限制学校的城市和无限制的学校依然像上面一样背包(DP),但是(k1)(k2)的定义要稍作修改:

    • (k1_{i,j})表示前(i)不含限制学校的城市蓝阵营有(j)人的方案数。
    • (k2_{i,j})表示前(i)无限制的学校鸭派系有(j)人的方案数。

    然后,对于含限制学校的城市和有限制的学校,我们单独考虑。

    其实也只要设(f_{i,j,k})表示(i)个含限制学校的城市,蓝阵营有(j)人,鸭派系有(k)的方案数,暴力(DP)即可。

    关于内存和时间的一些优化

    数组似乎开不下?

    滚存一下即可。

    复杂度似乎过不了?

    这时我们就要用一个很普通但实用的优化:记录下当前城市总人数与(c0)(min)学校总人数与(d0)(min),然后以这个为(DP)转移的上界,复杂度就得到了大大优化。

    具体实现详见代码。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 1000
    #define V 2500
    #define X 998244353
    #define Inc(x,y) ((x+=(y))>=X&&(x-=X))
    #define mem(x,v) memset(x,v,sizeof(x))
    using namespace std;
    int n,m,k,sum,c0,c1,d0,d1,bl[N+5],s[N+5],t[N+5],p[N+5],q[N+5];
    int k1[V+5],k2[V+5],f[V+5][V+5],g[V+5][V+5];vector<int> o[N+5];
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define tn (x<<3)+(x<<1)
    		#define D isdigit(c=tc())
    		char c,*A,*B,FI[FS];
    	public:
    		I FastIO() {A=B=FI;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
    		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    }F;
    I int XSum(CI x,CI y) {return x+y>=X?x+y-X:x+y;}
    I int XSub(CI x,CI y) {return x-y<0?x-y+X:x-y;}
    int main()
    {
    	RI Ttot,i,j,k,x,y,sz,l1,l2,ans;F.read(Ttot);W(Ttot--)
    	{
    		for(mem(k1,0),mem(k2,0),mem(f,0),mem(g,0),mem(t,0),i=1;i<=m;++i) o[i].clear();//清空
    		for(F.read(n,m,c0,c1,d0,d1),sum=0,i=1;i<=n;++i)//读入学校信息
    			F.read(bl[i],s[i]),p[i]=-1,sum+=s[i],t[bl[i]]+=s[i];//统计总人数和城市人数
    		for(i=1;i<=m;++i) q[i]=0;for(F.read(k),i=1;i<=k;++i)//读入限制
    			F.read(x,y),p[x]=y,q[bl[x]]=1,o[bl[x]].push_back(x);//标记城市有限制学校,将该学校扔入vector
    		if(c0+c1<sum||d0+d1<sum) {puts("0");continue;}//如果肯定无解,输出0并continue
    		#define L1(x) (l1^c0&&(l1+=t[x])>c0&&(l1=c0))//维护城市总人数与c0的min值
    		#define L2(x) (l2^d0&&(l2+=s[x])>d0&&(l2=d0))//维护学校总人数与d0的min值
    		for(k1[l1=0]=i=1;i<=m;++i) if(L1(i),t[i]&&!q[i])//如果城市含限制学校或人数为0跳过(注意人数为0必须判!我一开始偷懒没判就WA了。。。)
    			for(j=l1;j>=t[i];--j) Inc(k1[j],k1[j-t[i]]);//背包转移
    		for(k2[l2=0]=i=1;i<=n;++i) if(L2(i),s[i]&&!~p[i])//与上面类似
    			for(j=l2;j>=s[i];--j) Inc(k2[j],k2[j-s[i]]);
    		for(i=1;i<=c0;++i) Inc(k1[i],k1[i-1]);for(i=1;i<=d0;++i) Inc(k2[i],k2[i-1]);//统计前缀和
    		for(f[0][0]=i=1,l1=l2=0;i<=m;++i)
    		{
    			if(!t[i]||!q[i]) continue;for(j=0;j<=l1;++j) for(k=0;k<=l2;++k) g[j][k]=f[j][k];//将f复制一遍给g
    			for(x=0,sz=o[i].size();x^sz;++x) for(y=o[i][x],L2(y),j=l1;~j;--j) for(k=l2;~k;--k)//枚举有限制的城市进行转移
    				f[j][k]=XSum(p[y]^1?f[j][k]:0,k>=s[y]&&p[y]?f[j][k-s[y]]:0),//判断无法选择的情况
    				g[j][k]=XSum(p[y]^3?g[j][k]:0,k>=s[y]&&p[y]^2?g[j][k-s[y]]:0);//与上面类似
    			for(L1(i),j=l1;~j;--j) for(k=l2;~k;--k)//统计,也差不多是一个背包
    				f[j][k]=XSum(j>=t[i]?f[j-t[i]][k]:0,g[j][k]);
    		}
    		#define S1(x) XSub(k1[c0-(x)],sum-c1-(x)-1>=0?k1[sum-c1-(x)-1]:0)//求出合法范围内的方案数
    		#define S2(y) XSub(k2[d0-(y)],sum-d1-(y)-1>=0?k2[sum-d1-(y)-1]:0)//与上面类似
    		for(ans=i=0;i<=c0;++i) for(j=0;j<=d0;++j) Inc(ans,1LL*f[i][j]*S1(i)%X*S2(j)%X)%X;//最后计算答案
    		printf("%d
    ",ans);//输出答案
    	}return 0;
    }
    
  • 相关阅读:
    475. Heaters
    69. Sqrt(x)
    83. Remove Duplicates from Sorted List Java solutions
    206. Reverse Linked List java solutions
    100. Same Tree Java Solutions
    1. Two Sum Java Solutions
    9. Palindrome Number Java Solutions
    112. Path Sum Java Solutin
    190. Reverse Bits Java Solutin
    202. Happy Number Java Solutin
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5289.html
Copyright © 2011-2022 走看看