官方题解:

If you choose any n - 1 roads then price of reducing overall dissatisfaction is equal to min(c1, c2, ..cn - 1) where сi is price of reducing by 1 dissatisfaction of i-th edge. So the best solution is to choose one edge and reduce dissatisfaction of it until running out of budget.

Let's construct minimal spanning tree using Prim or Kruskal algorithm using edges of weights equal to dissatisfaction and calculate minimal price of reducing dissatisfaction. Time complexity — .

Now we can iterate over edges implying that current is the one to be reduced to minimum. For example, for every edge we can build new MST and recalculate answer. It's . Therefore we should use this fact: it's poinless to reduce dissatisfaction of edges which weren't selected to be main.

Then we can transform original MST instead of constructing m new ones. Add next edge to MST, now it contains a cycle from which edge with maximal dissatisfaction is about to be deleted. This can be achieved in such a way: find LCA of vertices of new edge in  and using binary lifting with precalc in  find the edge to delete.

Time complexity — .

大意是:

  经分析所有的预算用在一条干道上最合算(这条干道的修路花费是所挑选的n-1条干道中最小的),首先建立一棵最小生成树MST,然后枚举M条边,假设当前枚举到的边编号是i,将所有预算用到边 i 上然后添加到MST中形成一个环,找到环中权值最大的边删除。设边i的两端点为 a, b, 找到结点u = LCA(MST,a, b),那么这个环就是由 a~u,b~u 以及边 i 围成。

  如何找到权值最大的边呢?可以通过倍增法,具体做法如下:

  设mw[a][j]表示结点 a 到它的第 2^j 倍祖先的路径上权值最大的边编号,类似LCA倍增法的做法预处理出所有结点的mw[a][j]值

for(int j = ; (<<j) < n; j++) //倍增
for(int i = ; i <= n; i++) if(pa[i][j-] != -){
pa[i][j] = pa[pa[i][j-]][j-]; //计算节点i的第2^j倍祖先
int e1 = mw[i][j-];
int e2 = mw[pa[i][j-]][j-];
mw[i][j] = weight[e1] < weight[e2] ? e2 : e1;
}

然后计算更换边之后新树的权值,找到使总干道权值最低的更新方法,最后跑一边最小生成树算法即可。

代码如下:

 #include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue> using namespace std;
