轉盤:蒟蒻的第一道黑,這題是貪心和線段樹遞迴合併的綜合題。
貪心
破環成鏈的 trick 自然不用多說。
首先觀察題目,很容易發現一個性質:只走一圈的方案一定最優。這個很容易證,因為再繞一圈回來標記前面的和等前面的標記完之後繼續走是等價的,並且再繞一圈甚至可能更劣。
於是,我們只用走一圈,並且在走路的途中走走停停,就可以走出最優解。
但這依然不怎麼好求,透過觀察發現:我們把所有停下的時間移到前面來,在起點就把要停的時間全部停掉,和走走停停是等價的,並且這樣更好求。
接下來我們就來推式子了:
假設當前的時間是 \(t\),我們從 \(i\) 出發,要走到 \(j\) 處,並且 \(j\) 處的最早出現時間為 \(a_j\),那麼可以列出方程:
移項得:
因此在此時 \(t\) 的最小值就是 \(\max_{j=i}^{i+n-1}(a_j-j+i)\)。
由於每次走路還要計算時間,所以總時間為 \(\max_{j=i}^{i+n-1}(a_j-j+i)+n-1\)。
因為要最小化,所以答案就是 \(\min_{i=1}^{n}(\max_{j=i}^{i+n-1}(a_j-j+i))+n-1\)。
線段樹與遞迴
根據上面的分析,我們需要維護 \(\min_{i=1}^{n}(\max_{j=i}^{i+n-1}(a_j-j+i))+n-1\) 這個式子。
首先 \(\max_{j=i}^{i+n-1}(a_j-j+i)\) 是很好維護的,我們可以透過一個線段樹來維護。
但是外面的那個 \(\min_{i=1}^{n}\) 怎麼維護?實際上我們可以透過線上段樹上遞迴來實現。
設 \(x\) 表示線段樹上某個節點的最小值。因為我們只需要求出起點在 \(1\) 到 \(n\) 中的最小值(後面一部分是複製兩倍後的,所以和前面的重複了,可以不要計算),所以 \(x\) 就是這個節點左半部分的最小值,而不是整個節點的最小值。但也不是說按整個節點算就不可以了,只是說這樣做程式碼好寫、思路好想一些。
接下來考慮如何遞迴合併資訊:
注意:當前我們處在的節點是 \(l\)。
當遞迴到葉子節點時
由式子可知,直接返回 \(i+\max(lson_{max},r_{max})\) 即可。
當 \(rson_{max}< r_{max}\) 時
\(r_{max}\) 依然是最大值,所以要接下來遞迴 \(l\) 的左兒子 \(lson\),因為右兒子已經確定貢獻了,貢獻就是 \(\min(dfs(lson,r_{max}),mid+1+r_{max})\)。
當 \(rson_{max}\ge r_{max}\) 時
\(rson_{max}\) 把 \(r_{max}\) 擠下去了,所以要接下來遞迴 \(l\) 的右兒子 \(rson\),以找到右兒子的那個最大值在哪,才能確定貢獻,左兒子的貢獻就是 \(\min(x_l,dfs(rson,r_{max}))\)。
注意 \(x_l\) 與 \(dfs(p,r_{max})\) 函式的定義不同,其一是表示左半部分的最小值,另一個是表示全部區間的最小值。
其餘部分就是按照線段樹常規的配置來寫了。
程式碼
#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
const int N=200005;
int n,m,q,a[N],lastans=0;
struct node{
int l,r;
int mx,mi;
}tr[4*N];
int dfs(int p,int mxr)
{
if(tr[p].l==tr[p].r)return (tr[p].l+max(mxr,tr[p].mx));
int mid=(tr[p].l+tr[p].r)>>1;
if(tr[rc].mx<mxr)return min(dfs(lc,mxr),mid+1+mxr);
return min(tr[p].mi,dfs(rc,mxr));
}
void pushup(int p)
{
tr[p].mx=max(tr[lc].mx,tr[rc].mx);
tr[p].mi=dfs(lc,tr[rc].mx);
}
void build(int p,int ln,int rn)
{
tr[p]={ln,rn,a[ln]-ln,ln+a[ln]-ln};
if(ln==rn)return;
int mid=(ln+rn)>>1;
build(lc,ln,mid);
build(rc,mid+1,rn);
pushup(p);
}
void update(int p,int x,int v)
{
if(tr[p].l==x&&tr[p].r==x)
{
tr[p].mx=v-x;
tr[p].mi=x+v-x;
return;
}
int mid=(tr[p].l+tr[p].r)>>1;
if(x<=mid)update(lc,x,v);
else update(rc,x,v);
pushup(p);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=n;i++)cin>>a[i],a[i+n]=a[i];
build(1,1,2*n);
cout<<tr[1].mi+n-1<<endl;
lastans=tr[1].mi+n-1;
while(m--)
{
int x,y;
cin>>x>>y;
if(q)x^=lastans,y^=lastans;
update(1,x,y);
update(1,x+n,y);
cout<<tr[1].mi+n-1<<endl;
lastans=tr[1].mi+n-1;
}
return 0;
}