日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[集训队作业2018] 三角形(贪心,堆,线段树合并)

發布時間:2023/12/3 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [集训队作业2018] 三角形(贪心,堆,线段树合并) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

傳送門

首先,在結點uuu放上w[u]w[u]w[u]個石子后,出于貪心考慮,下一步一定會把uuu的所有兒子vvv上的石子收回手中。

轉換題意:
cntcntcnt為當下樹上的石子數,對每個結點uuu可以執行一次操作:

  • step1. cnt+=w[u]cnt+=w[u]cnt+=w[u]
  • step2. cnt?=∑v∈son(u)w[v]cnt-=\sum_{v\in son(u)}w[v]cnt?=vson(u)?w[v]

當且僅當對uuu的所有兒子vvv都執行過操作,才能對uuu執行操作。
問歷史上cntcntcnt的最大值最小可以是多少?

先不考慮 兒子都操作完父親才能操作的限制,思考一個簡單版問題:有nnn個操作(a[i],b[i])(a[i],b[i])(a[i],b[i]),其含義為:先令cnt+=a[i]cnt+=a[i]cnt+=a[i],再令cnt+=b[i]cnt+=b[i]cnt+=b[i]。對這nnn個操作排序,使歷史上cntcntcnt的最大值最小。(a[i]=w[i],b[i]=?∑w[v]a[i]=w[i],b[i]=-\sum w[v]a[i]=w[i],b[i]=?w[v]

對于一段操作i,i+1,...,ji,i+1,...,ji,i+1,...,j,設經過這段操作后cnt=cnt+deltacnt=cnt+deltacnt=cnt+delta,且執行這段操作途中歷史上cntcntcnt的最大值為cnt+mxcnt+mxcnt+mx

那么每次向后添加一個二元組(a[j+1]=w[j+1],b[j+1]=?∑w[v])(a[j+1]=w[j+1],b[j+1]=-\sum w[v])(a[j+1]=w[j+1],b[j+1]=?w[v])
就相當于delta←delta+w[j+1]?∑w[v],mx←max(mx,mx+w[j+1])delta\leftarrow delta+w[j+1]-\sum w[v],mx\leftarrow max(mx,mx+w[j+1])deltadelta+w[j+1]?w[v],mxmax(mx,mx+w[j+1])

所以我們換一種方式,用二元組(w[i]?∑w[v],w[i])(w[i]-\sum w[v],w[i])(w[i]?w[v],w[i])來描述操作iii。
(相當于操作段iii(delta,mx)(delta,mx)(delta,mx)

由上面可以發現這個二元組是可以簡易復合的。我們的問題就是給這些二元組安排一個優先級,使得從左到右復合之后mxmxmx最小。

可以證明,對于二元組A,BA,BA,B,定義 +++ 為復合操作,AAA的優先級比BBB高當且僅當(A+B).max<(B+A).max(A+B).max<(B+A).max(A+B).max<(B+A).max,且這樣保證不存在二元組A,B,CA,B,CA,B,C滿足 A<B,B<C,C<AA<B,B<C,C<AA<B,B<C,C<A 。證明需要進行分類討論:

因為復合順序改變deltadeltadelta不變,所以只需考慮復合后的mxmxmx,顯然mxmxmx 越小越好

  • 對于deltadeltadelta為負的二元組,一定比deltadeltadelta為正的二元組優。
  • 對于deltadeltadelta都為負的二元組,mxmxmx越小的優先級越高。
  • 對于deltadeltadelta都為正的二元組,delta?mxdelta?mxdelta?mx越小的優先級越高。

然后我們就證明了不存在二元組A,B,CA,B,CA,B,C滿足A<B,B<C,C<AA<B,B<C,C<AA<B,B<C,C<A,即可以按照這個優先級給二元組排序,且這樣最小化最終的mxmxmx 。
這樣就解決了簡化問題。

考慮原問題。
uuu的所有兒子vvv都執行過操作才能對uuu執行操作 這個限制非常麻煩,考慮反轉:
對每個點uuu可以執行一次操作:

  • step1. 把∑v∈son(u)w[v]\sum_{v\in son(u)}w[v]vson(u)?w[v]接到序列ccc的最后面
  • step2. 把?w[u]-w[u]?w[u]接到序列ccc的最后面

uuu的父親fff執行過操作才能對uuu執行操作。
問序列ccc后綴和的最大值最小可以是多少。
(反轉前相當于每個操作先把w[u]w[u]w[u]接到ccc的最后面,再把?∑w[v]-\sum w[v]?w[v]接到ccc的最后面,求最終的ccc序列的前綴和的最大值最小可以是多少)

轉化后uuu點的操作對應的二元組為(∑w[v]?w[u],∑w[v])(\sum w[v]-w[u],\sum w[v])(w[v]?w[u],w[v])

觀察發現,任意兩個二元組合并以后優先級至少比之前的一個二元組優先級低,所以對于某一時刻優先級最高的二元組,就算其父親還沒有放,其也會在其父親被放之后馬上被放,所以可以直接將這個二元組與它的父親合并。那么用一個堆維護一下當前優先級最高的二元組,就得到了根的答案的操作序列。

最后有一個結論,對于任意一個節點,其答案的操作序列一定是根的操作序列的一個子序列,這里不太需要證明,直接從優先級的角度考慮就好了,所以用線段樹合并維護每一個子樹里的操作序列和結合后的答案即可,總復雜度 O(nlogn)O(nlogn)O(nlogn)。

#include<iostream> #include<cstdio> #include<vector> #include<set> using namespace std; typedef long long ll; const int inf=0x3f3f3f3f; const int N=2e5+10; int T,n,par[N],fa[N]; int vis[N],b[N],c[N],tim; int rt[N]; ll w[N],sw[N],ans[N]; vector<int> g[N],vec[N]; struct Node{ll delta,mx;int id;friend Node operator + (Node a,Node b){return (Node){a.delta+b.delta,max(a.mx,a.delta+b.mx),a.id};}friend bool operator < (Node a,Node b){ll tmp1=max(a.mx,a.delta+b.mx);ll tmp2=max(b.mx,b.delta+a.mx);if(tmp1!=tmp2) return tmp1<tmp2;if(a.delta!=b.delta) return a.delta<b.delta;if(a.mx!=b.mx) return a.mx<b.mx;return a.id<b.id;} }a[N]; set<Node> s; int find(int x){if(x==fa[x]) return x;return fa[x]=find(fa[x]); } void init(int x){vis[x]=1;b[x]=++tim;c[tim]=x;for(int i=0;i<vec[x].size();i++) init(vec[x][i]); } namespace Seg{int ls[N*30],rs[N*30],tot;Node tr[N*30];void pushup(int u){if(!ls[u]){tr[u]=tr[rs[u]];return;}if(!rs[u]){tr[u]=tr[ls[u]];return;}tr[u]=tr[ls[u]]+tr[rs[u]];}void insert(int &u,int l,int r,int pos){if(!u) u=++tot;if(l==r){tr[u]=a[l];return;}int mid=(l+r)>>1;if(pos<=mid) insert(ls[u],l,mid,pos);else insert(rs[u],mid+1,r,pos);pushup(u);}int merge(int x,int y){if(!x||!y) return x+y;ls[x]=merge(ls[x],ls[y]);rs[x]=merge(rs[x],rs[y]);pushup(x);return x;} } void dfs(int u){Seg::insert(rt[u],1,n,b[u]);for(int i=0;i<g[u].size();i++){int v=g[u][i];dfs(v);rt[u]=Seg::merge(rt[u],rt[v]);}ans[u]=Seg::tr[rt[u]].mx; } int main(){scanf("%d",&T);scanf("%d",&n);for(int i=2;i<=n;i++){scanf("%d",&par[i]);g[par[i]].push_back(i);}for(int i=1;i<=n;i++){scanf("%lld",&w[i]);sw[par[i]]+=w[i];fa[i]=i;}for(int i=1;i<=n;i++){a[i]=(Node){sw[i]-w[i],sw[i],i};s.insert(a[i]);}for(int i=1;i<=n;i++){Node now=*(s.begin()); s.erase(s.begin());int x=now.id;if(x==1||vis[par[x]]) init(x);else{int fx=find(par[x]);s.erase(a[fx]);a[fx]=a[fx]+a[x];s.insert(a[fx]);vec[fx].push_back(x),fa[x]=fx;}}for(int i=1;i<=n;i++){a[b[i]]=(Node){sw[i]-w[i],sw[i],i};}dfs(1);for(int i=1;i<=n;i++)printf("%lld ",max(w[i],ans[i]+w[i]));return 0; }

參考文章:
https://www.cnblogs.com/mangoyang/p/11735219.html

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的[集训队作业2018] 三角形(贪心,堆,线段树合并)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。