测试地址:Envy
题目大意: 给定一个带权无向连通图,每次询问给出
条图中的边,问这些边能不能同时处在这个图的一棵最小生成树中。
做法: 本题需要用到最小生成树+虚树+并查集。
居然没看题解想出来了这道神题,非常开心。
首先,判断
条边是不是能同时在最小生成树中,实际上只要求出包含这些边的边权和最小的生成树的权值,再和最小生成树比对即可。
怎么求呢?首先把
条边先连上,形成一些连通块,注意到一个性质:要连通两个连通块,使用最小生成树上的边一定是最优的。证明可以用反证法,如果用的不是最小生成树上的边而且更优,那么这个“最小生成树”就不是最小的。这时我们想到先用并查集维护连通性,然后再暴力做一次最小生成树。这样算出的结果肯定是对的,但时间复杂度爆炸,因此我们需要优化。
注意到已经和其他点连通的点至多有
个,我们想到使用虚树。这时我们发现,虚树外的那些最小生成树的边是一定要连的,不然就不连通,因此我们只需考虑虚树内的连通性。虚树上一条边就代表原图中的一条路径,且这些路径上的点都没有和其它点连通。我们尝试把这条路径缩成一条边。首先,路径内的点要连通,那么肯定是从路径的两端连树边,直到中间还有一条边没连上为止。这时,如果我们要求路径的两端不连通,那么我们就贪心地选择最大的边割掉即可,这条最大边显然可以通过倍增找到。于是要求路径两端连通就相当于在不连通的基础上,连通了那条最大的边,因此我们发现只有这条最大的边和这条路径的最优解有关,这样我们就把一条路径缩成了一条边。于是虚树中就只有
条边了,这时候再做最小生成树,时间复杂度就是
,因此总的复杂度为
,可以接受。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,q,first[500010]={0},tot=0,totke,pos[500010],tim=0;
int ma[500010],mb[500010],pre[500010][21]={0},dep[500010]={0};
int p[2000010],totp,pe[500010],tote,fa[500010];
int st[500010],top;
ll mw[500010],mx[500010][21]={0},dis[500010]={0};
struct kruskaledge
{
int a,b;
ll w;
}me[500010];
struct treeedge
{
int v,next;
ll w;
}e[1000010];
bool cmp(kruskaledge a,kruskaledge b)
{
return a.w<b.w;
}
void insert(int a,int b,ll w)
{
e[++tot].v=b;
e[tot].next=first[a];
e[tot].w=w;
first[a]=tot;
}
int find(int x)
{
int r=x,i=x,j;
while(r!=fa[r]) r=fa[r];
while(i!=r) j=fa[i],fa[i]=r,i=j;
return r;
}
void merge(int x,int y)
{
int fx=find(x),fy=find(y);
fa[fx]=fy;
}
ll kruskal(ll init,bool type)
{
ll tot=init;
sort(me+1,me+totke+1,cmp);
for(int i=1;i<=totke;i++)
if (find(me[i].a)!=find(me[i].b))
{
tot+=me[i].w;
merge(me[i].a,me[i].b);
if (type)
{
insert(me[i].a,me[i].b,me[i].w);
insert(me[i].b,me[i].a,me[i].w);
}
}
return tot;
}
void dfs(int v)
{
pos[v]=++tim;
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=pre[v][0])
{
pre[e[i].v][0]=v;
dep[e[i].v]=dep[v]+1;
mx[e[i].v][0]=e[i].w;
dis[e[i].v]=dis[v]+e[i].w;
dfs(e[i].v);
}
}
bool cmpp(int a,int b)
{
return pos[a]<pos[b];
}
int lca(int a,int b)
{
if (dep[a]<dep[b]) swap(a,b);
for(int i=20;i>=0;i--)
if (dep[pre[a][i]]>=dep[b])
a=pre[a][i];
if (a==b) return a;
for(int i=20;i>=0;i--)
if (pre[a][i]!=pre[b][i])
a=pre[a][i],b=pre[b][i];
return pre[a][0];
}
ll findmx(int a,int b)
{
ll ans=0;
for(int i=20;i>=0;i--)
if (dep[pre[a][i]]>=dep[b])
{
ans=max(ans,mx[a][i]);
a=pre[a][i];
}
return ans;
}
void solve()
{
int newp=totp;
sort(p+1,p+totp+1,cmpp);
for(int i=1;i<totp;i++)
p[++newp]=lca(p[i],p[i+1]);
totp=newp;
sort(p+1,p+totp+1,cmpp);
newp=0;
for(int i=1;i<=totp;i++)
if (i==1||p[i]!=p[newp])
p[++newp]=p[i];
totp=newp;
ll basis=0,bestval=0;
for(int i=1;i<=totp;i++)
fa[p[i]]=p[i];
for(int i=1;i<=tote;i++)
{
if (find(ma[pe[i]])==find(mb[pe[i]]))
{
printf("NO\n");
return;
}
merge(ma[pe[i]],mb[pe[i]]);
basis+=mw[pe[i]];
}
top=0;
totke=0;
for(int i=1;i<=totp;i++)
{
while(top&&lca(st[top],p[i])!=st[top]) top--;
st[++top]=p[i];
if (top>1)
{
ll sum,mx;
sum=dis[st[top]]-dis[st[top-1]];
mx=findmx(st[top],st[top-1]);
bestval+=sum;
basis+=sum-mx;
if (find(st[top])!=find(st[top-1]))
{
me[++totke].a=st[top];
me[totke].b=st[top-1];
me[totke].w=mx;
}
}
}
if (kruskal(basis,0)==bestval) printf("YES\n");
else printf("NO\n");
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%lld",&ma[i],&mb[i],&mw[i]);
me[i].a=ma[i],me[i].b=mb[i],me[i].w=mw[i];
}
totp=n;
totke=m;
for(int i=1;i<=totp;i++)
fa[i]=i;
kruskal(0,1);
dep[1]=1;
dfs(1);
for(int i=1;i<=20;i++)
for(int j=1;j<=n;j++)
{
pre[j][i]=pre[pre[j][i-1]][i-1];
mx[j][i]=max(mx[j][i-1],mx[pre[j][i-1]][i-1]);
}
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d",&tote);
totp=0;
for(int i=1;i<=tote;i++)
{
scanf("%d",&pe[i]);
p[++totp]=ma[pe[i]],p[++totp]=mb[pe[i]];
}
solve();
}
return 0;
}