祭第五道黑题
叫序列操作的题怎么都这么恶心
coding30min,debug三小时,线段树你值得拥有
太久不写博客了,本来想集训完回家再一起写,但是de了线段树的bug真的敲不动主席树了,写(水)篇题解吧。
题意如下:
给定一个长度为n的序列。
有m次操作,操作分为:
1.区间加;
2.区间取负;
3.询问区间中选c个数乘积的和。
n,m<=50000,c<=10。
题目来源:bzoj2962
区间操作问题,很容易想到用线段树。
先放一下闫神讲的极简做法:(不是做法极简是说的极简)
维护区间选0~c个数的答案。
合并时枚举两边各选几个数。
区间加和区间取反都可以打标记。
时间复杂度O(mc2logn)
orz
反正我没完全懂,于是把这做法具体一点讲:
在题解里看到一篇讲了写线段树要思考的五个问题,觉得很好理解,这里借鉴一下思路来讲:
1.考虑每个区间维护哪些值?
构成线段树的基本元素之一,显然要考虑:
一般来说,维护的值,取决于题目询问的是什么。对于这题来说,就是维护每个区间选0~c(20)个数的答案。
2.需要添加哪些标记?
取决于会涉及如何修改树中的数据,加用常规的懒标记实现即可,区间取负可以直接标记。
3.标记如何叠加?
同区间若取了两次负,视为不取。
关于优先级,其实这道题哪个标记优先都可以写,本蒟蒻为了方便定义取负优先。
4.如何整体修改区间?
(1):加法:假设区间 an—am +x
计算贡献为:(an+x)(a(n+1)+x)...(am+x)
=(an*a(n+1)+x*(an+a(n+1))+x^2)....(am+x)
=an*a(n+1)*a(n+2)*...*am+x*(an+a(n+1)+...+a(m-1))+...+x^n
找到规律了吧:结果为 sigma (j=0~i) x^(i-j)·随机 j 个a
不怎么方便,考虑一下优化。
右眼观察(滑稽)发现,特定的随机的j个a在计算过程中会被多次求值,因此只要直接计算它被包含了多少次就行了。
什么情况下这j个a会被选呢?从(p-j)个数中任意选出(i-j)个数的时候啊,因为这时,显然已经有j个数确定了,这时能选择多少种,最有多少种情况包括了这j个a。
so:ans[i] = sigma(j=0~i) x^(i-j)·ans[j]·C(p-j,i-j)
预处理组合数就行咯。
(2)取负:上面的神仙推导您都看懂了那理解这个应该相当简单了:
对于奇数个-1相乘,结果为-1,对于偶数个,结果是1,所以若考虑的是区间内奇数个元素,则取负,偶数个直接跳过,它好我也好。
5.区间如何合并?
ans[now][i]= sigma (j=0~i) ans[ls][j] * ans[rs][i-j]
毕竟是黑题,再附一些要注意的点:
1.处理加操作时,ans要从小到大维护。
2.选数时,要特判选0个时直接return。
上代码吧,这码量很线段树,很序列操作:
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> using namespace std; #define gc c=getchar() #define r(x) read(x) #define ll long long #define ls (rt<<1) #define rs (rt<<1|1) template<typename T> inline void read(T&x){ x=0;T k=1;char gc; while(!isdigit(c)){if(c=='-')k=-1;gc;} while(isdigit(c)){x=x*10+c-'0';gc;}x*=k; } const int N=1e5+7; const int p=19940417; ll C[N][21]; struct Seg { bool tag1; ll tag2,a[21]; int siz; Seg() { tag1=0; tag2=0; memset(a,0,sizeof(a)); siz=1; } inline const ll& operator [](const int &x)const { return a[x]; } inline ll& operator [](const int &x) { return a[x]; } inline void reverse() { tag1^=1; if(tag2)tag2=p-tag2; for(int i=1;i<20;i+=2)if(a[i])a[i]=p-a[i]; } inline void increase(int x) { (tag2+=x)%=p; for(int i=min(20,siz);i;--i) { ll Tmp=x; for(int j=i-1;j;--j,(Tmp*=x)%=p) { (a[i]+=Tmp*a[j]%p*C[siz-j][i-j]%p)%=p; } (a[i]+=C[siz][i]*Tmp)%=p; } } }tr[N<<2]; inline Seg operator + (const Seg &a,const Seg &b) { Seg ret; ret[0]=1; ret.siz=a.siz+b.siz; for(int i=1;i<=min(ret.siz,20);++i) { ret[i]=(a[i]+b[i])%p; for(int j=1;j<i;++j) { (ret[i]+=a[j]*b[i-j])%=p; } } return ret; } inline void update(int rt) { tr[rt]=tr[ls]+tr[rs]; } inline void pushdown(int rt) { if(tr[rt].tag1) { tr[ls].reverse(); tr[rs].reverse(); tr[rt].tag1=0; } if(tr[rt].tag2){ tr[ls].increase(tr[rt].tag2); tr[rs].increase(tr[rt].tag2); tr[rt].tag2=0; } } int a[N]; void build(int rt,int l,int r) { if(l==r) { tr[rt][0]=1; tr[rt][1]=a[l]; return ; } int mid=(l+r)>>1; build(ls,l,mid); build(rs,mid+1,r); update(rt); } void reverse(int rt,int l,int r,int x,int y) { if(x<=l&&r<=y){ tr[rt].reverse(); return; } pushdown(rt); int mid=(l+r)>>1; if(x<=mid)reverse(ls,l,mid,x,y); if(y>mid)reverse(rs,mid+1,r,x,y); update(rt); } void increase(int rt,int l,int r,int x,int y,int v) { if(x<=l&&r<=y){ tr[rt].increase(v); return; } pushdown(rt); int mid=(l+r)>>1; if(x<=mid)increase(ls,l,mid,x,y,v); if(y>mid)increase(rs,mid+1,r,x,y,v); update(rt); } Seg query(int rt,int l,int r,int x,int y) { if(x<=l&&r<=y)return tr[rt]; pushdown(rt); int mid=(l+r)>>1; if(y<=mid)return query(ls,l,mid,x,y); else if(x>mid)return query(rs,mid+1,r,x,y); else return query(ls,l,mid,x,y)+query(rs,mid+1,r,x,y); } inline void pre(int n) { for(int i=0;i<=n;++i { C[i][0]=C[i][i]=1; for(int j=1;j<=min(i-1,20);++j) { C[i][j]=(C[i-1][j]+C[i-1][j-1])%p; } } } int main() { int n,q;r(n),r(q);pre(n); for(int i=1;i<=n;++i)r(a[i]),a[i]=(a[i]%p+p)%p; build(1,1,n); while(q--) { char opt[5];scanf("%s",opt); int l,r,x;r(l),r(r); switch(opt[0]) { case 'I':{r(x);x=(x%p+p)%p;increase(1,1,n,l,r,x);break;} case 'R':{reverse(1,1,n,l,r);break;} case 'Q':{r(x),printf("%lld ",query(1,1,n,l,r)[x]);break;} } } }
滚回寝室睡觉了,不然又要“晚上不要睡,早上不要起,你反了你了!”
祝各位黑题+1A