星球大战 bzoj1015 洛谷1197 JSOI2008

        本蒟蒻蒟蒻竟然也写博客了!我之前看过一些神犇在CSDN上的博客,觉得诸位神犇十分厉害,orz,然而forever_shi还是个一道各省省选都没写过的蒟蒻。本是我的第一篇博客,纪念我A掉的第一道bzoj的题目,并纪念我A掉的第一道各省的往年省选题,虽然这道题对于许多神犇来说十分简单。如有错误请各位神犇指正。
好了,进入正题,题意是给一堆点,有若干边把他们相连,每次要删去一个点及所有与之相连的边,求连通块个数,并输出一开始时的连通块个数。
        乍一看这个题好像可以并查集,但是要删点不会是什么lct之类的吧?然后发现似乎没法用lct(因为我不会,而且似乎也不能用),那就果断并查集。但是并查集不支持删改操作,于是我最初想了个每次修改都重新跑一遍并查集,发现复杂度爆表。。。
        突然灵机一动,有的题目正向做不容易做时可以逆向考虑。那么可以发现从最后要删的全删了的情况往前依次连边就可以使用并查集维护了,维护的时候有一些小细节,我觉得我写得挺麻烦的,但是我觉得本题的关键还是在逆向考虑上,其他的都是细节问题。
        下面贴一下AC代码。(forever_shi并不追求代码写得“好看”,所以代码丑,而且基本不去写快读)。


#include <bits/stdc++.h>
using namespace std;
struct node
{
 int next,to;
}e[400001];
int n,m,k,head[400001],cnt,book[400001],a[400001],f[400001],sum[400010];
int ji[400001],fan[400001];
void add(int from,int to)
{
 e[++cnt].next=head[from];
 e[cnt].to=to;
 head[from]=cnt;
}
int getr(int x)
{
 if(x==f[x])
 return x;
 else
 {
  f[x]=getr(f[x]);
  return f[x];
 }
}
void charu(int x,int y)
{
 int rx=getr(x),ry=getr(y);
 if(rx!=ry)
 {
  f[rx]=ry;
 }
 return;
}
int query(int x,int y)
{
 int rx=getr(x),ry=getr(y);
 if(rx!=ry)
 {
  f[rx]=ry;
  return 1;
 } 
 else
 return 0; 
}
int main()
{
 scanf("%d%d",&n,&m);
 for(int i=1;i<=m;i++)
 {
  int x,y;
  scanf("%d%d",&x,&y);
  add(x,y);
  add(y,x);
 }
 scanf("%d",&k);
 for(int i=1;i<=k;i++)
 {
  scanf("%d",&book[i]);
  fan[book[i]]=i;
  a[book[i]]=1;
 }
 for(int i=0;i<=n-1;i++)
 f[i]=i;
 for(int i=0;i<=n-1;i++)
 {
  if(a[i]==0)
  {
   for(int j=head[i];j;j=e[j].next)
   {
    if(a[e[j].to]==0)
    charu(e[j].to,i);
   }
  }
 }
 for(int i=0;i<=n-1;i++)
 {
  if(a[i]==0)
  ji[getr(i)]=1;
 }
 for(int i=0;i<=n-1;i++)
 {
  if(ji[i]==1)
  sum[k+1]++;
 }
 for(int i=k;i>=1;i--)
 {
  int x=book[i];
  sum[i]=sum[i+1]+1;
  for(int j=head[x];j;j=e[j].next)
  {
   if((a[e[j].to]==0)||(fan[e[j].to]>i))
   {
    sum[i]-=query(e[j].to,x);
   }   
  }  
 }
 for(int i=1;i<=k+1;i++)
 printf("%d\n",sum[i]);
 return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值