@AkamemakA
2022-11-20T06:52:28.000000Z
字数 4039
阅读 137
题解 算法 杂题
给定一个 个点 条边的无向图,保证图联通。每条边有两个属性 和 ,表示使这条边的权值每降低 要花费 的花费。现在你有一个总费用 ,你可以降低某些边的权值(可以降为负数,且总费用不得超过 ) 。问降低权值后,这张图可得到的最小生成树权值是多少,并且输出选为最小生成树中的边的编号及权值。
首先明确一点:题目中说可以降低某些边的权值,其实如果我们只选 最小的那条边,并只降低它的权值,这样一定可以使权值降低的最多(一点贪心的思想)。
我们可以首先找出这张图的最小生成树,并将在树上的边标记。然后,我们考虑降低哪条边的权值。
如果降低树边的权值
其实非常好做。由开头所讲,只需要找到树边中 最小的那条边,将其 减少 即可。
如果降低非树边的权值
这种情况稍微有些麻烦,我们来看下面这张图:

黑色的边表示树边,而红色的这条边表示我们选择的要降低权值的这条边。
由树的性质,我们不难发现:给树上两点间加上一条边后,便会生成一个环,由加上的边与两点间简单路径组成。要想其重新变回一棵树,那么必须从两点间的简单路径上去删除一条边。由贪心的思想,我们知道,要是最后答案最小,那么删去的边一定是两点间简单路径上权值最大的边。
于是,我们可以枚举每一条非树边,找到其连接的两点的树上简单路径上权值最大的边,计算备选答案。
备选答案就是 ,其中 表示原图最小生成树的权值和。
对于找两点间权值最大的边,我们可以请出我们的树链剖分。码量虽然大,但是很好理解呀!
最后对于路径的输出(真恶心) , 把要选的边标记即可。有很多小细节,在代码里有呈现。本人蒟蒻,不喜勿喷!
#include<iostream>#include<algorithm>using namespace std;#define int long longconst int N=2e5+5;int n,m,S;int w[N],rec[N];int sum,rev;int he[N],ne[N<<1],go[N<<1],tot;int fa[N];struct node{int u,v,we,c,id;bool operator <(const node W) const{return we<W.we;}}a[N];bool st[N];inline void add(int a,int b){ne[++tot]=he[a];he[a]=tot;go[tot]=b;}inline int find(int x){if(x!=fa[x]) return fa[x]=find(fa[x]);else return x;}inline void kruskal(){//最小生成树模板int cnt=0;sort(a+1,a+m+1);for(int i=1;i<=m;i++){int A=find(a[i].u),B=find(a[i].v);if(A!=B){fa[B]=A;add(a[i].u,a[i].v);add(a[i].v,a[i].u);//建树cnt++;st[i]=1;sum+=a[i].we;//st[i]=1表示第i条边是一条树边//sum存储最小生成树的权值和}if(cnt==n-1) break;}}int top[N],dep[N],f[N],son[N],si[N];int dfn[N],nw[N],cnt;inline void dfs1(int u,int p){f[u]=p;dep[u]=dep[p]+1;si[u]=1;for(int i=he[u];i;i=ne[i]){int v=go[i];if(v==p) continue;dfs1(v,u);si[u]+=si[v];if(si[son[u]]<si[v]) son[u]=v;}}inline void dfs2(int u,int tp){top[u]=tp;dfn[u]=++cnt;nw[cnt]=w[u];if(son[u]) dfs2(son[u],tp);else return ;for(int i=he[u];i;i=ne[i]){int v=go[i];if(v==f[u]||v==son[u])continue;dfs2(v,v);}}struct Seg_Tree{int l,r;int val;#define lson (p<<1)#define rson (p<<1|1)}t[N<<2];inline void build(int p,int l,int r){t[p].l=l;t[p].r=r;if(l==r){t[p].val=nw[l];return ;}int mid=l+r>>1;build(lson,l,mid);build(rson,mid+1,r);t[p].val=max(t[lson].val,t[rson].val);}inline int query(int p,int l,int r){if(t[p].l>=l&&t[p].r<=r)return t[p].val;int res=0;if(t[lson].r>=l) res=max(res,query(lson,l,r));if(t[rson].l<=r) res=max(res,query(rson,l,r));return res;}inline int query_max(int x,int y){int res=0;while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]])swap(x,y);res=max(res,query(1,dfn[top[x]],dfn[x]));x=f[top[x]];}if(dep[x]>dep[y]) swap(x,y);res=max(res,query(1,dfn[son[x]],dfn[y]));//边权转点权,最后一部分的上点应取 son[x]return res;}//---------------以上均为模板---------------//signed main(){cin>>n>>m;for(int i=1;i<=n;i++) fa[i]=i;for(int i=1;i<=m;i++) cin>>a[i].we,a[i].id=i; //由于要输出边的序号,将其存进结构体for(int i=1;i<=m;i++) cin>>a[i].c;for(int i=1;i<=m;i++)cin>>a[i].u>>a[i].v;cin>>S;kruskal(); //最小生成树,同时建树rev=sum; //选非树边时方便得到备选答案,将最小生成树的权值拷贝一份dfs1(1,0);//由于题中给的是边权,我们可以将边权化为点权,用边连接的两点中深度更大的那个点的权值作为这条边的权值。for(int i=1;i<=m;i++){if(!st[i]) continue; //只处理树边if(dep[a[i].u]>dep[a[i].v]) w[a[i].u]=a[i].we,rec[a[i].u]=i;else w[a[i].v]=a[i].we,rec[a[i].v]=i;//w记录树中点权。rec表示该点向上连接的边的编号,输出时要用}dfs2(1,1);build(1,1,n);int minn=0x3f3f3f3f,id=0;//首先处理树边for(int i=1;i<=m;i++)if(st[i]){if(a[i].c<minn){minn=a[i].c;id=i;//minn找到树边中c[i]的最小值,id记录该边的编号}}int r=S/minn;sum-=r;//备选答案之一:选择树边bool flag=1; //由于两种情况在输出路径时的处理方式不同,因此记录改用哪种处理方式。int rd=0,cut=0;//如果选择了某条非树边比选择树边更优//那么flag标记为0,cut记录选择的那条边的权值,rd记录那条边的编号//蒟蒻想不出更多变量名了呜呜呜for(int i=1;i<=m;i++)if(!st[i]){int res=query_max(a[i].u,a[i].v);//找到两点简单路径中边权最大的边int r=S/a[i].c;if(rev-res+a[i].we-r<sum){ //解析中公式有呈现sum=rev-res+a[i].we-r;flag=0;rd=i;cut=res;//备选答案之二:选择非树边}}cout<<sum<<endl;if(!flag){ //如果选非树边更优int x=a[rd].u,y=a[rd].v;//由于query_max只能找到最大的数值而不能找到到底是那条边,因此枚举简单路径上的边即可while(1){if(dep[x]<dep[y]) swap(x,y);if(w[x]==cut) { //如果当前边边权为要删去的边的边权st[rec[x]]=0; //rec在这里派上用场:边权化为点权,x向上连接的那条边即为要删去的边st[rd]=1; //这条非树边标记int r=S/a[rd].c;a[rd].we-=r;break;}x=f[x]; //树剖的f和并查集的fa别搞混了(掉了几次坑的蒟蒻如是说)//每次向上跳一个}for(int i=1;i<=m;i++)if(st[i])cout<<a[i].id<<" "<<a[i].we<<endl; //sort会打乱边的顺序,因此输出a[i].id}else{//选择树边就很简单了,之间把要选的那条边的 w 减去 S/minn 即可int r=S/minn;a[id].we-=r;for(int i=1;i<=m;i++)if(st[i])cout<<a[i].id<<" "<<a[i].we<<endl;}}