P4243 [JSOI2009]等差數列 題解
阿新 • • 發佈:2022-04-21
題目連結Link,題目內容不再贅述。
這題我用的是線段樹,因為是區間操作,線段樹比較好寫主要指的是程式碼比較短,而且錯誤率不高,常數對於我這種蒟蒻來說已經很不錯了。
不會線段樹的點這裡Link
下面我們來解決一下操作:
-
A s t a b
:把 \(s\sim t\) 的數分別加上一個等差數列,數列首項為 \(a\),公差為 \(b\)。
對於這個修改操作,很容易想到把線段樹維護的每個單點定義成差分陣列,即:
for(int i=1;i<=n;i++) b[i]=a[i]-a[i-1];
這樣這一個操作就可以通過一次區間操作和兩次單點修改來解決:
modify(1,l,l,x); if(l!=r) modify(1,l+1,r,y); if(r!=n) modify(1,r+1,r+1,-x-(r-l)*y);
這個地方要提醒大家注意操作的範圍。如果 \(l=r\) 只需要修改一個數即可,如果 \(r=n\) 就可以省去差分陣列最後的那個單點修改操作(這個卡了好久。
-
B s t
:問把區間 \([s,t]\) 劃分為若干個等差的段後,最少的段數。
這個操作才是這題黑色的原因,因為它並不像看起來那樣簡單。很容易會想到記錄每個點與上個點的差值是否相等,相等為 \(1\),不等為 \(0\),然後通過判斷真假來合併答案。
但是這樣會有很大的問題,最難解決的就是兩個數不論前一個數怎麼樣,這兩個數一定是等差數列。
這樣就會發現很難得到轉移方程。所以我們這樣來定義:
struct data{ int s[2][2],lval,rval; //lval和rval是左右端點的差分值 //其中s[0/1][0/1]表示去掉左端點或去掉右端點後這個區間的最小段數 //0表示不包含,1表示包含,如s[0][1]表示包含右端點但不包含左端點 };
這樣我們可以推出一個複雜的轉移,我是運用過載運算子來實現的。
t[p].x=t[ls].x+t[rs].x;
data operator +(const data &y)const{ data x; x.lval=lval,x.rval=y.rval; x.s[0][0]=s[0][1]+y.s[1][0]-(rval==y.lval); x.s[0][0]=min(x.s[0][0],min(s[0][0]+y.s[1][0],s[0][1]+y.s[0][0])); x.s[1][0]=s[1][1]+y.s[1][0]-(rval==y.lval); x.s[1][0]=min(x.s[1][0],min(s[1][0]+y.s[1][0],s[1][1]+y.s[0][0])); x.s[0][1]=s[0][1]+y.s[1][1]-(rval==y.lval); x.s[0][1]=min(x.s[0][1],min(s[0][0]+y.s[1][1],s[0][1]+y.s[0][1])); x.s[1][1]=s[1][1]+y.s[1][1]-(rval==y.lval); x.s[1][1]=min(x.s[1][1],min(s[1][0]+y.s[1][1],s[1][1]+y.s[0][1])); return x; }
可能不太好理解,我舉一個小例子:
x.s[0][0]=s[0][1]+y.s[1][0]-(rval==y.lval);
x.s[0][0]=min(x.s[0][0],min(s[0][0]+y.s[1][0],s[0][1]+y.s[0][0]));
- 對於一個區間的
s[0][0]
(即開區間,不再贅述)來說:
它可以是:
左子區間的左開右閉區間 \(+\) 右子區間的左閉右開區間
如果左子區間的右端點的差分值 \(=\) 右子區間右端點的差分值,那麼左子區間最右邊的那段就可以和右子區間最左邊的那段連為一段,即總段數 \(-1\)。
另外就是對於零散值的判斷,在上面程式碼的第二行進行了判斷。這樣就完成了。
注意一些線段樹具體細節即可。
點選檢視程式碼
#include<iostream>
#include<cstdio>
#define N (1000000+21)
#define INF (1000000+21)
#define ls p<<1
#define rs p<<1|1
#define md (t[p].l+t[p].r)>>1
//記得define要加括號!不然會死的很慘!
using namespace std;
struct data{
//新定義一個struct可以過載運算子,操作較為方便。
int s[2][2],lval,rval;
//過載運算子轉移
data operator +(const data &y)const{
data x;
x.lval=lval,x.rval=y.rval;
x.s[0][0]=s[0][1]+y.s[1][0]-(rval==y.lval);
x.s[0][0]=min(x.s[0][0],min(s[0][0]+y.s[1][0],s[0][1]+y.s[0][0]));
x.s[1][0]=s[1][1]+y.s[1][0]-(rval==y.lval);
x.s[1][0]=min(x.s[1][0],min(s[1][0]+y.s[1][0],s[1][1]+y.s[0][0]));
x.s[0][1]=s[0][1]+y.s[1][1]-(rval==y.lval);
x.s[0][1]=min(x.s[0][1],min(s[0][0]+y.s[1][1],s[0][1]+y.s[0][1]));
x.s[1][1]=s[1][1]+y.s[1][1]-(rval==y.lval);
x.s[1][1]=min(x.s[1][1],min(s[1][0]+y.s[1][1],s[1][1]+y.s[0][1]));
return x;
}
};
struct SegTree{int l,r,add;data x;}t[N*4];
int a[N],b[N];
int n,m;
//然後底下的都是線段樹的基本操作了
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
if(l==r){
t[p].x.lval=t[p].x.rval=b[l];
t[p].x.s[0][1]=t[p].x.s[1][0]=t[p].x.s[1][1]=1;
return;
}
int mid=(l+r)>>1;//儘量用位運算
build(ls,l,mid);
build(rs,mid+1,r);
t[p].x=t[ls].x+t[rs].x;
}
void pushup(int p,int v){
t[p].add+=v;
t[p].x.lval+=v;
t[p].x.rval+=v;
}
void pushdown(int p){
if(!t[p].add||t[p].l==t[p].r) return;
pushup(ls,t[p].add);
pushup(rs,t[p].add);
t[p].add=0;
}
data query(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r) return t[p].x;
pushdown(p);
int mid=md;
if(r<=mid) return query(ls,l,r);
if(l>mid) return query(rs,l,r);
return query(ls,l,r)+query(rs,l,r);
}
void modify(int p,int l,int r,int v){
if(l<=t[p].l&&t[p].r<=r){
pushup(p,v);
return;
}
pushdown(p);
int mid=md;
if(l<=mid) modify(ls,l,r,v);
if(r>mid) modify(rs,l,r,v);
t[p].x=t[ls].x+t[rs].x;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) b[i]=a[i]-a[i-1];//差分數列
build(1,1,n);
scanf("%d",&m);
for(int i=1;i<=m;i++){
char c;
int l,r,x,y;
cin>>c;//讀入字元一定不要用scanf和getchar!會出人命的!
if(c=='A'){
scanf("%d%d%d%d",&l,&r,&x,&y);
modify(1,l,l,x);
if(l!=r) modify(1,l+1,r,y);//注意這裡的判斷,不然可能會出現一些奇奇怪怪的錯誤,得不償失
if(r!=n) modify(1,r+1,r+1,-x-(r-l)*y);
}
else{
scanf("%d%d",&l,&r);
if(l==r) printf("1\n");
else printf("%d\n",query(1,l+1,r).s[1][1]);
}
}
return 0;
}