标签:暴力 就会 数组 sig lld while name 统计 include
很神的一题,成功改变了我印象中的
“点分治都是水题”
题面是一个经典的分数规划
二分
下面问题就转化成了
二分一个p,每一条边的边权改为\(w - p\),
询问树上是否有一个经过边数在\([L, U]\)的路径,
该路径经过的边权和为正
实际上,这个问题我们只需要找一条边数满足条件的经过边权和最大的路径就可以了
树上的路径统计问题--点分治
考虑对于当前分治到的点,如何找到一个路径,经过边数在\([L,U]\)且边权和最大
\(O(n^2)\)暴力肯定不行
思考一下,一棵树上肯定有很多从根节点出发的路径,他们的边数是相同的
由于我们只要最大,那些小的路径就完全不用考虑
就可以记一个数组tmp[i],表示从当前根节点经过i条边的路径的最大值
那么对于一个i,我们需要找到tmp[L - i]到tmp[U - i]中的最大值
实际上这个下标的范围是一个区间
若i不断减小,那么这个区间就会像数轴正方向平移吧
是不是滑动窗口啊
考虑用单调队列维护
现在还有一个问题:
如果就用单调队列会超时的
因为单调队列的初始化时\(max(U,len)\) 的,len为最先访问的子树的深度
如果最先访问到了一个深度非常大的子树,然后该节点的子树有很多
比如一个n/2的链,链的一端是一个n/2的菊花
那你单调队列初始化的复杂度实际上就是\(O(n^2)\)的了
所以为了避免这个情况,要先处理深度最小的子树
(改变子树的访问顺序是没有影响的,思考一下就可以发现)
这题卡常比较严重,可以把整个点分治的过程存在数组里
// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 1e5 + 10;
const int INF = 0x3f3f3f3f;
const double eps = 1e-6;
int head[MAXN], nxt[MAXN << 1], to[MAXN << 1], val[MAXN << 1], ind;
inline void add_edge(int u, int v, int w){
nxt[++ ind] = head[u]; head[u] = ind;
to[ind] = v; val[ind] = w;
}
int n, L, U;
inline void init(void){
scanf("%lld%lld%lld", &n, &L, &U);
int u, v, w;
for(register int i = 1; i < n; ++ i){
scanf("%lld%lld%lld", &u, &v, &w);
add_edge(u, v, w); add_edge(v, u, w);
}
}
int siz[MAXN], maxp[MAXN], nowp, root, tot, del[MAXN];
void dfs_root(int node, int fa){
siz[node] = 1; maxp[node] = 0;
for(register int i = head[node]; i; i = nxt[i]){
int j = to[i]; if(j == fa || del[j]) continue;
dfs_root(j, node);
siz[node] += siz[j];
maxp[node] = max(maxp[node], siz[j]);
}
maxp[node] = max(maxp[node], tot - siz[node]);
if(maxp[node] < nowp){
nowp = maxp[node]; root = node;
}
}
void findroot(int node){
nowp = INF; tot = siz[node];
dfs_root(node, 0);
}
vector<int>sub;
vector< vector<int> > hav[MAXN] ;
void dfs_dist(int node, int fa, int dis, int dep){
if(dep > U) return;
if(sub.size() == dep) sub.push_back(dis);
else sub[dep] = max(sub[dep], dis);
for(register int i = head[node]; i; i = nxt[i]){
int j = to[i]; if(j == fa || del[j]) continue;
dfs_dist(j, node, dis + val[i], dep + 1);
}
}
bool comp(vector<int> a, vector<int> b) {
return a.size() < b.size();
}
inline void SolveInit(int node){
del[node] = 1;
for(register int i = head[node]; i; i = nxt[i]){
int j = to[i]; if(del[j]) continue;
sub.clear(); sub.push_back(0);
dfs_dist(j, node, val[i], 1);
hav[node].push_back(sub);
}
sort(hav[node].begin(), hav[node].end(), comp);
for(register int i = head[node]; i; i = nxt[i]){
int j = to[i]; if(del[j]) continue;
findroot(j); SolveInit(root);
}
}
vector<int>now;
deque<int>Q;
inline bool JudgeSub(vector<int> a, vector<int> b, double mid){
Q.clear(); int tmp = 0;
for(register int i = b.size() - 1; i >= 0; -- i){
while(tmp < a.size() && tmp + i <= U){
while(!Q.empty() && a[Q.back()] - mid * Q.back() < a[tmp] - mid * tmp)
Q.pop_back();
Q.push_back(tmp ++);
}
while(!Q.empty() && Q.front() + i < L) Q.pop_front();
if(!Q.empty() && a[Q.front()] - mid * Q.front() + b[i] - mid * i > 0) return true;
}
return false;
}
inline vector<int> merge(vector<int>a, vector<int> b){
if(a.size() < b.size()) swap(a, b);
for(register int i = 0; i < b.size(); ++ i)
a[i] = max(a[i], b[i]);
return a;
}
inline bool check(double mid){
for(register int i = 1; i <= n; ++ i){
now.clear();
for(register int j = 0; j < hav[i].size(); ++ j){
if(JudgeSub(now, hav[i][j], mid)) return true;
now = merge(now, hav[i][j]);
}
}
return false;
}
double ans;
inline void work(void){
nowp = INF; tot = n; dfs_root(1, 0);
SolveInit(root);
double l = 0, r = 1e6, mid;
while(r - l >= eps){
mid = (l + r) / 2.0;
if(check(mid)) l = mid + eps, ans = mid;
else r = mid - eps;
}
printf("%.3lf", ans);
}
signed main(){
init();
work();
}
标签:暴力 就会 数组 sig lld while name 统计 include
原文地址:https://www.cnblogs.com/FlySakura/p/11621140.html