1. 程式人生 > 實用技巧 >【正睿2019暑假集訓】正睿891 蔡老闆與豪宅

【正睿2019暑假集訓】正睿891 蔡老闆與豪宅

題目連結

前置知識:fwt

提前約定:設\(S_i\)表示裝修隊\(i\)能裝修的點集;\(E_i\)表示裝修隊\(i\)能裝修的邊集,也就是\(S_i\)這個點集所生成的完全圖的邊集。

因為裝修隊的數量\(m\)很小,考慮狀壓DP。設\(dp[s]\)表示已經請了集合\(s\)裡的這些裝修隊,能實現的最大花費。

考慮預處理出\(f[s]\)表示集合\(s\)裡的裝修隊,能裝修的邊集的並的大小。也就是\(f[s]=\left|\bigcup_{i\in s}E_i\right|\)。那我們的DP就可以有如下轉移:

\[dp[s]=\max_{i\in s}\{dp[s\setminus i]+\text{cost}_i(f[s]-f[s\setminus i])\} \]

其中\(i\)表示列舉新添加了哪個裝修隊。\(\text{cost}_i(e)\)表示裝修隊\(i\),新裝修了\(e\)條邊,所收取的費用,也就是\(\text{cost}_i(e)=(a_ie^2+b_ie+c_i)\bmod2^{32}\)

DP的時間複雜度是\(O(2^mm)\),可以接受。於是問題轉化為求\(f[s]\)

考慮容斥:

\[\begin{align} f[s]&=\left|\bigcup_{i\in s}E_i\right|\\ &=\sum_{s'\subset s}(-1)^{|s'|+1}\left|\bigcap_{i\in s'}E_i\right| \end{align} \]

發現\(\left|\bigcap_{i\in s'}E_i\right|\)(也就是“邊集交”)的大小,之和點集交有關。具體來說,設\(g[s]\)表示集合\(s\)裡的裝修隊,能裝修的點集的交的大小,也就是\(g[s]=\left|\bigcap_{i\in s}S_i\right|\)。那麼\(\left|\bigcap_{i\in s'}E_i\right|={g[s]\choose 2}\)

如果知道了\(g\),那麼求\(f\),就相當於做高維字首和,可以用fwt or卷積,在\(O(\text{len}\log \text{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;
}