CF1503F Balance the Cards【构造+平面几何】
CF1503F Balance the Cards
题面
给定 2 n 2n 2n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 1\le n\le 2\cdot 10^5 1≤n≤2⋅105) 对数 a i a_i ai, b i b_i bi,其中 a i a_i ai 和 b i b_i bi 分别是 1 , − 1 , 2 , − 2 , … , n , − n 1,-1,2,-2,\ldots,n,-n 1,−1,2,−2,…,n,−n 的排列。 x > 0 x>0 x>0 表示第 x x x 种左括号, x < 0 x<0 x<0 表示第 − x -x −x 种右括号。你需要将这 2 n 2n 2n 对数重新排列,使得 a i a_i ai 和 b i b_i bi 分别是合法括号序列。
简要题解
把每一张牌看成一个点,从 x x x 向 − x -x −x 连边(存在 a , b a,b a,b 两类边),每个点的度数都是 2 2 2 ,所以得到的图会是若干个环组成,而且 a a a 和 b b b 的边交替出现。
考虑构造,如果存在 u → v → w u\to v\to w u→v→w 则可以将这三个点合并成一个点 ,例如 ( a , b ) → ( c , − b ) → ( − c , d ) (a,b)\to (c,-b)\to (-c,d) (a,b)→(c,−b)→(−c,d)可以合并成 ( a , d ) (a,d) (a,d) 。重复这个操作,如果最后能缩成只剩两个点 ( a , b ) , ( − a , − b ) (a,b),(-a,-b) (a,b),(−a,−b) 则构造出一组解。
证明这样构造是充要的:
这样构造能求出解的条件是,给一个环任意指定一个正方向为顺时针,顺时针方向的
a
a
a 类边和
b
b
b 类边的边数相差为
1
1
1 (因为每次合并三个点就是删掉了某个方向上的一条
a
a
a 边一条
b
b
b 边)。首先这是充分的,接下来证明必要性。
考虑一个合法的序列,在
x
x
x 和
−
x
-x
−x 之间画一个半圆,
a
a
a 在下面,
b
b
b 在上面。因为是合法括号序列,每个连通块都是简单回路。考虑一个点沿着回路走一圈面对的方向越过
x
x
x 轴正方向的次数就可以知道上文所说的条件是必要的。
#include <bits/stdc++.h>
#define N 800015
using namespace std;
const int o=200005;
int n,pos_a[N],pos_b[N],cnt;
struct node{
int a,b,u,v,w,id;
node(int A,int B,int U=0,int V=0,int W=0){ a=A,b=B,u=U,v=V,w=W; }
};
queue<int> q;
vector<node> ans,cc;
bool tag[N];
int to(int x){ return o+o-x; }
bool cmp(node x,node y){
return abs(x.a-o)==abs(y.a-o)?abs(x.b-o)<abs(y.b-o):abs(x.a-o)<abs(y.a-o);
}
void print(int x){
if(cc[x].u==0) cout<<cc[x].a-o<<' '<<cc[x].b-o<<'\n';
else print(cc[x].u),print(cc[x].v),print(cc[x].w);
}
int main(){
cin>>n;
int x,y; cc.push_back(node(0,0));
for(int i=1;i<=n+n;i++){
cin>>x>>y; x+=o,y+=o;
++cnt,cc.push_back(node(x,y)); pos_a[x]=i,pos_b[y]=i;
cc[i].id=i;
if((x<o&&o<y)||(y<o&&o<x)) q.push(i);
}
while(!q.empty()){
x=q.front(); q.pop();
if(tag[x]) continue;
int u=pos_a[to(cc[x].a)],v=pos_b[to(cc[x].b)];
if(u==v){ cout<<"NO"; return 0; }
if(cc[x].a>o) ++cnt,cc.push_back(node(cc[v].a,cc[u].b,v,x,u));
else ++cnt,cc.push_back(node(cc[v].a,cc[u].b,u,x,v));
tag[v]=tag[u]=tag[x]=1;
pos_a[cc[cnt].a]=cnt,pos_b[cc[cnt].b]=cnt;
cc[cnt].id=cnt;
if((cc[cnt].a<o&&o<cc[cnt].b)||(cc[cnt].b<o&&o<cc[cnt].a))
q.push(cnt);
}
for(int i=1;i<=cnt;i++) if(!tag[i]) ans.push_back(cc[i]);
sort(&ans[0],&ans[ans.size()],cmp);
for(int i=0;i<ans.size();i+=2){
if(ans[i].a!=to(ans[i+1].a)||ans[i].b!=to(ans[i+1].b)){ cout<<"NO"; return 0; }
if(ans[i].a<o&&ans[i].b>o){ cout<<"NO"; return 0; }
if(ans[i].a>o&&ans[i].b<o){ cout<<"NO"; return 0; }
if(ans[i].a<o) swap(ans[i],ans[i+1]);
}
cout<<"YES\n";
for(int i=0;i<ans.size();i++) print(ans[i].id);
}