【牛客CSP-S提高组赛前集训营1】C - 小w的魔术扑克【并查集】

题目大意:

题目链接:https://ac.nowcoder.com/acm/contest/1100/C

小w喜欢打牌,某天小w与dogenya在一起玩扑克牌,这种扑克牌的面值都在1到n,原本扑克牌只有一面,而小w手中的扑克牌是双面的魔术扑克(正反两面均有数字,可以随时进行切换),小w这个人就准备用它来出老千作弊。小w想要打出一些顺子,我们定义打出一个l到r的顺子需要面值为从l到r的卡牌各一张。小w想问问你,他能否利用手中的魔术卡牌打出这些顺子呢?


思路:

巧妙。
想象如果一张牌的两个数字分别为x,yx,y,我们将xxyy连边,那么最终就会形成几堆连通块。
如果一个连通块是一棵树,那么由于每一条边都可以选一个数字,那么显然不可以把这个连通块里的所有数字都选择上。
所以我们记录每一个连通块的边数、最大值和最小值,如果maxminsizemax-min\geq size,那么久不可能构成一个minmaxmin\sim max的顺子,也就是所有包含[min,max][min,max]的询问都不可以选择成功。
那么将每一棵树按照最小值排序,设last[i]last[i]表示从ii开始最多可以接到多少的顺子,那么从mm枚举到1,在每一个minimin\geq i的区间内取最小的maxmax,那么last[i]=max1last[i]=max-1
然后就可以每次询问O(1)O(1)回答了。


代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=100010;
int m,n,sum,pos,l,r,father[N],maxn[N],minn[N],size[N],last[N];

struct node
{
	int l,r;
}q[N];

bool cmp(node x,node y)
{
	return x.l<y.l;
}

int find(int x)
{
	return x==father[x]?x:father[x]=find(father[x]);
}

int main()
{
	//freopen("ex.in","r",stdin);
	//freopen("ans.txt","w",stdout);
	scanf("%d%d",&m,&n);
	for (int i=1;i<=m;i++)
		father[i]=maxn[i]=minn[i]=i;
	for (int i=1,x,y;i<=n;i++)
	{
		scanf("%d%d",&x,&y);
		x=find(x); y=find(y);
		if (x!=y)
		{
			maxn[x]=max(maxn[x],maxn[y]);
			minn[x]=min(minn[x],minn[y]);
			size[x]+=size[y]+1;
			father[y]=x;
		}
		else size[x]++;
	}
	for (int i=1;i<=m;i++)
		if (father[i]==i && size[i]<=maxn[i]-minn[i])
			q[++sum].l=minn[i],q[sum].r=maxn[i];
	sort(q+1,q+1+sum,cmp);
	pos=m;
	for (int i=m;i>=1;i--)
	{
		for (;q[sum].l==i;sum--)
			pos=min(pos,q[sum].r-1);
		last[i]=pos;
	}
	scanf("%d",&m);
	while (m--)
	{
		scanf("%d%d",&l,&r);
		if (last[l]>=r) printf("Yes\n");
			else printf("No\n");
	}
	return 0;
}
posted @ 2019-10-30 20:22  全OI最菜  阅读(171)  评论(0编辑  收藏  举报