• 【正睿2019暑假集训】正睿891 蔡老板与豪宅


    题目链接

    前置知识:fwt

    提前约定:设(S_i)表示装修队(i)能装修的点集;(E_i)表示装修队(i)能装修的边集,也就是(S_i)这个点集所生成的完全图的边集。

    因为装修队的数量(m)很小,考虑状压DP。设(dp[s])表示已经请了集合(s)里的这些装修队,能实现的最大花费。

    考虑预处理出(f[s])表示集合(s)里的装修队,能装修的边集的并的大小。也就是(f[s]=left|igcup_{iin s}E_i ight|)。那我们的DP就可以有如下转移:

    [dp[s]=max_{iin s}{dp[ssetminus i]+ ext{cost}_i(f[s]-f[ssetminus i])} ]

    其中(i)表示枚举新添加了哪个装修队。( ext{cost}_i(e))表示装修队(i),新装修了(e)条边,所收取的费用,也就是( ext{cost}_i(e)=(a_ie^2+b_ie+c_i)mod2^{32})

    DP的时间复杂度是(O(2^mm)),可以接受。于是问题转化为求(f[s])

    考虑容斥:

    [egin{align} f[s]&=left|igcup_{iin s}E_i ight|\ &=sum_{s'subseteq s}(-1)^{|s'|+1}left|igcap_{iin s'}E_i ight| end{align} ]

    发现(left|igcap_{iin s'}E_i ight|)(也就是“边集交”)的大小,之和点集交有关。具体来说,设(g[s])表示集合(s)里的装修队,能装修的点集的交的大小,也就是(g[s]=left|igcap_{iin s}S_i ight|)。那么(left|igcap_{iin s'}E_i ight|={g[s]choose 2})

    如果知道了(g),那么求(f),就相当于做高维前缀和,可以用fwt or卷积,在(O( ext{len}log ext{len})=O(2^mm))的时间里实现。于是问题进一步转化为求(g)

    因为点太多了,枚举每个(s),再求“点集交”显然不行。考虑每个点对(g)的贡献。每个点(u),会对应一个能装修到它的装修队集合(t_u)。发现如果(s)(t_u)的子集,则点(u)会对(g[s])产生(1)的贡献:表示点(u)在集合(s)的“点集交”里。于是我们可以构造出一个(g'[s]=sum_{u=1}^{n}[t_u=s]),然后对(g')高维后缀和(fwt and 卷积),就可以得到(g)了。

    至此,我们以(O(2^mm))的时间复杂度解决了本题。回顾一下过程:先求出“点集交”(g)(考虑每个点的贡献,fwt and卷积),再求出“边集并”(f)(容斥),最后做一个简单的状压DP。

    参考代码:

    //problem:ZR891
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
    template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
    
    const int MAXN=2e5,MAXM=20;
    int n,m;
    struct CostCalculater{
    	uint a,b,c;
    }cost[MAXM+5];
    vector<int>nodes[MAXM+5];
    int companies[MAXN+5],g[1<<MAXM];
    ll f[1<<MAXM],dp[1<<MAXM];
    int bitcnt[1<<MAXM];
    /*
    g[s]: s里的这些装修队,它们的点集交的大小
    f[s]: s里的这些装修队,它们的边集并的大小
    dp[s]: 已经请了s里的这些装修队,能实现的最大花费
    */
    template<typename T>
    void fwt_and(T* a,int n,T flag){
    	for(int i=1;i<n;i<<=1){
    		for(int j=0;j<n;j+=(i<<1)){
    			for(int k=0;k<i;++k){
    				a[j+k]+=a[j+k+i]*flag;
    			}
    		}
    	}
    }
    template<typename T>
    void fwt_or(T* a,int n,T flag){
    	for(int i=1;i<n;i<<=1){
    		for(int j=0;j<n;j+=(i<<1)){
    			for(int k=0;k<i;++k){
    				a[j+k+i]+=a[j+k]*flag;
    			}
    		}
    	}
    }
    
    int main() {
    	cin>>n>>m;
    	for(int i=1;i<=m;++i){
    		cin>>cost[i].a>>cost[i].b>>cost[i].c;
    		int sz;cin>>sz;
    		nodes[i].resize(sz);
    		for(int j=0;j<sz;++j){
    			cin>>nodes[i][j];
    			companies[nodes[i][j]]|=(1<<(i-1));
    		}
    	}
    	for(int i=1;i<=n;++i)
    		g[companies[i]]++;
    	fwt_and(g,1<<m,1);
    	g[0]=0;
    	for(int i=1;i<(1<<m);++i){
    		bitcnt[i]=bitcnt[i>>1]+(i&1);
    		f[i]=(ll)((bitcnt[i]&1)?1:-1)*g[i]*(g[i]-1)/2;
    	}
    	fwt_or(f,1<<m,1LL);
    	for(int i=1;i<(1<<m);++i){
    		for(int j=1;j<=m;++j)if((i>>(j-1))&1){
    			uint e=(f[i]-f[i^(1<<(j-1))]) % (1LL<<32);
    			uint c=(cost[j].a*e*e+cost[j].b*e+cost[j].c);
    			ckmax(dp[i],dp[i^(1<<(j-1))]+c);
    		}
    	}
    	cout<<dp[(1<<m)-1]<<endl;
    	return 0;
    }
    
  • 相关阅读:
    高精度计算
    高精度除以低精度
    P1258 小车问题
    POJ 2352 stars (树状数组入门经典!!!)
    HDU 3635 Dragon Balls(超级经典的带权并查集!!!新手入门)
    HDU 3938 Portal (离线并查集,此题思路很强!!!,得到所谓的距离很巧妙)
    POJ 1703 Find them, Catch them(确定元素归属集合的并查集)
    HDU Virtual Friends(超级经典的带权并查集)
    HDU 3047 Zjnu Stadium(带权并查集,难想到)
    HDU 3038 How Many Answers Are Wrong(带权并查集,真的很难想到是个并查集!!!)
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13297534.html
Copyright © 2020-2023  润新知