道路费用(缩点+并查集+最小生成树+状态压缩,详细的题解)

道路费用

一道溺水题

【问题描述】
幸福国度可以用 N 个城镇(用 1 到 N 编号)构成的集合来描述,这些城镇最开始由 M 条双向
道路(用 1 到 M 编号)连接。城镇 1 是中央城镇。保证一个人从城镇 1 出发,经过这些道路,可
以到达其他的任何一个城市。这些道路都是收费道路,道路 i 的使用者必须向道路的主人支付 ci 分
钱的费用。已知所有的这些 ci 是互不相等的。最近有 K 条新道路建成,这些道路都属于亿万富豪 Mr.
Greedy。 Mr. Greedy 可以决定每条新道路的费用(费用可以相同),并且他必须在明天宣布这些
费用。
两周以后,幸福国度将举办一个盛况空前的嘉年华!大量的参与者将沿着这些道路游行并前往中
央城镇。共计 pj 个参与者将从城镇 j 出发前往中央城镇。这些人只会沿着一个选出的道路集合前
行,并且这些选出的道路将在这件事的前一天公布。根据一个古老的习俗,这些道路将由幸福国度中
最有钱的人选出,也就是 Mr. Greedy。同样根据这个习俗,Mr. Greedy 选出的这个道路集合必
须使所有选出道路的费用之和最小,并且仍要保证任何人可以从城镇 j 前往城镇 1(因此, 这些选
出的道路来自将费用作为相应边边权的“最小生成树”)。如果有多个这样的道路集合,Mr. Greedy 可
以选其中的任何一个,只要满足费用和是最小的。
Mr. Greedy 很明确地知道,他从 K 条新道路中获得的收入不只是与费用有关。一条道路的收
入等于所有经过这条路的人的花费之和。更准确地讲,如果 p 个人经过道路 i,道路 i 产生的收入
为 ci*p 的积。注意 Mr. Greedy 只能从新道路收取费用,因为原来的道路都不属于他。
Mr. Greedy 有一个阴谋。他计划通过操纵费用和道路的选择来最大化他的收入。他希望指定每
条新道路的费用(将在明天公布),并且选择嘉年华用的道路(将在嘉年华的前一天公布),使得他在 K
HGOI-NOIP 第二轮模拟(十八)day1 ~ 4 ~
条新道路的收入最大。注意 Mr. Greedy 仍然需要遵循选出花费之和最小的道路集合的习俗。 你是
一个记者,你想揭露他的计划。为了做成这件事,你必须先写一个程序来确定 Mr. Greedy 可以通
过他的阴谋获取多少收入。
【输入】
第一行包含三个由空格隔开的整数 N,M 和 K。
接下来的 M 行描述最开始的 M 条道路。这 M 行中的第 i 行包含由空格隔开的整数 ai,bi 和
ci,表示有一条在 ai 和 bi 之间,费用为 ci 的双向道路。接下来的 K 行描述新建的 K 条道路。
这 K 行中的第 i 行包含由空格隔开的整数 xi 和 yi,表示有一条连接城镇 xi 和 yi 新道路。最
后一行包含 N 个由空格隔开的整数,其中的第 j 个为 pj,表示从城镇 j 前往城镇 1 的人数。
输入也满足以下约束条件。
 1 ≤ N ≤ 100000;
 1 ≤ K ≤ 20;
 1 ≤ M ≤ 300000;
 对每个 i 和 j,1 ≤ ci, pj ≤ 10^6;
 如果 i ≠ i’,则 ci ≠ ci’;
 在任意两个城市之间,最多只有一条道路(包括新建的道路)。
