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]); } }