BZOJ1758:[WC2010]重建计划
浅谈树分治:https://www.cnblogs.com/AKMer/p/10014803.html
题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=1758
先来讲讲部分分吧。
对于\(20\)%的数据
我们可以对于每个点为根\(dfs\)一遍,然后用\(deep\)在\([L,R]\)之间的点与当前根的距离除以深度来更新答案;
时间复杂度:\(O(n^2)\)
空间复杂度:\(O(n)\)
对于另外\(30\)%的数据
因为是一条链,我们可以把\(n-1\)条边拆出来放到一个数组里,就相当于找一个长度在\([L,R]\)之间的子段平均值最大。一看到平均值我们就可以想到二分。我们二分一个\(limit\),然后把每条边都减去\(limit\),这个时候问题就转化成了怎么去检查这个数组有没有子段长度在\([L,R]\)内,并且子段和大于\(0\)。子段和我们可以用前缀和相减来表示,用单调上升队列维护一下即可。队头就是在所有与当前点距离为\([L,R]\)之内的点,前缀和最小的那一个。用当前点的前缀和减去队头点的前缀和判断是否大于等于\(0\)即可。
时间复杂度:\(O(nlogans)\)
空间复杂度:\(O(n)\)
部分分代码如下:
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const double eps=1e-6;
bool line;
double ans;
double sum[maxn];
int n,L,R,tot,rt;
int a[maxn],deg[maxn];
int now[maxn],pre[maxn*2],son[maxn*2],val[maxn*2];
int read() {
int x=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
return x*f;
}
void add(int a,int b,int c) {
pre[++tot]=now[a];
now[a]=tot,son[tot]=b,val[tot]=c;
}
void dfs(int fa,int u,int dep,ll dis) {
if(dep>R)return;
if(L<=dep&&dep<=R)ans=max(ans,1.0*dis/dep);
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(v!=fa)dfs(u,v,dep+1,dis+val[p]);
}
void solve1() {
for(int i=1;i<=n;i++)
dfs(0,i,0,0);
printf("%.3lf\n",ans);
}
void make_a(int fa,int u) {
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(v!=fa)a[++n]=val[p],make_a(u,v);
}
struct data {
int pos;
double v;
data() {}
data(int _pos,double _v) {
pos=_pos,v=_v;
}
}list[maxn];
bool check(double limit) {
int h=0,t=0;
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+1.0*a[i]-limit;
for(int i=L;i<=n;i++) {
while(h!=t&&i-list[h].pos>R)h++;
while(t!=h&&list[t-1].v>sum[i-L])t--;
list[t++]=data(i-L,sum[i-L]);
if(sum[i]-list[h].v>0)return 1;
}
return 0;
}
void solve2() {
n=0;make_a(0,rt);
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+a[i];
double l=0,r=sum[n];
while(l<r-eps) {
double mid=(l+r)/2;
if(check(mid))l=mid;
else r=mid;
}
printf("%.3lf\n",l);
}
int main() {
n=read(),L=read(),R=read();
for(int i=1;i<n;i++) {
int a=read(),b=read(),c=read();
add(a,b,c),add(b,a,c);deg[a]++,deg[b]++;
}
for(int i=1;i<=n;i++) {
if(deg[i]>2)line=1;
if(deg[i]==1)rt=i;
}
if(n<=5000)solve1();//20%
else if(!line)solve2();//另外30%
return 0;
}
部分分已经提示了正解了。对于这题,我们可以二分一个答案,然后让每条边减去这个值,判断树里面有没有一条长度在\([L,R]\)之内的路径权值和大于等于\(0\)。同样的,我们也可以用单调队列维护。
一开始我觉得不好做,因为我\(dfs\)子树去更新答案的话还要还原单调队列。然后就傻傻地开栈记录被弹出队列的东西……假设当前点的子树做完之后再把被当前点弹出去的东西塞回去……然后\(TLE\)。
后来看了\(hzw\)的做法,把\(dfs\)改成\(bfs\),深度就会递增了,就不需要还原单调队列了。洛谷可以过,但是\(BZOJ\)过不了。没办法了,只能自己卡常了。
我们设\(f_i\)表示在已经遍历过的子树中长度为\(i\)的路径最大值是多少,\(g_i\)是当前要询问的子树内长度为\(i\)的路径最大值是多少,然后就直接用\(f\)和\(g\)两个数组跑单调队列就行了。另外还可以用到SPOJ1825 Free Tour II里的优化,把子树遍历顺序按照最深的深度从小到大排序,在\(BZOJ\)就可以过了。
\(BZOJ\)神仙还是多,\(rk1\)是打表的我们不管他,\(rk2\)居然是个边分治……根本看不懂他代码……
点分治版代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const double eps=1e-5;
bool vis[maxn];
int n,L,R,N,tot,mx,rt;
int siz[maxn],Q[maxn],V[maxn],depest[maxn];
double limit,ans,Mx,f[maxn],g[maxn],dis[maxn];
int now[maxn],pre[maxn*2],son[maxn*2],val[maxn*2];
int read() {
int x=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
return x*f;
}
void add(int a,int b,int c) {
pre[++tot]=now[a];
now[a]=tot,son[tot]=b,val[tot]=c;
}
bool cmp(int a,int b) {
return depest[a]<depest[b];
}
void find_rt(int fa,int u) {
int res=0;siz[u]=1;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]&&v!=fa)find_rt(u,v),siz[u]+=siz[v],res=max(res,siz[v]);
res=max(res,N-siz[u]);
if(res<mx)mx=res,rt=u;
}
void dfs(int fa,int u,int dep,ll len) {
siz[u]=1,dis[u]=len,mx=max(mx,dep);
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]&&v!=fa)dfs(u,v,dep+1,len+val[p]),siz[u]+=siz[v];
}
void make_g(int fa,int u,int dep) {
g[dep]=max(g[dep],dis[u]);
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]&&v!=fa)make_g(u,v,dep+1);
}
bool query(int a,int b) {
for(int i=1;i<=b;i++)f[i]-=limit*i,g[i]-=limit*i;
int h=0,t=0,pos=a;bool res=0;
for(int i=1;i<=b;i++) {
while(i+pos>=L&&pos>=0) {
while(h!=t&&f[Q[t-1]]<f[pos])t--;
Q[t++]=pos;pos--;
}
while(h!=t&&Q[h]+i>R)h++;
if(h!=t&&f[Q[h]]+g[i]>=0)res=1;
}
for(int i=1;i<=b;i++)f[i]+=limit*i,g[i]+=limit*i;
return res;
}
bool check(int u) {
for(int i=1;i<=depest[V[tot]];i++)
f[i]=g[i]=-1e15;
for(int i=1;i<=tot;i++) {
make_g(u,V[i],1);
if(query(depest[V[i-1]],depest[V[i]]))return 1;
for(int j=1;j<=depest[V[i]];j++)
f[j]=max(f[j],g[j]),g[j]=-1e15;
}
return 0;
}
void work(int u,int size) {
N=size,rt=mx=n+1,find_rt(0,u);
u=rt,vis[u]=1,tot=0;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v]) {
mx=0,dfs(u,v,1,val[p]);
depest[v]=mx,V[++tot]=v;
}
sort(V+1,V+tot+1,cmp);
double l=ans,r=Mx;
while(l<r-eps) {
limit=(l+r)/2;
if(check(u))l=limit;
else r=limit;
}ans=l;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[v])work(v,siz[v]);
}
int main() {
n=read(),L=read(),R=read();
for(int i=1;i<n;i++) {
int a=read(),b=read(),c=read();
add(a,b,c),add(b,a,c);Mx=max(Mx,1.0*c);
}work(1,n);printf("%.3lf\n",ans);
return 0;
}
边分治版代码如下:
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int,int> pii;
#define fr first
#define sc second
const int maxn=2e5+5;
const double eps=1e-5;
bool vis[maxn];
int n,tot,cnt,mx,id,L,R,N;
int siz[maxn],Q[maxn],dep[maxn],depest[maxn];
double ans,limit,Mx,f[maxn],g[maxn],dis[maxn];
int now[maxn],pre[maxn*2],son[maxn*2],val[maxn*2];
vector<pii>to[maxn];
vector<pii>::iterator it;
inline int read() {
int x=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
return x*f;
}
inline void add(int a,int b,int c) {
pre[++tot]=now[a];
now[a]=tot,son[tot]=b,val[tot]=c;
}
inline void find_son(int fa,int u) {
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(v!=fa)to[u].push_back(make_pair(v,val[p])),find_son(u,v);
}
inline void rebuild() {
tot=1;memset(now,0,sizeof(now));
for(int i=1;i<=cnt;i++) {
int size=to[i].size();
if(size<=2) {
for(it=to[i].begin();it!=to[i].end();it++) {
pii tmp=*it;
add(i,tmp.fr,tmp.sc),add(tmp.fr,i,tmp.sc);
}
}
else {
pii u1=make_pair(++cnt,0),u2;
if(size==3)u2=to[i].front();
else u2=make_pair(++cnt,0);
add(i,u1.fr,u1.sc),add(u1.fr,i,u1.sc);
add(i,u2.fr,u2.sc),add(u2.fr,i,u2.sc);
if(size==3) {
for(int j=1;j<=2;j++)
to[cnt].push_back(to[i].back()),to[i].pop_back();
}
else {
int p=0;
for(it=to[i].begin();it!=to[i].end();it++) {
if(!p)to[cnt-1].push_back(*it);
else to[cnt].push_back(*it);p^=1;
}
}
}
}
}
inline void find_edge(int fa,int u) {
siz[u]=1;
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[p>>1]&&v!=fa) {
find_edge(u,v),siz[u]+=siz[v];
if(abs(N-2*siz[v])<mx)
mx=abs(N-2*siz[v]),id=p>>1;
}
}
inline void dfs(int fa,int u,int Dep,double len) {
siz[u]=1,dis[u]=len,dep[u]=Dep,mx=max(mx,Dep);
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[p>>1]&&v!=fa)dfs(u,v,Dep+(val[p]!=0),len+val[p]),siz[u]+=siz[v];
}
inline void make(double *a,int fa,int u) {
a[dep[u]]=max(a[dep[u]],dis[u]);
for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
if(!vis[p>>1]&&v!=fa)make(a,u,v);
}
inline bool query(int a,int b) {
for(int i=1;i<=a;i++)f[i]-=limit*i;
for(int i=1;i<=b;i++)g[i]-=limit*i;
int h=0,t=0,pos=a;bool res=0;
for(int i=1;!res&&i<=b;i++) {
while(i+pos+(val[id<<1]>0)>=L&&pos>=0) {
while(h!=t&&f[Q[t-1]]<f[pos])t--;
Q[t++]=pos,pos--;
}
while(h!=t&&i+Q[h]+(val[id<<1]>0)>R)h++;
if(h!=t&&f[Q[h]]+g[i]+val[id<<1]-(val[id<<1]>0)*limit>=0)res=1;
}
for(int i=1;i<=a;i++)f[i]+=limit*i;
for(int i=1;i<=b;i++)g[i]+=limit*i;
return res;
}
inline bool check() {
int u1=son[id<<1],u2=son[id<<1|1];
if(depest[u1]>depest[u2])swap(u1,u2);
for(int i=1;i<=depest[u2];i++)f[i]=g[i]=-1e15;
make(f,0,u1),make(g,0,u2);
return query(depest[u1],depest[u2]);
}
inline void work(int u,int size) {
if(size<2)return;
N=size,mx=id=cnt+1,find_edge(0,u),vis[id]=1;
int u1=son[id<<1],u2=son[id<<1|1];
mx=0,dfs(0,u1,0,0),depest[u1]=mx;
mx=0,dfs(0,u2,0,0),depest[u2]=mx;
double l=ans,r=Mx;
while(l<r-eps) {
limit=(l+r)/2;
if(check())l=limit;
else r=limit;
}ans=l;
work(u1,siz[u1]),work(u2,siz[u2]);
}
int main() {
cnt=n=read(),L=read(),R=read();
for(int i=1;i<n;i++) {
int a=read(),b=read(),c=read();
add(a,b,c),add(b,a,c);Mx=max(Mx,1.0*c);
}find_son(0,1),rebuild();
work(1,cnt);printf("%.3lf\n",ans);
return 0;
}