【问题描述】
皮特现在是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]1].u,xj[1].v);
}
while(dep[u]!=dep[v])
{
if(maxtt1;
u=fa[u];
}
while(u!=v)
{
if(maxtt1;
u=fa[u];
if(maxtt2;
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<
暴力方法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.感觉还是很有收获!