点分治问题 ----------- luoguP2942 [WC2010]重建计划 [点分治 + bfs + 单调队列 + 预处理建树 + 二分 + 01分数规划]

本文介绍了如何运用二分搜索和点分治算法解决一个特殊的01分数规划问题,其中涉及到路径权值的计算和路径长度的限制。通过将求解最大平均值转化为判定问题,利用单调队列维护特定区间内的最大值,最后通过广度优先搜索和深度优先搜索进行优化。详细解题过程和代码实现使得复杂问题的解决步骤清晰明了。
摘要由CSDN通过智能技术生成

题目链接


解题思路:

1.对于这个 A v g v a l u e = ∑ e ∈ s v ( e ) ∣ s ∣ Avgvalue = \frac{\sum_{e\in s}v(e)}{|s|} Avgvalue=sesv(e) 求最大值,我们知道着很明显是一个01分数规划的式子,对于01分数规划问题我们可以通过二分,把求值问题转化为判定问题。
A v g v a l u e = ∑ e ∈ s v ( e ) ∣ s ∣ > = m i d Avgvalue = \frac{\sum_{e\in s}v(e)}{|s|} >= mid Avgvalue=sesv(e)>=mid
A v g v a l u e = ∑ e ∈ s v ( e ) > = ∣ s ∣ ∗ m i d Avgvalue = \sum_{e\in s}v(e) >= |s| * mid Avgvalue=esv(e)>=smid
A v g v a l u e = ∑ e ∈ s v ( e ) − ∣ s ∣ ∗ m i d > = 0 Avgvalue = \sum_{e\in s}v(e) - |s| * mid>=0 Avgvalue=esv(e)smid>=0
那么问题就变成了对于一个 m i d mid mid,我们要求是否存在一条路径(路径权值都减去 m i d mid mid)这条路径的权值和大于或者等于0

2.那么对于上面的问题可以用点分治求解,但是有一个问题就是路径的长度要限制在 [ L , R ] [L,R] [L,R]之间。

我们最朴实的思想就是分治每一个点求出各个子树中各个深度节点的路径权值和的最大值。如何维护 [ L , R ] [L,R] [L,R]之间的最大值呢?

我们这么看对于深度为1的点就是在 [ L − 1 , R − 1 ] [L-1,R-1] [L1,R1]之间找,对于深度为2的点就是在 [ L − 2 , R − 2 ] [L-2,R-2] [L2,R2]之间找,那么就有点像滑动窗口了,就可以用单调队列来维护固定区间最大值。

通过上面的思路,那么对于深度相同的点要放到一起,就是 b f s bfs bfs嘛!!

我们先bfs把整个子树的各个点的深度距离全都处理出来再跑单调队列,如果边跑边单调队列会 M L E MLE MLE因为要记录的东西太多了!!


#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e5 + 10;
const int N = 1e5 + 10;
const double eps = 1e-6;
typedef pair<double,int> PII;
struct node {
    int nxt, to;
    double cost;
}edge[maxn];
int G[N], idx;
int head[N], cnt;
int n, L, R;
double mid;
inline void add(int from, int to, double w) {
    edge[cnt] = {head[from],to,w};
    head[from] = cnt ++;
}
bool vis[N];
int max_son[N], siz[N], rt, MX = 1e9, max_node;
void getroot(int u, int fa) {
    max_son[u] = 0, siz[u] = 1;
    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(v == fa || vis[v]) continue;
        getroot(v,u);
        max_son[u] = max(max_son[u],siz[v]);
        siz[u] = siz[u] + siz[v];
    }
    max_son[u] = max(max_son[u],max_node - siz[u]);
    if(max_son[u] <= MX) MX = max_son[u], rt = u;
}

void rebuild(int u) {
    vis[u] = 1;
    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(vis[v]) continue;
        MX = 1e9, rt = 0;
        max_node = siz[v];
        getroot(v,u);
        G[idx++] = rt;
        rebuild(rt);
    }
}

int last, top;
int q[maxn], dep[maxn];
double dist[maxn], len[maxn];
bool visq[maxn];

