CodeForces892E 可撤销并查集/最小生成树
题意:给出一个 n 个点 m 条边的无向图,每条边有边权,共 Q 次询问,每次给出 ki 条边,问这些边能否同时在一棵最小生成树上。
这题乍一看看不出什么方法来,仔细一想发现除了确实看不出什么东西来。
但是我们从求最小生成树的kruskal算法里面可以发现,尽管一个图可以有多个最小生成树,但是想要把一条边替换掉领一条边变成最小生成树一定是两条边的权值相同。
所以说每一条权值不同的边是互相独立的,如果比这条边权值小的边全部联系起来之后这条边两端是联通的,说明这条边是不合法的。
这是考虑边是否可以加入最小生成树的情况,但是他给了很多条边,也就是说每次除了判断这条边的合法性之外还要对每一个询问单独判断,也就是判环。
怎么判环是个问题,但是对每个询问都建图判环肯定是不现实的,比较可行的说法是用并查集,将比所有w的边小的边联通之后判断他们能否联通而不形成环,注意到Q的数据是50w,每次都重新建一个并查集也不太现实,这时候就又要考虑掏出一手可撤销并查集。也就是对每一次的Union的两者存到一个栈里,如果要撤销这些操作就从栈顶弹出元素重新独立,对于不需要撤销的操作就直接不用入栈了,可撤销的并查集是不能路径压缩的,只能上一波按秩合并看上去好像优化了很多的样子。
因此,我们对于每一个询问用并查集判断一下可行性之后撤销,就很帅。
因此我们得出了一个具体的做法
先将所有的edge排序,但是由于给你的query是边的编号,排序之后编号就变动了,需要用一个ID数组来存储排序前的编号在排序后变成的编号
然后将所有的query存储下来,由于排序后的编号是按照权值递增的,直接按照编号排序就可以达到query内递增的效果。
如果每一个权值都暴力的跑一边每个询问,是无疑会TLE的,一个好的方法是用优先队列存储当前询问的状态,队内的结点存储当前询问的编号w,当前询问到了这个询问里数字编号,当前询问的这个数,优先队列的排列按w从小到大递增
然后对于每次出现过的w,就判断队列中是否有符合要求的询问,如果这个询问问到了头还没出错,他的ans就是1,如果在中间遇到出错了,ans就为0并且出队
经过测验,这样写可以优化很多。
#include <map> #include <set> #include <cmath> #include <queue> #include <stack> #include <vector> #include <string> #include <cstdio> #include <cstdlib> #include <cstring> #include <sstream> #include <iostream> #include <algorithm> #include <functional> using namespace std; const int MAXBUF=10000;char buf[MAXBUF],*ps=buf,*pe=buf+1; inline bool isdigit(const char& n) {return (n>='0'&&n<='9');} inline void rnext(){if(++ps==pe)pe=(ps=buf)+fread(buf,sizeof(char),sizeof(buf)/sizeof(char),stdin);} template <class T> inline bool in(T &ans){ #ifdef VSCode ans=0;T f=1;register char c; do{c=getchar();if ('-'==c)f=-1;}while(!isdigit(c)&&c!=EOF); if(c==EOF)return false;do{ans=(ans<<1)+(ans<<3)+c-48; c=getchar();}while(isdigit(c)&&c!=EOF);ans*=f;return true; #endif #ifndef VSCode ans =0;T f=1;if(ps==pe)return false;do{rnext();if('-'==*ps)f=-1;} while(!isdigit(*ps)&&ps!=pe);if(ps==pe)return false;do{ans=(ans<<1)+(ans<<3)+*ps-48; rnext();}while(isdigit(*ps)&&ps!=pe);ans*=f;return true; #endif }const int MAXOUT=10000; //*(int(*)[10])p char bufout[MAXOUT], outtmp[50],*pout = bufout, *pend = bufout+MAXOUT; inline void write(){fwrite(bufout,sizeof(char),pout-bufout,stdout);pout = bufout;} inline void out_char(char c){*(pout++)=c;if(pout==pend)write();} inline void out_str(char *s){while(*s){*(pout++)=*(s++);if(pout==pend)write();}} template <class T>inline void out_int(T x) {if(!x){out_char('0');return;} if(x<0)x=-x,out_char('-');int len=0;while(x){outtmp[len++]=x%10+48;x/=10;}outtmp[len]=0; for(int i=0,j=len-1;i<j;i++,j--) swap(outtmp[i],outtmp[j]);out_str(outtmp);} template<typename T, typename... T2> inline int in(T& value, T2&... value2) { in(value); return in(value2...); } #define For(i, x, y) for(register int i=x;i<=y;i++) #define _For(i, x, y) for(int i=x;i>=y;i--) #define Mem(f, x) memset(f,x,sizeof(f)) #define Sca(x) scanf("%d", &x) #define Scl(x) scanf("%lld",&x); #define Pri(x) printf("%d\n", x) #define Prl(x) printf("%lld\n",x); #define CLR(u) for(int i=0;i<=N;i++)u[i].clear(); #define LL long long #define ULL unsigned long long #define mp make_pair #define PII pair<int,int> #define PIL pair<int,long long> #define PLL pair<long long,long long> #define pb push_back #define fi first #define se second #define Vec Point typedef vector<int> VI; const double eps = 1e-9; const int maxn = 5e5 + 10; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; int N,M,tmp,K,Q; struct Edge{int u,v,w,id;}edge[maxn]; struct node{ int w,id,pointer; friend bool operator < (node a,node b){ return a.w > b.w; } }; VI query[maxn]; priority_queue<node> P; int Back[maxn][2]; int cnt; int tree[maxn]; int Size[maxn]; int ans[maxn],ID[maxn]; void init(){For(i,1,N) tree[i] = i,Size[i] = 1;} int find(int p){ if(p == tree[p]) return p; return find(tree[p]); } bool Union(int u,int v){ u = find(u); v = find(v); if(u == v) return false; if(Size[u] > Size[v]) swap(u,v); tree[u] = v; Size[v] += Size[u]; Back[++cnt][0]= u; Back[cnt][1] = v; return true; } void Cancel(){ while(cnt){ int u = Back[cnt][0]; int v = Back[cnt--][1]; tree[u] = u; Size[v] -= Size[u]; } } bool cmp(Edge a,Edge b) {return a.w < b.w;} int main() { in(N,M); init(); For(i,1,M) in(edge[i].u,edge[i].v,edge[i].w),edge[i].id = i; sort(edge + 1,edge + M + 1,cmp); For(i,1,M) ID[edge[i].id] = i; in(Q); For(i,1,Q){ in(tmp); while(tmp--){ int t; in(t); t = ID[t]; query[i].pb(t); } sort(query[i].begin(),query[i].end()); node h; h.id = i; h.pointer = 0; h.w = query[i][0]; P.push(h); } for(int i = 1; i <= M;){ int W = edge[i].w; int j=i+1;for(;j<=M&&edge[j].w==edge[i].w;j++);j--; while(!P.empty()){ node x = P.top(); if(x.w > j) break;P.pop(); int v = x.id; while(x.pointer < query[v].size() && query[v][x.pointer] <= j){ if(!Union(edge[query[v][x.pointer]].u,edge[query[v][x.pointer]].v)) break; x.pointer++; } Cancel(); if(x.pointer == query[v].size()) { ans[x.id] = 1; continue; } x.w = query[v][x.pointer]; if(x.w > j) P.push(x); } for(;i<=j;i++) Union(edge[i].u,edge[i].v); cnt = 0; } For(i,1,Q){ if(ans[i] == 1) puts("YES"); else puts("NO"); } #ifdef VSCode write(); system("pause"); #endif return 0; }