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]);
	}
}
posted @ 2019-08-08 18:54  Y15BeTa  阅读(157)  评论(0编辑  收藏  举报