luogu3639&bzoj3206 [Apio2013]道路费用

http://www.elijahqi.win/archives/1084
题目描述

幸福国度可以用 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 条新道路的收入最大。注意 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’; 

在任意两个城市之间,最多只有一条道路(包括新建的道路)。

输出格式:

你的程序必须输出恰好一个整数到标准输出,表示能获得的最大的收入。

输入输出样例

输入样例#1:

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
输出样例#1:

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)将没有任何收入。

我们将使用以下 5 类测例测试你的程序。

(国际 16 分,国内 15 分)N ≤ 10,M ≤ 20 且 K = 1;
(国际 18 分,国内 20 分)N ≤ 30,M ≤ 50 且 K ≤ 10;
(国际 22 分,国内 20 分)N ≤ 1,000,M ≤ 5,000 且 K ≤ 10;
(国际 22 分,国内 20 分)N ≤ 100,000,M ≤ 300,000 且 K ≤ 15;
(国际 22 分,国内 25 分)N ≤ 100,000,M ≤ 300,000 且 K ≤ 20。
洛谷上我死活就没有过去一个wa一个tle 还好bzoj过了
这很考验思考能力据说是前几届的南开学长来讲题%%%%%%%%%%%%

观察到题目中k=20非常小 可是这个非常小怎么搞 没法搞啊 其他还是这么大

题目要求是什么啊 你有k条路 都是你的 过路费你来定,但是每个城镇居民去1号点的时候只会走权值最小的

首先我们默认把k条边全部加入建立最小生成树 这时候如果还需要原图中的边那么原图中的这些边一定是将来也一定存在的

然后我们加完之后缩点 缩成不超过k个点 然后我们用2^k的复杂度去枚举我用的每条边的情况

然后加入 然后如果不构成树那么 则加入第二必选边 然后继续缩点直到树上只剩下我们枚举的这个集合里的边 然后以这里的一号作为根节点

然后再在非必选边中跑树上最小值 因为假如我比最小值大那么他们走的时候就不会经过我了

#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 110000
#define M 330000 
#define inf 0x7f7f7f7f
using namespace std;
inline int read(){
    int x=0;char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
    return x;
} 
struct node{
    int x,y,z,next;
}data[M],datak[30],datam[900],data3[60];
inline bool cmp(node a,node b){return a.z<b.z;}
int n,m,k,h[30],f[30][30],b[N],b1[30],dep[30],fa1[30],w[30],fa[N],num,bin[30],a[N];bool used[M];
long long size[30],size1[30],v[30],ans;
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void insert1(int x,int y){
    data3[++num].y=y;data3[num].next=h[x];h[x]=num;
    data3[++num].y=x;data3[num].next=h[y];h[y]=num;
}
void dfs(int x){
    v[x]+=size1[x];
    for (int i=h[x];i;i=data3[i].next){
        int y=data3[i].y;
        if (fa1[x]==y) continue;
        fa1[y]=x;dep[y]=dep[x]+1;dfs(y);
        v[x]+=v[y];
    }
}
inline void update(int x,int y,int z){
    if (dep[x]<dep[y]) swap(x,y);
    while (dep[x]!=dep[y]) w[x]=min(w[x],z),x=fa1[x];
    if (x==y) return;
    while (1){
        if (x==y) break;
        w[x]=min(w[x],z);w[y]=min(w[y],z);
        x=fa1[x];y=fa1[y];
    }
}
int main(){
   // freopen("toll17.in","r",stdin);
    n=read();m=read();k=read();
    for (int i=1;i<=m;++i) {
        int x=read(),y=read(),z=read();
        data[i].y=y;data[i].z=z;data[i].x=x;
    }
    sort(data+1,data+m+1,cmp);
    for (int i=1;i<=n;++i) fa[i]=i;
    for (int i=1;i<=k;++i){
        int x=read(),y=read();
        int xx=find(x),yy=find(y);datak[i].y=y;datak[i].x=x;
        if (xx!=yy) fa[xx]=yy;
    }for (int i=1;i<=n;++i) a[i]=read();
    for (int i=1;i<=m;++i){
        int x=data[i].x,y=data[i].y;
        int xx=find(x),yy=find(y);if (xx!=yy) fa[xx]=yy,used[i]=true;
    }for (int i=1;i<=n;++i) fa[i]=i;
    for (int i=1;i<=m;++i){
        if (used[i]) fa[find(data[i].x)]=find(data[i].y);
    }int cnt=0;
    for (int i=1;i<=n;++i){
        if (!b[find(i)]) b[fa[i]]=++cnt;
        b[i]=b[fa[i]];size[b[i]]+=a[i]; 
    }bin[0]=1;
    for (int i=1;i<=21;++i) bin[i]=bin[i-1]<<1;
    //重新建图  
    memset(f,0x7f,sizeof(f));
    for (int i=1;i<=m;++i){
        if (used[i]) continue;
        int x=data[i].x,y=data[i].y,z=data[i].z;x=b[x];y=b[y];
        if(z<f[x][y]) f[x][y]=f[y][x]=z;
    } num=0;
    for (int i=1;i<=cnt;++i)
        for (int j=i+1;j<=cnt;++j)
            if (f[i][j]<inf) {
                datam[++num].x=i;datam[num].y=j;datam[num].z=f[i][j];
            }
    sort(datam+1,datam+num+1,cmp);m=num; 
//    for (int i=1;i<=m;++i) printf("%d %d %d\n",datam[i].x,datam[i].y,datam[i].z);
    for (int i=1;i<=k;++i) 
        datak[i].x=b[datak[i].x],datak[i].y=b[datak[i].y];
    for (int s=1;s<=bin[k]-1;++s){
        for (int i=1;i<=cnt;++i) fa[i]=i;long long ans1=0;
        bool flag=false;int cnt2=0;memset(v,0,sizeof(v));
        memset(h,0,sizeof(h));memset(size1,0,sizeof(size1));memset(b1,0,sizeof(b1));
        for (int i=1;i<=k;++i){
            if (s&(bin[i-1])){
                int xx=find(datak[i].x),yy=find(datak[i].y);
                if (xx==yy){
                    flag=true;break;
                } 
                fa[xx]=yy;
            }
        }
        if (flag) continue;//不可以成环,成环则违反最小生成树定义
        for (int i=1;i<=m;++i) used[i]=0;
        for (int i=1;i<=m;++i){
            int xx=find(datam[i].x),yy=find(datam[i].y);
            if (xx!=yy) fa[xx]=yy,used[i]=1;
        }
        //单独对第二必连边进行连接
        for (int i=1;i<=cnt;++i) fa[i]=i;
        for (int i=1;i<=m;++i) if (used[i]) fa[find(datam[i].x)]=find(datam[i].y); 
        //针对 第二必连边进行缩点 
        for (int i=1;i<=cnt;++i){
            if (!b1[find(i)]) b1[fa[i]]=++cnt2;
            b1[i]=b1[fa[i]]; size1[b1[i]]+=size[i];
        } num=0;
        for (int i=1;i<=k;++i) 
            if (s&bin[i-1]) insert1(b1[datak[i].x],b1[datak[i].y]);
        int root=b1[b[1]];dep[root]=1;fa1[root]=0;dfs(root);
        memset(w,0x7f,sizeof(w));
        for (int i=1;i<=m;++i){
            if (!used[i]) update(b1[datam[i].x],b1[datam[i].y],datam[i].z);
        }
        for (int i=1;i<=cnt2;++i){
            if (i!=root) ans1+=v[i]*w[i];
        }
        ans=max(ans1,ans);
    }
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值