【题解】【集训队作业2018】三角形
真就讲完套路啥都简单……
题面
S 有一棵 nn 个点的有根树,每个点有权值 wiwi ,初始每个结点上都没有石子。
S 准备了一些石子,并把它们拿在手中。她可以进行以下两种操作任意多次:
- 从手中取 wiwi 个石子放在结点 ii 上,进行该操作要求结点 ii 的所有孩子 jj 上都有 wjwj 个石子。
- 将结点 ii 上的所有石子收回手中。
T 想知道对于每个 ii ,为了在结点 ii 上放 wiwi 个石子,S 至少需要准备多少石子。
思路
显然,当你往一个节点放石子的时候,每个儿子节点都是放满的。
那么每一次操作可以用一个二元组表示: (wi−∑wson,∑wson)(wi−∑wson,∑wson) ,表示这一次操作完成后手中石子的增量,和这次操作中石子数达到的最大值。
由于 “父亲受多个儿子限制” 很难处理,考虑把整个操作序列倒过来处理,那么一个点的限制就只有它的父亲。二元组变成了 (∑wson−wi,∑wson)(∑wson−wi,∑wson) ,需要最小化这个序列的历史最值。
考虑贪心,对每个操作求出它的优先度。
对于 x<0x<0 的情况,显然放在前面更优(使前缀更小),按 yy 升序;
如果 x≥0x≥0 ,试比较 (x,y)(x,y) 和 (x′,y′)(x′,y′) 的优劣:max(y,y′+x)<max(y′,x′+y)max(y,y′+x)<max(y′,x′+y)
(y′+x)<(x′+y)=>x−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] );
}
本文作者:MontesquieuE
本文链接:https://www.cnblogs.com/UntitledCpp/p/13913841.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 你所不知道的 C/C++ 宏知识
· 聊一聊 操作系统蓝屏 c0000102 的故障分析
· SQL Server 内存占用高分析
· .NET Core GC计划阶段(plan_phase)底层原理浅谈
· .NET开发智能桌面机器人:用.NET IoT库编写驱动控制两个屏幕
· 我干了两个月的大项目,开源了!
· 推荐一款非常好用的在线 SSH 管理工具
· 千万级的大表,如何做性能调优?
· 盘点!HelloGitHub 年度热门开源项目
· Phi小模型开发教程:用C#开发本地部署AI聊天工具,只需CPU,不需要GPU,3G内存就可以运行,