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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

【IOI2018】会议【笛卡尔树】【dp】【线段树】

發布時間:2023/12/3 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【IOI2018】会议【笛卡尔树】【dp】【线段树】 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

題意:長度為nnn的序列,qqq次詢問,每次給定一個區間,欽定區間中的一個位置xxx,使得區間所有點 與xxx之間的最大值(含端點) 之和 最小,輸出最小值。

n,q≤7.5×105n,q\leq7.5\times10^5n,q7.5×105

神仙題,不愧是IOI

首先有一個O(n2)O(n^2)O(n2)的 dp

f(l,r)=min?{f(l,k?1)+(r?k+1)hk,(k?l+1)hk+f(k+1,r)}f(l,r)=\min\{f(l,k-1)+(r-k+1)h_k,(k-l+1)h_k+f(k+1,r)\}f(l,r)=min{f(l,k?1)+(r?k+1)hk?,(k?l+1)hk?+f(k+1,r)}

其中kkkl,rl,rl,r中的最大值的位置(如有多個隨便選一個),即討論欽定的點在最大值的左側或右側,然后另一側的點貢獻都是最大值

我覺得我考場上能想到這步就不錯了

這個 dp 轉移已經O(1)O(1)O(1)了,也不好壓成一維,所以要么用可持久化之類的東西強行壓狀態,要么就不記錄無用的狀態

直接想的話兩條路都不好走,但注意到這個過程實際上是最值分治,自然地想到笛卡爾樹

哪里自然了啊kora

具體地講,建出序列的笛卡爾樹,然后在樹上做上面dp,每個點只記錄它代表的區間的dp值,這樣就可以O(n)O(n)O(n)處理出來了。

然而就算處理出來了你仍然無法快速計算答案,因為詢問區間可能會被拆成很多小段,你需要像平衡樹一樣沿著樹遞歸下去。而笛卡爾樹高是O(n)O(n)O(n)的,仍然可以被卡成狗。

不過思路感覺很對,考慮怎么優化

觀察一下這個dp方程式

f(l,r)=min?{f(l,k?1)+(r?k+1)hk,f(k+1,r)+(k?l+1)hk}f(l,r)=\min\{f(l,k-1)+(r-k+1)h_k,f(k+1,r)+(k-l+1)h_k\}f(l,r)=min{f(l,k?1)+(r?k+1)hk?,f(k+1,r)+(k?l+1)hk?}

套到樹上:當前子樹根結點是kkk,為了計算f(l,k?1)f(l,k-1)f(l,k?1)f(k+1,r)f(k+1,r)f(k+1,r),我們需要繼續往左右子樹遞歸計算,我們這樣子是不行的

但這個f(l,k?1)f(l,k-1)f(l,k?1)f(k+1,r)f(k+1,r)f(k+1,r)比較特殊:它們都有一個端點是固定的!

為了敘述方便,下面只討論f(k+1,r)f(k+1,r)f(k+1,r),左邊的f(l,k?1)f(l,k-1)f(l,k?1)是同理的

我們要是知道右子樹的區間的所有前綴的dp信息就好了

看上去很扯,但實際上是可行的!

假設我們分別知道kkk的左右子樹的前綴信息,也就是知道f(l,l...k?1)f(l,l...k-1)f(l,l...k?1)f(k+1,k+1...r)f(k+1,k+1...r)f(k+1,k+1...r),現在考慮怎么合并f(l,l...r)f(l,l...r)f(l,l...r)

顯然左邊是不用管的

對于右邊,我們再把這個方程式搬出來。為了看著順眼,我把rrr換成了iii

f(l,i)=min?{f(l,k?1)+(i?k+1)hk,f(k+1,i)+(k?l+1)hk}f(l,i)=\min\{f(l,k-1)+(i-k+1)h_k,f(k+1,i)+(k-l+1)h_k\}f(l,i)=min{f(l,k?1)+(i?k+1)hk?,f(k+1,i)+(k?l+1)hk?}

注意這個iii是在[k+1,r][k+1,r][k+1,r]內的,冷靜分析一下,發現這個東西就是在原來的基礎上整體加一個數,然后和一個一次函數取min?\minmin,因為是個區間,所以可以用線段樹維護!

而這個方程是可以找到一個分界點,使得分界點左邊取左邊的值,右邊取右邊的值。原因是iii每增加111,左邊的值固定增加hkh_khk?,而右邊增加f(k+1,i+1)?f(k+1,i)f(k+1,i+1)-f(k+1,i)f(k+1,i+1)?f(k+1,i),區間往右擴張一位增加的代價總不可能大于區間最大值吧……所以左邊遲早會超過右邊,而且不會被反超。線段樹上二分即可。

什么?線段樹開不下?

但仔細想想會發現不同的位置是不會沖突的,即每個點xxx維護它當前處理的左端點到xxx的dp值,所以開一個線段樹就可以了。

