[联合集训6-26] 树上染色 决策单调性

我们设 c o s t ( y , x ) 表示从 y 刷到 x 的代价,设 d u = h d e p t h ( u ) ,

c o s t ( y , x ) = C y ( d x ( d x + 1 ) 2 d y ( d y 1 ) 2 ) + C y 2 ( d x d y + 1 ) H y

c o s t ( y , x ) = 1 2 ( C y d x 2 + ( C y + 2 C y 2 ) d x + 2 ( C y d y ( d y 1 ) 2 + C y 2 ( 1 d y ) H y ) )

那么 c o s t ( y , x ) 就化成了一个关于 d x 的二次函数的形式, 2 c o s t ( y , x ) = A d x 2 + B d x + C ,其中
A = C y

B = C y + 2 C y 2

C = 2 ( C y d y ( d y 1 ) 2 + C y 2 ( 1 d y ) H y )

观察两个不同的贡献函数 c o s t ( y , x ) , c o s t ( y , x ) ,其做差后 Δ A = C y C y , Δ B = C y C y + 2 ( C y 2 C y 2 ) ,可以发现对称轴 Δ B 2 Δ A = 1 + ( C y + C y ) 2 < 0 ,说明两函数在正半轴只有一个交点,而且决策关于 C i 单调。
于是我们可以用类似斜率优化DP的单调队列做法,用一个set维护按 C i 有序的决策集合。多个子树set可以用启发式合并,就是暴力把小集合里的元素拿出来插入到大集合里,并左右判一下即可。对于整个set加一个值可以直接打lazy标记。

代码:

#include<bits/stdc++.h>
#define N 200010
#define ll long long
#define its set<node>::iterator 
#define rits set<node>::reverse_iterator  
using namespace std;
int n,tote,mxd,con[N],nxt[N<<1],to[N<<1],d[N],sz[N],id[N];
ll C[N],H[N],f[N],lazy[N];
struct node
{
    ll c,h;
    friend bool operator <(const node a,const node b)
    {
        if(a.c==b.c) return a.h>b.h;
        return a.c>b.c;
    }
};
set<node> s[N];
bool chkmin(ll &a,ll b)
{
    return (a<=b?0:(a=b,1));
}
ll cal(node a,ll x)
{
    return a.c*x*x+(a.c+a.c*a.c*2)*x+a.h;
}
double inter(node a,node b)
{
    double A=a.c-b.c,B=a.c-b.c+(a.c*a.c-b.c*b.c)*2,C=a.h-b.h;
    return (sqrt(B*B-A*C*4)*(A>0?1:-1)-B)/(A*2);
}
void adde(int x,int y)
{
    to[++tote]=y;
    nxt[tote]=con[x];
    con[x]=tote;
}
its pre(its x)
{
    its t=x;t--;
    return t;
}
its suc(its x)
{
    its t=x;t++;
    return t;
}
bool check(node a,node b)
{
    if(a.c<=b.c&&a.h<=b.h) return 1;
    return 0;
}
void dfs0(int v,int fa)
{
    mxd=max(d[v],mxd);
    sz[v]=1;
    for(int p=con[v];p;p=nxt[p])
        if(to[p]!=fa)
            d[to[p]]=d[v]+1,dfs0(to[p],v),sz[v]+=sz[to[p]];
}
void ins(set<node> &a,node p)
{
    its t0=a.insert(p).first;
    if(t0!=a.begin()&&suc(t0)!=a.end())
        if(check(*suc(t0),*t0)||inter(*suc(t0),*t0)<=inter(*t0,*pre(t0))) {a.erase(t0);return ;}
    if(t0!=a.begin())
        while(pre(t0)!=a.begin())
            if(check(*t0,*pre(t0))||inter(*t0,*pre(t0))<=inter(*pre(t0),*pre(pre(t0)))) a.erase(pre(t0));
            else break;
    if(suc(t0)!=a.end())
        while(suc(suc(t0))!=a.end())
            if(inter(*suc(suc(t0)),*suc(t0))<=inter(*suc(t0),*t0)) a.erase(suc(t0));
            else break;
}
void dp(int v,int fa)
{
    ll tot=0,hs=0;
    for(int p=con[v];p;p=nxt[p])
        if(to[p]!=fa)
        {
            dp(to[p],v),tot+=f[to[p]];
            if(sz[to[p]]>sz[hs]) hs=to[p];
        }   
    for(int p=con[v];p;p=nxt[p])
        if(to[p]!=fa)
        {
            its t;
            lazy[id[to[p]]]+=tot-f[to[p]];
            for(t=s[id[to[p]]].begin();suc(t)!=s[id[to[p]]].end();)
                if(inter(*suc(t),*t)>d[v]) break;
                else s[id[to[p]]].erase(t++);  
            chkmin(f[v],cal(*t,d[v])+lazy[id[to[p]]]);      
        }       
    if(!hs) id[v]=v;
    else
    {
        id[v]=id[hs];
        for(int p=con[v];p;p=nxt[p])
            if(to[p]!=fa&&to[p]!=hs)
                for(its t=s[id[to[p]]].begin();t!=s[id[to[p]]].end();t++)
                {
                    node x=(*t);
                    x.h+=lazy[id[to[p]]]-lazy[id[v]];
                    ins(s[id[v]],x);
                }
    }   
    node x;
    x.c=C[v];
    x.h=2ll*(-H[v]+C[v]*C[v]*(1ll-d[v])-C[v]*(d[v]-1ll)*d[v]/2ll);
    chkmin(f[v],cal(x,d[v])+tot);
    x.h+=tot-lazy[id[v]];       
    ins(s[id[v]],x);    
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld%lld",&H[i],&C[i]);
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        adde(x,y);adde(y,x);
    }   
    memset(f,0x3f,sizeof(f));
    dfs0(1,0);
    for(int i=1;i<=n;i++)
        d[i]=mxd-d[i];
    dp(1,0);
    printf("%lld",f[1]>>1); 
    return 0;
}

猜你喜欢

转载自blog.csdn.net/dofypxy/article/details/80889982