inline void bfs(int u, double w) {
    last = 1, top = 0;
    q[++top] = u;
    dist[u] = w;
    dep[u] = 1; 
    for(int i = last; i <= top; ++ i) {
        int t = q[i];
        if(visq[t]) continue;
        visq[t] = 1;
        for(int i = head[t]; ~i; i = edge[i].nxt) {
            int v = edge[i].to;
            if(vis[v] || visq[v]) continue;
            q[++top] = v;
            dist[v] = dist[t] + edge[i].cost;
            dep[v] = dep[t] + 1;
        }
    }
    for(int i = last; i <= top; ++ i) visq[q[i]] = false;
}
int stk[maxn << 1];
int MXD;
//stk[i] 单调队列里面第i个数是 str[i] 表示长度
inline bool check(int u) {
    // bfs队列 tip 队头,top队尾
    // 单调队列 t 是队列的头,w是队列的尾巴 q是队列
	int t = 1, w = 0, tip = last;
	for (int i = min(R, dep[q[top]]); i >= 0; --i)//枚举限定区间
	{
		int tl = i >= L ? 0 : L - i, tr = R - i;//判断该深度的限定范围 [tl,tr] = [L - i, R - i]
		while (t <= w && dep[stk[t]] < tl) ++t;//如果队列存在元素,并且队头的长度 已经小于 L - i了踢出单调队列
		while (tip <= top && dep[q[tip]] < tl) ++tip; //如果队头元素小于 L - i 了踢出bfs队列
		while (tip <= top && dep[q[tip]] <= tr)//限制len[q[tip]] >= L - i, 了
		{
			while (t <= w && dist[stk[w]] + eps <= dist[q[tip]]) -- w; //如果单调队列的尾巴元素比要新加的还小,就踢出去,维护一单调递减的
			stk[++ w] = q[tip ++];//入队
		}
		if (t <= w && len[i] + dist[stk[t]] >= -eps) 
			return true;
	}
	return false;
}

void dfs(int u, int fa, double val, int dep) {//更新答案
    len[dep] = max(len[dep],val);
    MXD = max(MXD,dep);
    if(dep > R) return;
    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(v == fa || vis[v]) continue;
        dfs(v,u,val+edge[i].cost,dep+1);
    }
}

bool Div(int u) {
    for(int i = 0; i <= MXD; ++ i) len[i] = -1e9;
    MXD = 0;
    vis[u] = 1;
    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(vis[v]) continue;
        bfs(v,edge[i].cost);
        if(check(v)) return true;
        for(int j = last; j <= top; ++ j)
		   len[dep[q[j]]] = max(len[dep[q[j]]],dist[q[j]]);
		MXD = max(MXD,dep[q[top]]);
    }
    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(vis[v]) continue;
        if(Div(G[idx++])) return true;
    }
    return false;
}

inline bool Tmp(double mid) {
    for(int i = 1; i <= n; ++ i)
       for(int j = head[i]; ~j; j = edge[j].nxt) {
           edge[j].cost -= mid;
       }

    bool f = Div(G[idx++]);

    for(int i = 1; i <= n; ++ i)
       for(int j = head[i]; ~j; j = edge[j].nxt) {
           edge[j].cost += mid;
       }
    return f;
}

int main() {
    memset(head,-1,sizeof(head));
    //............................
    scanf("%d",&n);
    for(int i = 0; i <= n; ++ i) len[i] = -1e9;
    scanf("%d%d",&L,&R);
    for(int i = 1; i < n; ++ i) {
        int u, v;
        double val;
        scanf("%d%d%lf",&u,&v,&val);
        add(u,v,val);
        add(v,u,val); 
    }

    max_node = n, MX = 1e9;
    getroot(1,0);
    G[idx ++] = rt;
    rebuild(rt);

    double l = 0.0, r = 1e6;
    while(r - l > 1e-5) {
        memset(vis,0,sizeof(vis));
        idx = 0;
        mid = (l + r) / 2.0;
        if(Tmp(mid)) l = mid;
        else r = mid;
    }
    printf("%.3f",l);
    return 0;
}
/*
6
3 3 
1 2 2
2 4 5
2 5 10
1 3 4
3 6 3

5
2 4
1 2 1
2 3 2
3 4 3
4 5 4
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值