本蒟蒻蒟蒻竟然也写博客了!我之前看过一些神犇在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;
}
好了,进入正题,题意是给一堆点,有若干边把他们相连,每次要删去一个点及所有与之相连的边,求连通块个数,并输出一开始时的连通块个数。
乍一看这个题好像可以并查集,但是要删点不会是什么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;
}