• 【洛谷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;
    }
    
  • 相关阅读:
    动态载入DLL
    在DELPHI中使用自定义光标
    Delphi实现提取可执行文件内部所有图标
    delphi 网络函数
    delphi制作dll
    实现半透明效果
    自绘按钮,添加Color属性(转载)
    为汉语名字生成首字母助记码
    DELPHI 获取本月 的第一天 和 最后一天
    GRID用法(取行、列值;定位选中某行等等)
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5289.html
Copyright © 2020-2023  润新知