Luogu P1197 [JSOI2008]星球大战
题外话:
说真的这是一道不写篇博客都对不起我半天debug的时间
真的顺手太可怕了
正着删点然后每次判联通的复杂度还是很高的,显然不是这么做的
这个时候我们需要逆向思维(√
考虑从最终状态出发,将删点变成增加点,好像复杂度要低了那么一点点(大概是一大点点吧
利用一个标记数组判断当前点是否还没有被加入图中,首先利用并查集计算最终状态的连通块个数,
然后倒序向图中加点:
首先先把某个点扔进图中,ans++;
然后对这个点的所有边进行一波操作,判断是不是会和其他的连通块一起(并查集),变成大联通块(ans--)
最后倒序输出就好了
是离线做法?
CODE:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
const int mxn=400010;
int n,m,k;
int ans,p[mxn],P,fa[mxn];
bool bj[mxn];
int K[mxn];
struct node {
int to,nxt;
}e[mxn<<1];
int head[mxn],ecnt;
void add(int from,int to) {
++ecnt;
e[ecnt].nxt=head[from];
head[from]=ecnt;
e[ecnt].to=to;
}
int find(int x) {
if(fa[x]!=x)
fa[x]=find(fa[x]);
return fa[x];
}
int main() {
n=read();
m=read();
for(int i=1,x,y;i<=m;i++) {
x=read();
x++;
y=read();
y++;
add(x,y);
add(y,x);
}
k=read();
for(int i=1,a;i<=k;i++) {
a=read();
K[i]=++a;
bj[K[i]]=1;
}
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++) {
if(bj[i]) continue;
for(int j=head[i],v;j;j=e[j].nxt) {
v=e[j].to;
if(bj[v]) continue;
int u=find(v);
int I=find(i);
if(u!=I)
fa[u]=I;
}
}
for(int i=1;i<=n;i++)
if(bj[i]==0&&fa[i]==i) ans++;
p[++P]=ans;
for(int i=k;i>=1;i--) {
bj[K[i]]=0;ans++;
for(int j=head[K[i]],v;j;j=e[j].nxt) {
v=e[j].to;
if(bj[v]==1) continue;
int u=find(v);
int z=find(K[i]);
if(u!=z) {
ans--;
fa[u]=z;
}
}
p[++P]=ans;
}
for(int i=P;i>=1;i--)
printf("%d\n",p[i]);
return 0;
}
debug日志:
1.把K[]开成了bool数组,还以为自己读入读炸了
2.思路bug,傻不愣登的以为将K[i]点和某个连通块合并以后,如果又有一个属于这个连通块的点与K[i]有边,会影响答案。然后考虑丢点的时候,直接用head[K[i]]!=0判断是不是又自成一连通块了,忘记它有的点还没加进来。
3.顺手什么的太恶心了。习惯于
for(int i=head[u],v;i;i=e[i].nxt)
v=e[i].to;
的写法,于是就写成了:
for(int j=head[u],v;j;;j=e[i].nxt)
v=e[i].to;