[Wc2010]重建计划 (二分 + 长链剖分 + 线段树)

题面

在这里插入图片描述

Input

第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai,Bi,Vi分别表示道路(Ai,Bi),其价值为Vi 其中城市由1…N进行标号

Output

输出最大平均估值,保留三位小数

Sample Input

4
2 3
1 2 1
1 3 2
1 4 3

Sample Output

2.500

Hint

N<=100000,1<=L<=U<=N-1,Vi<=1000000

Source

BZOJ1758 【Wc2010】重建计划

题解

首先我们可以想到一个 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn) 的树形DP做法:

  • 先用0/1分数规划二分答案,把每条边权减去答案,找边权和大于等于 0 的路径。
  • 然后 d p i , j dp_{i,j} dpi,j 表示 i i i 点向下延伸的一条长为 j j j 的链的最大边权和,合并儿子时判断是否存在合法的路径,计算完后再判断一下 i i i 向下延伸是否存在合法路。
  • d p i , j dp_{i,j} dpi,j 抽象成 i i i 子树内距离 i i i j j j 的点,那么最多就是每两个点在 l c a lca lca 处产生一次合法判断,且每个点都要算自己所有DP值,证明复杂度是 O ( n 2 ) O(n^2) O(n2) 的。
  • 总复杂度算上二分 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)

这个做法是可以优化的,因为我们发现,遍历到第一个儿子的时候,原先的 “ d p i , ⋯ dp_{i,\cdots} dpi,” 并没有值,因此可以直接从第一个儿子处承接过来(说继承不太好),我们只要安排一个儿子先 d  ⁣ f  ⁣ s d\!f\!s dfs ,然后承接过来,再和其他儿子暴力合并就能优化。

但是如果重链剖分的话,可能每次合并儿子时还要新扩展一些值,且复杂度得不到保证(因为笔者证不出来 😕)。

所以我们可以用长链剖分,用线段树维护每条长链的DP值的最大值,方便判断合法路径,转移时是单点修改,继承承接时则是整条链的区间加和一个单点修改。

为了不写懒标记(好调试,代码短),优化线段树单点查询的复杂度到 O ( 1 ) O(1) O(1) (无懒标记的zkw线段树单点查询和全局查询都是 O ( 1 ) O(1) O(1) 的),可以在每条链顶存一个全局加的标记(我们一般叫它 t a g tag tag)。

长链剖分下,除了长儿子以外的儿子在合并过来时,最长的链都不会比长儿子长,就不会访问到没计算过的DP值,只会访问到 短链长度 个DP值,由于其他儿子都是一条长链的链顶,所以相当于每条长链都只会在承接的过程中被构造一次,在链顶被遍历一次,复杂度就为 O ( 总 链 长 ⋅ log ⁡ n ) O(总链长\cdot\log n) O(logn) ,即 O ( n log ⁡ n ) O(n\log n) O(nlogn)

总复杂度算上二分 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

CODE

#include<map>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define LL long long
#define ULL unsigned long long
#define DB double
#define ENDL putchar('\n')
#define eps 1e-5
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s == '-')f=-f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
const int MOD = 1000000007;
int n,m,i,j,s,o,k,L,U;
DB tre[MAXN<<2];
int M;
void maketree(int n) {
	M=1;while(M<n+2)M<<=1;
	for(int i = 1;i < (M<<1);i ++) tre[i] = -1e13;
}
void addtree(int x,DB y) {
	int s = M+x;tre[s] = y;s >>= 1;
	while(s) tre[s] = max(tre[s<<1],tre[s<<1|1]),s >>= 1;
}
DB findtree(int l,int r) {
	if(l > r) return -1e13;
	int s = M+l-1,t = M+r+1; DB as = -1e13;
	while(s || t) {
		if((s>>1) ^ (t>>1)) {
			if(!(s&1)) as = max(as,tre[s^1]);
			if(t & 1) as = max(as,tre[t^1]);
		}else break;
		s >>= 1;t >>= 1;
	}return as;
}
struct it{
	int v,w; it(){v=w=0;}
	it(int V,int W){v=V;w=W;}
};
vector<it> g[MAXN];
int d[MAXN],len[MAXN],se[MAXN],son[MAXN],tp[MAXN],ll[MAXN],rr[MAXN],tim;
DB lz[MAXN];
void dfs0(int x,int fa) {//d[],len[],son[],se[]
	d[x] = d[fa] + 1;
	len[x] = 1; son[x] = 0;
	for(int i = 0;i < (int)g[x].size();i ++) {
		int y = g[x][i].v;
		if(y != fa) {
			dfs0(y,x);
			if(len[y] > len[son[x]]) son[x] = y,se[x] = g[x][i].w;
			len[x] = max(len[x],len[y]+1);
		}
	}return ;
}
void dfs1(int x,int fa) {//tp[],ll[],rr[]
	if(son[fa] == x) tp[x] = tp[fa];
	else tp[x] = x;
	if(tp[x] == x) {
		ll[x] = tim + 1;
		rr[x] = tim + len[x];
		tim += len[x];
	}
	for(int i = 0;i < (int)g[x].size();i ++) {
		int y = g[x][i].v;
		if(y != fa) {
			dfs1(y,x);
		}
	}return ;
}
bool flag;
DB sub;
void dfs(int x,int fa) {
	int st = ll[tp[x]] + d[x] - d[tp[x]];
	if(!son[x]) {
		addtree(st,-lz[tp[x]]);
		return ;
	}
	dfs(son[x],x);
	lz[tp[x]] += (DB)se[x]-sub;
	addtree(st,-lz[tp[x]]);
	for(int i = 0;i < (int)g[x].size();i ++) {
		int y = g[x][i].v;
		if(y != fa && y != son[x]) {
			dfs(y,x);
			DB ady = (DB)g[x][i].w-sub;
			for(int j = len[y]-1;j >= 0;j --) {
				if(j+1 <= U && j+len[x] >= L) {
					int rd = min(len[x]-1,U-j-1);
					int ld = max(0,L-j-1);
					if(findtree(st+ld,st+rd) + lz[tp[x]] + findtree(ll[y]+j,ll[y]+j) + lz[y] + ady >= 0)
						flag = 1;
				}
			}
			for(int j = len[y]-1;j >= 0;j --) {
				DB nm = findtree(ll[y]+j,ll[y]+j)+lz[y]+ady;
				DB nm2 = findtree(st+j+1,st+j+1)+lz[tp[x]];
				addtree(st+j+1,max(nm,nm2)-lz[tp[x]]);
			}
		}
	}
	if(findtree(st+L,st+min(len[x]-1,U))+lz[tp[x]] >= 0) flag = 1;
	return ;
}
bool check(DB md) {
	maketree(tim);
	flag = 0;sub = md;
	for(int i = 1;i <= n;i ++) lz[i] = 0.0;
	dfs(1,0);
	return flag;
}
int main() {
	n = read();
	L = read();U = read();
	for(int i = 1;i < n;i ++) {
		s = read();o = read();k = read();
		g[s].push_back(it(o,k));
		g[o].push_back(it(s,k));
	}
	dfs0(1,0);
	dfs1(1,0);
	DB l = 0,r = 1000000.0,mid;
	while(r-l >= eps) {
		mid = (l + r) / 2.0;
		if(check(mid)) l = mid;
		else r = mid;
	}
	printf("%.3f\n",l);
	return 0;
}
posted @ 2021-03-01 22:16  DD_XYX  阅读(4)  评论(0编辑  收藏  举报