2016北京集訓 陣列

2022-05-09 11:51:09 字數 2956 閱讀 8302

portal --> broken qwq

​  給你乙個陣列,每個元素有乙個顏色,要求支援兩種操作:

1、修改某個元素的顏色

2、詢問這個陣列有多少個自取件內沒有重複的顏色

​​  資料範圍:\(n<=10^5,m<=2n\),顏色大小在\(1\sim n\)之間

​​  這題。。本來應該是乙個樹套樹題

​​  但是為什麼一定要用樹套樹呢對吧qwq

​​  首先是套路:考慮維護乙個\(pre\)陣列,表示每個節點的前乙個最近的和它顏色一樣的節點,那麼我們考慮固定乙個右端點,將左端點往左移,直到再往前移一位就會導致當前區間內的\(pre\)的最大值在這個區間內,這個時候區間的長度就是這個右端點對答案的貢獻

​  然而。。直接這樣做是不行的因為我們要支援修改和多組詢問qwq

​  所以這裡考慮用線段樹維護乙個神秘的東西:對於線段樹中的乙個節點,假設它對應的區間是\([l,r]\),那麼我們維護乙個\(mx\)和\(ans\),分別表示當前區間內\(pre\)的最大值,以及,只考慮當前區間的\(pre\)限制的右端點貢獻之和(這個概念描述起來有點神秘,具體一點就是:首先這個\(ans\)記錄的是該區間中的每個點作為右端點算得的貢獻之和,但是這個貢獻在計算的時候,只考慮當前區間內的\(pre\)的影響,更加直觀一點來說就是可以理解為移動左端點求\(pre\)的最大值的時候,如果說新加進來的位置不在\([l,r]\)區間內,就不取\(max\)),然後答案就應該是線段樹根節點的\(ans\)值了,對於葉子節點來說\(ans=x-pre[x]\),其中\(x\)是這個葉子節點在陣列中對應的位置

​  現在考慮怎麼維護這個東西

​  為了方便接下來的描述,約定用\(x\)表示當前區間,\(l\)表示當前區間的左兒子(左半部分),\(r\)表示當前區間的右兒子(右半部分),然後我們考慮怎麼用\(ans[l]\)和\(ans[r]\)求得\(ans[x]\),這裡需要根據\(mx[l]\)和\(mx[r]\)的大小關係進行一些討論,首先我們先看最簡單的情況:

(1)如果說\(mx[l]>=mx[r]\):首先\(ans[l]\)肯定還是會作為\(ans[x]\)的一部分的,因為加入的東西在後面,不會影響\(ans[l]\)的貢獻,然後我們看\([mid+1,r]\)區間中的元素,因為\(mx[r]<=mx[l]\),也就是說後面的元素作為右端點的時候左端點停下的地方肯定在\(mx[l]+1\)這個位置,所以我們可以直接計算貢獻:\(ans[x]=ans[l]+(mid-mx[l])*(r-mid)+\sum\limits_^i\),具體一點的話就是\([mid+1,r]\)中的每個元素對應的左端點可以先走到\(mid+1\)這個位置,再走到\(mx[l]+1\)的位置

​​  接下來看複雜一點的另一種情況:

(2)如果說\(mx[l],那麼說明\([mid+1,r]\)中有一部分的元素對應的左端點可以走到\(mx[l]+1\),有的在\([mid+1,r]\)中的某個位置就停下了,這個時候我們就不能直接計算答案了,由於\(pre\)的\(max\)值在左端點的移動過程中是遞增的,所以我們可以考慮遞迴求解,在遞迴求解的時候也是通過這個右兒子左兒子的\(mx\)值和\(mx[l]\)進行比較,如果可以直接計算答案就直接計算,否則繼續遞迴,遇到葉子的話也是返回,因為只有乙個節點了可以直接計算

​  最後就是\(pre\)的修改,我們只要對每個值用乙個\(set\)隨便維護一下下標位置就好啦

​​  **大概長這個樣子

#include#include#include#include#define ll long long

using namespace std;

const int n=1e5+10,seg=n*4;

int pre[n],a[n];

setrec[n];

set::iterator it,fr,nxt;

int n,m;

ll sum(int l,int r)

namespace seg

void pushup(int x,int l,int r)

void _build(int x,int l,int r)

int mid=l+r>>1;

ch[x][0]=++tot; _build(ch[x][0],l,mid);

ch[x][1]=++tot; _build(ch[x][1],mid+1,r);

pushup(x,l,r);

} void build(int _n)

void _update(int x,int d,int lx,int rx)

int mid=lx+rx>>1;

if (d<=mid) _update(ch[x][0],d,lx,mid);

else _update(ch[x][1],d,mid+1,rx);

pushup(x,lx,rx);

} void update(int d)

void debug(int x,int l,int r)

int mid=l+r>>1;

debug(ch[x][0],l,mid);

debug(ch[x][1],mid+1,r);

} void debug()

}/*}}}*/

void update(int x,int delta)

rec[a[x]].erase(x);

a[x]=delta;

rec[a[x]].insert(x);

it=rec[a[x]].find(x);

fr=it; nxt=it; --fr; ++nxt;

if (nxt!=rec[a[x]].end())

pre[x]=*fr;

seg::update(x);

}void debug()

int main()

seg::build(n);

scanf("%d",&m);

for (int i=1;i<=m;++i)

//debug();

}}

2016北京集訓試題17 陣列 線段樹

線段樹亂搞orz。定義pre i 為從i點往前找到第1個顏色和點i相同的點。樹狀陣列記錄max和sum。max記錄區間 l,r 內pre的最大值,sum記錄區間 l,r 內的答案總和。注意 最終的答案是取 n n 1 2 sum max pre i 1 leq i leq r 即列舉所有子區間的右節...

(2016北京集訓十四) xsy1557 task

限制可以看成圖狀結構,每個任務的對物品數量的影響可以看成權值,只不過這個權值用乙個五元組來表示。那麼題意要求的就是最大權閉合子圖,網路流經典應用。1 include2 include3 include4 include5 include6 include7 define inf 1000000000...

xsy1531 北京集訓2016 魔法遊戲

orz sjk 有一棵樹,兩個人每個節點上有乙個權值,兩個人輪流選擇乙個根節點將其權值 k 除以 2,k 1 若除到 0 就刪去此根節點,它的兒子變成新根節點,刪掉最後乙個點贏。求先手還是後手必勝。首先考慮一些只有乙個根節點的樹,如果以二進位制角度看,每次除以 2,k 1 的數相當於拿掉一些二進位制...