题面
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
题解
首先我们可以想到一个 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;
}