zoukankan      html  css  js  c++  java
  • 【题解】【集训队作业2018】三角形

    题目链接

    真就讲完套路啥都简单……

    题面

    S 有一棵 (n) 个点的有根树,每个点有权值 (w_i) ,初始每个结点上都没有石子。

    S 准备了一些石子,并把它们拿在手中。她可以进行以下两种操作任意多次:

    1. 从手中取 (w_i) 个石子放在结点 (i) 上,进行该操作要求结点 (i) 的所有孩子 (j) 上都有 (w_j) 个石子。
    2. 将结点 (i) 上的所有石子收回手中。

    T 想知道对于每个 (i) ,为了在结点 (i) 上放 (w_i) 个石子,S 至少需要准备多少石子。

    思路

    显然,当你往一个节点放石子的时候,每个儿子节点都是放满的。

    那么每一次操作可以用一个二元组表示: ((w_i-sum w_{son},sum w_{son})) ,表示这一次操作完成后手中石子的增量,和这次操作中石子数达到的最大值。

    由于 “父亲受多个儿子限制” 很难处理,考虑把整个操作序列倒过来处理,那么一个点的限制就只有它的父亲。二元组变成了 ((sum w_{son}-w_i,sum w_{son})) ,需要最小化这个序列的历史最值。

    考虑贪心,对每个操作求出它的优先度。

    对于 (x<0) 的情况,显然放在前面更优(使前缀更小),按 (y) 升序;

    如果 (xge 0) ,试比较 ((x,y))((x',y')) 的优劣:(max(y,y'+x)<max( y',x'+y))

    ((y'+x)< (x'+y) => x-y>x'-y')

    如此就得到了全局最优的序列。

    现在,每次找最优的一个,如果这个点的父亲还没处理,那么就和父亲合并即可(一旦做了父亲就做,因为优先级高)。注意到每棵子树的最优序列是全局的子序列,线段树合并得到答案。

    代码

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int N=2e5+10;
    struct  node            //二元组
    {
            ll x,y; int head,tail;
            node operator + ( const node &tmp ) const { return (node){x+tmp.x,max(y,x+tmp.y),head,tmp.tail}; }
            bool operator < ( const node &tmp ) const            
            {
                    int t1=(x>=0),t2=(tmp.x>=0);
                    if( t1!=t2 ) return x<tmp.x;
                    if ( !t1 ) 
                            if ( y!=tmp.y ) return y<tmp.y;
                            else return head<tmp.head;
                    if ( (y-x)!=(tmp.y-tmp.x) ) return (y-x)>(tmp.y-tmp.x);
                    return head<tmp.head;
            }
    }lis[N];
    
    struct SegmentTree
    {
            node x; int l,r;
    }tr[N*40];
    int f[N],n,id[N],fa[N],tr2[N],tot,nxt[N];
    ll w[N],sum[N],ans[N],val[N];
    bool vis[N];
    vector<int> ve[N];
    set<node> s;
    
    int find( int x ) { return x==f[x] ? x : f[x]=find(f[x]); }
    
    int merge( int u,int v )
    {
            if ( !u || !v ) return u | v;
            tr[u].l=merge( tr[u].l,tr[v].l ); tr[u].r=merge( tr[u].r,tr[v].r );
            tr[u].x=tr[tr[u].l].x+tr[tr[u].r].x;
            return u;
    }
    
    void insert( int &cnt,int l,int r,int x,int id )
    {
            cnt=++tot;
            if ( l==r ) { tr[cnt].x=(node){val[id],sum[id],l,l}; return; }
            int mid=(l+r)>>1;
            if ( x<=mid ) insert( tr[cnt].l,l,mid,x,id );
            else insert( tr[cnt].r,mid+1,r,x,id );
            tr[cnt].x=tr[tr[cnt].l].x+tr[tr[cnt].r].x;
    }
    
    void dfs( int u,int fa )
    {
            insert( tr2[u],1,n,id[u],u );
            for ( vector<int> :: iterator it =ve[u].begin(); it!=ve[u].end(); it++ )
            {
                    int v=*it; dfs( v,u ); tr2[u]=merge( tr2[u],tr2[v] );
            }
            ans[u]=tr[tr2[u]].x.y+w[u];
    }
    
    int main()
    {
            int cas; scanf( "%d%d",&cas,&n );
            for ( int i=2; i<=n; i++ )
                    scanf( "%d",&fa[i] ),ve[fa[i]].push_back(i);
            for ( int i=1; i<=n; i++ )
                    scanf( "%lld",&w[i] ),sum[fa[i]]+=w[i],f[i]=i;
            
            for ( int i=1; i<=n; i++ )
            {
                    val[i]=sum[i]-w[i]; lis[i]=(node){val[i],sum[i],i,i};
                    s.insert(lis[i]);
            }
            vis[0]=1; int now=0;
            while ( !s.empty() )
            {
                    set<node>:: iterator it=s.begin(); node x=*it; s.erase(it);
                    if ( vis[fa[x.head]] )             //父亲节点已经处理过了
                    {
                            nxt[now]=x.head; 
                            while ( now!=x.tail ) vis[now]=1,now=nxt[now];
                            vis[now]=1;
                    }
                    else                    //否则和父亲合并
                    {
                            int pf=find( fa[x.head] ); s.erase( lis[pf] );
                            nxt[lis[pf].tail]=lis[x.head].head; lis[pf]=lis[pf]+lis[x.head];
                            f[find(x.head)]=pf; s.insert( lis[pf] );
                    }       
            }
            int cnt=0; now=0;
            for ( int i=1; i<=n; i++ )
                    cnt++,now=nxt[now],id[now]=i;
            dfs( 1,0 );
    
            for ( int i=1; i<=n; i++ )
                    printf( "%lld ",ans[i] );
    }
    
  • 相关阅读:
    A3纸双面打印对折页设置方法
    BAD SYSTEM CONFIG INFO蓝屏解决办法
    局域网内Win7系统怎么开启远程桌面
    word批量删除换行符
    excel复制到word,表格格式错乱解决办法
    word取消回车自动编号
    被深信服上网行为管理器AC拒绝的操作如何正常访问
    震旦AD248复印机如何复印身份证正反面
    Win10开机后黑屏需强制关机再开机才能进桌面怎么办
    Windows已经发现此文件有一个问题
  • 原文地址:https://www.cnblogs.com/UntitledCpp/p/13913841.html
Copyright © 2011-2022 走看看