【牛客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想问问你,他能否利用手中的魔术卡牌打出这些顺子呢?
思路:
巧妙。
想象如果一张牌的两个数字分别为,我们将和连边,那么最终就会形成几堆连通块。
如果一个连通块是一棵树,那么由于每一条边都可以选一个数字,那么显然不可以把这个连通块里的所有数字都选择上。
所以我们记录每一个连通块的边数、最大值和最小值,如果,那么久不可能构成一个的顺子,也就是所有包含的询问都不可以选择成功。
那么将每一棵树按照最小值排序,设表示从开始最多可以接到多少的顺子,那么从枚举到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;
}