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

53 篇文章 0 订阅
20 篇文章 0 订阅

题面

在这里插入图片描述

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;
}

2021/11/9 Update:精简版代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define LL long long
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
LL read() {
	LL f=1,x=0;int s = getchar();
	while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s=getchar();}
	while(s >= '0' && s <= '9') {x = (x<<3) + (x<<1) + (s^48);s = getchar();}
	return f * x;
}
int n,m,s,o,k,M,L,R;
DB tre[MAXN<<2];
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) {for(int s = M+x;s > 0;s >>= 1) tre[s] = max(tre[s],y);}
DB findtree(int l,int r) {
	DB as = -1e13; if(l > r) return -1e13;
	for(int s = M+l-1,t = M+r+1;(s>>1) != (t>>1);s >>= 1,t >>= 1) {
		if(!(s&1)) as = max(as,tre[s^1]);
		if(t & 1) as = max(as,tre[t^1]);
	}return as;
}
DB FDP(int x) {return tre[M+x];}
int hd[MAXN],v[MAXN<<1],nx[MAXN<<1],cne,w[MAXN<<1];
void ins(int x,int y,int z) {nx[++ cne] = hd[x]; v[cne] = y; hd[x] = cne; w[cne] = z;}
int d[MAXN],le[MAXN],son[MAXN],si[MAXN],dfn[MAXN],tim;
void dfs0(int x,int ff) { // *d , *le , *son , *si , tim
	d[x] = d[ff] + 1; le[x] = 0; son[x] = 0; tim = 0;
	for(int i = hd[x],y = v[i];i;i = nx[i],y = v[i]) 
		if(y != ff) {
			dfs0(y,x);  le[x] = max(le[x],le[y] + 1);
			if(!son[x] || le[y] > le[son[x]]) son[x] = y,si[x] = i;
		}
	return ;
}
void dfs1(int x,int ff) { // *dfn
	dfn[x] = ++ tim;
	if(son[x]) dfs1(son[x],x);
	for(int i = hd[x];i;i = nx[i]) 
		if(v[i] != ff && v[i] != son[x])
			dfs1(v[i],x);
	return ;
}
DB lz[MAXN],w2[MAXN<<1],ans;
void dfs(int x,int ff) { // calculate
	lz[x] = 0;
	if(son[x]) {dfs(son[x],x); lz[x] = lz[son[x]] + w2[si[x]];}
	addtree(dfn[x],-lz[x]);
	ans = max(ans,findtree(dfn[x] + L,dfn[x] + min(R,le[x])) + lz[x]);
	for(int i = hd[x],y = v[i];i;i = nx[i],y = v[i])
		if(y != ff && y != son[x]) {
			dfs(y,x);
			for(int j = dfn[y];j <= dfn[y] + le[y];j ++) {
				int lt = j - dfn[y] + 1;
				DB wt = FDP(j) + lz[y] + w2[i];
				ans = max(ans,wt + findtree(dfn[x] + max(0,L-lt),dfn[x] + min(le[x],R-lt)) + lz[x]);
			}
			for(int j = dfn[y];j <= dfn[y] + le[y];j ++) {
				int lt = j - dfn[y] + 1;
				DB wt = FDP(j) + lz[y] + w2[i];
				addtree(dfn[x] + lt,wt - lz[x]);
			}
		}
	return ;
}
bool check(DB m) {
	for(int i = 1;i <= cne;i ++) w2[i] = (DB)w[i] - m;
	maketree(n); ans = -1e13; dfs(1,0);
	return ans >= 0.0;
}
int main() {
	n = read();L = read();R = read();
	for(int i = 1;i < n;i ++) {
		s = read();o = read();k = read();ins(s,o,k);ins(o,s,k);
	}
	dfs0(1,0); dfs1(1,0);
	DB l = 0,r = 1e6,mid;
	while(l < r-1e-9) {
		mid = (l + r) / 2;
		if(check(mid)) l = mid;
		else r = mid;
	}
	printf("%.3f\n",r);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值