这道题一开始的想法是瞎贪心一波,建张图出来,然后跑带花树,看看是否有完美匹配。
描述一下如何建图,首先贪心,如果连接一个已知点对的半圆方向能向上就向上,否则向下。
再引入一个概念,对于一个未确定点
P
,定义
考虑存在一个方向
dx
使得
f(dx,P1)
等于
f(dx,P2)
的点对
(P1,P2)
之间连一条边,跑下带花树,我的算法就结束啦。
下面是正解的开始。
定义
g(b)
为半圆b所包含的点集,可以证明前面的图存在完美匹配就等价与所有的
g(b)
大小都是偶数。
并且,前述的贪心建图,是不对的,应当枚举。
枚举完了之后,可以通过前缀和的思想将问题转化为二分图判定,复杂度最坏
O(2n/2n)
。理论上是不能过的,但是TC上8086ms硬给卡过去了,实际上可以用些特技来加速。
下面是蒟蒻的代码:
#include<cstring>
#include<iostream>
#include<cstdio>
#include<string>
#include<vector>
const int N=115;
class DisjointSemicircles{
int f[N],n,i,j,o,l[N],r[N],cnt,a[N],id[N],st[2][N],w[2],rr[N],ll[N];
static const int D=53;
inline int gfa(int x){return f[x]==x?x:f[x]=gfa(f[x]);}
inline void dif(int x,int y){//different
f[gfa(x)]=gfa(y+D);f[gfa(x+D)]=gfa(y);
}
inline void equ(int x,int y){//equal
f[gfa(x)]=gfa(y);f[gfa(x+D)]=gfa(y+D);
}
public:inline std::string getPossibility(std::vector<int>labels){
n=labels.size();memset(l,-1,sizeof l);memset(r,-1,sizeof r);memset(id,-1,sizeof id);
memset(ll,-1,sizeof ll);memset(rr,-1,sizeof rr);
for(i=0;i<n;++i)if(~labels[i]){
if(id[labels[i]]==-1)id[labels[i]]=cnt++;j=id[labels[i]];
if(~l[j])rr[ll[i]=l[j]]=r[j]=i;
else l[j]=i;
}
if(!cnt)return "POSSIBLE";
//for(i=0;i<n;++i)printf("%d ",ll[i]);putchar('\n');
//for(i=0;i<n;++i)printf("%d ",rr[i]);putchar('\n');
for(i=0;i<1<<cnt;++i){
for(j=0;j<=n;++j)f[j]=j,f[j+D]=j+D;
for(j=0,w[0]=w[1]=0;j<n;++j)if(~labels[j]){
o=i>>id[labels[j]]&1;
if(~rr[j]){
if(w[o] && st[o][w[o]]<rr[j])break;st[o][++w[o]]=rr[j];
}else if(~ll[j])--w[o];
}
if(j<n)continue;
for(j=0;j<cnt;++j)if(l[j]!=r[j]){
if(i>>j&1){
dif(l[j],l[j]+1);
dif(r[j],r[j]+1);
}else{
equ(l[j],l[j]+1);
equ(r[j],r[j]+1);
}
if(l[j]==r[j]-1)continue;
if(i>>j&1){
equ(l[j]+1,r[j]);
}else{
if((r[j]-l[j]-1)&1)dif(l[j]+1,r[j]);
else equ(l[j]+1,r[j]);
}
}
for(j=0,equ(0,n);j<=n && gfa(j)!=gfa(j+D);++j);
if(j>n)return /*printf("%d\n",i),*/"POSSIBLE";
}
return "IMPOSSIBLE";
}
};