【Codeforces827D/CF827D】Best Edge Weight(最小生成树性质+倍增/树链剖分+线段树)
题目
分析
倍增神题……(感谢T*C神犇给我讲qwq)
这道题需要考虑最小生成树的性质。首先随便求出一棵最小生成树,把树边和非树边分开处理。
首先,对于非树边\((u,v)\)(表示一条两端点为\(u\)和\(v\)的边,下同)。考虑Kruskal算法的过程,它必定成为树边的充要条件是它的权值小于树上\(u\)到\(v\)之间的路径上的某条边\(e\),这样就会选中这条边来连接\(u\)和\(v\)所在的连通块而不是选中\(e\)。因此,非树边的答案就是它两端点之间树上路径上最大边的权值减\(1\)(如果相等则两条边都可以选,不符合“必定成为树边”)。
然后,考虑对于树边\((u,v)\)的情况。把上面的情况反过来考虑,如果有一条非树边\((a,b)\),树上\(a\)和\(b\)的路径要经过\((u,v)\),那么只有当任意这样的\((a,b)\)的权值都大于\((u,v)\)时,\((u,v)\)才必定不会被别的边替换下来,也就是必定成为树边。因此,树边的答案就是所有上述\((a,b)\)中权值最小的边的权值减\(1\)。
那么,对于非树边\((u,v,w)\),我们要查询\((u,v)\)间路径上的最大边权,并且需要用\(w\)来更新这段路径上所有树边的答案(即把这些边的答案与\(w\)取最小值)。注意这两种操作是互不干扰的,不要混淆……
那么这明显就是个树剖+线段树裸题了。区间查最大值和区间修改为最小值都是线段树的基本操作,文末有代码,不再赘述。
好现在开始隆重介绍某位神仙给我讲的倍增做法,比树剖+线段树少一个\(logn\)。以下为了方便叙述,默认\(1\)号点为树根,\(fa[a][i]\)表示\(a\)点往上走\(2^i\)步所到的点。
区间查最大值也是倍增的经典应用,不必多言。区间取最小是这个算法最精妙之处。考虑用\(minn[a][i]\)表示所有一端是\(a\)及其子树,另一端是\(fa[a][i]\)及其祖先的非树边的最小权值。\(minn[a][0]\)就是\(a\)和\(a\)的父亲之间的边的答案。那么,当我们更新\(minn[a][i]\)时,也要尝试更新\(minn[a][i-1]\)和\(minn[fa[a][i-1][i-1]\)(覆盖了大区间的非树边一定会覆盖该区间的子区间),这是一个递归的过程。然而每次修改都递归一次的复杂度是\(O(n)\)的(最坏会被卡到深度为\(logn\)的满二叉树),\(O(nm)\)显然是过不去的。
但是我们要注意两件事。第一,\(minn[a][i]\)只会变小不会变大,且修改之间不会互相影响;第二,全部非树边考虑完后才会查询。基于这两件事,我们可以全部修改尽可能大的区间,最后再一起递归下去。这个操作类似于线段树的pushdown。这一段非常重要和巧妙,单独给出代码。(注意要分层处理,所以\(j\)应当在外层循环)
for (int j = B - 1; j > 0; j--)
for (int i = 1; i <= n; i++)
{
minn[i][j - 1] = min(minn[i][j - 1], minn[i][j]);
minn[fa[i][j - 1]][j - 1] = min(minn[fa[i][j - 1]][j - 1], minn[i][j]);
}
本题其余细节详见下方的代码。
代码:
法1:倍增(\(143\)行,\(561\)ms,\(65200\)KB)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
namespace zyt
{
const int N = 2e5 + 10, M = 2e5 + 10, B = 20, INF = 0x3f3f3f3f;
int n, m;
struct ed
{
int u, v, w, id;
bool in_tree;
bool operator < (const ed &b) const
{
return w < b.w;
}
}e[M];
struct edge
{
int to, w, id;
};
vector<edge> g[N];
int p[N], pre[N], dep[N], maxx[N][B], minn[N][B], fa[N][B], ans[M];
int f(const int x)
{
return x == p[x] ? x : p[x] = f(p[x]);
}
void dfs(const int u, const int f)
{
dep[u] = dep[f] + 1;
fa[u][0] = f;
for (int i = 1; i < B; i++)
{
fa[u][i] = fa[fa[u][i - 1]][i - 1];
maxx[u][i] = max(maxx[u][i - 1], maxx[fa[u][i - 1]][i - 1]);
}
for (int i = 0; i < g[u].size(); i++)
{
int v = g[u][i].to;
if (v == f)
continue;
pre[v] = g[u][i].id;
maxx[v][0] = g[u][i].w;
dfs(v, u);
}
}
int query_max(int a, int b)
{
int ans = 0;
if (dep[a] < dep[b])
swap(a, b);
for (int i = B - 1; i >= 0; i--)
if (dep[fa[a][i]] >= dep[b])
ans = max(ans, maxx[a][i]), a = fa[a][i];
if (a == b)
return ans;
for (int i = B - 1; i >= 0; i--)
if (fa[a][i] != fa[b][i])
{
ans = max(ans, max(maxx[a][i], maxx[b][i]));
a = fa[a][i], b = fa[b][i];
}
return max(ans, max(maxx[a][0], maxx[b][0]));
}
void change_min(int a, int b, const int w)
{
if (dep[a] < dep[b])
swap(a, b);
for (int i = B - 1; i >= 0; i--)
if (dep[fa[a][i]] >= dep[b])
minn[a][i] = min(minn[a][i], w), a = fa[a][i];
if (a == b)
return;
for (int i = B - 1; i >= 0; i--)
if (fa[a][i] != fa[b][i])
{
minn[a][i] = min(minn[a][i], w);
minn[b][i] = min(minn[b][i], w);
a = fa[a][i];
b = fa[b][i];
}
minn[a][0] = min(minn[a][0], w);
minn[b][0] = min(minn[b][0], w);
}
void Kruskal()
{
for (int i = 1; i <= n; i++)
p[i] = i;
for (int i = 0; i < m; i++)
{
int u = e[i].u, v = e[i].v, x = f(u), y = f(v);
if (x != y)
{
p[x] = y;
g[u].push_back((edge){v, e[i].w, e[i].id});
g[v].push_back((edge){u, e[i].w, e[i].id});
e[i].in_tree = true;
}
}
}
int work()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
memset(minn, INF, sizeof(int[n + 1][B]));
for (int i = 0; i < m; i++)
{
cin >> e[i].u >> e[i].v >> e[i].w;
e[i].id = i;
}
sort(e, e + m);
Kruskal();
dfs(1, 0);
for (int i = 0; i < m; i++)
if (!e[i].in_tree)
{
ans[e[i].id] = query_max(e[i].u, e[i].v) - 1;
change_min(e[i].u, e[i].v, e[i].w);
}
for (int j = B - 1; j > 0; j--)
for (int i = 1; i <= n; i++)
{
minn[i][j - 1] = min(minn[i][j - 1], minn[i][j]);
minn[fa[i][j - 1]][j - 1] = min(minn[fa[i][j - 1]][j - 1], minn[i][j]);
}
for (int i = 2; i <= n; i++)
ans[pre[i]] = minn[i][0] - 1;
for (int i = 0; i < m; i++)
if (ans[i] < INF - 1)
cout << ans[i] << ' ';
else
cout << "-1 ";
return 0;
}
}
int main()
{
return zyt::work();
}
法2:树链剖分+线段树(\(230\)行,\(826\)ms,\(34600\)KB)
(我很傻地第一次漏了size[u]+=size[v]搞成了随机剖分还以为是因为卡常所以T……菜死我了)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
namespace zyt
{
const int N = 2e5 + 10, M = 2e5 + 10, B = 20, INF = 0x3f3f3f3f;
int n, m;
struct ed
{
int u, v, w, id;
bool in_tree;
bool operator < (const ed &b) const
{
return w < b.w;
}
}e[M];
struct edge
{
int to, w, id;
};
vector<edge> g[N];
int p[N], pre[N], pre_w[N], dep[N], w[N], son[N], size[N], dfn[N], top[N], fa[N], ans[N], cnt;
int f(const int x)
{
return x == p[x] ? x : p[x] = f(p[x]);
}
namespace Tree_Chain_Cut
{
void dfs1(const int u, const int f)
{
dep[u] = dep[f] + 1;
fa[u] = f;
size[u] = 1;
for (int i = 0; i < g[u].size(); i++)
{
int v = g[u][i].to;
if (v == f)
continue;
pre[v] = g[u][i].id;
pre_w[v] = g[u][i].w;
dfs1(v, u);
if (size[v] > size[son[u]])
son[u] = v;
size[u] += size[v];
}
}
void dfs2(const int u, const int t)
{
dfn[u] = ++cnt;
w[dfn[u]] = pre_w[u];
top[u] = t;
if (son[u])
dfs2(son[u], t);
for (int i = 0; i < g[u].size(); i++)
{
int v = g[u][i].to;
if (dfn[v])
continue;
dfs2(v, v);
}
}
namespace Segment_Tree
{
struct node
{
int minn, maxx, tag;
}tree[N * 4];
inline void update(const int rot)
{
tree[rot].minn = min(tree[rot << 1].minn, tree[rot << 1 | 1].minn);
tree[rot].maxx = max(tree[rot << 1].maxx, tree[rot << 1 | 1].maxx);
}
inline void pushdown(const int rot)
{
int tag = tree[rot].tag;
tree[rot << 1].minn = min(tree[rot << 1].minn, tag);
tree[rot << 1].tag = min(tree[rot << 1].tag, tag);
tree[rot << 1 | 1].minn = min(tree[rot << 1 | 1].minn, tag);
tree[rot << 1 | 1].tag = min(tree[rot << 1 | 1].tag, tag);
tree[rot].tag = INF;
}
void build(const int rot, const int lt, const int rt)
{
tree[rot].minn = tree[rot].tag = INF;
if (lt == rt)
{
tree[rot].maxx = w[lt];
return;
}
int mid = (lt + rt) >> 1;
build(rot << 1, lt, mid);
build(rot << 1 | 1, mid + 1, rt);
update(rot);
}
void change_min(const int rot, const int lt, const int rt, const int ls, const int rs, const int w)
{
if (ls <= lt && rt <= rs)
{
tree[rot].minn = min(tree[rot].minn, w);
tree[rot].tag = min(tree[rot].tag, w);
return;
}
pushdown(rot);
int mid = (lt + rt) >> 1;
if (ls <= mid)
change_min(rot << 1, lt, mid, ls, rs, w);
if (rs > mid)
change_min(rot << 1 | 1, mid + 1, rt, ls, rs, w);
update(rot);
}
int query_max(const int rot, const int lt, const int rt, const int ls, const int rs)
{
if (ls <= lt && rt <= rs)
return tree[rot].maxx;
pushdown(rot);
int mid = (lt + rt) >> 1, ans = 0;
if (ls <= mid)
ans = max(ans, query_max(rot << 1, lt, mid, ls, rs));
if (rs > mid)
ans = max(ans, query_max(rot << 1 | 1, mid + 1, rt, ls, rs));
return ans;
}
int query_min(const int rot, const int lt, const int rt, const int pos)
{
if (lt == rt)
return tree[rot].minn;
pushdown(rot);
int mid = (lt + rt) >> 1, ans = 0;
if (pos <= mid)
return query_min(rot << 1, lt, mid, pos);
else
return query_min(rot << 1 | 1, mid + 1, rt, pos);
return ans;
}
}
int query_max(int a, int b)
{
int ans = 0;
while (top[a] != top[b])
{
if (dep[top[a]] < dep[top[b]])
swap(a, b);
ans = max(ans, Segment_Tree::query_max(1, 1, n, dfn[top[a]], dfn[a]));
a = fa[top[a]];
}
if (a != b)
{
if (dep[a] < dep[b])
swap(a, b);
ans = max(ans, Segment_Tree::query_max(1, 1, n, dfn[b] + 1, dfn[a]));
}
return ans;
}
int query_min(const int a)
{
return Segment_Tree::query_min(1, 1, n, dfn[a]);
}
void change_min(int a, int b, const int w)
{
while (top[a] != top[b])
{
if (dep[top[a]] < dep[top[b]])
swap(a, b);
Segment_Tree::change_min(1, 1, n, dfn[top[a]], dfn[a], w);
a = fa[top[a]];
}
if (a != b)
{
if (dep[a] < dep[b])
swap(a, b);
Segment_Tree::change_min(1, 1, n, dfn[b] + 1, dfn[a], w);
}
}
}
void Kruskal()
{
for (int i = 1; i <= n; i++)
p[i] = i;
for (int i = 0; i < m; i++)
{
int u = e[i].u, v = e[i].v, x = f(u), y = f(v);
if (x != y)
{
p[x] = y;
g[u].push_back((edge){v, e[i].w, e[i].id});
g[v].push_back((edge){u, e[i].w, e[i].id});
e[i].in_tree = true;
}
}
}
int work()
{
using namespace Tree_Chain_Cut;
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 0; i < m; i++)
{
cin >> e[i].u >> e[i].v >> e[i].w;
e[i].id = i;
}
sort(e, e + m);
Kruskal();
dfs1(1, 0);
dfs2(1, 1);
Segment_Tree::build(1, 1, n);
for (int i = 0; i < m; i++)
if (!e[i].in_tree)
{
ans[e[i].id] = query_max(e[i].u, e[i].v) - 1;
change_min(e[i].u, e[i].v, e[i].w);
}
for (int i = 2; i <= n; i++)
ans[pre[i]] = query_min(i) - 1;
for (int i = 0; i < m; i++)
if (ans[i] < INF - 1)
cout << ans[i] << ' ';
else
cout << "-1 ";
return 0;
}
}
int main()
{
return zyt::work();
}
【Codeforces827D/CF827D】Best Edge Weight(最小生成树性质+倍增/树链剖分+线段树)的更多相关文章
- 【bzoj2238】Mst 最小生成树+树链剖分+线段树
题目描述 给出一个N个点M条边的无向带权图,以及Q个询问,每次询问在图中删掉一条边后图的最小生成树.(各询问间独立,每次询问不对之后的询问产生影响,即被删掉的边在下一条询问中依然存在) 输入 第一行两 ...
- NOIP 2013 货车运输【Kruskal + 树链剖分 + 线段树 】【倍增】
NOIP 2013 货车运输[树链剖分] 树链剖分 题目描述 Description A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在 ...
- Educational Codeforces Round 3 E. Minimum spanning tree for each edge 最小生成树+树链剖分+线段树
E. Minimum spanning tree for each edge time limit per test 2 seconds memory limit per test 256 megab ...
- CF827D Best Edge Weight[最小生成树+树剖/LCT/(可并堆/set启发式合并+倍增)]
题意:一张图求每条边边权最多改成多少可以让所有MST都包含这条边. 这题还是要考察Kruskal的贪心过程. 先跑一棵MST出来.然后考虑每条边. 如果他是非树边,要让他Kruskal的时候被选入,必 ...
- 【CodeForces】827 D. Best Edge Weight 最小生成树+倍增LCA+并查集
[题目]D. Best Edge Weight [题意]给定n个点m条边的带边权无向连通图,对每条边求最大边权,满足其他边权不变的前提下图的任意最小生成树都经过它.n,m<=2*10^5,1&l ...
- 严格次小生成树[BJWC2010] (树链剖分,倍增,最小生成树)
题目链接 Solution 有几点关键,首先,可以证明次小生成树一定是由最小生成树改变一条边而转化来. 所以需要枚举所有非最小生成树的边\((u,v)\).并且找到 \(u\) 到 \(v\) 的边中 ...
- UVA 11354 - Bond (最小生成树 + 树链剖分)
题目链接~~> 做题感悟:这题開始看到时感觉不是树不优点理,一想能够用 Kruskal 处理成树 ,然后就好攻克了. 解题思路: 先用 Kruskal 处理出最小生成树.然后用树链剖分 + 线段 ...
- NOIP2015 运输计划 - 二分 + 树链剖分 / (倍增 + 差分)
BZOJ CodeVS Uoj 题目大意: 给一个n个点的边带权树,给定m条链,你可以选择树中的任意一条边,将它置为0,使得最长的链长最短. 题目分析: 最小化最大值,二分. 二分最短长度mid,将图 ...
- HDU3710 Battle over Cities(最小生成树+树链剖分+倍增+线段树)
Battle over Cities Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Othe ...
随机推荐
- log4j.properties配置内容的理解
一直知道log4j是用来记录日志的,但一直没去看log4j到底是怎么用的,这两天看了几个log4j.properties配置语句详解的帖子,在这里简陋地记录一下. 在完全不知道log4j怎么用的时候, ...
- Git——跟踪或取消跟踪文件
在Git是用过程中,可能遇到以下情况: 1.被跟踪文件里面有不想跟踪的文件. 2.每次用git status查看状态时总是列出未被跟踪的文件. 解决方法: 1.当被跟踪的文件里面有不想跟踪的文件时,使 ...
- jquery ajax报Uncaught TypeError :Illegal invocation
使用jquery ajax异步提交的时候报Uncaught TypeError :Illegal invocation错误,报错信息如图: 上网查了一下jquery的这个错误,导致这个错误的原因有俩点 ...
- BZOJ2521 最小生成树 最小割
5.26 T2:最小生成树 Description Secsa最近对最小生成树问题特别感兴趣.他已经知道如果要去求出一个n个点.m条边的无向图的最小生成树有一个Krustal算法和另一个Prim的算法 ...
- 武大OJ 706.Farm
Farmer John has a farm. Betsy, a famous cow, loves running in farmer John's land. The noise she made ...
- 26、Java并发性和多线程-线程池
以下内容转自http://ifeve.com/thread-pools/: 线程池(Thread Pool)对于限制应用程序中同一时刻运行的线程数很有用.因为每启动一个新线程都会有相应的性能开销,每个 ...
- css3 transform对其他样式影响,(尤其是position:flixed)
1.transform 会为当前元素添加 position : relative 特性: 当 magin 为负值的时候,就会覆盖到前面的 元素, 然而如果给前面元素添加了transform 属性后,前 ...
- 将list转为json字符串
//确保JSP和servlet的编码方式一致 resp.setContentType("text/html;charset=GBK"); List<String> jy ...
- golang中select case 的用途到底是啥
https://nanxiao.gitbooks.io/golang-101-hacks/content/posts/select-operation.html ------------------- ...
- Mysql net start mysql启动,提示发生系统错误 5 拒绝訪问 解决之道
当前用户的操作权限太低了,出了问题 出错问题截屏例如以下: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/4 ...