【输出】
输出一个整数,表示能获得的最大的收入。
【输入输出样例】
kunai.in kunai.out
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
【样例一解释】
在样例中,Mr. Greedy 应该将新道路(1,3)的费用设置为 5 分钱。
在这个费用 下,他可以选择道路(3,5),(1,2),(2,4)和(1,3)来最小
化总费用,这个费用为 14。 从城镇 3 出发的 30 个人和从城镇 5 出发
的 50 个人将经过新道路前往城镇 1,因 此他可以获得为(30+50)×
5=400 分钱的最好收入。
如果我们这样做,将新道路(1,3)的费用设置为 10 分钱。根据传统
的限制, Mr. Greedy 必须选择(3,5),(1,2),(2,4)和(2,3),因
为这是唯一费用最小的集合。 因此,在嘉年华的过程中道路(1,3)将没有
任何收入。
HGOI-NOIP 第二轮模拟(十八)day1 ~ 5 ~
【数据范围】
我们将使用以下 5 类测例测试你的程序。
1. (15 分)N ≤ 10,M ≤ 20 且 K = 1;
2. (20 分)N ≤ 30,M ≤ 50 且 K ≤ 10;
3. (20 分)N ≤ 1,000,M ≤ 5,000 且 K ≤ 10;
4. (20 分)N ≤ 100,000,M ≤ 300,000 且 K ≤ 15;
5. (25 分)N ≤ 100,000,M ≤ 300,000 且 K ≤ 20。。

额……早上考试的时候做了一点猜想,先把M做一遍最小生成树,然后用K里面的边尝试是否能覆盖,譬如说最小生成树里有一条边1——>2——>3,K里有1——>2,1——>3,那么就能覆盖,emm但是码力弱,时间有所剩无几(被第一题的反例烦死了,就是上一个博客的那个DP,一时之间没想出正解),这个暴力就打崩了。

emmmm我的想法和正解有一定接近,但是emmm没打emmmm

这个因为有10万条边第一因为本身就不能暴力来最小生成树,所以需要缩点。
其实缩点也不是什么玄妙的操作,只要把强联通分量视作一个点就好了。
缩点学习

好进入正解
首先我们队K路里的边进行最小生成树,然后不够的用M路里面的边来补,那么这些用来补的边是我们最后的方案中必须的边(自己想为什么)
然后把这些补充的边所连接的点连接的点缩点

这里写图片描述

黑色的边为M的边,红色的边为K的边

这里写图片描述

绿色的是我们最小生成树选的边,紫色的圈表示这些点被缩了

这是我们缩完点的图
这里写图片描述

好了此时我们的红边可以赋值为
比如说
1——>2的红边就是1(缩点后)到4(缩完点)之间连接的最短边的值
以此类推

好了
然后就是大量的缩点和并查集
自己参悟吧少年。
额等等
状态压缩(别的同学都是用0101010 1表示用,0表示不用是一开始找补充的边时候用的)
然后找路上最短边用倍增

void dp(int x)
{
    sum[x]=val[x];
    for(int i=last[x];i;i=ed[i].next)
        if(ed[i].to!=fa2[x])
        {
            dep[ed[i].to]=dep[x]+1;
            fa2[ed[i].to]=x;
            dp(ed[i].to);
            sum[x]+=sum[ed[i].to];
        }
}
    for(int i=1;i<=K;i++)
    {
        int u=q[i].u,v=q[i].v;
        if(dep[u]>dep[v])swap(u,v);
        while(dep[v]!=dep[u])mn[v]=min(mn[v],q[i].w),v=fa2[v];
        while(u!=v)
        {
            mn[v]=min(mn[v],q[i].w);
            mn[u]=min(mn[u],q[i].w);
            u=fa2[u];v=fa2[v];
        }
    }
