ZOJ 3261 Connections in GalaxyWar(并查集:离线处理)
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3261
题意:
有n个点和m条边,每个点带有一个权值p[i]。现在给出Q条命令,要你输出对应的答案。命令格式如下:
query u :该命令需要输出当前与u点相连的点编号x,x要满足p[x]是所有与u相连的点中最大的 且 p[x]>p[u]。如果有多个满足条件的x存在,那么就输出编号最小的那个x点的编号。
destroy u v:该命令将删除u与v的边。(保证在执行该命令前u与v之间有一条边)
(假设输入的m条边不重复)
分析:
每个点看成并查集中的一个点,那么query u的时候就是查找u所属的连通分量中p[]值最大的点编号。所以我们可以始终让一个连通分量的根就是那个p[]值最大的点 且 在合并连通分量的时候依然维持这一性质。
由于并查集只能增加边,而题目的命令却只有删除边,所以自然想到将所有命令先预读入内存,然后从最后一条命令往前,一条一条添加边处理。
具体处理过程如下:
首先我们读入M条边,但是此时先不连接任何边。接着我们读入每条命令,并且将会被删除的边标记出来。然后我们将那些没有被删除的边都用并查集连接起来。
然后我们逆序处理每条命令(从最后一条命令开始),如果是query命令就是返回该分量的根编号(还需要判断根的p值是否>u的p值)。如果是destory u v 命令,其实就是添加u v边的命令(不是删除边了,想想为什么)。
注意:如果根不是自己但是根的能量和自己一样,照样不能作为求援对象.
AC代码(新):200ms
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
const int maxn=10000+5;
int p[maxn];//power值
//并查集
int fa[maxn];
int findset(int x)
{
return fa[x]==-1? x:fa[x]=findset(fa[x]);
}
int bind(int u,int v)
{
int fu=findset(u);
int fv=findset(v);
if(fu != fv)
{
if(p[fu]>p[fv] || (p[fu]==p[fv] && fu<fv) )//fu是根
{
fa[fv]=fu;
}
else//fv是根
{
fa[fu]=fv;
}
return 1;
}
return 0;
}
//边
struct Edge
{
int u,v;
Edge(){}
Edge(int u,int v):u(u),v(v){}
bool operator<(const Edge &rhs)const
{
return u<rhs.u || (u==rhs.u && v<rhs.v);
}
}edges[20000+5];
bool vis[20000+5];
//命令
struct command
{
int type;
int v;
}coms[50000+5];
int main()
{
int n,m,Q;
bool first=true;
while(scanf("%d",&n)==1)
{
if(!first) printf("\n");
first=false;
for(int i=0;i<n;i++)
scanf("%d",&p[i]);
scanf("%d",&m);
map<Edge,int> mp;//边与边的编号的映射
for(int i=0;i<m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
if(u>v) swap(u,v);
edges[i]=Edge(u,v);//可优化
mp[edges[i]]=i;
}
memset(vis,0,sizeof(vis));//vis[i]==true表第i条边已被删除
scanf("%d",&Q);
for(int i=0;i<Q;i++)
{
char str[100];
int u,v;
scanf("%s",str);
if(str[0]=='q')
{
scanf("%d",&u);
coms[i].type=0;
coms[i].v=u;
}
else if(str[0]=='d')
{
scanf("%d%d",&u,&v);
if(u>v) swap(u,v);
int id=mp[Edge(u,v)];//获取对应边的编号
vis[id]=1;//删除此边
coms[i].type = 1;
coms[i].v=id;
}
}
//连通所有未被删除的边
memset(fa,-1,sizeof(fa));
for(int i=0;i<m;i++)if(vis[i]==false)
{
bind(edges[i].u,edges[i].v);
}
//逆序处理所有命令并将query结果保存在vc中
vector<int> vc;
for(int i=Q-1;i>=0;i--)
{
if(coms[i].type == 0)//query命令
{
int root = findset(coms[i].v);
if(p[root]>p[coms[i].v])
vc.push_back(root);
else
vc.push_back(-1);
}
else//destroy命令
{
int u=edges[coms[i].v].u, v=edges[coms[i].v].v;
bind(u,v);
}
}
for(int i=vc.size()-1;i>=0;i--)
printf("%d\n",vc[i]);
}
return 0;
}
AC代码:160ms
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
const int MAXN=10000+100;
int F[MAXN];
int v[MAXN];
//int val[MAXN];//最大值的下标
//int num[MAXN];//最大值
int findset(int a)
{
if(F[a]==-1)return a;
return F[a]=findset(F[a]);
}
void bind(int i,int j)//保持能量大的作为根,能量相同则编号小的做根
{
int fa=findset(i);
int fb=findset(j);
if(fa!=fb)
{
if(v[fa]==v[fb])//能量相同
{
if(fa<fb)//编号小的做根
{
F[fb]=fa;
}
else if(fa>fb)
{
F[fa]=fb;
}
}
else if(v[fa] != v[fb])//能量不同
{
if(v[fa]<v[fb])//fb能量大,做根
{
F[fa]=fb;
}
else if(v[fa]>v[fb])
{
F[fb]=fa;
}
}
}
}
struct command
{
int type;//0查询,1销毁
int u,v;
}coms[50000+100];
struct edge
{
int u,v;
}edges[20000+100];
bool vis[20000+100];
map<int,int>mp[MAXN];
int main()
{
int n,m;
bool first=true;
while(scanf("%d",&n)==1)
{
if(first)first=false;
else printf("\n");
memset(F,-1,sizeof(F));
for(int i=0;i<n;i++)
{
scanf("%d",&v[i]);
//val[i]=i;
//num[i]=v[i];
mp[i].clear();
}
scanf("%d",&m);//读边数
for(int i=0;i<m;i++)
{
vis[i]=true;//该边可行
int u,v;
scanf("%d%d",&u,&v);
if(u>v)swap(u,v);
edges[i].u=u;
edges[i].v=v;
mp[u][v]=i;
}
int Q;
scanf("%d",&Q);
for(int i=0;i<Q;i++)
{
char str[20];
scanf("%s",str);
if(str[0]=='q')
{
coms[i].type=0;
scanf("%d",&coms[i].u);
}
else if(str[0]=='d')
{
coms[i].type=1;
int u,v;
scanf("%d%d",&u,&v);
if(u>v)swap(u,v);
coms[i].u=u;
coms[i].v=v;
vis[ mp[u][v] ]=false;
}
}
for(int i=0;i<m;i++)if(vis[i])
{
int u=edges[i].u,v=edges[i].v;
bind(u,v);
}
int tot=0;
int ans[50000+100];
for(int i=Q-1;i>=0;i--)
{
if(coms[i].type==0)
{
int fa=findset(coms[i].u);
if(fa!=coms[i].u&&v[fa]>v[coms[i].u])//这里一定要记住加上后面的判断,如果根不是自己但是根的能量和自己一样,照样不能作为求援对象
ans[tot]=fa;
else
ans[tot]=-1;
tot++;
}
else if(coms[i].type==1)
{
bind(coms[i].u,coms[i].v);
}
}
for(int i=tot-1;i>=0;i--)
printf("%d\n",ans[i]);
}
}