小w的魔术扑克(树状数组+并查集)

题目链接

满分做法:

将正反两面连边,发现如果一个连通块的变数>=n条时,那么这个连通块一定能满足保证每个面值都能够被提供至少一次。如果它是一棵树,无论怎么调整,都会漏掉一个面值无法打出。

于是最后询问的就是一个线段中是否包含一个完整的限制线段(他的起点为一棵树中编号最小的,终点为最大的),这个可以用离线树状数组解决,把询问线段和限制线段按照右端点从小到大排序。

一次处理询问,把右端点小于此询问线段右端点的限制线段加到树状数组中(在左端点+1),当区间和为0时可以满足条件,不为0则不能打出。

#include<queue>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxm=1e5+7;
int n,k,q;
int f[maxm],bian[maxm],dian[maxm],maxx[maxm],minn[maxm];
int c[maxm];
bool ans[maxm];
int find(int x)
{
  if(x!=f[x])
  f[x]=find(f[x]);
  return f[x]; 
}
int lowbit(int x)
{
 return x&(-x);
}
void add(int x)
{
 for(;x<=n;x+=lowbit(x))
 c[x]+=1;  
}
int ask(int x)
{
 int ans=0;
 for(;x;x-=lowbit(x))
 ans+=c[x];
 return ans;   
}
struct node
{
  int l,r,id;
  bool operator <(const node &s) const
  {
   return r<s.r;
  }
}qwq[maxm],qaq[maxm];
int main()
{
 scanf("%d%d",&n,&k);
 for(int i=1;i<=n;i++)
 {
   f[i]=i;
   bian[i]=0;
   dian[i]=1;
   maxx[i]=i;
   minn[i]=i;
 }
 for(int i=1;i<=k;i++)
 {
  int x,y;
  scanf("%d%d",&x,&y);
  int fx=find(x);
  int fy=find(y);
  if(fx==fy)
  {
    bian[fx]++;
    continue;
  }
  if(fx!=fy)
  {
    f[fx]=fy;
    dian[fy]+=dian[fx];
    bian[fy]+=bian[fx]+1;
    dian[fx]=bian[fx]=0;
    maxx[fy]=max(maxx[fy],maxx[fx]);
    minn[fy]=min(minn[fy],minn[fx]);
  }
 }
 int tot=0;
 for(int i=1;i<=n;i++)
 {
   if(dian[i]==bian[i]+1)//一个点也是一棵树
   {
     qwq[++tot].l=minn[i];
     qwq[tot].r=maxx[i];
   }
 }
 scanf("%d",&q);
 for(int i=1;i<=q;i++)
 {
  int x,y;
  scanf("%d%d",&x,&y);
  qaq[i].l=x,qaq[i].r=y,qaq[i].id=i;
 }
 sort(qwq+1,qwq+tot+1);
 sort(qaq+1,qaq+q+1);
 int top=1;
 for(int i=1;i<=q;i++)
 {
  while(top<=tot&&qwq[top].r<=qaq[i].r)
  {
   add(qwq[top].l);
   top++;
  }
  if(ask(qaq[i].r)-ask(qaq[i].l-1))//有完整线段
  ans[qaq[i].id]=1;
 }
 for(int i=1;i<=q;i++)
 {
  if(ans[i])
  printf("No\n");
  else
  printf("Yes\n"); 
 }
 return 0; 
}
posted @ 2019-10-30 14:34  lihan123  阅读(307)  评论(0编辑  收藏  举报