#include<bits/stdc++.h>
#define ll long long
#define inf 1000000000
using namespace std;
ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
ll ans;
int n,m,K,top,cnt,st;
int fa[100005],fa2[100005],p[100005];
struct edge{
    int u,v,w;
}e[300005],ne[25],q[300005];
struct data{
    int to,next;
}ed[50];
int po[25],dep[100005],last[100005],mn[100005];
ll val[100005],sum[100005];
bool mark[300005];
void insert(int u,int v)
{
    ed[++cnt]=(data){v,last[u]};last[u]=cnt;
    ed[++cnt]=(data){u,last[v]};last[v]=cnt;
}
int find(int x)
{
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
int find2(int x)
{
    return x==fa2[x]?x:fa2[x]=find2(fa2[x]);
}
bool operator<(edge a,edge b)
{
    return a.w<b.w;
}
void dp(int x)
{
    sum[x]=val[x];
    for(int i=last[x];i;i=ed[i].next)
        if(ed[i].to!=fa2[x])
        {
            dep[ed[i].to]=dep[x]+1;
            fa2[ed[i].to]=x;
            dp(ed[i].to);
            sum[x]+=sum[ed[i].to];
        }
}
void solve()
{
    cnt=0;
    for(int i=1;i<=K+1;i++)
    {
        int p=po[i];
        last[p]=fa2[p]=0;
        fa[p]=p;mn[p]=inf;
    }
    for(int i=1;i<=K;i++)
        if(mark[i])
        {
            int x=find(ne[i].u),y=find(ne[i].v);
            if(x==y)return;
            fa[x]=y;
            insert(ne[i].u,ne[i].v);
        }
    for(int i=1;i<=K;i++)
    {
        int x=find(q[i].u),y=find(q[i].v);
        if(x!=y)fa[x]=y,insert(q[i].u,q[i].v);
    }
    dp(st);
    for(int i=1;i<=K;i++)
    {
        int u=q[i].u,v=q[i].v;
        if(dep[u]>dep[v])swap(u,v);
        while(dep[v]!=dep[u])mn[v]=min(mn[v],q[i].w),v=fa2[v];
        while(u!=v)
        {
            mn[v]=min(mn[v],q[i].w);
            mn[u]=min(mn[u],q[i].w);
            u=fa2[u];v=fa2[v];
        }
    }
    ll inc=0;
    for(int i=1;i<=K;i++)
        if(mark[i])
        {
            int u=ne[i].u,v=ne[i].v;
            if(dep[u]>dep[v])swap(u,v);
            inc+=mn[v]*sum[v];
        }
    ans=max(inc,ans);
}
void dfs(int x)
{
    if(x==K+1)
    {
        solve();return;
    }
    mark[x]=0;dfs(x+1);
    mark[x]=1;dfs(x+1);
}
int main()
{
    //freopen("toll.in","r",stdin);
    //freopen("toll.out","w",stdout);
    n=read();m=read();K=read();
    for(int i=1;i<=m;i++)
        e[i].u=read(),e[i].v=read(),e[i].w=read();
    sort(e+1,e+m+1);
    for(int i=1;i<=K;i++)
        ne[i].u=read(),ne[i].v=read();
    for(int i=1;i<=n;i++)p[i]=read();
    for(int i=1;i<=n;i++)fa[i]=fa2[i]=i;
    for(int i=1;i<=K;i++)
        fa[find(ne[i].u)]=find(ne[i].v);
    for(int i=1;i<=m;i++)
    {
        int u=e[i].u,v=e[i].v;
        if(find(u)!=find(v))
        {
            fa[find(u)]=fa[find(v)];
            fa2[find2(u)]=fa2[find2(v)];
        }
    }
    st=find2(1);
    for(int i=1;i<=n;i++)
    {
        val[find2(i)]+=p[i];
        if(find2(i)==i)po[++po[0]]=i;
    }
    for(int i=1;i<=K;i++)
        ne[i].u=find2(ne[i].u),ne[i].v=find2(ne[i].v);
    for(int i=1;i<=m;i++)
        e[i].u=find2(e[i].u),e[i].v=find2(e[i].v);
    for(int i=1;i<=m;i++)
    {
        int p=find2(e[i].u),q=find2(e[i].v);
        if(p!=q)mark[i]=1,fa2[p]=q;
    }
    for(int i=1;i<=m;i++)
        if(mark[i])q[++top]=e[i];
    dfs(1);
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值