Luogu P1197 [JSOI2008]星球大战
经巨佬们指教,得知要用:逆向思维法(这个一定要想到啊QwQwQ)
之后想了想,发现我大致思路跟第一篇题解比较像(后来AC代码的细节不一样,我是在每次‘恢复’循环开始时计数的,题解是循环末尾,大同小异吧)
但我在每次计数的写法上调了好长时间
最初想的是每次枚举每个结点看是否fa[i]==i,后来才知道肯定会超时
但我不知道我这种做法为什么除了TLE 以外还全WA了,也许是什么东西打错了?懒散的我就没有深究了QwQ(未解之谜QwQ)
因为TLE,我就立刻看了题解,改进方法
其实这个计数完全可以O(n)
计数核心思想:
-
最初(恢复前)n-k个联通快(k为攻击次数)
-
恢复一个先加一块
-
merge函数内部:若merge成功,fa[fx]=fy的同时联通快减一块
计数相关语句已用//标出。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
int n,m,k,cnt,fa[400005],head[400005],at[400005],ans[400005];
int tot;
bool vis[400005];
struct edge{
int u,v,next;
bool exit;
}e[1000005];
inline void add(int u,int v){
e[++cnt].v=v;
e[cnt].u=u;
e[cnt].next=head[u];
head[u]=cnt;
}
inline int getfa(int v){
if(fa[v]==v)return v;
fa[v]=getfa(fa[v]);
return fa[v];
}
inline void merge(int x,int y){
int fx=getfa(x),fy=getfa(y);
if(fx!=fy)tot--,fa[fx]=fy;//
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)fa[i]=i;
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",&at[i]);
vis[at[i]]=1;
}
tot=n-k;//
for(int i=1;i<=cnt;i++){
if(!vis[e[i].u]&&!vis[e[i].v]){
merge(e[i].u,e[i].v);
}
}
for(int i=k;i>=1;i--){
ans[i]=tot++;//
vis[at[i]]=0;
for(int j=head[at[i]];j!=-1;j=e[j].next){
if(!vis[e[j].v])merge(at[i],e[j].v);
}
}
ans[0]=tot;//
for(int i=0;i<=k;i++){
printf("%d\n",ans[i]);
}
}