「WC2010」重建计划(长链剖分/点分治)
「WC2010」重建计划(长链剖分/点分治)
题目描述
有一棵大小为 \(n\) 的树,给定 \(L, R\) ,要求找到一条长度在 \([L, R]\) 的路径,并且路径上边权的平均值最大
\(1 \leq n,L,R \leq 10^5\)
解题思路 :
前几天沉迷初赛来写几道数据结构恢复一下代码能力,坑填完之后可能就要开始啃思维题了QwQ。
这个题貌似长链剖分和点分复杂度都是 \(O(nlog^2n)\) 的,点分好久都没碰了,长链剖分也只有暑假里口胡了几个多校的题而已,先讲做法吧
这个题很显然可以分数规划,二分答案后问题转化为每条边边权变为\(w_i - mid\) ,判断能不能找到一条长度在 \([L, R]\) 且边权和非负的路径,实际上可以求一条满足条件且边权和最大的路。
点分治:
比较显然的做法是对于每一个分治中心用一个数据结构来维护到点分中心的每一种长度的路径的最值,然后暴力拼合路径即可,这样做的话总复杂度是 \(O(nlog^3n)\) ,感觉不太能松的过去。
简单观察发现,对于当前长度 \(i\) ,随着 \(i\) 递增可行的区间是单调左移的,所以可以用一个单调队列来处理每一个分治中心的答案。不过要注意的是,总的区间大小等价于当前处理到的最长的路径长度,如果先处理一条很长的路径,剩下的点数很多的话复杂度就退化到 \(O(n^2logn)\) 了。
解决方法是对每一个儿子按照其最长路径长度排序(也就是最大深度),这样处理每一个儿子的复杂度不会超过其最长路径长度,复杂度就是正确的 \(O(nlog^2n)\) 了,类似的套路也可以在 \(\text{BZOJ Normal}\) 见到
/*program by mangoyang*/
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
int f = 0, ch = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
const int N = 200005;
const double eps = 1e-4;
int a[N], b[N], nxt[N], head[N], cnt, n, L, R;
inline void add(int x, int y, int z){
a[++cnt] = y, b[cnt] = z, nxt[cnt] = head[x], head[x] = cnt;
}
namespace Tree{
double f[N], res, mid;
vector<int> g[N];
vector<double> c[N];
int sz[N], q[N], vis[N], all, mn, firt, root;
inline void getroot(int u, int fa){
int msize = 0; sz[u] = 1;
for(int p = head[u]; p; p = nxt[p]){
int v = a[p];
if(v == fa || vis[v]) continue;
getroot(v, u), sz[u] += sz[v];
if(sz[v] > msize) msize = sz[v];
}
msize = max(msize, all - sz[u]);
if(msize <= mn) mn = msize, root = u;
}
inline void build(int u){
int ls = all; vis[u] = 1;
for(int p = head[u]; p; p = nxt[p]){
int v = a[p];
if(vis[v]) continue;
mn = all = sz[v] > sz[u] ? ls - sz[u] : sz[v];
getroot(v, u), g[u].push_back(root), build(root);
}
}
inline void realmain(){
mn = all = n, getroot(1, 0), build(firt = root);
}
inline bool cmp(int A, int B){
return c[A].size() < c[B].size();
}
inline void getdis(int u, int fa, int x, int dep, double dis){
if(c[x].size() < dep + 1) c[x].push_back(dis);
else c[x][dep] = max(c[x][dep], dis);
for(int p = head[u]; p; p = nxt[p]){
int v = a[p];
if(!vis[v] && v != fa)
getdis(v, u, x, dep + 1, dis + b[p] - mid);
}
}
inline void solve(int u){
vis[u] = 1;
vector<int> now; now.clear(); int len = 0;
for(int p = head[u]; p; p = nxt[p]){
int v = a[p];
if(vis[v]) continue;
c[v].clear(), getdis(v, u, v, 0, b[p] - mid);
now.push_back(v), len = Max(len, c[v].size());
}
sort(now.begin(), now.end(), cmp);
for(int i = 1; i <= len; i++) f[i] = -inf;
int mx = 0;
for(int pos = 0; pos < now.size(); pos++){
int x = now[pos], h = 1, t = 0, p = 0;
for(int i = c[x].size() - 1; ~i; i--){
while(h <= t && q[h] + i + 1 < L) h++;
while(p <= mx && i + p + 1 < L) p++;
while(p <= mx && i + p + 1 <= R){
while(h <= t && f[p] >= f[q[t]]) t--;
q[++t] = p, p++;
}
if(h <= t) res = max(res, c[x][i] + f[q[h]]);
}
for(int i = 0; i < c[x].size(); i++)
f[i+1] = max(f[i+1], c[x][i]);
mx = c[x].size();
}
for(int i = 0; i < g[u].size(); i++) solve(g[u][i]);
}
inline bool check(double x){
mid = x, res = -inf;
memset(vis, 0, sizeof(vis)), solve(firt);
return res >= eps;
}
}
int main(){
read(n), read(L), read(R);
for(int i = 1, x, y, z; i < n; i++){
read(x), read(y), read(z);
add(x, y, z), add(y, x, z);
}
Tree::realmain();
double l = 0, r = 1000000, ans = 0;
while(l + eps < r){
double mid = (l + r) / 2.0;
if(Tree::check(mid)) l = mid, ans = mid; else r = mid;
}
printf("%.3lf", ans);
return 0;
}
长链剖分
本质上点分做的事情是每次合并多个以深度为下标的数组求最值,观察发现这个题可以长链剖分。每一次保留重链的向上路径,并把轻链的信息一一合并上去。
但是这里就不能用单调队列优化了,因为重链是一开始就要被保留的,如果用单调队列的话复杂度会被同样的东西卡掉,所以必须要用线段树来维护答案。
具体实现的话只需要维护一下 \(tag\) 标记实现在重链的链头加点,每次在线段树里面查指定范围内的最值合并即可,复杂度也是 \(O(nlog^2n)\)
/*program by mangoyang*/
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
int f = 0, ch = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
const int N = 200005;
const double eps = 1e-4;
double tag[N], f[N], ans, mid;
int a[N], b[N], nxt[N], head[N], cnt;
int dfn[N], dep[N], ms[N], w[N], tot, n, L, R;
inline void add(int x, int y, int z){
a[++cnt] = y, b[cnt] = z, nxt[cnt] = head[x], head[x] = cnt;
}
struct SegmentTree{
#define lson (u << 1)
#define rson (u << 1 | 1)
double s[N<<2];
inline void clear(){
for(int i = 0; i < (N << 2); i++) s[i] = -inf;
}
inline void modify(int u, int l, int r, int pos, double x){
if(l == r) return (void) (s[u] = max(s[u], x));
int mid = l + r >> 1;
if(pos <= mid) modify(lson, l, mid, pos, x);
else modify(rson, mid + 1, r, pos, x); s[u] = Max(s[lson], s[rson]);
}
inline double query(int u, int l, int r, int L, int R){
if(l >= L && r <= R) return s[u];
int mid = l + r >> 1; double res = -inf;
if(L <= mid) res = max(res, query(lson, l, mid, L, R));
if(mid < R) res = max(res, query(rson, mid + 1, r, L, R));
return res;
}
}Seg;
inline void dfs(int u, int fa){
for(int p = head[u]; p; p = nxt[p]){
int v = a[p];
if(v == fa) continue;
dfs(v, u);
if(dep[v] >= dep[ms[u]]) ms[u] = v, w[u] = b[p];
if(dep[v] + 1 > dep[u]) dep[u] = dep[v] + 1;
}
}
inline void split(int u, int fa){
if(!dfn[u]) dfn[u] = ++tot; int pu = dfn[u];
if(ms[u]) split(ms[u], u), tag[pu] = tag[pu+1] + w[u] - mid;
Seg.modify(1, 1, n, pu, f[pu] = -tag[pu]);
if(L <= dep[u]){
double tmp = Seg.query(1, 1, n, pu + L, pu + min(dep[u], R));
ans = max(ans, tmp + tag[pu]);
}
for(int p = head[u]; p; p = nxt[p]){
int v = a[p], pv = dfn[v];
if(v == fa || v == ms[u]) continue;
split(v, u);
for(int i = 0; i <= dep[v]; i++){
int l = pu + max(0, L - i - 1), r = pu + min(dep[u], R - i - 1);
double tmp = Seg.query(1, 1, n, l, r);
ans = max(ans, tmp + tag[pv] + tag[pu] + f[pv+i] + b[p] - mid);
}
for(int i = 0; i <= dep[v]; i++){
double tmp = tag[pv] + f[pv+i] + b[p] - mid - tag[pu];
if(tmp > f[pu+i+1]) Seg.modify(1, 1, n, pu + i + 1, f[pu+i+1] = tmp);
}
}
}
inline bool check(){
Seg.clear();
ans = -inf, split(1, 0); return ans >= eps;
}
int main(){
read(n), read(L), read(R);
for(int i = 1, x, y, z; i < n; i++){
read(x), read(y), read(z);
add(x, y, z), add(y, x, z);
}
dfs(1, 0);
double l = 0, r = 1000000, realans = 0;
while(l + eps < r){
mid = (l + r) / 2.0;
if(check()) l = mid, realans = mid; else r = mid;
}
printf("%.3lf", realans);
return 0;
}
「WC2010」重建计划(长链剖分/点分治)的更多相关文章
- [WC2010]重建计划 长链剖分
[WC2010]重建计划 LG传送门 又一道长链剖分好题. 这题写点分治的人应该比较多吧,但是我太菜了,只会长链剖分. 如果你还不会长链剖分的基本操作,可以看看我的长链剖分总结. 首先一看求平均值最大 ...
- BZOJ 1758 / Luogu P4292 [WC2010]重建计划 (分数规划(二分/迭代) + 长链剖分/点分治)
题意 自己看. 分析 求这个平均值的最大值就是分数规划,二分一下就变成了求一条长度在[L,R]内路径的权值和最大.有淀粉质的做法但是我没写,感觉常数会很大.这道题可以用长链剖分做. 先对树长链剖分. ...
- [WC2010][BZOJ1758]重建计划-[二分+分数规划+点分治]
Description 传送门 Solution 看到那个式子,显然想到分数规划...(不然好难呢) 然后二分答案,则每条边的权值设为g(e)-ans.最后要让路径长度在[L,U]范围内的路径权值&g ...
- Bzoj4016/洛谷P2993 [FJOI2014] 最短路径树问题(最短路径问题+长链剖分/点分治)
题面 Bzoj 洛谷 题解 首先把最短路径树建出来(用\(Dijkstra\),没试过\(SPFA\)\(\leftarrow\)它死了),然后问题就变成了一个关于深度的问题,可以用长链剖分做,所以我 ...
- UOJ#33-[UR #2]树上GCD【长链剖分,根号分治】
正题 题目链接:https://uoj.ac/problem/33 题目大意 给出\(n\)个点的一棵树 定义\(f(x,y)=gcd(\ dis(x,lca),dis(y,lca)\ )\). 对于 ...
- loj 2955 「NOIP2018」保卫王国 - 树链剖分 - 动态规划
题目传送门 传送门 想抄一个短一点ddp板子.然后照着Jode抄,莫名其妙多了90行和1.3k. Code /** * loj * Problem#2955 * Accepted * Time: 26 ...
- BZOJ1758[Wc2010]重建计划——分数规划+长链剖分+线段树+二分答案+树形DP
题目描述 输入 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai, ...
- BZOJ.1758.[WC2010]重建计划(分数规划 点分治 单调队列/长链剖分 线段树)
题目链接 BZOJ 洛谷 点分治 单调队列: 二分答案,然后判断是否存在一条长度在\([L,R]\)的路径满足权值和非负.可以点分治. 对于(距当前根节点)深度为\(d\)的一条路径,可以用其它子树深 ...
- 2019.01.21 bzoj1758: [Wc2010]重建计划(01分数规划+长链剖分+线段树)
传送门 长链剖分好题. 题意简述:给一棵树,问边数在[L,R][L,R][L,R]之间的路径权值和与边数之比的最大值. 思路: 用脚指头想都知道要01分数规划. 考虑怎么checkcheckcheck ...
随机推荐
- R3—日期处理
一. 问题引入 下面是一个房地产价格数据,现在想要提取2008年6月份的数据进行分析,在R中该如何操作呢? city price bedrooms squarefeet lotsize latitud ...
- SMTP暴力破解
这里实现一个SMTP的暴力破解程序,实验搭建的是postfix服务器,猜解用户名字典(user.txt)和密码字典(password.txt)中匹配的用户名密码对, 程序开发环境是: WinXP VC ...
- MFC CListCtrl 将一个列表的选中项添加到另一个列表
MFC CListCtrl 将一个列表的选中项添加到另一个列表, 用VC6.0实现: 简单记录一下自己的学习历程, 和大家分享,如果对你有用,我很高兴. 1.新建一个基于对话框的工程(Dialog-B ...
- 基于canvas的图片编辑合成器
在我们日常的前端开发中,经常会要给服务器上传图片,但是局限很大,图片只能是已有的,假设我想把多张图片合成一张上传就需要借助图片编辑器了,但是现在我们有了canvas合成就简单多了 首先我们看图片编辑器 ...
- Entity Framework(EF的Database First方法)
EntityFramework,是Microsoft的一款ORM(Object-Relation-Mapping)框架.同其它ORM(如,NHibernate,Hibernate)一样, 一是为了使开 ...
- 64_m3
molequeue-doc-0.8.0-2.20161222giteb397e.fc26.no..> 05-Apr-2017 10:04 451570 molequeue-libs-0.8.0- ...
- 不老的神器:安全扫描器Nmap渗透使用指南【转】
介绍 nmap是用来探测计算机网络上的主机和服务的一种安全扫描器.为了绘制网络拓扑图Nmap的发送特制的数据包到目标主机然后对返回数据包进行分析.Nmap是一款枚举和测试网络的强大工具. 特点 主机探 ...
- 191.Number of 1Bits---位运算---《剑指offer》10
题目链接:https://leetcode.com/problems/number-of-1-bits/description/ 题目大意:与338题类似,求解某个无符号32位整数的二进制表示的1的个 ...
- UVA题解三
UVA题解三 UVA 127 题目描述:\(52\)张扑克牌排成一列,如果一张牌的花色或者数字与左边第一列的最上面的牌相同,则将这张牌移到左边第一列的最上面,如果一张牌的花色或者数字与左边第三列的最上 ...
- openjudge-NOI 2.6-1944 吃糖果
题目链接:http://noi.openjudge.cn/ch0206/1944/ 题解: 递推,题目中给出了很详细的过程,不讲解 #include<cstdio> int n; int ...