NEERC 2016年B題 Binary code

讀研以後好久沒有研究算法競賽的題目了。這段時間算法課,剛好作業是要講算法,就選了一道題目研究了一下,感覺還是蠻有意思的。但是說實話本弱雞太菜了,靠自己肯定解決不了這麼難的題,就參考了別人的代碼啦(這個也找了半天,有好多人的實現都沒看懂)。而且這個過程也接觸到了以前沒有接觸過的構圖技巧,叫什麼前後綴優化建圖,還是很有意義的。

題目鏈接(裏面B題):

https://codeforces.com/gym/101190

題意:

給定n個0/1串,每個串至多有一個問號。問你是否能夠找到方案,使得這些問號用0/1填充後,各個串之間互不爲前綴。如果是,輸出YES,並輸出方案。否則,輸出NO。

代碼主要參考:

https://blog.csdn.net/kzn2683331518/article/details/101431239

主要思路:

互不爲前綴,出現前綴這種字眼,可以聯想到字典樹。

對於問號的處理,用0替代插入字典樹,用1替代插入至字典樹中。

然後呢,替代字符串要麼取0要麼取1,對應於命題True/False,同時還有限制條件,即一個樹的分支上最多隻能有一個替代字符串,對分支上任意兩個串a、串b,有條件(not a) or (not b),這樣看就轉換成了2-sat問題。

關於字典樹,2-sat讀者可以知乎百度上搜搜了解下啦,或者也可以參考算法競賽入門經典訓練指南

(要是參加比賽的話私以爲2-sat可能出現比較少,但裏面用到的tarjan算法求有向圖強聯通分量這個更重要更普遍點,讀者最好理解這個算法)

然而事情還沒有結束。考慮極端情形,如果n個串在一個分支上,這樣加限制條件的話,限制條件數就有n的平方級別個,太多了,這個時候就有一種叫前後綴優化的東西~~~(點到爲止,主要看代碼註釋)

下面就是自己參考了別人代碼,寫了一遍,加了一點註釋的結果:

#include<bits/stdc++.h>
using namespace std;
const int maxn=4e6+10;
vector<int> G[maxn];
//loc[node]存儲替代問號以後,字符串終結點在node上的字符串序號 
vector<int> loc[maxn];
int sumn,n;
int no[maxn],lowlink[maxn],sccno[maxn],dfs_block,scc_cnt;
int wh[maxn],pos[maxn];
int ch[maxn][2],father[maxn],tot;
string str[maxn];
stack<int> S;
//加邊u->v,即若u則必須v 
void add_clause(int u,int v){
	G[u].push_back(v);
}
void link(int u,int v){
	add_clause(u,v);
	add_clause(v^1,u^1);  //逆否命題 
}
//tarjan算法中深度優先遍歷部分 
void dfs(int u) {
	no[u]=lowlink[u]=++dfs_block;
	S.push(u);
	for (int i=0;i<G[u].size();i++) {
		int v=G[u][i];
		if (!no[v]) {
			dfs(v);
			lowlink[u]=min(lowlink[u],lowlink[v]);
		} else if (!sccno[v]) {
			lowlink[u]=min(lowlink[u],no[v]);	
		}
	}
	if (lowlink[u]==no[u]) {
		scc_cnt++;
		for (;;) {
			int x=S.top();
			S.pop();
			sccno[x]=scc_cnt;
			if (x==u)
				break;
		}
	}
}
void two_sat_solve(int sumn,int n) {
	for (int i=0;i<=sumn;i++) {
		if (!no[i])
			dfs(i);
	}
	//sccno[x]表示x屬於的強聯通分量號 
	for (int i=1;i<=n;i++)
		if (sccno[i<<1]==sccno[i<<1|1]) {
			puts("NO");
			return;
		}
	puts("YES");
	for (int i=1;i<=n;i++) {
		//wh[i]表示字符串i中問號出現的位置,值爲-1則未出現 
		if (wh[i]!=-1)
			str[i][wh[i]]=(sccno[i<<1]<sccno[i<<1|1]?'0':'1');
		puts(str[i].c_str());
	}
}
//利用字典樹插入,返回各替代問號以後字符串終結點在樹中序號 
int trie_insert(const char *s) {
	int u=0;
	for (int i=0;s[i];i++) {
		int c=s[i]^48;  //根據s[i]爲'0'或'1'獲取c值0或1
		if (!ch[u][c]) {
			ch[u][c]=++tot;
			father[tot]=u;
		}	
		u=ch[u][c];
	}
	return u;  
}
//處理輸入 
void dealInput(){
	freopen("binary.in","r",stdin);
	freopen("binary.out","w",stdout);
	cin>>n;
	for (int i=1;i<=n;i++){
		cin>>str[i];
		int whpos=-1;
		int sz=str[i].size();
		for (int j=0;j<sz;j++)
			if (str[i][j]=='?'){
				whpos=j;
				break;
			}
		//第一種情況:字符串中沒有問號(也抽象成一個用0一個用1,但是終結點還是對應原字符串插入終結位置) 
		if (whpos==-1) {
			pos[i<<1]=pos[i<<1|1]=trie_insert(str[i].c_str());
			add_clause(i<<1|1,i<<1);
		}
		//第二種情況:字符串中有問號 
		else {
			//用0替代 
			str[i][whpos]='0';
			pos[i<<1]=trie_insert(str[i].c_str());
			//用1替代
			str[i][whpos]='1';
			pos[i<<1|1]=trie_insert(str[i].c_str()); 
		}
		wh[i]=whpos;
	}
}
//結束輸入處理後進行加邊操作,返回總結點數 
int deal_edge(){
	int s=(n<<1|1)+1;
	//樹上所有點node,若從根到father[node]上選1個替代字符串點,則從根到node選1個替代字符串點 
	for (int node=1;node<=tot;node++)
		link(s+(father[node]<<1),s+(node<<1));
	for (int i=1;i<=n;i++) {
		int tnode0=pos[i<<1];
		int tnode1=pos[i<<1|1];
		if (wh[i]==-1) {
			loc[tnode0].push_back(i<<1);   
			link(i<<1,s+(father[tnode0]<<1|1));   //字符串i用0替代問號,root~father[tnode0]上不能再有替代字符串點 
			link(i<<1,s+(tnode0<<1));  //字符串i用0替代問號,root~tnode0上有1個替代字符串點
		}else{
			loc[tnode0].push_back(i<<1);   
			link(i<<1,s+(father[tnode0]<<1|1));   
			link(i<<1,s+(tnode0<<1));  
			loc[tnode1].push_back(i<<1|1);     //用1替代,情況類似 
			link(i<<1|1,s+(father[tnode1]<<1|1));  
			link(i<<1|1,s+(tnode1<<1));  
		}
	}
	s+=(tot<<1|1)+1;
	for (int node=1;node<=tot;node++) {
		//noden存儲node上替代字符串點的數目,顯然根據題意只能從裏面選1個 
		int noden=loc[node].size();
		//node上沒有替代字符串點,不處理 
		if (!noden)  
			continue;
		for (int i=0;i<noden;i++){
			if (i) {
				link(s+(i-1<<1),s+(i<<1));  //這個node上前i-1個裏選了1個,則前i個裏選1個 
				link(loc[node][i],s+((i-1<<1)|1));  //這個node上選了第i個點,前i-1個裏面不能再選 
			}
			link(loc[node][i],s+(i<<1));   //這個node上選了第i個點,則前i個裏選1個
		}
		s+=noden<<1;
	}
	return s;
}
int main() {
	dealInput(); 
	int sumn=deal_edge();
	two_sat_solve(sumn,n);
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章