Description
Input
Output
对于每个询问操作,输出满足条件的子区间数
Sample Input
3
1 2 3
3
0
1 1 2
0
Sample Output
6
4
Solution
对于每个位置,考虑以这个位置开头的子区间,最右能延伸到哪里,设为suf[i]。
为了维护这个,设一个next[i]表示第i个位置之后的第一个同颜色的位置再哪里
那么suf[i]就是next[i]的后缀min
转换成模型:对于给定序列,这个序列构成的单调栈的权值和(也可以是最大值,最小值等)
听说这个模型是wc2013的楼房重建?
设find(i,j,p)表示在[i,j]区间后面加入一个p之后的单调栈的权值和
在这题中,单调栈是单调上升的
那么对于区间的右半边,求出最小值rmin
如果rmin<p,那么p对左半边区间没有影响,答案是find(i,mid,rmin)+find(mid+1,j,p)
否则,右边被完全弹出,答案是find(i,mid,p)+p*(j-m)
如果i=j,直接考虑这个点是否会被p弹掉,输出p或本身值
但是这样会超时
设fm[i,j]表示将[i,j]区间的右边最小值加入左边的权值和,即find(i,mid,rmin)
这样在find的时候直接调用这个,是O(1)的,查询次数是log的
在每次修改时维护这个fm,也是会修改log次
最终复杂度是O(nlog2n)
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define N 501000
#define INF 1010580540
#define ll long long
using namespace std;
int a[N],n,b[N],nxt[N];
ll t[N],g[N],fm[N];
set<int> c[N];
typedef set<int> :: iterator it;
ll find(ll v,ll x,ll y,ll p)
{
if(x==y) return g[v]<=p?t[v]:p;
ll m=(x+y)/2,rm=g[v*2+1];
if(p<=rm) return find(v*2,x,m,p)+p*(y-m);
return fm[v]+find(v*2+1,m+1,y,p);
}
void ins(int v,int i,int j,int x,int y)
{
if(i==j)
{
t[v]=g[v]=y;
return;
}
int m=(i+j)/2;
if(x<=m) ins(v*2,i,m,x,y);
else ins(v*2+1,m+1,j,x,y);
t[v]=t[v*2]+t[v*2+1];
g[v]=min(g[v*2],g[v*2+1]);
fm[v]=find(v*2,i,m,g[v*2+1]);
}
int main()
{
memset(g,60,sizeof(g));
scanf("%d",&n);
fo(i,1,n) scanf("%d",&a[i]),c[a[i]].insert(i),c[i].insert(n+1),c[i].insert(0),b[i]=n+1;
fd(i,n,1)
ins(1,1,n,i,nxt[i]=b[a[i]]),b[a[i]]=i;
int ac;scanf("%d",&ac);
while(ac--)
{
int z;scanf("%d",&z);
if(z==0)
{
ll ans=find(1,1,n,n+1);
printf("%lld\n",ans-(ll)((ll)n*(ll)(n+1)/2));
}
else
{
int x,y;scanf("%d%d",&x,&y);
int q=a[x];
it pos=c[q].find(x);
int z=*(--pos);
nxt[z]=nxt[x];c[q].erase(x);
if(z>0) ins(1,1,n,z,nxt[z]);
a[x]=y;q=y;c[q].insert(x);
pos=c[q].find(x);
z=*(--pos);nxt[z]=x;
if(z>0) ins(1,1,n,z,nxt[z]);
pos++;
z=*(++pos);nxt[x]=z;
ins(1,1,n,x,z);
}
}
}