link

Description

幸福国度可以用 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\le 10^5,k\le 20,m\le 3\times 10^5\)

Solution

挺有意思的一个题目,写一下题解吧,毕竟自己都是看了题解才做出来的。

可以想到一个 \(2^km\log n\) 的做法,就是我们枚举哪些后加边一定在最小生成树,然后对没有加进最小生成树的边 \((u,v,w)\),我们将 \(u\to v\) 路径上的所有边的权值都与 \(w\) 取 \(\min\),那么一条后加边最大可以取到就可以确定了,然后就可以计算贡献了。

考虑缩点。你可以发现的是,假如我们钦定后加边都一定要加入,那么这种情况还能加入的已存在的边一定会在后来取到,所以我们就可以缩点了,缩点之后可能会存在的一开始就有的边就最多只有 \(k\) 条了。然后我们就可以做到 \(\Theta(2^kk^2+m\log m)\)。

Code

#include <bits/stdc++.h>
using namespace std; #define Int register int
#define int long long
#define ll long long
#define MAXN 400005 template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> inline void chkmin (T &a,T b){a = min (a,b);}
template <typename T> inline void chkmax (T &a,T b){a = max (a,b);} int n,m,k,st,p[MAXN];
vector <int> pt,g[MAXN]; struct edge{
int u,v,w;
bool operator < (const edge &p)const{return w < p.w;}
}e[MAXN],eg[MAXN]; int t[MAXN],fa[MAXN];
int findSet (int x){return fa[x] == x ? x : fa[x] = findSet (fa[x]);}
void unionSet (int u,int v){fa[findSet (u)] = findSet (v);} void link (int u,int v){
g[u].push_back (v),
g[v].push_back (u);
} void kruskal (){
sort (e + 1,e + m + 1);
for (Int i = 1;i <= m;++ i) t[i] = 0;
for (Int i = 1;i <= m;++ i) t[i] = (findSet (e[i].u) != findSet (e[i].v)),unionSet (e[i].u,e[i].v);
} int sum[MAXN],tur[MAXN],dep[MAXN],par[MAXN],mxv[MAXN];
void dfs (int u,int fa){
par[u] = fa,dep[u] = dep[fa] + 1,sum[u] = p[u];
for (Int v : g[u]) if (v ^ fa) dfs (v,u),sum[u] += sum[v];
} void makeit (int u,int v,int w){
if (dep[u] < dep[v]) swap (u,v);
while (dep[u] > dep[v]) chkmin (mxv[tur[u]],w),u = par[u];
while (u ^ v) chkmin (mxv[tur[u]],w),chkmin (mxv[tur[v]],w),u = par[u],v = par[v];
} signed main(){
read (n,m,k);
for (Int i = 1;i <= m;++ i) read (e[i].u,e[i].v,e[i].w);
for (Int i = 1;i <= k;++ i) read (e[m + i].u,e[m + i].v);
for (Int i = 1;i <= n;++ i) read (p[i]),fa[i] = i;
for (Int i = m + 1;i <= m + k;++ i) unionSet (e[i].u,e[i].v);
kruskal ();
for (Int i = 1;i <= n;++ i) fa[i] = i;
for (Int i = 1;i <= m;++ i) if (t[i]) unionSet (e[i].u,e[i].v);
for (Int i = 1;i <= n;++ i) if (findSet (i) == i) pt.push_back (i);else p[fa[i]] += p[i];
st = findSet (1);int tot = 0;
for (Int i = 1;i <= m + k;++ i) e[i].u = findSet (e[i].u),e[i].v = findSet (e[i].v);
kruskal ();
for (Int i = 1;i <= m;++ i) if (t[i]) eg[++ tot] = e[i];
for (Int i = 1;i <= tot;++ i) e[i] = eg[i];
for (Int i = 1;i <= k;++ i) e[tot + i] = e[m + i];
m = tot;ll ans = 0;
for (Int S = 0;S < (1 << k);++ S){
for (Int u : pt) fa[u] = u,g[u].clear ();
for (Int i = 1;i <= k;++ i) if (S >> i - 1 & 1) unionSet (e[m + i].u,e[m + i].v);
kruskal ();
for (Int i = 1;i <= k;++ i)
if (S >> i - 1 & 1) link (e[m + i].u,e[m + i].v),t[m + i] = 1;
else t[m + i] = 0;
int cnt = 0;
for (Int i = 1;i <= m + k;++ i) cnt += t[i];
if (cnt > pt.size() - 1) continue;
for (Int i = 1;i <= m;++ i) if (t[i]) link (e[i].u,e[i].v);
dfs (st,0);
for (Int i = 1;i <= m + k;++ i) if (t[i]){
int u = e[i].u,v = e[i].v;
if (dep[v] > dep[u]) tur[v] = i;
else tur[u] = i;
if (i > m) mxv[i] = 1e9;
}
for (Int i = 1;i <= m;++ i) if (!t[i]) makeit (e[i].u,e[i].v,e[i].w);
ll res = 0;
for (Int i = m + 1;i <= m + k;++ i) if (t[i]){
int u = e[i].u,v = e[i].v;
res += 1ll * (sum[dep[v] > dep[u] ? v : u]) * mxv[i];
}
chkmax (ans,res);
}
write (ans),putchar ('\n');
return 0;
}

