[JOISC 2017 Day1] 港口设施
一、题目
二、解法
首先考虑只有一个港口的情况,发现合法的充要条件是所有 \([a,b]\) 不交。
由于两个港口是独立的,所以问题转化成:求出有多少种二染色方案,使得同色的线段不交。我们把满足 \(a<c<b<d\) 的线段 \([a,b]\) 和 \([c,d]\) 之间连一条边,然后问题就变成了求连通块个数或者判断无解。
暴力连边是 \(O(n^2)\) 的,这东西貌似不太好线段树优化建图。我们考虑按右端点从小到大扫描所有线段 \([a,b]\),并且维护右端点更大的线段组成的集合 \(S\),那么当前点连接的点是 \(S\) 中的一段连续区间(左端点在 \([a,b]\) 中)
一个关键的 \(\tt observation\) 是:一段区间被连接后的效果是,区间中的点必然同色。
所以可以维护 \(nx_i\) 表示和点 \(i\) 同色的点最多延伸到哪里,那么修改变成暴力访问一段区间,并且把这段区间的 \(nx\) 全部指向区间的右端点。这等价于访问一条链并且修改它们的 \(fa\) 为链顶,所以可以套用并查集路径压缩的复杂度分析。
具体实现中,把左右端点拍到数轴上,然后扫描这个数轴。如果遇到左端点就把它加入 \(S\) 中,如果遇到右端点就区间连边,并且把它从 \(S\) 中删除,维护 \(S\) 也可以用并查集。时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <vector>
using namespace std;
const int M = 2000005;
const int MOD = 1e9+7;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,a[M],b[M],c[M],l[M],nx[M],fa[M];
vector<int> g[M];
int find(int x)
{
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
int qkpow(int a,int b)
{
int r=1;
while(b>0)
{
if(b&1) r=1ll*r*a%MOD;
a=1ll*a*a%MOD;
b>>=1;
}
return r;
}
int dfs(int u)
{
for(int v:g[u])
{
if(~c[v])
{
if(c[v]==c[u]) return 0;
continue;
}
c[v]=c[u]^1;if(!dfs(v)) return 0;
}
return 1;
}
signed main()
{
n=read();
for(int i=1;i<=n;i++)
b[read()]=b[read()]=i,c[i]=-1;
for(int i=1;i<=2*n;i++)
fa[i]=nx[i]=i;
for(int i=1;i<=2*n;i++)
{
int u=b[i];
if(!l[u]) {a[++m]=u,l[u]=m;continue;}
for(int j=fa[l[u]]=find(l[u]+1);j<=m;)
{
g[a[j]].push_back(u);
g[u].push_back(a[j]);
int t=find(nx[j]+1);nx[j]=m;j=t;
}
}
for(int i=1;i<=n;i++)
if(c[i]==-1 && (k++,c[i]=0,!dfs(i)))
{puts("0");return 0;}
printf("%d\n",qkpow(2,k));
}