标签:typedef color names math long turn 技术 记录 scanf
线段树乱搞orz。
定义pre[i]为从i点往前找到第1个颜色和点i相同的点。树状数组记录max和sum。max记录区间[l,r]内pre的最大值,sum记录区间[l,r]内的答案总和。注意:最终的答案是取
$n*(n+1)/2-\sum _{r=1}^{n}max(pre[i],1\leq i\leq r)$,即枚举所有子区间的右节点,找最左的左节点。因此,sum[x](记录区间[l,r])在更新的时候,sum[x的左节点]可以直接加上,而对于x的右节点要保证区间[mid+1,r]的数和max[x的左节点]取最大值。这个可以在log(n)内搞定。
然后。。就ok了。
我怕是学了个假的线段树。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<set> using namespace std; typedef long long ll; const int N=1e5+10; int n,m; int col[N],pre[N]; set<int>id[N]; ll sum[N<<2],mx[N<<2]; ll solve(int k,int l,int r,ll x) { if (l==r) return max(x,mx[k]); int mid=(l+r)/2; if (mx[k]<=x) return x*(r-l+1); if (mx[k<<1]<=x) return 1ll*x*(mid-l+1)+solve(k<<1|1,mid+1,r,x); return solve(k<<1,l,mid,x)+sum[k]-sum[k<<1]; } void pushup(int k,int mid,int r) { mx[k]=max(mx[k<<1],mx[k<<1|1]); sum[k]=sum[k<<1]+solve(k<<1|1,mid+1,r,mx[k<<1]); } void modify(int k,int l,int r,int x,int y) { if (l==r){sum[k]=mx[k]=y;return;} int mid=(l+r)/2; if (x<=mid) modify(k<<1,l,mid,x,y);else modify(k<<1|1,mid+1,r,x,y); pushup(k,mid,r); } void build(int k,int l,int r) { if (l==r){sum[k]=mx[k]=pre[l];return;} int mid=(l+r)/2; build(k<<1,l,mid);build(k<<1|1,mid+1,r); pushup(k,mid,r); } set<int>::iterator q,l,r; int x,to; int main() { scanf("%d",&n); for (int i=1;i<=n;i++) id[i].insert(0); for (int i=1;i<=n;i++) { scanf("%d",&col[i]); pre[i]=*(--id[col[i]].end()); id[col[i]].insert(i); } build(1,1,n); int Q,opt; scanf("%d",&Q); while (Q--) { scanf("%d",&opt); if (!opt) printf("%lld\n",1ll*n*(n+1)/2-sum[1]); else { scanf("%d%d",&x,&to); q=id[col[x]].find(x); l=r=q;l--;r++; if (r!=id[col[x]].end()) { pre[*r]=*l; modify(1,1,n,*r,*l); } id[col[x]].erase(q); col[x]=to;id[to].insert(x); q=id[to].find(x); l=r=q;l--;r++; if (r!=id[to].end()) { pre[*r]=x; modify(1,1,n,*r,x); } pre[x]=*l; modify(1,1,n,x,*l); } } }
标签:typedef color names math long turn 技术 记录 scanf
原文地址:https://www.cnblogs.com/coco-night/p/9733342.html