UOJ356 JOI2017春季合宿 Port Facility

Problem

UOJ

Solution

这题和POI的Railway感觉好像,虽然最终做法并不一样。。
由于代码稍微有点难写,这里就先口胡一下吧 ,代码大概是咕咕咕了

当两个线段相交时它们不能放在同一个栈中,判定能否二分图染色,然后计算联通块个数 t o t tot tot,则答案就是 2 t o t 2^{tot} 2tot。我们可以沿用Railway的思路,先搞出一棵生成树,二分图染色后模拟判定是否合法。

对于一个点,它对应两个集合,正集和反集,正集表示和它放同一个栈的线段,反集表示不能放同一个栈的。那么我们的问题就变成了如何维护这两个关系。

首先一个点的正集只有它自己,反集为空。考虑把一个点映射到平面上,表示为 ( l i , r i ) (l_i,r_i) (li,ri)。对于点 i i i,和这个点有连边的必须满足 l x &lt; l i &lt; r x &lt; r i l_x&lt;l_i&lt;r_x&lt;r_i lx<li<rx<ri ,那么可以考虑把线段按 l i l_i li 排序后进行插入,这样第一个不等号必然满足,所有连边的点都会存在于 y ∈ ( l i , r i ) y\in (l_i,r_i) y(li,ri) 之中。

注意到 l i l_i li 是单调递增的,那么可以对所有加入的并查集按 r i r_i ri 维护一个小根堆,如果它小于当前 l i l_i li 就不可能再参与连边了,直接删除,否则连边。连边就是把正集和它的反集合并,反集和它的正集合并。然而我们不能每次暴力扫所有的并查集,不妨把这些小根堆的堆顶都拿出来,再建一棵小根堆,这样就可以保证复杂度了。合并用左偏树支持即可。

最后模拟用栈模拟一遍进站出站的事件即可判定。势能分析一下,这两种堆每个点都只会进一次,时间复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn),常数比set啥的小,空间复杂度优秀 ,代码难写


在UOJ的统计里翻到了奥妙重重的 O ( n ) O(n) O(n) 解法。

可以从网上的set解法中出发,把线段按照 l i l_i li 有序地放入链表中,按 r i r_i ri 来进行处理。这里同样要维护正集和反集。那么每条线段的作用就是与链表中一段连续的区间中所有线段连边。为了减少冗余操作,我们就希望能跳过已经被连过边的线段,显然保留最右边的线段即当前这条最优。链表可以方便地用并查集实现,代码中的数组为nt。

Code

#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=2000010,mod=1e9+7;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    if(f) x=-x;
}
int n,ans,top,t[maxn],fa[maxn],lnk[maxn],nt[maxn],stk[maxn],pos[maxn];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int nxt(int x){return nt[x]==x?x:nt[x]=nxt(nt[x]);}
int merge(int x,int y)
{
	int fx=find(x),fy=find(y),dx=find(x+n),dy=find(y+n);
	if(fx==fy||dx==dy) return 0;
	fa[dy]=fx;fa[dx]=fy;
	return 1;
}
int power(int x,int y)
{
	int res=1;
	for(;y;y>>=1,x=(ll)x*x%mod)
	  if(y&1)
	    res=(ll)res*x%mod;
	return res;
}
void input()
{
	int l,r;
	read(n);
	for(int i=1;i<=n;i++)
	{
		read(l);read(r);
		t[l]=t[r]=i;
	}
}
int main()
{
	input();
	for(int i=n<<1;i;i--){fa[i]=i;nt[i]=i;lnk[i]=i+1;}
	for(int i=1;i<=(n<<1);i++)
	{
		if(!pos[t[i]]){stk[++top]=t[i];pos[t[i]]=top;continue;}
		int x=nxt(pos[t[i]]+1),y;
		while(x<=top)
		{
			if(!merge(t[i],stk[x])){puts("0");return 0;}
			y=nxt(lnk[x]);lnk[x]=top+1;x=y;
		}
		nt[pos[t[i]]]=pos[t[i]]+1;
	}
	for(int i=n<<1;i;i--) if(find(i)==i) ++ans;
	ans=power(2,ans>>1);
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值