P3372 【模板】线段树 1
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define lc p<<1 p*2
#define rc p<<1|1 p*2+1
const int N=100005;
typedef struct node{
int l,r,sum,tag;
}node;
node tr[4*N];
int arr[N];
int n,q;
void build(int p,int l,int r){
tr[p]={l,r,arr[l],0};
if(l==r) return;
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushup(int p){ 孩子区间向父区间更新
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){ 父区间向孩子区间更新
if(tr[p].tag){
tr[lc].sum+=tr[p].tag*(tr[lc].r-tr[lc].l+1);
tr[rc].sum+=tr[p].tag*(tr[rc].r-tr[rc].l+1);
tr[lc].tag+=tr[p].tag;
tr[rc].tag+=tr[p].tag;
tr[p].tag=0;
}
}
void update(int p,int l,int r,int x){
if( l<=tr[p].l && r>=tr[p].r ){
tr[p].sum+=x*(tr[p].r-tr[p].l+1);
tr[p].tag+=x;
return;
}
pushdown(p); 先下放懒标记
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) update(lc,l,r,x);
if(r>mid) update(rc,l,r,x);
pushup(p);
}
int query(int p,int l,int r){
if( l<=tr[p].l && r>=tr[p].r ) return tr[p].sum;
pushdown(p);
int res=0;
int mid=(tr[p].r+tr[p].l)>>1;
if(l<=mid) res+=query(lc,l,r);
if(r>mid) res+=query(rc,l,r);
return res;
}
void solve(){
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>arr[i];
build(1,1,n);
while(q--){
int op; cin>>op;
if(op==1){
int x,y,k; cin>>x>>y>>k;
update(1,x,y,k);
}
else{
int x,y; cin>>x>>y;
cout<<query(1,x,y)<<endl;
}
}
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
solve();
return 0;
}
总结:区间加,维护区间和,最最最典的板子,没啥好说的。
P2003 [CRCI 2008] PLATFORME 平板
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lc p<<1
#define rc p<<1|1
const int N=500;
typedef struct node{
int l,r,h,tag;
}node;
typedef struct myarr{
int h0,l0,r0;
}myarr;
bool cmp(myarr a,myarr b){
return a.h0<b.h0;
}
node tr[40000];
myarr arr[N];
void build(int p,int l,int r){
tr[p]={l,r,0,0};
if(l==r) return;
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
}
void pushup(int p){
tr[p].h=max(tr[lc].h,tr[rc].h);
}
void pushdown(int p){
if(tr[p].tag) {
tr[lc].h = max(tr[lc].h, tr[p].tag);
tr[rc].h = max(tr[rc].h, tr[p].tag);
tr[lc].tag = max(tr[lc].tag, tr[p].tag);
tr[rc].tag = max(tr[rc].tag, tr[p].tag);
tr[p].tag = 0;
}
}
void update(int p,int x,int y,int k){
if( x<=tr[p].l&&tr[p].r<=y ){
tr[p].h=max(tr[p].h,k);
tr[p].tag=max(tr[p].tag,k);
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(x<=mid) update(lc,x,y,k);
if(y>mid) update(rc,x,y,k);
pushup(p);
}
int query(int p,int x,int y){
if(x==tr[p].l&&y==tr[p].r) return tr[p].h;
pushdown(p);
int res=0;
int mid=(tr[p].l+tr[p].r)>>1;
if(x<=mid) res=max(res,query(lc,x,y));
if(y>mid) res=max(res,query(rc,x,y));
return res;
}
void solve(){
int n;
cin>>n;
int maxn=0;
for(int i=1;i<=n;i++){
cin>>arr[i].h0>>arr[i].l0>>arr[i].r0;
maxn=max(maxn,arr[i].r0);
}
build(1,1,maxn-1); 转换为格子!!!
sort(arr+1,arr+n+1,cmp);
int ans=0;
for(int i=1;i<=n;i++){
ans+=arr[i].h0-query(1,arr[i].l0,arr[i].l0);
ans+=arr[i].h0-query(1,arr[i].r0-1,arr[i].r0-1);
update(1,arr[i].l0,arr[i].r0-1,arr[i].h0);
}
cout<<ans<<"\n";
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
solve();
return 0;
}
总结:承接第一个板子。最最简单的修改第一个模板。只需要改成维护区间最大值即可。然后这题最好转换为一个一个的格子来做..
P3373 【模板】线段树 2
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define lc p<<1
#define rc p<<1|1
typedef struct node{
int l,r,sum,tag1,tag2; tag1为加法懒标记,tag2为乘法懒标记
}node;
const int N=100005;
node tr[4*N];
int n,q,mod;
int arr[N];
void build(int p,int l,int r){
tr[p]={l,r,arr[l],0,1}; tag2初始化应该是1!!
if(l==r) return;
int mid=(l+r)>>1;
if(l<=mid) build(lc,l,mid);
if(r>mid) build(rc,mid+1,r);
tr[p].sum=(tr[lc].sum+tr[rc].sum)%mod;
}
void pushup(int p){
tr[p].sum=(tr[lc].sum+tr[rc].sum)%mod;
}
void pushdown(int p){
int tag1=tr[p].tag1,tag2=tr[p].tag2;
tr[lc].sum=(tr[lc].sum*tag2+tag1*(tr[lc].r-tr[lc].l+1))%mod; 先乘后加!!
tr[rc].sum=(tr[rc].sum*tag2+tag1*(tr[rc].r-tr[rc].l+1))%mod;
tr[lc].tag1=tr[lc].tag1*tag2+tag1,tr[lc].tag2*=tag2; 新的乘上的tag2会影响原本的tag1吗???---我觉得会
tr[lc].tag1%=mod,tr[lc].tag2%=mod;
tr[rc].tag1=(tr[rc].tag1*tag2)+tag1,tr[rc].tag2*=tag2;
tr[rc].tag1%=mod,tr[rc].tag2%=mod;
tr[p].tag1=0,tr[p].tag2=1; tag2初始值为1.
}
void update(int p,int l,int r,int x,int typ){
if( l<=tr[p].l && r>=tr[p].r ){
if(typ==1){ 区间乘
tr[p].sum*=x,tr[p].sum%=mod;
tr[p].tag2*=x,tr[p].tag2%=mod;
tr[p].tag1*=x,tr[p].tag1%=mod; tag2会影响之前的tag1
tr[p].tag1=tr[p].tag1*(tr[p].r-tr[p].l+1)*x%mod; 处理tag2对tag1的影响
不对..如果多次连续对tag2更新的话,tag1的值会多加了
不能把区间的也算上。
return;
}
else{ 区间加
tr[p].sum+=x*(tr[p].r-tr[p].l+1),tr[p].sum%=mod;
tr[p].tag1+=x;
return;
}
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) update(lc,l,r,x,typ);
if(r>mid) update(rc,l,r,x,typ);
pushup(p);
}
int query(int p,int l,int r){
if( l<=tr[p].l && r>=tr[p].r ){
return tr[p].sum;
}
int res=0;
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) res+=query(lc,l,r),res%=mod;
if(r>mid) res+=query(rc,l,r),res%=mod;
return res%mod;
}
本题关键在于tag1和tag2...后加上的tag2会影响前面的tag1...
解决方案:先乘后加
例如:
①操作使:tr[1].tag1+=2;
②再操作使:tr[1].tag2*=3; 实际上在这个时候,tag2是会影响tag1的,但是不会影响3操作加上的4.所以更新tag2的时候,可以同时更新tag1.
③再操作使:tr[1].tag1+=4;
void solve(){
cin>>n>>q>>mod;
for(int i=1;i<=n;i++) cin>>arr[i];
build(1,1,n);
int op,x,y,k;
while(q--){
cin>>op;
if(op==1){ 区间乘
cin>>x>>y>>k;
update(1,x,y,k,1);
}
else if(op==2){ 区间加
cin>>x>>y>>k;
update(1,x,y,k,2);
}
else if(op==3){ 查询
cin>>x>>y;
cout<<query(1,x,y)<<endl;
}
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
solve();
return 0;
}
总结:在模板1的基础上,增加了区间乘的操作。所以需要两个懒标记,tag1记录加法,tag2记录乘法。这里有个问题就是tag2标记会影响已有的tag1。所以需要稍稍变动一点。
update时,当增加tag2之后,需要更新当前tag1。剩下的问题在于pushdown,应该先下放tag2,再下放tag1,即"先乘后加"。而tag2对tag1的影响已经提前处理了。
C-图腾_第二十届西南科技大学ACM程序设计竞赛(同步赛) (nowcoder.com)
#define lc p<<1
#define rc p<<1|1
typedef struct node{
int l,r,sum;
int tag;
}node;
const int N=300005;
node tr[4*N];
int n,m,q,init=0;
set<int> position;
unordered_map<int,int> totem;
void build(int p,int l,int r){
tr[p]={l,r,init,-1};
if(l==r) return;
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushup(int p){
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){
if(tr[p].tag!=-1){ 不能以0为初始值
tr[lc].sum=(tr[lc].r-tr[lc].l+1)*tr[p].tag;
tr[rc].sum=(tr[rc].r-tr[rc].l+1)*tr[p].tag;
tr[lc].tag=tr[p].tag;
tr[rc].tag=tr[p].tag;
tr[p].tag=-1;
}
}
void update(int p,int l,int r,int x){
if(tr[p].l>=l&&tr[p].r<=r){
tr[p].sum=(tr[p].r-tr[p].l+1)*x;
tr[p].tag=x;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(mid>=l) update(lc,l,r,x);
if(mid<r) update(rc,l,r,x);
pushup(p);
}
int query(int p,int l,int r){
if(tr[p].l>=l&&tr[p].r<=r) return tr[p].sum;
int res=0;
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(mid>=l) res+=query(lc,l,r);
if(mid<r) res+=query(rc,l,r);
return res;
}
void process(int pos,int val){
auto lesser=--position.lower_bound(pos);
auto bigger=position.upper_bound(pos);
int ll=*lesser,rr=*bigger;
int numl=totem[ll],numr=totem[rr];
if(ll+1<=pos-1) update(1,ll+1,pos-1,numl*val);
if(pos+1<=rr-1) update(1,pos+1,rr-1,val*numr);
update(1,pos,pos,0);
}
void solve(){ C---线段树
cin>>n>>m>>q;
vector<int> a;
for(int i=0;i<m;i++){
int x; cin>>x;
a.emplace_back(x);
position.emplace(x);
}
for(int i=0;i<m;i++){
int x; cin>>x;
totem[a[i]]=x;
}
init=totem[1]*totem[m];
build(1,1,n);
update(1,1,1,0),update(1,n,n,0); init
for(int i=1;i<m-1;i++){
int pos=a[i],val=totem[a[i]];
process(pos,val);
}
while(q--){
int op,x,y; cin>>op>>x>>y;
if(op==1){
position.emplace(x),totem[x]=y;
process(x,y);
}
else if(op==2) cout<<query(1,x,y)<<endl;
}
}
思路:二分下标,找到需要修改的区间。用线段树进行区间修改。
码题集OJ-小度的01串 (matiji.net)
思路:思路参考该题评论区。线段树维护的w0,w1,tag,分别是该区间以0开头需要的代价为w0,该区间以1开头需要的代价为w1,以及该区间被修改的次数tag。
“如果要用线段树维护区间信息,那么该信息应该满足区间可加性(也就是根节点的信息可以用左右子节点的信息计算出来),而差异度w0和w1满足这个性质。因为我们维护的序列是有序序列,即父节点维护的序列一定是左子树的序列接上右子树序列(不会发生翻转),因此,父节点的与01串和10串的差异度,其实就是两个儿子的差异度相加。”
“但是,父节点的信息并不是两个儿子的差异度简单相加,如果左子树的长度为奇数,那么不管将他改成01串还是10串,他的首尾都会是相同的,因此,如果要将右节点的序列接在左节点的序列后面,就要以不同于左儿子尾部字符的字符开头,例如,如果左儿子长为101,那么右儿子一定要以0开头,此时父节点的w1应该等于左节点的w1加上右节点的w0。”
“同时,我们还需要维护一个翻转序列的操作,对于一个01序列,如果将他翻转的话,我们与01串的差异度和10串的差异度刚好就会发生交换,因为所有的1变成了0,0变成了1。”用tag维护区间被修改的次数,pushdown时判断tag奇偶性决定是否要翻转。
#define lc p<<1
#define rc p<<1|1
const int N=300005;
typedef struct node{
int l,r,w0,w1,tag;
}node;
node tr[4*N];
int n,q;
string str;
void build(int p,int l,int r){
tr[p]={l,r,0,0,0};
if(l==r){
char c=str[l-1];
if(c=='1') tr[p].w0=1;
if(c=='0') tr[p].w1=1;
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
if( (tr[lc].r-tr[lc].l+1)&1 ){ 根据上一个区间的奇偶拼接。
tr[p].w0=tr[lc].w0+tr[rc].w1;
tr[p].w1=tr[lc].w1+tr[rc].w0;
}
else{
tr[p].w0=tr[lc].w0+tr[rc].w0;
tr[p].w1=tr[lc].w1+tr[rc].w1;
}
// tr[p].w0=tr[lc].w0+tr[rc].w0;
// tr[p].w1=tr[lc].w1+tr[rc].w1;
}
void pushup(int p){ 根据上一个区间的奇偶拼接。
if( (tr[lc].r-tr[lc].l+1)&1 ){
tr[p].w0=tr[lc].w0+tr[rc].w1;
tr[p].w1=tr[lc].w1+tr[rc].w0;
}
else{
tr[p].w0=tr[lc].w0+tr[rc].w0;
tr[p].w1=tr[lc].w1+tr[rc].w1;
}
// tr[p].w0=tr[lc].w0+tr[rc].w0;
// tr[p].w1=tr[lc].w1+tr[rc].w1;
}
void pushdown(int p){
if(tr[p].tag&1){
swap(tr[lc].w0,tr[lc].w1);
swap(tr[rc].w0,tr[rc].w1);
tr[lc].tag+=tr[p].tag;
tr[rc].tag+=tr[p].tag;
tr[p].tag=0;
}
}
void update(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r){
swap(tr[p].w0,tr[p].w1);
tr[p].tag++;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) update(lc,l,r);
if(r>mid) update(rc,l,r);
pushup(p);
}
pair<int,int> query(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r){
if( !((tr[p].l-l)&1) || tr[p].l==l ) return {tr[p].w0,tr[p].w1}; 根据上一个区间的奇偶拼接。
else return {tr[p].w1,tr[p].w0};
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
pair<int,int> res={0,0},ask;
if(l<=mid) {
ask=query(lc,l,r);
res.first+=ask.first;
res.second+=ask.second;
}
if(r>mid) {
ask=query(rc,l,r);
res.first+=ask.first;
res.second+=ask.second;
}
return res;
}
void solve(){ BD202413--小度的01串-(王者)-线段树
cin>>n>>q;
cin>>str;
build(1,1,n);
while(q--){
int op,l,r; cin>>op>>l>>r;
if(op==1) update(1,l,r);
if(op==2){
pair<int,int> ans=query(1,l,r);
cout<<min(ans.first,ans.second)<<endl;
}
}
}
5.龙骑士军团【算法赛】 - 蓝桥云课 (lanqiao.cn)
const int N=200005;
typedef struct node{
int l,r,pre,suf,sum;
}node;
node tr[4*N];
int n,q;
int arr[N];
void pushup(int p){
这个拼接是不对的。。
// if(tr[lc].pre==tr[lc].sum&&tr[rc].pre>0) tr[p].pre=tr[lc].sum+tr[rc].pre; 另一边大于0才拼接
// else tr[p].pre=tr[lc].pre;
// if(tr[rc].suf==tr[rc].sum&&tr[lc].suf>0) tr[p].suf=tr[rc].sum+tr[lc].suf; 另一边大于0才拼接
// else tr[p].suf=tr[rc].suf;
tr[p].pre=max(tr[lc].pre,tr[lc].sum+tr[rc].pre);
tr[p].suf=max(tr[rc].suf,tr[rc].sum+tr[lc].suf);
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void build(int p,int l,int r){
tr[p]={l,r,0,0,0};
if(l==r){
tr[p].pre=arr[l];
tr[p].suf=arr[l];
tr[p].sum=arr[l];
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
pair<int,int> queryL(int p,int l,int r){ 找ab区间的suf
if(l<=tr[p].l&&r>=tr[p].r) return {tr[p].suf,tr[p].sum};
int mid=(tr[p].l+tr[p].r)>>1;
pair<int,int> askL,askR;
if(l>mid) return queryL(rc,l,r); 全在左区间
if(r<=mid) return queryL(lc,l,r); 全在右区间
askL=queryL(lc,l,r),askR=queryL(rc,l,r);
return {max(askR.first,askR.second+askL.first),askL.second+askR.second}; merge
}
pair<int,int> queryR(int p,int l,int r){ 找bc区间的pre
if(l<=tr[p].l&&r>=tr[p].r) return {tr[p].pre,tr[p].sum};
int mid=(tr[p].l+tr[p].r)>>1;
pair<int,int> askL,askR;
if(l>mid) return queryR(rc,l,r);
if(r<=mid) return queryR(lc,l,r);
askL=queryR(lc,l,r),askR=queryR(rc,l,r);
return {max(askL.first,askL.second+askR.first),askL.second+askR.second};second是askL.second+askR.second
}
int querySum(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r) return tr[p].sum;
int mid=(tr[p].l+tr[p].r)>>1;
int res=0;
if(l<=mid) res+=querySum(lc,l,r);
if(r>mid) res+=querySum(rc,l,r);
return res;
}
龙骑士军团-----merge! 法一:维护区间的pre和suf.
https://www.lanqiao.cn/problems/19680/learning/?contest_id=193
void solve(){
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>arr[i];
build(1,1,n);
while(q--){
int a,b,c,d; cin>>a>>b>>c>>d;
if(b+1<=c-1) cout<<queryL(1,a,b).first+querySum(1,b+1,c-1)+queryR(1,c,d).first<<endl;
else cout<<queryL(1,a,b).first+queryR(1,c,d).first<<endl;
}
}
const int N=200005;
typedef struct node{
int p,l,r,premin,premax;
}node;
node tr[4*N];
int n,q;
int arr[N];
void pushup(int p){
tr[p].premin=min(tr[lc].premin,tr[rc].premin);
tr[p].premax=max(tr[lc].premax,tr[rc].premax);
}
void build(int p,int l,int r){
tr[p]={p,l,r,LONG_LONG_MAX,LONG_LONG_MIN};
if(l==r){
tr[p].premin=arr[l];
tr[p].premax=arr[l];
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
int queryMin(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r) return tr[p].premin;
int res=LONG_LONG_MAX; 开longlong!!!
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) res=min(res,queryMin(lc,l,r));
if(r>mid) res=min(res,queryMin(rc,l,r));
return res;
}
int queryMax(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r) return tr[p].premax;
int res=LONG_LONG_MIN; 开longlong!!!
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) res=max(res,queryMax(lc,l,r));
if(r>mid) res=max(res,queryMax(rc,l,r));
return res;
}
龙骑士军团----法二:维护区间中前缀和的premin和premax.premax-premin即为查询结果,这个方法更好实现--wa了一发res没取LONG_LONG
法三--st表,区间最值查询
https://www.lanqiao.cn/problems/19680/learning/?contest_id=193
void solve(){
cin>>n>>q;
for(int i=1;i<=n;i++) {
cin>>arr[i];
arr[i]+=arr[i-1]; 用前缀和来建树!
}
build(1,0,n); 细节:0-n
while(q--){
int a,b,c,d; cin>>a>>b>>c>>d;
cout<<queryMax(1,c,d)-queryMin(1,a-1,b-1)<<endl; 选到点a-1,意味着a,b区间全取;最多减到b-1是因为至少选一个
}
}
//自敲的测试用例
//22 46 91 91 65 77 77 79 91--expect
20 9
1 -5 3 4 -9 11 13 -10 1 16 -20 34 20 14 -15 10 5 9 -8 11
1 3 5 8
13 16 18 20
5 9 11 20
3 9 14 20
3 8 9 13
7 12 15 18
8 13 16 19
6 11 13 17
5 16 17 20
总结:维护区间左端,右端最大的连续子段和。
P1083 [NOIP2012 提高组] 借教室 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
const int N=1000006;
typedef struct node{
int p,l,r,minn,tag;
}node;
node tr[4*N];
int arr[N];
void pushup(int p){
tr[p].minn=min(tr[lc].minn,tr[rc].minn);
}
void pushdown(int p){
if(tr[p].tag){
tr[lc].minn-=tr[p].tag;
tr[rc].minn-=tr[p].tag;
tr[lc].tag+=tr[p].tag;
tr[rc].tag+=tr[p].tag;
tr[p].tag=0;
}
}
void build(int p,int l,int r){
tr[p]={p,l,r,INT_MAX,0};
if(l==r){
tr[p].minn=arr[l];
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
void update(int p,int l,int r,int x){
if(l<=tr[p].l&&r>=tr[p].r){
tr[p].minn-=x;
tr[p].tag+=x;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) update(lc,l,r,x);
if(r>mid) update(rc,l,r,x);
pushup(p);
}
int query(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r) return tr[p].minn;
pushdown(p); 第一发少了pushdown
int mid=(tr[p].l+tr[p].r)>>1;
int res=INT_MAX;
if(l<=mid) res=min(res,query(lc,l,r));
if(r>mid) res=min(res,query(rc,l,r));
return res;
}
int n,m;
借教室----区间修改,维护区间最小值--easy
https://www.luogu.com.cn/problem/P1083
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>arr[i];
build(1,1,n);
for(int i=1;i<=m;i++){
int need,l,r; cin>>need>>l>>r;
if(need<=query(1,l,r)) update(1,l,r,need);
else{
cout<<"-1"<<endl<<i;
return;
}
}
cout<<"0";
}
总结:区间维护最小值。
P1972 [SDOI2009] HH的项链 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
const int N=1000006;
typedef struct node{
int p,l,r,sum,tag;
}node;
node tr[4*N];
int n,m;
int a[N],mark[N],ans[N];
typedef struct myp{
int l,r,idx0;
bool operator < (const myp &x) const{
return r<x.r;
}
}myp;
myp b[N];
void build(int p,int l,int r){
tr[p]={p,l,r,0,0};
if(l==r) return;
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
}
void pushup(int p){
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){
int tag=tr[p].tag;
if(tag){
tr[lc].sum-=tag;
tr[rc].sum-=tag;
tr[lc].tag+=tag;
tr[rc].tag+=tag;
tr[p].tag=0;
}
}
void add(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r){
tr[p].sum++;
tr[p].tag++;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) add(lc,l,r);
if(r>mid) add(rc,l,r);
pushup(p);
}
void delt(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r){
tr[p].sum--;
tr[p].tag--;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) delt(lc,l,r);
if(r>mid) delt(rc,l,r);
pushup(p);
}
int query(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r) return tr[p].sum;
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
int res=0;
if(l<=mid) res+=query(lc,l,r);
if(r>mid) res+=query(rc,l,r);
return res;
}
HH的项链--巧妙思维+线段树
思路:来自题解的一句话--对于若干个询问的区间[l,r],如果他们的r都相等的话,
key:那么项链中出现的同一个数字,一定是只关心出现在最右边的那一个的.--这就是这题的关键思维.
因为这个性质,那么就需要离线处理。把询问存下来,按r从小到大处理,就可以利用上面这条性质了。
对于样例1,2,3,4,3,5;
当i==5时,3出现了重复,那么此时,第一个3已经是没有意义的了,因为r是递增的,
如果询问的[l,r]中的l能包含第一个3,那么也必然能包含第二个3.但是3只能被算作一次。
多次重复的数字也是一样处理,擦去前一个的贡献,记录当前这个的贡献。---单点修改
https://www.luogu.com.cn/problem/P1972
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
cin>>m;
for(int i=1;i<=m;i++) {
cin>>b[i].l>>b[i].r;
b[i].idx0=i;
}
sort(b+1,b+m+1);
int idx=1;
for(int i=1;i<=n;i++){
if(!mark[a[i]]){
mark[a[i]]=i;
add(1,i,i); 单点修改,实际上树状数组更好维护
}
else{
while(idx<=m&&b[idx].r<i){
ans[b[idx].idx0]=query(1,b[idx].l,b[idx].r);
idx++;
}
delt(1,mark[a[i]],mark[a[i]]); 单点修改
add(1,i,i);
mark[a[i]]=i;
}
}
while(idx<=m){
ans[b[idx].idx0]=query(1,b[idx].l,b[idx].r);
idx++;
}
for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
}
总结:下一题的铺垫题。离线处理。详细见注解。
P4113 [HEOI2012] 采花 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#define lowbit(x) (x)&(-x)
const int N=2000006;
int c[N];
int n,k,m; k似乎没用--的确没用
int a[N],ans[N];
pair<int,int> mark[N];
typedef struct myp{
int l,r,idx0;
bool operator < (const myp &x) const{
return r<x.r;
}
}myp;
myp b[N];
void add(int pos){ 单点修改
for(int i=pos;i<=n;i+=lowbit(i)){
c[i]+=1;
}
}
void delt(int pos){ 单点修改
for(int i=pos;i<=n;i+=lowbit(i)){
c[i]-=1;
}
}
int query(int pos){
int sum=0;
for(int i=pos;i;i-=lowbit(i)){
sum+=c[i];
}
return sum;
}
同上一题(https://www.luogu.com.cn/problem/P1972)一样,离线处理,读入查询之后,按r从小到大排序,依次处理.
key:上一题只关心最后一个出现的数字位置. 这一题只关心最后两个同一个数字出现的位置.上一题完全是这题的铺垫.
对于样例:1 2 2 2 3 1;
当i==3和i==4时会出现重复.
当i==3时,应该把上一次出现的2的位置add.
当i==4时,应该把上上次出现当2的位置delt,把上次出现的2的位置add,并且记录当前位置.
并且处理b[idx].r<=i的查询.因为此时的查询是不漏不重的.
采花--和上一题的思想很像很像--线段树TLE了,改成树状数组试试看--AC了
https://www.luogu.com.cn/problem/P4113
void solve(){
cin>>n>>k>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++) {
cin>>b[i].l>>b[i].r;
b[i].idx0=i;
}
sort(b+1,b+m+1);
int idx=1;
for(int i=1;i<=n;i++){
if(mark[a[i]].first==0) mark[a[i]].first=i;
else if(mark[a[i]].second==0) {
mark[a[i]].second=i;
add(mark[a[i]].first); 单点修改
}
else if(mark[a[i]].first<mark[a[i]].second){
delt(mark[a[i]].first);
add(mark[a[i]].second);
mark[a[i]].first=i;
}
else if(mark[a[i]].first>mark[a[i]].second){
delt(mark[a[i]].second);
add(mark[a[i]].first);
mark[a[i]].second=i;
}
while(idx<=m&&b[idx].r<=i){ 可取等
ans[b[idx].idx0]=query(b[idx].r)-query(b[idx].l-1);
idx++;
}
if(idx>m) break;
}
while(idx<=m){
ans[b[idx].idx0]=query(b[idx].r)-query(b[idx].l-1);
idx++;
}
for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
}
总结:洛谷紫题。上一题是这题的铺垫。离线处理。详细见注解。
数据结构 (nowcoder.com)
const int N=10004;
typedef struct node{
int p,l,r,sum1,sum2,tag1,tag2;
}node;
node tr[4*N];
int n,m;
int arr[N];
void pushup(int p){
tr[p].sum1=tr[lc].sum1+tr[rc].sum1;
tr[p].sum2=tr[lc].sum2+tr[rc].sum2;
}
先下放乘的tag2,再下放加的tag1.
因为tag2的变动会影响tag1,而tag1的变动不会影响tag2.
tag2变动时对tag1的影响已经处理了.
int quickpow(int a,int b){
int res=1;
while(b){
if(b&1) res*=a;
a=a*a;
b>>=1;
}
return res;
}
void pushdown(int p){
int tag1=tr[p].tag1;
int tag2=tr[p].tag2;
if(tag2!=1){ 乘法懒标记
tr[lc].sum1*=tag2;
tr[rc].sum1*=tag2;
tr[lc].sum2*=quickpow(tag2,2);
tr[rc].sum2*=quickpow(tag2,2);
tr[lc].tag2*=tag2,tr[lc].tag1*=tag2;mark1:需要更新tag1吗..?数据太弱了,这里把tag1乱改/删去都能过
tr[rc].tag2*=tag2,tr[rc].tag1*=tag2;经过自测样例,这两行是需要的,但是评测居然测不出来..
tr[p].tag2=1;
}
if(tag1){ 加法懒标记
sum2需要的是原本的sum1值,先更新sum2. ps:先更新sum2
tr[lc].sum2+=(tr[lc].r-tr[lc].l+1)*tag1*tag1+2*tag1*(tr[lc].sum1); 这两行tag1全写成了tag2..
tr[rc].sum2+=(tr[rc].r-tr[rc].l+1)*tag1*tag1+2*tag1*(tr[rc].sum1);
tr[lc].sum1+=(tr[lc].r-tr[lc].l+1)*tag1;
tr[rc].sum1+=(tr[rc].r-tr[rc].l+1)*tag1;
tr[lc].tag1+=tag1;
tr[rc].tag1+=tag1;
tr[p].tag1=0;
}
}
void build(int p,int l,int r){
tr[p]={p,l,r,0,0,0,1}; mark2:tag2初始值为1..--ok
if(l==r){
tr[p].sum1=arr[l];
tr[p].sum2=arr[l]*arr[l];
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
void add(int p,int l,int r,int x){
if(l<=tr[p].l&&r>=tr[p].r){
tr[p].sum2+=(tr[p].r-tr[p].l+1)*x*x+2*x*(tr[p].sum1); mark3:先更新sum2--ok
tr[p].sum1+=(tr[p].r-tr[p].l+1)*x;
tr[p].tag1+=x;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) add(lc,l,r,x);
if(r>mid) add(rc,l,r,x);
pushup(p);
}
void multi(int p,int l,int r,int x){
if(l<=tr[p].l&&r>=tr[p].r){
tr[p].sum1*=x;
tr[p].sum2*=quickpow(x,2);
tr[p].tag2*=x;
mark4:这样处理tag1对sum2的影响是正确的吗..? 把这行注释了也能过...但是在这里乱改tag1又会wa
经过自测样例,这一行是需要的,但是评测居然测不出来..
tr[p].tag1*=x;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) multi(lc,l,r,x);
if(r>mid) multi(rc,l,r,x);
pushup(p);
}
int query1(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r) return tr[p].sum1;
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
int res=0;
if(l<=mid) res+=query1(lc,l,r);
if(r>mid) res+=query1(rc,l,r);
return res;
}
int query2(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r) return tr[p].sum2;
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
int res=0;
if(l<=mid) res+=query2(lc,l,r);
if(r>mid) res+=query2(rc,l,r);
return res;
}
数据结构--维护区间和,区间元素的平方 和,区间加,区间乘
https://ac.nowcoder.com/acm/problem/19246
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>arr[i];
build(1,1,n);
int op,l,r,x;
while(m--){
cin>>op>>l>>r;
if(op==1) cout<<query1(1,l,r)<<endl;
else if(op==2) cout<<query2(1,l,r)<<endl;
else if(op==3){
cin>>x;
multi(1,l,r,x);
}
else if(op==4){
cin>>x;
add(1,l,r,x);
}
}
}
//自敲样例--可以测mark1和mark4:
42 882--expect
10 7
2 2 2 2 2 2 2 2 2 2
4 1 3 2
4 1 10 1
3 1 5 2
3 1 3 2
4 1 2 1
1 1 2
2 1 2
总结:比后面那题少头疼一点点。先处理乘法tag再处理加法tag
线段树 (nowcoder.com)
const int N=100005;
typedef struct node{
int p,l,r,sum1,sum2,tag1,tag2; sum1是区间和,sum2是区间两两相乘之和,tag1为加法懒标记,tag2为乘法懒标记.
}node;
node tr[4*N];
int n,m,mod;
int arr[N];
void pushup(int p){
tr[p].sum1=(tr[lc].sum1+tr[rc].sum1)%mod;
简单推一下公式易知--少了个右sum2!!!!
tr[p].sum2=((tr[lc].sum2+tr[rc].sum2)%mod+(tr[lc].sum1*tr[rc].sum1)%mod)%mod;
}//ok
void pushdown(int p){ sum1,sum2的更新..
int tag1=tr[p].tag1%mod;
int tag2=tr[p].tag2%mod;
// if(tag1){
// (tr[lc].sum2+=((tag1*(tr[lc].r-tr[lc].l))%mod*tr[lc].sum1)%mod+
// (tag1*tag1)%mod*(tr[lc].r-tr[lc].l+1-1)*(1+tr[lc].r-tr[lc].l)/2%mod)%=mod;
// (tr[rc].sum2+=((tag1*(tr[rc].r-tr[rc].l))%mod*tr[rc].sum1)%mod+
// (tag1*tag1)%mod*(tr[rc].r-tr[rc].l+1-1)*(1+tr[rc].r-tr[rc].l)/2%mod)%=mod;
// }
if(tag2!=1){
(tr[lc].sum2*=(tag2*tag2)%mod)%=mod;
(tr[rc].sum2*=(tag2*tag2)%mod)%=mod;
(tr[lc].sum1*=tag2)%=mod;
(tr[rc].sum1*=tag2)%=mod;
(tr[lc].tag2*=tag2)%=mod,(tr[lc].tag1*=tag2)%=mod; mark
(tr[rc].tag2*=tag2)%=mod,(tr[rc].tag1*=tag2)%=mod;
tr[p].tag2=1;
}
if(tag1){
警钟长鸣:算等差数列/2的地方不要乱取mod,算完再取mod,不然遇到奇数/2直接褒姒!!!
(tr[lc].sum2+=((tag1*(tr[lc].r-tr[lc].l))%mod*tr[lc].sum1)%mod+
(tag1*tag1)%mod*(tr[lc].r-tr[lc].l+1-1)*(1+tr[lc].r-tr[lc].l)/2%mod)%=mod;
(tr[rc].sum2+=((tag1*(tr[rc].r-tr[rc].l))%mod*tr[rc].sum1)%mod+
(tag1*tag1)%mod*(tr[rc].r-tr[rc].l+1-1)*(1+tr[rc].r-tr[rc].l)/2%mod)%=mod;
(tr[lc].sum1+=((tr[lc].r-tr[lc].l+1)*tag1)%mod)%=mod;
(tr[rc].sum1+=((tr[rc].r-tr[rc].l+1)*tag1)%mod)%=mod;
(tr[lc].tag1+=tag1)%=mod;
(tr[rc].tag1+=tag1)%=mod;
tr[p].tag1=0;
}
}
加乘加 乘加乘
void build(int p,int l,int r){
tr[p]={p,l,r,0,0,0,1};
if(l==r){
tr[p].sum1=arr[l];
tr[p].sum2=0;
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
void add(int p,int l,int r,int x){ 逐一检查sum2 sum1的公式
if(l<=tr[p].l&&r>=tr[p].r){
警钟长鸣:算等差数列/2的地方不要乱取mod,算完再取mod,不然遇到奇数/2直接褒姒!!!
(tr[p].sum2+=((x*(tr[p].r-tr[p].l))%mod*tr[p].sum1)%mod x*(区间长度-1)
+(x*x)%mod*(tr[p].r-tr[p].l+1-1)*(1+tr[p].r-tr[p].l)/2%mod)%=mod;后面这一坨是等差数列求和
(tr[p].sum1+=(x*(tr[p].r-tr[p].l+1))%mod)%=mod;
(tr[p].tag1+=x)%=mod;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) add(lc,l,r,x);
if(r>mid) add(rc,l,r,x);
pushup(p);
}
void multi(int p,int l,int r,int x){
if(l<=tr[p].l&&r>=tr[p].r){
(tr[p].sum2*=(x*x)%mod)%=mod;
(tr[p].sum1*=x)%=mod;
(tr[p].tag2*=x)%=mod;
(tr[p].tag1*=x)%=mod; mark
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) multi(lc,l,r,x);
if(r>mid) multi(rc,l,r,x);
pushup(p);
}
pair<int,int> query(int p,int l,int r){ 用merge
if(l<=tr[p].l&&r>=tr[p].r) return {tr[p].sum1,tr[p].sum2};
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l>mid) return query(rc,l,r);
if(r<=mid) return query(lc,l,r);
pair<int,int> askl=query(lc,l,r),askr=query(rc,l,r);
return {(askl.first+askr.first)%mod,((askl.second+askr.second)%mod+(askl.first*askr.first)%mod)%mod}; {sum1,sum2}
}
深刻理解tag1和tag2.!
深刻理解pushdown!!,即使ac了,还是不是很清楚为什么当tag2,tag1同时存在时,tag1更新sum2时,用的sum1是tag2更新后的sum1..
线段树
https://ac.nowcoder.com/acm/problem/212880
void solve(){ 这个题要疯狂取mod,数字非常庞大.---多取不是乱取,在等差数列那里乱取直接褒姒!!
cin>>n>>m>>mod;
for(int i=1;i<=n;i++) cin>>arr[i];
build(1,1,n);
int op,l,r,x;
while(m--){
cin>>op>>l>>r;
if(op==1) {
cin>>x;
add(1,l,r,x);
}
else if(op==2){
cin>>x;
multi(1,l,r,x);
}
else if(op==3) cout<<query(1,l,r).second<<endl;
}
}
总结:比较头疼的维护。记得先处理乘法tag再处理加法tag,然后看清楚推导的式子。
H-仓鼠的鸡蛋_牛客竞赛数据结构专题班树状数组、线段树练习题(重现赛)@IR101 (nowcoder.com)
const int N=300005;
int n,m,k;
int arr[N];
typedef struct node{
int p,l,r,bsk,cnt;
}node;
node tr[4*N];
void pushup(int p){
if(tr[lc].cnt<k&&tr[rc].cnt<k) tr[p].bsk=max(tr[lc].bsk,tr[rc].bsk);
else if(tr[lc].cnt<k) tr[p].bsk=tr[lc].bsk;
else if(tr[rc].cnt<k) tr[p].bsk=tr[rc].bsk;
else tr[p].bsk=0;
}
void build(int p,int l,int r){
tr[p]={p,l,r,0,0};
if(l==r){
tr[p].bsk=m;
tr[p].cnt=0;
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
void update(int p,int l,int r,int x){
if(l<=tr[p].l&&r>=tr[p].r){
tr[p].bsk-=x,tr[p].cnt++;
return;
}
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) update(lc,l,r,x);
if(r>mid) update(rc,l,r,x);
pushup(p);
}
int ask(int p,int x){ 线段树二分
if(tr[p].l==tr[p].r) return tr[p].l;
if(tr[lc].bsk>=x&&tr[lc].cnt<k) return ask(lc,x);tr[].cnt<k,在根节点之前全为0,没问题的.但是根节点就会被判到了
else if(tr[rc].bsk>=x&&tr[rc].cnt<k) return ask(rc,x);
return -1;
}
仓鼠的鸡蛋
https://ac.nowcoder.com/acm/contest/86173/H
void solve(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++) cin>>arr[i];
build(1,1,n);
for(int i=1;i<=n;i++){
int res=ask(1,arr[i]);
if(res!=-1) update(1,res,res,arr[i]);
cout<<res<<endl;
}
}
总结:线段树二分(ask),区间维护区间中可用的最大的篮子空间,实际上的更新都是单点更新。并且每次查询实际上都会查询到叶子节点。
[ABC322F] Vacation Query - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
const int N=500005;
typedef struct node{
int l,r;
int ml0,ml1,mr0,mr1,mx0,mx1;
int tag;
}node;
node tr[N<<2];
int n,q;
string str;
node merge(node trlc,node trrc){
node res;
res.mx0=max({trlc.mx0,trrc.mx0,trlc.mr0+trrc.ml0});直接拼,如果拼不了(那个值也不会取到),也不会影响答案
res.mx1=max({trlc.mx1,trrc.mx1,trlc.mr1+trrc.ml1});
左端
trlc.ml0==trlc.r-trlc.l+1?res.ml0=trlc.ml0+trrc.ml0:res.ml0=trlc.ml0;
trlc.ml1==trlc.r-trlc.l+1?res.ml1=trlc.ml1+trrc.ml1:res.ml1=trlc.ml1;
右端
trrc.mr0==trrc.r-trrc.l+1?res.mr0=trlc.mr0+trrc.mr0:res.mr0=trrc.mr0;
trrc.mr1==trrc.r-trrc.l+1?res.mr1=trlc.mr1+trrc.mr1:res.mr1=trrc.mr1;
其余的值全部都要更新
res.l=trlc.l,res.r=trrc.r,res.tag=0;
return res;
}
void pushdown(int p){
int tag=tr[p].tag;
if(tag&1){
swap(tr[lc].ml0,tr[lc].ml1);
swap(tr[lc].mr0,tr[lc].mr1);
swap(tr[lc].mx0,tr[lc].mx1);
swap(tr[rc].ml0,tr[rc].ml1);
swap(tr[rc].mr0,tr[rc].mr1);
swap(tr[rc].mx0,tr[rc].mx1);
tr[lc].tag+=tag;
tr[rc].tag+=tag;
tr[p].tag=0;
}
}
void build(int p,int l,int r){
tr[p]={l,r,0,0,0,0,0,0,0};
if(l==r){
if(str[l-1]=='0') tr[p].ml0=1,tr[p].mr0=1,tr[p].mx0=1;
if(str[l-1]=='1') tr[p].ml1=1,tr[p].mr1=1,tr[p].mx1=1;
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
tr[p]=merge(tr[lc],tr[rc]);
}
void update(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r){
swap(tr[p].ml0,tr[p].ml1);
swap(tr[p].mr0,tr[p].mr1);
swap(tr[p].mx0,tr[p].mx1);
tr[p].tag++;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) update(lc,l,r);
if(r>mid) update(rc,l,r);
tr[p]=merge(tr[lc],tr[rc]);
}
node query(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r) return tr[p]; l>=..
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l>mid) return query(rc,l,r);
if(r<=mid) return query(lc,l,r);
return merge(query(lc,l,r),query(rc,l,r));
}
Vacation Query
//https://www.luogu.com.cn/problem/AT_abc322_f
void solve(){ G
cin>>n>>q;
cin>>str;
build(1,1,n);
while(q--){
int op,l,r;
cin>>op>>l>>r;
if(op==1) update(1,l,r);
if(op==2) cout<<query(1,l,r).mx1<<endl;
}
}
总结:该题要维护01串的区间最大连续1的个数,并且要区间修改,翻转01。实现更新直接swap就好了,还有注意merge直接取max即可,不用判中间是否字符是否相同,如果不同的话也不会影响取max的结果。该题开辟了merge的应用,以后就用merge吧,比pushup的作用大很多,还可以用在query,非常关键,非常有用,非常好用.
2024“钉耙编程”中国大学生算法设计超级联赛(3) - Virtual Judge (vjudge.net)
Problem - 7463 (hdu.edu.cn)
思路:周日爽切,wa了一发小错,没有判到询问区间相等的区间长度为1的情况. 思维上不难想,主要是写merge比较麻烦,跟PTA模拟题一样,讨论清楚各种情况,注意细节!其他的没什么了。主要是merge的分类讨论!
const int N=300005;
typedef struct node{
int l,r,lnum,rnum,tag; tag记录加法
int sta; 当前区间的状态0,1,2,3,4,5;分别代表:单独一个点,相同,严格升,严格降,单峰,什么都不是.
}node;
node tr[4*N];
int n,q;
int arr[N];
详细讨论清楚
node merge(node tlc,node trc){
node res={0,0,0,0,0,0};
分类讨论各种情况,确定res的sta.
if(tlc.sta==5||trc.sta==5) res.sta=5;
else if(tlc.sta==0&&trc.sta==0){ 两个都是单独的
if(tlc.lnum>trc.rnum) res.sta=3;
if(tlc.lnum==trc.rnum) res.sta=1;
if(tlc.lnum<trc.rnum) res.sta=2;
}
else if(tlc.sta==0){ 左是单独的
if(trc.sta==1) tlc.rnum==trc.rnum?res.sta=1:res.sta=5;
if(trc.sta==2) tlc.rnum<trc.lnum?res.sta=2:res.sta=5;
if(trc.sta==3) tlc.rnum==trc.lnum?res.sta=5:tlc.rnum>trc.lnum?res.sta=3:res.sta=4; 注意
if(trc.sta==4) tlc.rnum<trc.lnum?res.sta=4:res.sta=5;
}
else if(trc.sta==0){ 右是单独的
if(tlc.sta==1) trc.lnum==tlc.rnum?res.sta=1:res.sta=5;
if(tlc.sta==2) trc.lnum==tlc.rnum?res.sta=5:trc.lnum>tlc.rnum?res.sta=2:res.sta=4; 注意
if(tlc.sta==3) trc.lnum<tlc.rnum?res.sta=3:res.sta=5;
if(tlc.sta==4) trc.lnum<tlc.rnum?res.sta=4:res.sta=5;
}
else{ 左右都不是0,5
1情况
if(tlc.sta==1&&trc.sta==1&&tlc.rnum==trc.lnum) res.sta=1;
else if(tlc.sta==1||trc.sta==1) res.sta=5;
2,3,4情况
else if(tlc.sta==2){
if(trc.sta==2) tlc.rnum<trc.lnum?res.sta=2:res.sta=5;
else if(trc.sta==3) tlc.rnum!=trc.lnum?res.sta=4:res.sta=5;
else if(trc.sta==4) tlc.rnum<trc.lnum?res.sta=4:res.sta=5;
}
else if(tlc.sta==3){
if(trc.sta==2) res.sta=5;
if(trc.sta==3) tlc.rnum>trc.lnum?res.sta=3:res.sta=5;
if(trc.sta==4) res.sta=5;
}
else if(tlc.sta==4){
if(trc.sta==2) res.sta=5;
if(trc.sta==3) tlc.rnum>trc.lnum?res.sta=4:res.sta=5;
if(trc.sta==4) res.sta=5;
}
}
res.l=tlc.l,res.r=trc.r;
res.lnum=tlc.lnum,res.rnum=trc.rnum;
res.tag=0;
return res;
}
void pushup(int p){ tr[p]=merge(tr[lc],tr[rc]); }
void pushdown(int p){
int tag=tr[p].tag;
if(tag){
tr[lc].lnum+=tag,tr[lc].rnum+=tag,tr[lc].tag+=tag;
tr[rc].lnum+=tag,tr[rc].rnum+=tag,tr[rc].tag+=tag;
tr[p].tag=0;
}
}
void build(int p,int l,int r){
tr[p]={l,r,0,0,0,0};
if(l==r){
tr[p].lnum=arr[l];
tr[p].rnum=arr[r];
tr[p].sta=0;
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
void add(int p,int l,int r,int x){
if(l<=tr[p].l&&r>=tr[p].r){
tr[p].lnum+=x,tr[p].rnum+=x,tr[p].tag+=x;
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) add(lc,l,r,x);
if(r>mid) add(rc,l,r,x);
pushup(p);
}
node query(int p,int l,int r){
if(l<=tr[p].l&&r>=tr[p].r) return tr[p];
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l>mid) return query(rc,l,r);
if(r<=mid) return query(lc,l,r);
return merge(query(lc,l,r),query(rc,l,r));
}
#define print0 cout<<"0"<<endl
#define print1 cout<<"1"<<endl
G - 单峰数列
https://vjudge.net/contest/643851#problem/G
https://acm.hdu.edu.cn/showproblem.php?pid=7463
一发入魂!! 小错不算错,wa的一发是询问区间是否相等,然后个数是1,没判到. 加了ask.sta==0即可. 还是一发入魂!
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>arr[i];
build(1,1,n);
cin>>q;
int l,r,x;
while(q--){
int op; cin>>op>>l>>r;
if(op==1){
cin>>x,add(1,l,r,x);
continue;
}
node ask=query(1,l,r);
if(op==2) ask.sta==0||ask.sta==1 ? print1 : print0;
if(op==3) ask.sta==0||ask.sta==2 ? print1 : print0;
if(op==4) ask.sta==0||ask.sta==3 ? print1 : print0;
if(op==5) ask.sta==4 ? print1 : print0;
}
}
Problem - 1796D - Codeforces--Maximum Subarray
思路:显而易见的是,当x为正的时候,选择长度为k的连续区间总是最优的,因为这样最可能被最大的连续区间包含. 当x为负的时候,只需要简单转化一下:k=n-k,x=-x; (线段树写法不担心k的规模)
o(n)枚举长度为k的连续区间。o(logn)查询每次枚举之后的最大子段和。
线段树写法:
显而易见的是,当x为正的时候,选择长度为k的连续区间总是最优的,因为这样最可能被最大的连续区间包含.
当x为负的时候,只需要简单转化一下:k=n-k,x=-x; (线段树写法不担心k的规模)
枚举连续的长度为k的区间为所选.用线段树logn即可求得当前序列的最大子段和.
#define lc p<<1
#define rc p<<1|1
int n,k,x;
int arr[200005];
typedef struct node{
int l,r,lmx,rmx,mx,sum;
}node;
node tr[4*200005];
inline node merge(node llc,node rrc){
node res={llc.l,rrc.r,0,0,0,0};
res.sum=llc.sum+rrc.sum;
res.mx=max({llc.mx,rrc.mx,llc.rmx+rrc.lmx});
res.lmx=max(llc.lmx,llc.sum+rrc.lmx); 这里不用分类讨论,rrc.lmx可能非常大,可以弥补llc.sum的负影响
res.rmx=max(rrc.rmx,rrc.sum+llc.rmx);
return res;
}
inline void build(int p,int l,int r){
tr[p]={l,r,0,0,0,0};
if(l==r){
tr[p].lmx=arr[l];
tr[p].rmx=arr[l];
tr[p].mx=arr[l];
tr[p].sum=arr[l];
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
tr[p]=merge(tr[lc],tr[rc]);
}
inline void update(int p,int l,int r,int xx){
if(l<=tr[p].l&&r>=tr[p].r){
tr[p].lmx+=xx;
tr[p].rmx+=xx;
tr[p].mx+=xx;
tr[p].sum+=xx;
return;
}
单点修改不需要pushdown
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid) update(lc,l,r,xx);
if(r>mid) update(rc,l,r,xx);
tr[p]=merge(tr[lc],tr[rc]);
}
https://codeforces.com/problemset/problem/1796/D
void solve(){ D 线段树写法
cin>>n>>k>>x;
int ans=0;
if(x<0) k=n-k,x=-x;
for(int i=1;i<=n;i++) {
cin>>arr[i],arr[i]-=x;
if(i<=k) arr[i]+=2*x;
}
build(1,1,n);
for(int i=k;i<=n;i++) {
ans=max(ans,tr[1].mx);
if(i<=n-1){
update(1,i-k+1,i-k+1,-2*x);
update(1,i+1,i+1,2*x);
}
}
cout<<ans<<endl;
}
to be continue...