1. 程式人生 > 實用技巧 >arc 045 d 題解

arc 045 d 題解

arc 045 d

首先,有解的充要條件是什麼?

若我們將橫座標一樣的merge起來,縱座標一樣的merge起來。有解的必要條件是每一個聯通塊的大小是偶數。那麼怎麼證明是充分的呢?

我們可以將這些點一層一層的畫出來:

若一行是沒有連的點個數是偶數,就直接兩兩消除。不然就讓那個下面有點的剩下來。

不過有一種情況需要注意,就是那個下面有點的和上面連過了(上圖的最後四個點)這種情況就將上面的那個移下來。

這樣我們可以將點看作邊,連線了y行和x列。

這樣問題就轉化成:刪除某一條邊,判斷剩下的每一個聯通塊內的邊的個數是否都是偶數。

到這裡就有了兩種做法:

1. 線段樹+可撤銷並查集

將每一條邊出現的時間弄到線段樹上。然後一條一條的加邊,回退的時候撤銷操作。期間用並查集維護連通性,和邊的條數。

const int MAXN=100000+20;
bool rest[MAXN*2];
int n,x[MAXN*2],y[MAXN*2];
struct DSU{
	int fa[MAXN*4],val[MAXN*4],Rank[MAXN*4];
	int cnt;
	void init(int N){
		rb(i,1,N)
			fa[i]=i,val[i]=0,Rank[i]=1;
	}
	DSU(){cnt=0;}
	int root(int u){
		if(fa[u]==u) return u;
		return root(fa[u]);
	}
	stack<int> Operation;
	stack<mp> Oldval;
	void merge(int u,int v){
		u=root(u);
		v=root(v);
		if(u==v){
			Operation.push(u);
			Oldval.push(II(u,val[u]));
			Oldval.push(II(v,val[v]));
			return;
		}
		if(Rank[u]>Rank[v]){
			Operation.push(v);
			Oldval.push(II(u,val[u]));
			Oldval.push(II(v,val[v]));
			fa[v]=u;
			cnt-=val[v];
			cnt-=val[u];
			val[u]^=val[v];
			cnt+=val[u];
			val[v]=0;
		}
		else{
			if(Rank[u]==Rank[v]){
				Oldval.push(II(u,val[u]));
				Oldval.push(II(v,val[v]));
				Operation.push(-v);
				Rank[u]++;
				fa[v]=u;
				cnt-=val[v];
				cnt-=val[u];
				val[u]^=val[v];
				cnt+=val[u];
				val[v]=0;
			}
			else{
				Operation.push(u);
				Oldval.push(II(u,val[u]));
				Oldval.push(II(v,val[v]));
				fa[u]=v;
				cnt-=val[v];
				cnt-=val[u];
				val[v]^=val[u];
				cnt+=val[v];
				val[u]=0;
			}
		}
	}
	void add(int u){
		u=root(u);
		cnt-=val[u];
		val[u]^=1;
		cnt+=val[u];
	}
	void undo(){
		assert(!Operation.empty());
		int v=Operation.top();
		if(v<0){
			Rank[fa[-v]]--;
		}
		fa[abs(v)]=abs(v);
		Operation.pop();
		int X,Y;
		X=Oldval.top().FIR;
		Y=Oldval.top().SEC;
		cnt-=val[X];
		val[X]=Y;
		cnt+=Y;
		Oldval.pop();
		X=Oldval.top().FIR;
		Y=Oldval.top().SEC;
		Oldval.pop();
		cnt-=val[X];
		val[X]=Y;
		cnt+=Y;
	}
}dsu;
const int N=1<<18;
vector<mp> tree[N+N];
int Limit;
void add_edge(int u,int v,int a,int b,int now=1,int l=1,int r=N+1){
	if(r<=a||l>=b) return ;
	if(r<=b&&l>=a){
		tree[now].PB(II(u,v));
		return ;
	}
	int mid=(l+r)>>1;
	add_edge(u,v,a,b,now<<1,l,mid);
	add_edge(u,v,a,b,now<<1|1,mid,r);
}
void run(int now=1,int l=1,int r=N+1){
	if(l>2*n+1) return;
	for(auto it:tree[now]){
		dsu.merge(it.FIR,it.SEC);
	}
	for(auto it:tree[now]){
		dsu.add(it.SEC);
	} 
	if(l==r-1){
		rest[l]=(dsu.cnt==0);
	}
	else{
		int mid=(l+r)>>1;
		run(now<<1,l,mid);
		run(now<<1|1,mid,r);
	}
	for(auto it:tree[now]){
		dsu.add(it.SEC);
	}
	for(auto it:tree[now]){
		dsu.undo();
	}
} 
int main(){
	scanf("%d",&n);
	for(int i=1;i<=2*n+1;++i){
		scanf("%d%d",&x[i],&y[i]);
	}
	dsu.init(4*n+2);
	Limit=2*n+1;
	for(int i=1;i<=2*n+1;++i){
		if(i!=1)
			add_edge(x[i],y[i]+Limit,1,i);
		if(i!=2*n+1)
			add_edge(x[i],y[i]+Limit,i+1,2*n+2);
	}
	run();
	for(int i=1;i<=2*n+1;++i){
		puts(rest[i]? "OK":"NG");
	}
	return 0;
}

