题意:
题解:
这道题我一开始就用并查集和dfs去做,发现超时了,就我那渣渣的优化水平,怎么都优化不了,所以去看了别人的博客,发现还可以这样做。。听说是用到了可持续化的思想…这玩意不太懂啊,ORZ。。。
做法就是将每个点到根节点的链路上的并查集关系存储起来,然后再在询问的时候将k个并查集关系合并起来,这样就不用每次询问一个数的时候都并查集一次,从而达到优化。
至于怎么看,请看我的代码。
开二维数组f[][]是将树上每个节点的并查集关系记录下来。第一维表示的是第几个节点存储的并查集关系,其他的基本都和并查集差不多,只是多了一个存储多个节点的并查集这层的伟大想法,ORZ。。好难想到。
参考博客:
http://blog.youkuaiyun.com/pootree/article/details/54970941
顺便发一下我超时的代码,第一份是超时代码,第二份是正确代码。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN=10000+7;
struct node
{
int u,v;
}map[MAXN];
int fa[MAXN];
int n,m;
int a[MAXN];
int find(int p)
{
while(p!=fa[p])
{
fa[p]=fa[fa[p]];
p=fa[p];
}
return p;
}
bool Union(int p,int q)
{
int P=find(p);
int Q=find(q);
if(P==Q)
return true;
else
{
fa[Q]=P;
}
return false;
}
void init()
{
for(int i=0;i<=n;i++)
fa[i]=i;
}
void dfs(int x)
{
if(x==1)
{
Union(map[x].u,map[x].v);
return ;
}
if(a[x]!=x){
Union(map[x].u,map[x].v);
// printf("@@@%d %d\n",map[x].u,map[x].v);
dfs(a[x]);
}
}
int main()
{
int t,Case=1;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
init();
a[1]=1;
for(int i=2;i<=m;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
map[i].u=x,map[i].v=y;
}
int q,k;
scanf("%d",&q);
printf("Case #%d:\n",Case++);
while(q--)
{
scanf("%d",&k);
for(int i=1;i<=k;i++)
{
int x;
scanf("%d",&x);
dfs(x);
}
int ans=0;
for(int i=1;i<=n;i++)
if(fa[i]==i)
ans++,fa[i]=i;
printf("%d\n",ans);
// for(int i=1;i<=n;i++)
// printf("%d %d\n",i,fa[i]);
}
}
}
#include<stdio.h>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN=10000+7;
const int MAXM=500+7;
struct node
{
int u,v;
}map[MAXN];
int f[MAXN][MAXM];
int n,m;
vector<int> a[MAXN];
int find(int x,int p)
{
while(p!=f[x][p])
{
f[x][p]=f[x][f[x][p]];
p=f[x][p];
}
return p;
// return f[x][p] == p ? p : f[x][p] = find(x, f[x][p]);
}
void Union(int p,int q,int x)
{
int P=find(x,p);
int Q=find(x,q);
if(P!=Q)
{
if(P>Q)
{
int t=P;
P=Q;
Q=t;
}
f[x][Q]=f[x][P];//这里也可以写成f[x][Q]=P,居然比前面的写法快点。。。但是这里为了逻辑上说的过去即表示的是第x个点到根节点的并查集
}
}
void dfs(int x,int fa)
{
for(int i=1;i<=n;i++)//这里表示的儿子节点的并查集继承父节点的并查集关系。比如1是2的父亲,那么2的儿子3的父亲也可以说是1.
f[x][i]=f[fa][i];
Union(map[x].u,map[x].v,x);
for(int i=0;i<a[x].size();i++)
if(a[x][i]!=fa)
dfs(a[x][i],x);
}
void init()
{
for(int i=0;i<=n;i++)
f[0][i]=i;
}
int main()
{
int t,Case=1;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<=m;i++)
a[i].clear();
init();
for(int i=2;i<=m;i++){
int x;
scanf("%d",&x);
a[x].push_back(i);
}
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
map[i].u=x,map[i].v=y;
}
dfs(1,0);
int q,k;
scanf("%d",&q);
printf("Case #%d:\n",Case++);
for(int i=1;i<=n;i++)
f[m+1][i]=i;
while(q--)
{
scanf("%d",&k);
for(int i=1;i<=k;i++)//这里的操作就是k个并查集合并起来
{
int x;
scanf("%d",&x);
for(int j=1;j<=n;j++)//将第x个点的并查集关系与m+1个点的并查集的关系联系起来,即相当于将k个并查集的关系合并到新建立的m+1点的并查集中去。
Union(find(x,j),j,m+1);
}
int ans=0;
for(int i=1;i<=n;i++)
if(f[m+1][i]==i)
ans++;
else
f[m+1][i]=i;
printf("%d\n",ans);
}
}
}
/*
这道题我一开始就用并查集和dfs去做,发现超时了,就我那渣渣的优化水平,怎么都优化不了,所以去看了别人的博客,发现还可以这样做。。听说是用到了可持续化的思想...这玩意不太懂啊,ORZ。。。
做法就是将每个点到根节点的链路上的并查集关系存储起来,然后再在询问的时候将k个并查集关系合并起来,这样就不用每次询问一个数的时候都并查集一次,从而达到优化。
至于怎么看,请看我的代码。
开二维数组f[][]是将树上每个节点的并查集关系记录下来。
*/