線段樹合并應該也可以

f(l,k?1)f(l,k-1)f(l,k?1)的話再開一棵線段樹,左右倒過來就可以了

然后把詢問離線,每組詢問掛在區間最大值的點上面,建笛卡爾樹的時候順便處理一下就可以了。

復雜度O(nlog?n)O(n\log n)O(nlogn)

#include <iostream> #include <cstring> #include <cctype> #include <cstdio> #include <vector> #define MAXN 750005 using namespace std; inline int read() {int ans=0;char c=getchar();while (!isdigit(c)) c=getchar();while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();return ans; } typedef long long ll; int n; struct SegmentTree {#define lc p<<1#define rc p<<1|1struct node{int l,r,tag;ll k,b,lv,rv;}t[MAXN<<2];void build(int p,int l,int r){t[p].l=l,t[p].r=r;if (l==r) return;int mid=(l+r)>>1;build(lc,l,mid),build(rc,mid+1,r);}inline void pushcov(int p,ll k,ll b){t[p].k=k,t[p].b=b;t[p].lv=k*t[p].l+b,t[p].rv=k*t[p].r+b;t[p].tag=1;}inline void pushadd(int p,ll k,ll b){t[p].k+=k,t[p].b+=b;t[p].lv+=k*t[p].l+b,t[p].rv+=k*t[p].r+b;if (!t[p].tag) t[p].tag=2; }inline void pushdown(int p){if (t[p].tag){if (t[p].tag==1) pushcov(lc,t[p].k,t[p].b),pushcov(rc,t[p].k,t[p].b);if (t[p].tag==2) pushadd(lc,t[p].k,t[p].b),pushadd(rc,t[p].k,t[p].b);t[p].tag=t[p].k=t[p].b=0;}}inline void update(int p){t[p].lv=t[lc].lv,t[p].rv=t[rc].rv;} ll query(int p,int k){if (t[p].l==t[p].r) return t[p].lv;pushdown(p);if (k<=t[lc].r) return query(lc,k);return query(rc,k);}void search(int p,int l,int r,ll k1,ll b1,ll b2){if (l<=t[p].l&&t[p].r<=r){if (k1*t[p].l+b1<=t[p].lv+b2&&k1*t[p].r+b1<=t[p].rv+b2) return pushcov(p,k1,b1);if (t[p].lv+b2<=k1*t[p].l+b1&&t[p].rv+b2<=k1*t[p].r+b1) return pushadd(p,0,b2);}if (r<t[p].l||t[p].r<l) return;pushdown(p);search(lc,l,r,k1,b1,b2),search(rc,l,r,k1,b1,b2);update(p);} }lt,rt; int h[MAXN]; inline int Max(const int& x,const int& y){return h[x]>h[y]? x:y;} int st[20][MAXN],LOG[MAXN]; inline int rmq(int l,int r) {int t=LOG[r-l+1];return Max(st[t][l],st[t][r-(1<<t)+1]); } int ql[MAXN],qr[MAXN]; ll ans[MAXN]; vector<int> lis[MAXN]; void solve(int l,int r) {if (l>r) return;int k=rmq(l,r);solve(l,k-1),solve(k+1,r);for (int i=0;i<(int)lis[k].size();i++){int L=ql[lis[k][i]],R=qr[lis[k][i]];ans[lis[k][i]]=h[k]*(R-L+1ll);if (L<k) ans[lis[k][i]]=min(ans[lis[k][i]],rt.query(1,L)+(R-k+1ll)*h[k]);if (k<R) ans[lis[k][i]]=min(ans[lis[k][i]],(k-L+1ll)*h[k]+lt.query(1,R));}ll tl=rt.query(1,l),tr=lt.query(1,r);lt.search(1,k,r,h[k],tl+h[k]*(1ll-k),(k-l+1ll)*h[k]);rt.search(1,l,k,-h[k],tr+h[k]*(k+1ll),(r-k+1ll)*h[k]); } int main() {n=read();int q=read();for (int i=1;i<=n;i++) h[i]=read();lt.build(1,1,n);rt.build(1,1,n);for (int i=1;i<=n;i++) st[0][i]=i;for (int j=1;j<20;j++)for (int i=1;i+(1<<(j-1))<=n;i++)st[j][i]=Max(st[j-1][i],st[j-1][i+(1<<(j-1))]);LOG[0]=-1;for (int i=1;i<=n;i++) LOG[i]=LOG[i>>1]+1;for (int i=1;i<=q;i++) ql[i]=read()+1,qr[i]=read()+1,lis[rmq(ql[i],qr[i])].push_back(i);solve(1,n);for (int i=1;i<=q;i++) printf("%lld\n",ans[i]);return 0; }

總結

以上是生活随笔為你收集整理的【IOI2018】会议【笛卡尔树】【dp】【线段树】的全部內容,希望文章能夠幫你解決所遇到的問題。

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