2. 用邊雙縮點

顯然非橋邊不會影響連通性,只會影響奇偶性。

橋邊會斷開某些聯通塊,這種情況需要記錄兩邊的奇偶性。

yutaka1999:

using namespace std;
typedef long long int ll;
typedef pair <int,int> P;

struct UF
{
	int par[SIZE],rank[SIZE];
	
	void init(int n)
	{
		for(int i=0;i<n;i++)
		{
			par[i]=i;
			rank[i]=0;
		}
	}
	int find(int x)
	{
		if(par[x]==x) return x;
		return par[x]=find(par[x]);
	}
	void unite(int x,int y)
	{
		x=find(x);
		y=find(y);
		if(x==y) return;
		if(rank[x]<rank[y])
		{
			par[x]=y;
		}
		else
		{
			par[y]=x;
			if(rank[x]==rank[y]) rank[x]++;
		}
	}
	bool same(int x,int y)
	{
		return find(x)==find(y);
	}
};
struct edge
{
	int to,id;
	edge(int to=0,int id=0):to(to),id(id){}
};
UF uf;
vector <edge> vec[SIZE];
vector <edge> tree[SIZE];
vector <int> nd[SIZE];
int left[SIZE],right[SIZE];
int low[SIZE],ord[SIZE];
int cnt[SIZE];
bool ok[SIZE],use[SIZE],vis[SIZE];
int now_id;

void lowlink(int v,int p=-1)
{
	use[v]=true;
	low[v]=ord[v]=now_id++;
	for(int i=0;i<vec[v].size();i++)
	{
		edge e=vec[v][i];
		if(e.to!=p)
		{
			if(!use[e.to])
			{
				lowlink(e.to,v);
				low[v]=min(low[v],low[e.to]);
			}
			else
			{
				low[v]=min(low[v],ord[e.to]);
			}
		}
	}
}
bool bridge(int s,int t)//true なら橋
{
	if(ord[s]>ord[t]) swap(s,t);//ord[s]<=ord[t]
	return low[t]>ord[s];
}
void dfs(int v,int p=-1)
{
	vis[v]=true;
	for(int i=0;i<nd[v].size();i++) ok[nd[v][i]]=true;
	for(int i=0;i<tree[v].size();i++)
	{
		edge e=tree[v][i];
		if(e.to!=p)
		{
			dfs(e.to,v);
			cnt[v]+=cnt[e.to]+1;
			if(cnt[e.to]%2==0) ok[e.id]=true;
		}
	}
}
int sz(int v,int p=-1)
{
	vis[v]=true;
	int ret=cnt[v];
	for(int i=0;i<tree[v].size();i++)
	{
		edge e=tree[v][i];
		if(e.to!=p)
		{
			ret+=sz(e.to,v);
			ret++;
		}
	}
	return ret;
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<2*n+1;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);x--;y--;
		left[i]=x,right[i]=y+2*n+1;
		vec[left[i]].push_back(right[i]);
		vec[right[i]].push_back(left[i]);
	}
	for(int i=0;i<2*(2*n+1);i++)
	{
		if(!use[i])
		{
			lowlink(i);
		}
	}
	uf.init(4*n+5);
	for(int i=0;i<2*n+1;i++)
	{
		if(!bridge(left[i],right[i]))
		{
			uf.unite(left[i],right[i]);
		}
	}
	for(int i=0;i<2*n+1;i++)
	{
		if(bridge(left[i],right[i]))
		{
			int l=uf.find(left[i]);
			int r=uf.find(right[i]);
			tree[l].push_back(edge(r,i));
			tree[r].push_back(edge(l,i));
		}
		else
		{
			int v=uf.find(left[i]);
			nd[v].push_back(i);
			cnt[v]++;
		}
	}
	int ct=0,pos=-1;
	for(int i=0;i<2*(2*n+1);i++)
	{
		if(!vis[i]&&uf.find(i)==i)
		{
			if(sz(i)%2==1)
			{
				ct++;
				pos=i;
				dfs(i);
			}
		}
	}
	if(ct>1)
	{
		memset(ok,false,sizeof(ok));
	}
	for(int i=0;i<2*n+1;i++)
	{
		if(ok[i])
		{
			puts("OK");
		}
		else
		{
			puts("NG");
		}
	}
	return 0;
}