WC2010 重建计划

传送门

这道题……如bin哥所说,算法不难想,但是很难写,而且还有些卡常……

其实这题我做的很奇怪……就是大致的算法都是对的但是我和别人的实现方法一点都不一样……(我怎么总是这么奇怪的做题)

因为要求平均值,所以就可以想到01分数规划,二分答案,然后要求的是树上的一条路径,那就是点分治咯。想到这就已经是两个(log)了,如果要是三个(log)估计死活过不去。再考虑考虑怎么合并,合并的复杂度还必须是(O(n))的。借鉴着上一道题的做法,很自然能想到用(dis_x)表示重心的每棵子树中长度为(x)的路径的长度,只需要保留最长的那一条就行了,这个直接dfs一遍就行(但是好像所有人都用bfs?)之后就是咋合并了。

(tmp_i)表示前s-1棵子树之内长为i的路径最大值。发现每一条路径只能和长度在一段区间之内的路径合并更新答案,然后这个区间随着枚举的值左右端点都递增,那么使用单调队列维护一下就好。

结果写的时候感觉自己像没学过单调队列……写错两次。这里我的实现方法是,从小到大枚举当前子树的链长,先倒着把长度为(l+1~u)的链放在单调队列里,然后每次枚举的时候把长度为(l-i)的压入单调队列,之后就都一样了。最后要更新答案,并且把数组还原回(-INF),方便之后递归使用。

别人都是对于每一个重心二分答案,然后我是……直接在外面二分答案,然后把最长链求完更新。所以一开始我还建了点分树。程序一开始交上去T了8个点……后来改小了二分的次数(其实35次就够用了),在合并过程中,我记录子树链长最大值(maxdep),这样单调队列一开始只需要压入(l+1~maxdep)的值就可以了。这样其实可以过了,不过还是挺慢的……

最后加了个重要剪枝,子树(size)小于(l)的不建点分树,然后还优化了一下其他的小地方,比原来快了很多。发现自己和hzwer跑的差不多快,干脆不卡了,就这样吧。

看一下代码。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#define rep(i,a,n) for(register int i = a;i <= n;i++)
#define per(i,n,a) for(register int i = n;i >= a;i--)
#define enter putchar('
')
#define pr pair<int,int>
#define mp make_pair
#define fi first
#define sc second
using namespace std;
typedef long long ll;
const int M = 100005;
const int N = 10000005;
const double INF = 1e13;
 
int read()
{
   int ans = 0,op = 1;
   char ch = getchar();
   while(ch < '0' || ch > '9')
   {
      if(ch == '-') op = -1;
      ch = getchar();
   }
   while(ch >='0' && ch <= '9')
   {
      ans *= 10;
      ans += ch - '0';
      ch = getchar();
   }
   return ans * op;
}

struct edge
{
   int next,to;
   double d,v;
}e[M<<1],e1[M<<1];

int n,size[M],hson[M],G,sum,l,u,x,y,Head,tail,q[M],head[M],ecnt,fq[M],st;
int head1[M],ecnt1,dep[M],mdep,maxdep;
double z,ans,tmp[M],dis[M],L,R,mid;
bool vis[M];

inline void add(int x,int y,double z)
{
   e[++ecnt].to = y;
   e[ecnt].d = z;
   e[ecnt].next = head[x];
   head[x] = ecnt;
}

inline void add1(int x,int y)
{
   e1[++ecnt1].to = y;
   e1[ecnt1].next = head1[x];
   head1[x] = ecnt1;
}

void getG(int x,int fa)
{
   size[x] = 1,hson[x] = 0;
   for(int i = head[x];i;i = e[i].next)
   {
      if(vis[e[i].to] || e[i].to == fa) continue;
      getG(e[i].to,x);
      size[x] += size[e[i].to],hson[x] = max(hson[x],size[e[i].to]);
   }
   hson[x] = max(hson[x],sum - size[x]);
   if(hson[x] < hson[G]) G = x;
}

void build(int x)
{
   vis[x] = 1;
   for(int i = head[x];i;i = e[i].next)
   {
      if(vis[e[i].to] || size[e[i].to] < l) continue;
      sum = size[e[i].to],G = 0;
      getG(e[i].to,x),add1(x,G);
      build(G);
   }
   vis[x] = 0;
}

void getdis(int x,int fa,double d,int depth)
{
   dis[depth] = max(dis[depth],d),mdep = max(mdep,depth);
   for(int i = head[x];i;i = e[i].next)
   {
      if(vis[e[i].to] || e[i].to == fa) continue;
      getdis(e[i].to,x,d+e[i].v-mid,depth+1);
   }
}

void solve(int x)
{
   int cur = 0;
   vis[x] = 1,maxdep = 0;
   for(int i = head[x];i;i = e[i].next)
   {
      if(vis[e[i].to]) continue;
      cur++,mdep = 0;
      getdis(e[i].to,x,e[i].v-mid,1);
      maxdep = max(maxdep,mdep);
      if(cur != 1)
      {
	 Head = 1,tail = 0;
	 per(j,min(maxdep,u),l+1)
	 {
	    while(Head <= tail && tmp[q[tail]] <= tmp[j]) tail--;
	    q[++tail] = j;
	 }
	 rep(j,0,mdep)
	 {
	    while(Head <= tail && tmp[q[tail]] <= tmp[l-j]) tail--;
	    q[++tail] = l-j;
	    while(Head <= tail && q[Head] + j > u) Head++;
	    ans = max(ans,dis[j] + tmp[q[Head]]);
	 }
      }
      rep(j,1,mdep) tmp[j] = max(tmp[j],dis[j]),dis[j] = -INF; 
   }
   rep(i,1,maxdep) tmp[i] = dis[i] = -INF;
   for(int i = head1[x];i;i = e1[i].next) solve(e1[i].to);
   vis[x] = 0;
}

int main()
{
   n = read(),l = read(),u = read();
   rep(i,1,n-1) x = read(),y = read(),scanf("%lf",&z),add(x,y,z),add(y,x,z);
   L = 0,R = 1e6;
   G = 0,sum = n,hson[G] = n;
   getG(1,0),st = G,build(G);
   rep(i,1,n) dis[i] = tmp[i] = -INF;
   rep(t,1,35)
   {
      double mid = (L+R) / 2.0;
      ans = -INF,solve(st);
      if(ans > 0) L = mid;
      else R = mid;
   }
   printf("%.3lf
",L);
   return 0;
}

原文地址:https://www.cnblogs.com/captain1/p/10092478.html