1. 程式人生 > 其它 >Deltix Round, Spring 2021 (open for everyone, rated, Div. 1 + Div. 2) D. Love-Hate

Deltix Round, Spring 2021 (open for everyone, rated, Div. 1 + Div. 2) D. Love-Hate

Deltix Round, Spring 2021 (open for everyone, rated, Div. 1 + Div. 2) D. Love-Hate

題目連結:https://codeforces.ml/contest/1523/problem/D

題意:

n (1≤n≤2*10^5)個朋友來聚會,總共有m (1≤p≤m≤60)個話題,每個朋友最多喜歡p (p≤15)個話題,你需要選擇一些話題,使得喜歡所有你選擇話題的人數大於等於總人數的一半。求最多可以選擇多少個話題,輸出一種方案。

題解:

一種隨機的思路。

最後得到的答案一定是某一個方案的子集,那麼列舉每一種方案去和其他的方案求交集似乎可行。

如何求解交集?

狀態壓縮,對p進行狀壓,將狀態設計成是否選擇模板中的每個1。

問題變為找出一個可行的方案,他被覆蓋到的次數超過(n + 1)/2,即給定一個狀態,如何快速列舉其子集並統計其出現的個數

狀態S和他所有父集被覆蓋的次數就是方案可以覆蓋的個數,對於每個狀態狀壓列舉其子集,然後維護一個cnt 陣列計數

所以考慮用狀壓 dp 去維護字首和的思想,假設現在有兩個狀態 i , j,滿足 i是 j的一個子集,即 i & j = j,則根據字首和的思想,可以有 cnt[j] += cnt[i]

所以以O(n * p)的複雜度處理初始的cnt陣列,然後O(p * 2 ^ p)狀壓dp維護一下字首和

解題程式碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) 
const int INF = 0x3f3f3f3f;
const int N = 1e6+100;
mt19937_64 eng(time(NULL));
LL s[N];
int cnt[(1<<15)+100];
int a[N];
int main(){
	int n,m,p;
	cin >> n >> m >> p;
	for(int i = 1;i <= n;i++) {
		string str;
		cin >> str;
		for(int j = 0;j < m;j++) {
			if(str[j] == '1') {
				s[i] |= (1LL << j);//將要求轉為數字儲存,方便異或計算 
			}
		}
	}
	iota(a + 1,a + 1 + n, 1);//將a陣列賦值為 1,2,3,4,5... 
	shuffle(a + 1,a + 1 + n,eng);//打亂順序 
	
	int ans = -1;
	string res;
	for(int t = 1;t <= min(50,n);t++) {
		memset(cnt,0,sizeof(cnt));//每回列舉重置 
		int p = a[t];//獲取隨機後的位置 
		vector<int>bits;//儲存1的位置 
		for(int j=0;j<m;j++) {//當前隨機到的資料裡哪裡有1 
			if((s[p] >> j) & 1) {
				bits.push_back(j);
			}
		}
		int sz = bits.size();
		for(int i = 1;i <= n;i++) { //n*p
			int state = 0;//取交集後的狀態 
			for(int j=0;j<sz;j++) {
				if((s[i] >> bits[j]) & 1) {
					state |= (1<<j);
				}
			}
			cnt[state]++;//這裡可以知道對於當前的狀態與後可一提供多少個 
		}
		
		//將子併到父中去 
		for(int j=0;j<sz;j++) {//p*2^p
			for(int i = 0;i < 1<<sz;i++) {
				if(((i>>j)&1)==0) {
					cnt[i] += cnt[i|(1<<j)];
				}
			}
		}
		
		//__builtin_popcount 計算二進位制中有幾個1 
		//列舉當前隨機方案的子集 
		for(int i = 0;i< 1 << sz;i++) {
			//如果當前方案滿足個數而且其中的1更多即選的更多 
			if(cnt[i] >= (n+1)/2 && __builtin_popcount(i) > ans) {
				ans = __builtin_popcount(i);//更新新的方案 
				res = string(m,'0');//將res初始化,長度為m個0 
				//將res賦值為當前狀態 
				for(int j = 0;j < sz;j++) {
					if((i>>j)&1) {
						res[bits[j]]='1';
					}
				}
			}
		}
	}
	cout << res << endl;
	return 0;
}