[二进制分组 dsu on tree 二次函数] Codechef KILLER Painting Tree

首先我们可以列一个dp 按深度 fu 表示链剖分中选择了从 u 到当前深度的祖先这条链所能得到的最小答案
我们发现这条链的贡献是一个关于祖先深度二次函数 具体形式我忘了 而其他贡献则是一路上来兄弟子树中的最大值累加
这样就很明确了 是要求对每一个点维护一个二次函数 还要资瓷子树加 查询子树中x=k最小值

首先询问一坨二次函数最大值 有一个经典做法来自 BZOJ2646
我们可以维护 n 条函数的下轮廓 这个轮廓应该是O(n)段的
注意这个轮廓是资瓷合并的 O(n) 段和 O(n) 段可以在 O(n) 时间内合并 具体就是从左往右求交点比较下谁小咯 实现起来有点细节

但是我们发现这个轮廓是不资瓷插入一条二次函数的 这样的复杂度还是 O(n) 但是我们可以二进制分组 这样就能在均摊 O(logn) 完成插入
资瓷插入有什么好呢 我们使用dsu on tree 的技巧 就能够总复杂度 O(nlog2n)

这样子树加就能够很轻易的实现了

常数感人

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<vector>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<iomanip>
#define PB push_back
#define cl(x) memset(x,0,sizeof(x))
using namespace std;
typedef long long ll;
typedef long double ld;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
inline void read(ld &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

const int N=100005;

struct edge{
  int u,v,next;
}G[N<<1];
int head[N],inum;
inline void add(int u,int v,int p){
  G[p].u=u; G[p].v=v; G[p].next=head[u]; head[u]=p;
}
#define V G[p].v

int n;
ld H[N],C[N],d[N],h;
int size[N],son[N];

inline void dfs(int u,int fa){
  h=max(h,d[u]); size[u]=1;
  for (int p=head[u];p;p=G[p].next)
    if (V!=fa)
      d[V]=d[u]+1,dfs(V,u),size[u]+=size[V];
  int maxv=0; son[u]=0;
  for (int p=head[u];p;p=G[p].next)
    if (V!=fa && size[V]>maxv)
      maxv=size[son[u]=V];
}

const ld eps=1e-11;
const ld oo=1e6;

struct Func{
  ld a,b,c;
  Func() { }
  Func(ld a,ld b,ld c):a(a),b(b),c(c) { }
  ld f(ld x){
    return a*x*x+b*x+c;
  }
}F[N];

struct P{
  ld l,r; int p;
  P(){ }
  P(ld l,ld r,int p):l(l),r(r),p(p) { }
};

inline ld pos(const Func &a,const Func &b,ld l,ld r){
  ld A=a.a-b.a,B=a.b-b.b,C=a.c-b.c;
  if (fabs(A)<eps){
    if (fabs(B)<eps) return r;
    ld x=-C/B;
    if (x>l+eps&&x<r) return x;
    return r;
  }
  ld d=B*B-4*A*C;
  if (d<-eps) return r;
  d=sqrt(d);
  ld x=(-B-d)/(2*A),y=(-B+d)/(2*A),ret=r;
  if (x>l+eps&&x<r) ret=x;
  if (y>l+eps&&y<r) ret=min(ret,y);
  return ret;
}

struct Bin{
  vector<Func> e; int tot;

  vector<ld> q; vector<P> c;
  void merge(vector<P>&f,const vector<P>&a,const vector<P>&b){
    int na=a.size(),nb=b.size(),pa=0,pb=0;

    c.clear(); q.clear(); q.PB(-oo);
    while (pa<na && pb<nb)
      if (a[pa].r<b[pb].r || pb==nb){
    if (a[pa].r>q.back()+eps) q.PB(a[pa].r); pa++;
      }else{
    if (b[pb].r>q.back()+eps) q.PB(b[pb].r); pb++;
      }
    while (pa<na){
      if (a[pa].r>q.back()+eps) q.PB(a[pa].r); pa++;
    }
    while (pb<nb){
      if (b[pb].r>q.back()+eps) q.PB(b[pb].r); pb++;
    }

    for (int i=0,j=0,k=0;k<q.size()-1;k++){
      ld l=q[k],r=q[k+1];
      while (i<na && a[i].r+eps<r) i++;
      while (j<nb && b[j].r+eps<r) j++;
      if (i==na || a[i].l>l+eps){ c.PB(P(l,r,b[j].p)); continue; }
      if (j==nb || b[j].l>l+eps){ c.PB(P(l,r,a[i].p)); continue; }
      while (l+eps<r){
    ld m=pos(e[a[i].p],e[b[j].p],l,r),mid=(l+m)/2;
    if (e[a[i].p].f(mid)<e[b[j].p].f(mid))
      c.PB(P(l,m,a[i].p));
    else
      c.PB(P(l,m,b[j].p));
    l=m;
      }
    }
    for (int i=0,j;i<c.size();i=j){
      j=i; while (j<c.size() && c[i].p==c[j].p) j++;
      f.PB(P(c[i].l,c[j-1].r,c[i].p));
    }
  }
  ld query(const vector<P>&v,ld o){
    int l=0,mid,r=v.size()-1;
    while (l<=r){
      mid=(l+r)>>1;
      if (v[mid].r+eps<o)
    l=mid+1;
      else if (v[mid].l-eps>o)
    r=mid-1;
      else
    return e[v[mid].p].f(o);
    }
  }

  ld tag;
  vector<P> v[20];
  int cnt,size[20];
  Bin(){
    e.PB(Func()); tot=0; tag=0; cnt=0;
  }
  void clear(){
    for (int i=1;i<=cnt;i++) v[i].clear(); 
    cnt=0; tot=0; tag=0; e.clear(); e.PB(Func());
  }

  vector<P> tmp;
  void Add(Func a){
    a.c-=tag;
    e.PB(a); ++tot;
    ++cnt; size[cnt]=1; v[cnt].PB(P(-oo,oo,tot));
    while (cnt>1 && size[cnt]==size[cnt-1]){
      tmp.clear(); merge(tmp,v[cnt],v[cnt-1]);
      v[cnt-1]=tmp; v[cnt].clear(); cnt--; size[cnt]<<=1;
    }
  }
  ld Query(ld x){
    ld ans=1e30;
    for (int i=1;i<=cnt;i++)
      ans=min(ans,query(v[i],x));
    return ans+tag;
  }


}B[N];
int bi[N];
int bcnt;

ld tag[N];
ld minv[N];

inline void Add(int u,int fa,ld T,Bin &B){
  T+=tag[u];
  F[u].c+=T;
  B.Add(F[u]);
  F[u].c-=T;
  for (int p=head[u];p;p=G[p].next)
    if (V!=fa)
      Add(V,u,T,B);
}

inline void dp(int u,int fa){
  for (int p=head[u];p;p=G[p].next)
    if (V!=fa)
      dp(V,u);
  ld sum=0;
  for (int p=head[u];p;p=G[p].next)
    if (V!=fa){
      minv[V]=B[bi[V]].Query(d[V]);
      sum+=minv[V];
    }
  if (son[u]){
    bi[u]=bi[son[u]];
    B[bi[u]].tag+=sum-minv[son[u]];
    tag[son[u]]+=sum-minv[son[u]];
    for (int p=head[u];p;p=G[p].next)
      if (V!=fa && V!=son[u]){
    tag[V]+=sum-minv[V];
    Add(V,u,0,B[bi[u]]);
      }
  }else{
    bi[u]=++bcnt;
  }
  F[u].a=C[u]/2.0;
  F[u].b=-C[u]*C[u]-C[u]*h-C[u]/2.0;
  F[u].c=-C[u]*d[u]*(d[u]+1)/2.0-H[u]+sum+(d[u]+1)*(C[u]*C[u]+C[u]*h);
  B[bi[u]].Add(F[u]);
}

int main(){
  freopen("t.in","r",stdin);
  freopen("t.out","w",stdout);
  int T,x,y;
  read(T);
  while (T--){
    read(n);
    for (int i=1;i<=n;i++) read(H[i]),read(C[i]);
    for (int i=1;i<n;i++) read(x),read(y),add(x,y,++inum),add(y,x,++inum);
    h=0; dfs(1,0);
    dp(1,0);
    ld ans=B[bi[1]].Query(0);
    printf("%.0lf\n",(double)ans);
    //cout<<fixed<<setprecision(0)<<ans<<endl;
    cl(head); inum=0; cl(tag); cl(bi);
    for (int i=1;i<=bcnt;i++) B[i].clear(); bcnt=0;
  }
  return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值