题解 [APIO2013]道路费用的更多相关文章

  1. [BZOJ3206][APIO2013]道路费用(最小生成树)

    3206: [Apio2013]道路费用 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 568  Solved: 266[Submit][Status ...

  2. [Bzoj3206][Apio2013]道路费用(kruscal)(缩点)

    3206: [Apio2013]道路费用 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 536  Solved: 252[Submit][Status ...

  3. 洛谷P3639 [APIO2013] 道路费用 [生成树的特殊算法]

    题目传送门 道路费用 格式难调,题面就不放了. 分析: 这是一道要细(yan)心(jing)的生成树的好(gui)题. 首先我们看到$k$的范围非常小,那么我们就可以直接$2^k$枚举每一条加边是否选 ...

  4. 题解 洛谷 P3639 【[APIO2013]道路费用 】

    不难想到可以\(2^k\)去枚举\(k\)条新边的选择方案,然后加入原图中的边来使图连通,用当前方案的收益去更新答案,但是这样复杂度过不去. 可以先把\(k\)条新边都连上,然后再加入边权从小到大排序 ...

  5. [APIO2013]道路费用

    题目描述 幸福国度可以用 N 个城镇(用 1 到 N 编号)构成的集合来描述,这些城镇 最开始由 M 条双向道路(用 1 到 M 编号)连接.城镇 1 是中央城镇.保证一个 人从城镇 1 出发,经过这 ...

  6. BZOJ3206 [Apio2013]道路费用

    首先我们强制要求几条待定价的边在MST中,建出MST 我们发现这个MST中原来的边是一定要被选上的,所以可以把点缩起来,搞成一棵只有$K$个点的树 然后$2^K$枚举每条边在不在最终的MST中,让在最 ...

  7. bzoj 3206: [Apio2013]道路费用【最小生成树+并查集】

    参考:http://hzwer.com/6888.html 把k条道路权值设为0,和其他边一起跑MST,然后把此时选中的其他边设为必选,在新图中加上必选变缩成k个点,把所有边重标号,枚举k跳边的选取情 ...

  8. 题解 [HAOI2012]道路

    题目传送门 题目大意 给出一个 \(n\) 个点 \(m\) 条边的有向图,问每一条边在多少个最短路径中出现. \(n\le 1500,m\le 5000\) 思路 算我孤陋寡闻了... 很显然,我们 ...

  9. [APIO2013]

    A.机器人 题目大意:给定一个n*m的地图,有一些障碍物和k个机器人,你每次可以选择一个机器人往任意一个方向推,遇到转向器会转向,两个编号相邻的机器人可以合并,求最少推多少次可以全部合并. $n,m\ ...

随机推荐

  1. SpEL表达式注入漏洞学习和回显poc研究

    目录 前言 环境 基础学习和回显实验 语法基础 回显实验 BufferedReader Scanner SpEL漏洞复现 低版本SpringBoot中IllegalStateException CVE ...

  2. Go测试--性能测试分析

    目录 前言 认识数据 benchstat 分析一组样本 分析两组样本 小结 前言 benchmark测试是实际项目中经常使用的性能测试方法,我们可以针对某个函数或者某个功能点增加benchmark测试 ...

  3. Linux中MySQL的安装以及卸载

    一.MySQL MySQL是一种开放源代码的关系型数据库管理系统,开发者为瑞典MySQL AB公司.在2008年1月16号被Sun公司收购.而2009年,SUN又被Oracle收购.目前 MySQL被 ...

  4. CSS004. 自定义滚动条样式(webkit)

    CSS /* 滚动条宽度 */ ::-webkit-scrollbar { width: 6px; } /* 轨道样式 */ ::-webkit-scrollbar-track { backgroun ...

  5. Swagger-初见

    目录 Swagger简介 SpringBoot集成Swagger 配置Swagger 配置扫描接口 配置Swagger开关 配置API分组 实体配置 常用注解 Swagger简介 前后端分离 前端 - ...

  6. rootfs -根文件系统制作

    目录 目录 目录 概述 概念 根文件系统是什么 根文件系统中有什么 根文件系统的形式 Busybox 简介 什么是 linuxrc VFS 简介 Busybox 工具 Busybox 目录结构 Men ...

  7. Vue组件传值(二)之 非父子组件传值

    Vue中非父子组件之间是如何实现通信的? 本章主要讲的是非父子组件传值,父子组件传值请看上一篇文章. 1.创建新的Vue实例引入项目中,通过$emit.$on来实现非父子组件传值: 1 <!DO ...

  8. 小Z的袜子 & 莫队

    莫队学习 & 小Z的袜子 引入 莫队 由莫涛巨佬提出,是一种离线算法 运用广泛 可以解决广大的离线区间询问题 莫队的历史 早在mt巨佬提出莫队之前 类似莫队的算法和莫队的思想已在Codefor ...

  9. Django学习day14BBS项目开发1.0

    每日测验 """ 1.简述auth模块功能 2.简述项目开发流程 3.简述bbs表设计 """ 内容回顾 auth模块 "&quo ...

  10. Feign超时不生效问题

    使用Feign作为RPC调用组件,可以配置连接超时和读取超时两个参数 使用Feign配置超时需要注意:Feign内部使用了负载均衡组件Ribbon,而Ribbon本身也有连接超时和读取超时相关配置一. ...