[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));
}
posted @ 2022-06-12 09:04  C202044zxy  阅读(261)  评论(4编辑  收藏  举报