【C++心路历程30】(APIO2013)道路费用
来源:互联网 发布:数控软件手机版 编辑:程序博客网 时间:2024/06/06 06:02
【问题描述】
皮特现在是C国最富有的人。
C国共有n个城市(用1~n 编号),现在这些城市由m条双向道路连接,其中城市1为首都。保证一个人从城市1出发,经过这些道路可以到达其他的任何一个城市。当然,所有的这些道路都是要收费的,使用道路i需要向该道路的所有者支付ci的费用。已知所有的ci互不相同。最近C国计划新建 条道路,毋庸置疑,当然是富豪皮特负责,因而新建的k条道路(也仅有这k条道路)是属于皮特的。
皮特可以自行决定着k条道路的费用,并且皮特将在明天公布这些费用。两周以后,C国将在首都举行盛大的阅兵式,其中共有pi个参与者从城市i出发。大量的参与者将沿着这些道路前往首都。这些人只会沿着一个选出的道路集合行进,根据一个古老的习俗,这些道路将由最富有的人,也就是皮特指定,并且皮特将在后天公布选出的集合。同样根据这个习俗,皮特选出的道路集合必须使所有选出的道路的费用之和最小,并且仍要保证每个城市都可以经选出的道路到达首都。也就是说,选出的道路来自以费用作为相应边权的最小生成树。如果有多个这样的集合,皮特可以任选。
尽管皮特现在是首富,但他依旧想尽办法敛财。他希望通过控制属于他的 条道路的费用以及所选取的道路集合来使自己的收入最大化。他明白,他获得的收益并不只与指定的费用有关,也与通过这条道路的人数有关。准确的说,如果有p 个人经过费用为ci 的道路,那么道路所有者就会获得p*ci 的收入。注意,皮特的选择必须符合习俗。
但是皮特并不够聪明,于是他来求助你。你现在需要做的就是计算皮特所能得到的最大收入!
【输入格式】
第一行包含三个由空格隔开的正整数n,m,k 。
接下来的m 行描述最开始的m 条道路。这ui,vi,ci 行每行包含三个空格隔开整数 ,表示在ui 和vi 之间有一条费用为ci 的双向道路。保证1<=ui,vi<=n ,且当i!=j 时,ci!=cj 。
接下来k 行描述k 条新道路。每行包含两个空格隔开的整数ai,bi ,表示有一条连接城市ai,bi 的新道路,其费用由皮特决定。保证1<=ai,ni<=n 。
最后一行包含n 个空格隔开的整数,其中第i 个表示pi ,即城市i 中要前往首都的人数。
保证在任意两个城市之间,最多有一条道路连接(包括新建的道路)。
【输出格式】
输出只有一行,包含一个整数,表示皮特能获得的最大收入。
【输入样例】
5 5 1
3 5 2
1 2 3
2 3 5
2 4 4
4 3 6
1 3
10 20 30 40 50
【输出样例】
400
【样例解释】
皮特应将新道路(1,3) 的费用设为5 。在这个费用下,他可以选择道路(3,5)、(1,3)、(2,4) 和 (1,3)。可以以证明,这样的收入是最大的。
大意:n个点,点权为Pi,m条边的图,新建不超过k条道路,每条费用由你决定,一条道路收取的费用为通过人数*边上的费用。但前提是所选边构成MST.在所有人都要去1号点集合的情况下,求你的最大收益。
感觉题解网上其他地方似乎已经很多啦。所以就大概说一下。
暴力枚举1(期望得分20,过k=1的数据)(O(k*m))
将k条边逐一加入到原图最小生成树中,替换原图上的最大边。在k>1时不能保证正确。
核心代码:
`LL LCA(int u,int v){ int maxtt=-1; if(dep[u]<dep[v]) { swap(u,v);swap(xj[1].u,xj[1].v); } while(dep[u]!=dep[v]) { if(maxtt<dist[u]-dist[fa[u]]) t1=u,t2=fa[u],maxtt=dist[u]-dist[fa[u]],aa=1; u=fa[u]; } while(u!=v) { if(maxtt<dist[u]-dist[fa[u]]) t1=u,t2=fa[u],maxtt=dist[u]-dist[fa[u]],aa=1; u=fa[u]; if(maxtt<dist[v]-dist[fa[v]]) t1=v,t2=fa[v],maxtt=dist[v]-dist[fa[v]],aa=2; v=fa[v]; } LL A=maxtt; return A;}void solve(){ LL ans=0; LL sum=kruskal(); dfs(1,0,1,0);//给dfs做准备 LL cc=LCA(xj[1].u,xj[1].v); if(aa==1) g[xj[1].v].push_back(xj[1].u);//这里比较蠢 有其他方法判断儿子/父亲 else g[xj[1].u].push_back(xj[1].v); memset(fa,0,sizeof(fa));memset(dep,0,sizeof(dep));memset(dist,0,sizeof(dist)); dfs2(1,0); if(aa==1) aa=sz[xj[1].u]; else aa=sz[xj[1].v]; ans=cc*aa; cout<<ans;}`
暴力方法2(期望得分35)(注意到k<20,2^k枚举算法)(O(2^k*m*k))
对k条边枚举,共2^k种可能性:
对于每一种可能:
1:构造最终的最小生成树,优先使用选中的k条边
2:确定选中边的权值——将原图中还未选的边,加入进行LCA,则选中边边权为经过它的最小值。
核心代码:
void solve(){ memset(g,0,sizeof(g)); initial(n); int cnt=0;memset(used,0,sizeof(used)); for(int i=1;i<=k;i++) if(vis[i]) //将选的边优先加入并查集 { int u=xj[i].u,v=xj[i].v; if(judge(u,v)) continue; merge(u,v);cnt++; g[u].push_back(v); g[v].push_back(u); } sort(E+1,E+1+m); for(int i=1;i<=m;i++) { int u=E[i].u,v=E[i].v; if(judge(u,v)) continue; merge(u,v);cnt++; g[u].push_back(v); g[v].push_back(u); used[i]=1; if(cnt==n-1) break; } memset(fa,0,sizeof(fa));memset(dep,0,sizeof(dep)); dfs(1,0,1);//给LCA做准备 for(int i=1;i<=k;i++) minv[i]=inf; for(int i=1;i<=m;i++) { if(!used[i]) { int u=E[i].u,v=E[i].v;LL c=E[i].c; LCA(u,v,c); } } memset(fa,0,sizeof(fa));memset(sz,0,sizeof(sz)); dfs2(1,0); LL ans=0; for(int i=1;i<=k;i++) if(vis[i]) { int u=xj[i].u,v=xj[i].v; LL faa=min(sz[u],sz[v]); ans+=faa*minv[i]; } ANS=max(ans,ANS);}void run(int i,int r)//枚举2^k种可能方案 { if(i>k) { if(r==0) return ; solve();return ; } vis[i]=1;run(i+1,r+1);vis[i]=0; run(i+1,r);}
算法三:
注意到原图中只有经过k条边才有可能收钱,即在其他边中无论怎么样移动都无关,即有些点是粘在一起的,与本题所求无关。即可以“缩点”。
怎么缩点?
在原图中,优先使用k条边生成MST,则k条边将原图分成k+1个连通块,每个连通块即可缩为一个点。(连通块中移动不干扰题目,并与k条边绝对无关)
再求这k+1个点的最小生成树,即原图压缩成只有(k+1)个点,k条边的新图。(这张图在本题所求中是与原图等价的,故而可以缩点),再通过算法二求解这张图即可。
时间复杂度:O(m*logm + 2^k*k*k)
核心代码:
void dfs3(int i,int f,int cc)//方便缩点{//g belong[i]=cc,w[cc]=w[cc]+p[i]; for(int p=first[i];p;p=g[p].next) { int j=g[p].to; if(j==f) continue; dfs3(j,i,cc); }}void yasuo(){ initial(n); int cnt=0; for(int i=1;i<=k;i++) //将选的边优先加入并查集 { int u=xj[i].u,v=xj[i].v; if(judge(u,v)) continue; merge(u,v);cnt++; // addedge(u,v,g); // addedge(v,u,g);//我要一棵假树! } sort(E+1,E+1+m); for(int i=1;i<=m;i++) { int u=E[i].u,v=E[i].v; if(judge(u,v)) continue; merge(u,v);cnt++; addedge(u,v,g); addedge(v,u,g); if(cnt==n-1) break; } for(int i=1;i<=n;i++) if(!belong[i]) dfs3(i,0,++cc); np=0,memset(first,0,sizeof(first)); kruskal2();}
P.S.
1.在枚举完边之后的操作中,如需要清空数组,请清空小数组(或新开小数组),原数组仍然很大,清空很慢!
2.可以剪枝的地方:在枚举2^k可能性时,如加入的k条边已经与原图构成环,可以跳出。
3.我自己程序足足写了250行,看起来就很冗杂+弱鸡,就不贴了……
4.感觉还是很有收获!
- 【C++心路历程30】(APIO2013)道路费用
- 3206: [Apio2013]道路费用
- BZOJ3206: [Apio2013]道路费用
- bzoj 3206: [Apio2013]道路费用 最小生成树
- bzoj3206 [Apio2013]道路费用(kruskal+并查集+状压枚举+dfs)
- 心路历程
- 心路历程
- 心路历程
- 心路历程
- 心路历程
- 心路历程
- 心路历程
- 心路历程
- 心路历程
- 心路历程
- 心路历程
- 心路历程
- 心路历程
- Vue.js2.0-饿了么商家
- echarts图表大小
- WIN2008服务器开放Tomcat/Mysql端口
- 数据格式与数据类型
- 欢迎使用CSDN-markdown编辑器
- 【C++心路历程30】(APIO2013)道路费用
- 思考,提升
- ora-01219 数据库未打开:仅允许在固定表/视图中;
- Deep Compression代码实现
- 二维数组中的查找
- godep更新提示no packages can be updated
- LintCode 解题记录 7.11 ~ 7.16
- 用vb插件代码控制工程运行,暂停,停止【用vb插件代码控制工程运行,暂停,停止】
- 机器学习的核心要素