[APIO2013]道路费用
II.[APIO2013]道路费用
这个,明显就很可以枚举掉有哪些边出现在MST上。但是,如何设置权值使得要求出现的边全数出现呢?
我们考虑裸的Kruskal算法。首先,先用冰茶姬将要求出现的边加入生成树(明显此时如果已经出现了环,则此边集本身即不合法,可以直接跳过该边集),然后将原图中的边按照权值排序后依次加入生成树。
假如添加一条原图中的边使得两个连通块联通,显然这条边应该出现在MST上;否则,该边便不应该出现在MST上。而这个的充要条件,便是现在的MST上,该边连接的两个节点间的路径中,所有边的权值都不大于当前试图加入的边的权值。于是就直接遍历路径上所有边并让其边权与当前边权取即可。
明显该算法是的,就算使用树上倍增也是的,根本不可能通过所有数据。考虑优化。
观察到条边最多与个点有关,换言之无论它们有哪些边在MST中,它们所影响的只是少部分节点。这就引起了一个重要推论——在所有个MST中,有大部分边是相同的。那么,具体是哪些边相同呢?
我们考虑先将条边全数加入MST中(不管出不出现环),得到一张图。现在如果我们加入了更少的边,明显该图中的连通块数只会更多不会更少;故此时MST中还需添加的边集,一定是所有种MST的公共边集。于是我们就可以预先把这些公共边集加入生成树(换言之,就是把原图缩点)。在缩完点后,明显此图最多剩余个连通块(即,考虑一棵包含所有条边的MST,然后再拆去此条边,最多就剩余个连通块)。
假如我们加入的新边是条边的某个子集的话,明显它可能并不能使此个连通块全数联通;故我们需要再加入一些原边。而此处的原边,一定是此个连通块的MST上的边,即最多条边。
于是我们只需首先把被钦定的子集中的边加入MST(如上文所说,记得判环),然后依次考虑该条原图中的边并尝试加入MST即可。
此时,该算法便被优化到了。使用树上倍增,可以优化到,但明显因为过小,该优化没有任何意义。直接使用的算法即可通过。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,p,q,r,dsu[100100],col[100100];
ll num[100100],res,now;
void init(int len){for(int i=1;i<=len;i++)dsu[i]=i;}
int find(int x){return dsu[x]==x?x:dsu[x]=find(dsu[x]);}
bool merge(int x,int y){x=find(x),y=find(y);if(x==y)return false;dsu[y]=x;return true;}
struct edge{
int x,y,z;
edge(int X=0,int Y=0,int Z=0){x=X,y=Y,z=Z;}
friend bool operator <(const edge &u,const edge &v){return u.z<v.z;}
}e[300100];
int u[300100],v[300100],w[300100];
bool on[300100];
vector<int>g[300100];
bool dfs1(int x,int fa,int dest,int lim){
if(x==dest)return true;
for(auto i:g[x]){
int y=(u[i]^v[i]^x);
if(y==fa)continue;
if(dfs1(y,x,dest,lim)){w[i]=min(w[i],lim);return true;}
}
return false;
}
ll dfs2(int x,int fa){
ll ret=num[x];
for(auto i:g[x]){
int y=(u[i]^v[i]^x);
if(y==fa)continue;
ll tmp=dfs2(y,x);
if(i<p)now+=tmp*w[i];
ret+=tmp;
}
return ret;
}
void func(int mask){
init(r);
for(int i=1;i<=r;i++)g[i].clear();
for(int i=0;i<p;i++)if((mask&(1<<i))&&merge(u[i],v[i]))g[u[i]].push_back(i),g[v[i]].push_back(i),w[i]=0x3f3f3f3f;
for(int i=p;i<q;i++)if(merge(u[i],v[i]))g[u[i]].push_back(i),g[v[i]].push_back(i);else dfs1(u[i],0,v[i],w[i]);
now=0;
dfs2(col[1],0);
res=max(res,now);
}
int main(){
scanf("%d%d%d",&n,&m,&p);
for(int i=0,x,y,z;i<m;i++)scanf("%d%d%d",&x,&y,&z),e[i]=edge(x,y,z);
sort(e,e+m);
init(n);
for(int i=0;i<p;i++)scanf("%d%d",&u[i],&v[i]),merge(u[i],v[i]);
for(int i=0;i<m;i++)on[i]=merge(e[i].x,e[i].y);
init(n);
for(int i=0;i<m;i++)if(on[i])merge(e[i].x,e[i].y);
for(int i=1;i<=n;i++)if(dsu[i]==i)col[i]=++r;
for(int i=1,x;i<=n;i++)col[i]=col[find(i)],scanf("%d",&x),num[col[i]]+=x;
q=p;
for(int i=0;i<m;i++)if(merge(e[i].x,e[i].y))u[q]=col[e[i].x],v[q]=col[e[i].y],w[q]=e[i].z,q++;
for(int i=0;i<p;i++)u[i]=col[u[i]],v[i]=col[v[i]];
for(int i=1;i<(1<<p);i++)func(i);
printf("%lld\n",res);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?