const int maxn = 2e5 + ; #define rep(i, x, n) for(int i = x; i <= n; i++)
struct edge{
int u, v;
int w, c;
edge(int uu, int vv, int ww, int cc): u(uu), v(vv), w(ww), c(cc){}
bool operator<(const edge& b) const {
return w < b.w;
}
}; typedef long long LL;
vector<edge> E;
int r[maxn];
vector<int> G[maxn]; void add_edge(int a, int b, int w, int c) {
E.push_back(edge(a, b, w, c));
int id = E.size() - ;
G[a].push_back(id);
G[b].push_back(id);
} int n, m, S;
int w[maxn];
int c[maxn];
int fa[maxn];
LL w_mst;
int findfa(int x) {return x == fa[x] ? x : fa[x] = findfa(fa[x]);} //并查集
vector<int> G2[maxn];
bool cmp(int a, int b) {
return E[a] < E[b];
}
vector<int> A;
LL kurskal() { //计算MST
LL ans = ;
for(int i = ; i <= n; i++) fa[i] = i;
for(int i = ; i < m; i++) r[i] = i;
sort(r, r+m, cmp);
for(int i = ; i < m; i++) {
edge e = E[r[i]];
int x = findfa(e.u);
int y = findfa(e.v);
if(x != y) {
fa[x] = y;
ans += e.w;
A.push_back(r[i]); //将添加的边同时保存在A中
G2[e.u].push_back(r[i]);
G2[e.v].push_back(r[i]);
}
}
return ans;
}
int dep[maxn];
int pa[maxn][]; //表示结点a的第 2^j 倍祖先
int mw[maxn][]; //表示结点 a 到它的第 2^j 倍祖先的路径上权值最大的边编号
void dfs(int u, int f, int d) { //dfs计算MST中所有结点的深度,初始化pa数组和mw数组
dep[u] = d;
pa[u][] = f;
for(int i = ; i < G2[u].size(); i++) {
edge& e = E[G2[u][i]];
int v = e.u == u ? e.v : e.u;
if(v != f) {
mw[v][] = G2[u][i];
dfs(v, u, d+);
}
}
}
void pre() { //预处理出所有结点的mw,pa
for(int j = ; (<<j) < n; j++)
for(int i = ; i <= n; i++) if(pa[i][j-] != -){
pa[i][j] = pa[pa[i][j-]][j-];
int e1 = mw[i][j-];
int e2 = mw[pa[i][j-]][j-];
mw[i][j] = E[e1] < E[e2] ? e2 : e1;
}
} int lca(int a, int b, int &me) { //计算最近公共祖先的同时算出a,b路径中权值最大的边保存到me中
me = -;
if(dep[a] < dep[b]) swap(a, b);
int i, j;
for(i = ; (<<i) <= dep[a]; i++);
i--;
for(j = i; j >= ; j--)
if(dep[a] - (<<j) >= dep[b]) {
int e_id = mw[a][j];
a = pa[a][j];
if(me == -) me = e_id;
me = E[me] < E[e_id] ? e_id : me;
}
if(a == b) return a;
for(j = i; j >= ; j--)
if(pa[a][j] != - && pa[a][j] != pa[b][j]) {
int e1 = mw[a][j];
int e2 = mw[b][j];
a = pa[a][j];
b = pa[b][j];
if(me == -) me = e1;
me = E[me] < E[e1] ? e1 : me;
me = E[me] < E[e2] ? e2 : me;
}
int e1 = mw[a][];
int e2 = mw[b][];
if(me == -) me = e1;
me = E[me] < E[e1] ? e1 : me;
me = E[me] < E[e2] ? e2 : me;
return pa[a][];
}
int main() {
scanf("%d%d", &n, &m);
for(int i = ; i <= m; i++) scanf("%d", &w[i]);
for(int i = ; i <= m; i++) scanf("%d", &c[i]);
for(int i = ; i <= m; i++) {
int a, b;
scanf("%d%d", &a, &b);
add_edge(a, b, w[i], c[i]);
}
scanf("%d", &S);
w_mst = kurskal();
memset(pa, -, sizeof pa);
memset(mw, , sizeof mw);
dfs(, -, );
pre();
LL ans_w = w_mst;
int ans_e = -;
for(int i = ; i <= m; i++) {
int e = i-;
int me;
lca(E[e].u, E[e].v, me);
LL temp = w_mst - E[me].w + E[e].w - S/c[i];
//cout << i <<" : me : "<< me <<endl;
if(temp < ans_w) {
ans_w = temp;
ans_e = e;
//cout << "ans_w : " << ans_w<< "ans_e : " << ans_e+1 <<endl;
}
}
if(ans_e != -) E[ans_e].w -= S/c[ans_e+];
A.clear();
w_mst = kurskal();
cout << ans_w << endl;
for(int i = ; i < A.size(); i++)
cout << A[i] + << " " << E[A[i]].w << endl;
return ;
}

