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;
}
posted @ 2018-12-05 21:24  AKMer  阅读(246)  评论(2编辑  收藏  举报