arc 045 d 題解
阿新 • • 發佈:2020-12-03
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; }