The 8th Hebei Collegiate Programming Contest: F - 3 Split

nkxjlym發表於2024-09-20

給定n個點的競賽圖,給出一個方案把n個點分為A,B,C三個非空集合,使得所有的邊要麼連線著相同集合的點,要麼從A指向B,要麼從B指向C,要麼從C指向A。

考慮欽定點 1 屬於 B 集合,考察 1 的所有邊。以 1 為起點的邊,其終點必然屬於 B 或 C 中的一個;同理,以 1 為終點的邊,其起點必然屬於 B 或 A 中的一個。並且,競賽圖保證了所有點都與 1 有邊,即上述討論可以覆蓋到所有點。那麼除 1 外的每個點都有兩種選擇,可以轉化為 2-SAT 問題求解,簡單分析可以知道每一條與 1 無關的邊都可以轉換為 2-SAT 上的一個限制。時間為 \(O(n^2)\)

換個思路,其實競賽圖+非空集合這兩個限制疊加在一起是非常緊的。我們可以透過考察三個點的兩兩關係證明如果存在方案,那麼方案在輪換等價的意義上是唯一的。

依然考慮欽定 1 屬於 B,並假設存在一個合法方案。想要構造一個新方案,不失一般性,假設至少有一個原本屬於 C 的點變得不再屬於 C。任意選取集合 A 中的一個點 a,集合 C 中的一個點 c,則一定有 \(a\to 1\)\(1\to c\)\(c\to a\)。如果 c 在新方案中不再屬於 C,因為有 \(1\to c\),那麼它只能屬於 B,由於存在 \(c\to a\),那麼 a 也必須改為屬於 B。由於 a 的任意性,原 A 集合內的所有點都必須改為 B。類似的,現在 a 被改為了 B,那麼集合 C 中的所有點也必須改為 B。也就是說,要想這個新方案合法,原 B 集合必須有一些點改為 A 和 C,但這也是不可能的。因為現在 a 和 c 都屬於了 B,如果有另一個屬於 B 的 b(要求 |B|>1) 變成了屬於 A,則不符合 \(a\to b\),同理這個 b 也不能屬於 C。綜上,要構造一個新方案,至少有一個原本屬於 A 或 C 的點會變為屬於 B,進一步會推出 A 和 C 集合必須為空,故不存在合法新方案。

於是可以欽定 \(1\in B\),列舉 \(x\in A\)\(y\in C\) 滿足是一個三元環,然後將剩下的點一個個加入進去。如果存在一個合法方案且這個方案中 \(1\in B\)\(x\in A\)\(y\in C\),那麼其所有頂點包含 1,x 和 y 的子圖也都存在合法方案且唯一,所以每次加入也最多有一種合法方式,一旦無法放入,說明最終的合法方案中 \((x,1,y)\) 不是分屬於 A B C,而是同屬於 B。那麼就換一對 \((x,y)\) 重複上述步驟。這樣做複雜度是 \(O(n^4)\)

為了找到正確的三元組 \((x,1,y)\),在沒有合法方案的最壞情況下需要列舉所有 \((x,y)\),這一步花了太多時間。我們考慮最佳化這一點。假設我們找到了第一個 \((x,1,y)\),此時如果存在合法方案並且 \((x,1,y)\) 在方案中並非分屬於 A B C(即同屬於 B),那麼說明存在一個 \((x',y')\),使得 \(1,x,y\in B\)\(x'\in A\)\(y'\in C\) 。所以我們不斷地去找有沒有可以把之前列舉的所有點放在 B 裡的新的 \(x'\)\(y'\),一旦找不到了,就說明在存在合法方案的情況下,最後列舉到的 \(x'\)\(y'\) 一定就在方案中分別屬於 A 和 C,而之前列舉的所有點包括 1 都屬於 B,然後再一個個把剩下的點加入即可,複雜度可以做到 \(O(n^2)\)
題解裡寫到用 random-shuffle 做到 n 方,不過沒怎麼搞清楚題解的意思。

這裡給出第二種做法的程式碼

#include<bits/stdc++.h>
using ll=long long;
using namespace std;
void solve();
int main(){
	int t=1;
	// cin>>t;
	while(t--)solve();
	return 0;
}


const int N=503;
int a[N][N];
int st[N];
void solve(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++){
			cin>>a[i][j];
			a[j][i]=a[i][j]^1;
		}
	for(int i=2;i<=n;i++){
		if(a[i][1])st[i]=1;
		else st[i]=2;
	}
	auto add=[&](int u){
		st[u]=0;
		for(int i=1;i<=n;i++)if(st[i]){
			if(st[i]==1&&a[u][i])st[i]=3;
			if(st[i]==2&&a[i][u])st[i]=3;
		}
	};
	int x=-1,y=-1; // x->1->y
	for(int i=2;i<=n;i++)if(st[i]==1){
		for(int j=2;j<=n;j++){
			if(a[j][i]&&st[j]==2){
				add(i);
				add(j);
				x=i;y=j;
				break;
			}
		}
	}
	if(x==-1){
		cout<<"0 0 0\n";
		return;
	}
	st[x]=-1;//A
	st[y]=-2;//C
	vector<int>A,B,C;
	A.push_back(x);
	C.push_back(y);
	for(int i=1;i<=n;i++)if(st[i]==0)B.push_back(i);
	for(int i=1;i<=n;i++){
		if(st[i]>0){
			int wb=a[i][1],wa=a[i][x],wc=a[i][y];
			for(auto j:A)if(wa!=-1&&(wa^a[i][j])){
				wa=-1;break;
			}
			for(auto j:B)if(wb!=-1&&(wb^a[i][j])){
				wb=-1;break;
			}
			for(auto j:C)if(wc!=-1&&(wc^a[i][j])){
				wc=-1;break;
			}
			if(wb==1&&wc==0)A.push_back(i);
			else if(wc==1&&wa==0)B.push_back(i);
			else if(wa==1&&wb==0)C.push_back(i);
			else{
				// cerr<<"!\n";
				cout<<"0 0 0\n";
				return;
			}
		}
	}
	cout<<A.size()<<" "<<B.size()<<" "<<C.size()<<endl;
	for(auto j:A)cout<<j<<" ";cout<<endl;
	for(auto j:B)cout<<j<<" ";cout<<endl;
	for(auto j:C)cout<<j<<" ";cout<<endl;
}

相關文章