[UOJ#276][清华集训2016]汽水[分数规划+点分治]

题意

给定一棵 \(n\) 个点的树,给定 \(k\) ,求 \(|\frac{\sum w(路径长度)}{t(路径边数)}-k|\)的最小值。

\(n\leq 5\times 10^5,k\leq 10^{13}\)

分析

  • 看到分数考虑分数规划,二分答案 \(x\),式子转化成 \(-x< \frac{\sum w}{t}-k< x\)

  • 将边权变为 \(w-k\) 消除 \(k\) 的影响。但是不能够直接求最长链。因为是路径,考虑点分治。

  • 二分答案 \(x\) 之后考虑两条路径组合 \((A_1,B_1),(A_2,B_2)\),其中 \(A\) 表示路径长度,\(B\) 表示路径边数。
    \(-x<\frac{A_1+A_2}{B_1+B_2}< x\) ,当 \(A_1+A_2 > 0\) 时只用考虑 \(< x\) 的条件,得到 \(A_1-B_1x< B_2x-A_2\),反之同理。

  • 现在考虑 \(A_1+A_2 > 0\) 的情况。先将所有路径按照 \(A\) 排序后从左边开始枚举路径,然后用一个指针从右往左维护所有 \(A_1+A_2> 0\) 的路径,然后维护 \(Bx-A\) 的最小值。但是有可能最小值和当前枚举的路径相同,所以再记一个次小值。

  • 由于要下取整,先求出 \(> ans\) 的最小整数解然后 \(-1\)

  • 总时间复杂度为 \(O(nlog^2n)\)

## 代码 ~~~cpp #include using namespace std; #define go(u) for(int i=head[u],v=e[i].to;i;i=e[i].lst,v=e[i].to) #define rep(i,a,b) for(int i=a;i<=b;++i) #define pb push_back typedef long long LL; inline int gi(){ int x=0,f=1;char ch=getchar(); while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-48;ch=getchar();} return x*f; } templateinline bool Max(T &a,T b){return ainline bool Min(T &a,T b){return b=0;--p) upd(data(st[p].a-st[p].b*mid,0,st[p].from),m1,m2); if(st[i].b*mid-st[i].a>(st[i].from==m1.from?m2.a:m1.a)) return 1; } return 0; } bool ck2(int u,LL mid){ int p=1; data m1(inf,-1,-1),m2(inf,-1,-1); for(int i=tp;i;--i){ for(;p<=tp&&st[p].a+st[i].a<0;++p) upd(data(-st[p].b*mid-st[p].a,0,st[p].from),m1,m2); if(st[i].b*mid+st[i].a>(st[i].from==m1.from?m2.a:m1.a)) return 1; } return 0; } void dfs(int u){ vis[u]=1,st[tp=1]=data(0,0,0); go(u)if(!vis[v]) { d[v]=data(e[i].c,1,v); getdep(v,u,v); } sort(st+1,st+1+tp); LL l=1,r=ans; while(l>1; if(ck1(u,mid)||ck2(u,mid)) r=mid; else l=mid+1; } Min(ans,l);
go(u)if(!vis[v])
	rt=0,sn=son[v],getrt(v,u),dfs(rt);

}
int main(){
scanf("%d%lld",&n,&k);
rep(i,1,n-1){
int a,b;LL w;
scanf("%d%d%lld",&a,&b,&w);
Add(a,b,w-k);
Min(ans,abs(w-k)+1);
}
sn=n,g[rt=0]=0x3f3f3f3f,getrt(1,0),dfs(rt);
printf("%lld\n",ans-1);
return 0;
}

posted @ 2018-12-02 19:14  fwat  阅读(260)  评论(2编辑  收藏  举报