codeforce 378 div 2 F —— Drivers Dissatisfaction (最小生成树,LCA,倍增)的更多相关文章

  1. Codeforces Round #378 (Div. 2) F - Drivers Dissatisfaction

    F - Drivers Dissatisfaction 题目大意:给你n个点,m条边,每个边都有一个权重w,每条边也有一个c表示,消耗c元可以把这条边的权重减1,求最多消耗s元的最小生成树. 思路:因 ...

  2. Codeforces Round #378 (Div. 2)F - Drivers Dissatisfaction GNU

    http://codeforces.com/contest/733/problem/F 题意:给你一些城市和一些路,每条路有不满意程度和每减少一点不满意程度的花费,给出最大花费,要求找出花费小于s的最 ...

  3. Drivers Dissatisfaction 最小生成树+LCA

    题意:给一张n个点m条边的连通图,每条边(ai,bi)有一个权值wi和费用ci, 表示这条边每降低1的权值需要ci的花费.现在一共有S费用可以用来降低某些边的权值 (可以降到负数),求图中的一棵权值和 ...

  4. Codeforces Round #378 (Div. 2)F

    题目:一个带权连通无向图,给第i条边权值减1需要花费ci元,你一共有S元,求最小生成树. 容易得出钱全部花在一条边上是最优的. 我们先做一遍最小生成树. 然后我们枚举减哪一条边. 如果这条边是树上的, ...

  5. CF733F Drivers Dissatisfaction【链剖】【最小生成树应用】

    F. Drivers Dissatisfaction time limit per test 4 seconds memory limit per test 256 megabytes input s ...

  6. Drivers Dissatisfaction

    Drivers Dissatisfaction time limit per test 4 seconds memory limit per test 256 megabytes input stan ...

  7. Codeforces Round #378 (Div. 2)

    A: 思路: 水题,没啥意思; B: 思路: 暴力,也没啥意思; C: 思路: 思维,可以发现从前往后和为b[i]的分成一块,然后这一块里面如果都相同就没法开始吃,然后再暴力找到那个最大的且能一开始就 ...

  8. Codeforces Educational Codeforces Round 44 (Rated for Div. 2) F. Isomorphic Strings

    Codeforces Educational Codeforces Round 44 (Rated for Div. 2) F. Isomorphic Strings 题目连接: http://cod ...

  9. Codeforces Round #485 (Div. 2) F. AND Graph

    Codeforces Round #485 (Div. 2) F. AND Graph 题目连接: http://codeforces.com/contest/987/problem/F Descri ...

随机推荐

  1. 从零起步 系统入门Python爬虫工程师

    从零起步 系统入门Python爬虫工程师 整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课程本身没问题,大家看的 ...

  2. 一款优秀的前端JS框架—AngularJS

      前  言 AngularJS 是一个 JavaScript 框架.它可通过 <script> 标签添加到 HTML 页面. AngularJS 通过 指令 扩展了 HTML,且通过 表 ...

  3. Leetcode859.Buddy Strings亲密字符串

    给定两个由小写字母构成的字符串 A 和 B ,只要我们可以通过交换 A 中的两个字母得到与 B 相等的结果,就返回 true :否则返回 false . 示例 1: 输入: A = "ab& ...

  4. PHPCMS快速建站系列之getcache()的用法

    /** * 读取缓存,默认为文件缓存,不加载缓存配置. * @param string $name 缓存名称 * @param $filepath 数据路径(模块名称) caches/cache_$f ...

  5. 推荐一个好用的git图形化工具

    之前一直用的是SmartGit,虽然贼好用,但是是收费的. 现在看到了一款免费也非常炫酷的工具,忍不住强烈推荐一下GitKraken rebase,cherry pick啥的用挺好用

  6. 【JZOJ4810】【NOIP2016提高A组五校联考1】道路规划

    题目描述 输入 输出 样例输入 5 1 4 5 2 3 3 4 2 1 5 样例输出 3 数据范围 样例解释 解法 模型显然. 设第一列为a[],第二列为b[],f[i]为前i个数的最大答案. 顺序枚 ...

  7. Android Bitmap缓存介绍

    转载自http://blog.csdn.net/linghu_java/article/details/8595717 Android中加载一个Bitmap(位图)到你的UI界面是非常简单的,但是如果 ...

  8. 【OI】操作树

    操作数,一般用来做那些对数列进行添加.撤销操作的题. 假设一开始有一个空数列,有三个操作 (1)在数列后加一个数 (2)求数列中某位置的值 (3)撤销掉最后进行的若干次操作(1和3) 考虑建一棵树,1 ...

  9. js树状菜单

    html部分 <ul class="tree"> <li><span><a href="#">JavaScrip ...

  10. MUI - 打开页面默认弹出键盘及返回关闭键盘

    打开页面默认弹出键盘及返回关闭键盘 http://www.cnblogs.com/phillyx/ (function(keyboard) { var openSoftKeyboard = funct ...