1. 程式人生 > 實用技巧 >分塊+莫隊+笛卡爾樹專題

分塊+莫隊+笛卡爾樹專題

這個夏天AK的第一場專題也

雖然後面的題目難得爆炸都是看題解的啦

但是AK了總比看都沒看過好那麼一丟丟吧我猜

嘻嘻

分塊

數列分塊入門 1

特別注意特判的就是如果 l 和 r 本來就是一個區間的話就需要特判,否則會多算。

分塊維護區間增量,小塊直接加到該加的位置上,詢問的時候\(a[i]\)的值為$$a[i]+add[block[i]],block[i]表示i屬於的塊的編號,add[block[i]]表示a[i]在被作為整塊的一部分處理時的增量$$

/****************************
* Author : W.A.R            *
* Date : 2020-08-12-15:41   *
****************************/
/*
*/
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<string>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
const ll mod=1e9+7;

namespace Fast_IO
{ //orz laofu
    const int MAXL((1 << 18) + 1);int iof, iotp;
    char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
    char Getchar(){
        if (ioiS == ioiT){
            ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
        }else return (*ioiS++);
    }
    void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
    void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
    inline int read(){
        int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    inline long long read_ll(){
        long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    template <class Int>void Print(Int x, char ch = '\0'){
        if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
        while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
    }
    void Getstr(char *s, int &l){
        for(ioc=Getchar();ioc==' '||ioc=='\n'||ioc=='\t';)ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
    }
    void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
} // namespace Fast_IO 
using namespace Fast_IO;

int block[maxn],blocks;
ll a[maxn],sum[maxn];

void add(int x,int y,ll c)
{
	for(int i=x;block[i]==block[x];i++)a[i]+=c;
	for(int i=block[x]+1;i<block[y];i++)sum[i]+=c;
	for(int i=y;block[i]==block[y];i--)a[i]+=c;
}

ll query(int x)
{
	return sum[block[x]]+a[x];
}

int main(){
	int op,n,l,r;
	ll c;
	
	scanf("%d",&n);blocks=sqrt(n);
	for(int i=1;i<=n;i++)block[i]=(i-1)/blocks;
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d%lld",&op,&l,&r,&c);
		if(op==0)
		{
			if(block[l]==block[r])
			{
				for(int j=l;j<=r;j++)a[j]+=c;
			}
			else add(l,r,c);
		}
		else
		{
			printf("%lld\n",query(r));
		}
	}
	
	
	return 0;
}

數列分塊入門 4

跟上一題不太一樣的就是詢問啦,上一題是單點詢問,這裡是區間詢問

分塊處理區間問題一般都是大塊分塊,小塊暴力

所以為了能處理大塊和,所以要多一個數組sum來記錄區間和啦

查詢的時候就是分塊的常規三步走啦,左散+中整+右散

特別注意特判的就是如果 l 和 r 本來就是一個區間的話就需要特判,否則會多算。(這是分塊的通用注意點啦)

/****************************
* Author : W.A.R            *
* Date : 2020-08-12-17:08   *
****************************/
/*
*/
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<string>
using namespace std;
typedef long long ll;
const ll maxn=1e6+10;
const ll mod=1e9+7;

namespace Fast_IO
{ //orz laofu
    const int MAXL((1 << 18) + 1);int iof, iotp;
    char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
    char Getchar(){
        if (ioiS == ioiT){
            ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
        }else return (*ioiS++);
    }
    void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
    void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
    inline int read(){
        int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    inline long long read_ll(){
        long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    template <class Int>void Print(Int x, char ch = '\0'){
        if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
        while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
    }
    void Getstr(char *s, int &l){
        for(ioc=Getchar();ioc==' '||ioc=='\n'||ioc=='\t';)ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
    }
    void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
} // namespace Fast_IO 
using namespace Fast_IO;

ll block[maxn],blocks;
ll a[maxn],sum[maxn],num[maxn];

void add(ll x,ll y,ll c)
{
	for(ll i=x;block[i]==block[x];i++)a[i]+=c,num[block[i]]+=c;
	for(ll i=block[x]+1;i<block[y];i++)sum[i]+=c,num[i]+=blocks*c;
	for(ll i=y;block[i]==block[y];i--)a[i]+=c,num[block[i]]+=c;
}

ll query(ll x,ll c)
{
	return ((sum[block[x]])%c+(a[x]%c))%c;
}

ll qu_query(ll x,ll y,ll c)
{
	ll ans=0;
	for(ll i=x;block[i]==block[x];i++)ans=(ans+query(i,c))%c;
	for(ll i=block[x]+1;i<block[y];i++)ans=(ans+num[i])%c;
	for(ll i=y;block[i]==block[y];i--)ans=(ans+query(i,c))%c;
	return ans%c;
}

int main(){
	ll op,n,l,r,c;
	scanf("%lld",&n);
	blocks=sqrt(n);
	for(ll i=1;i<=n;i++)scanf("%lld",&a[i]),block[i]=(i-1)/blocks,num[block[i]]+=a[i];
	
	for(ll i=1;i<=n;i++)
	{
		scanf("%lld%lld%lld%lld",&op,&l,&r,&c);
		if(op==0)
		{
			if(block[l]==block[r])
			{
				for(ll j=l;j<=r;j++)a[j]+=c,num[block[j]]+=c;
			}
			else add(l,r,c);
		}
		else
		{
			++c;
			if(block[l]==block[r])
			{
				ll ans=0;
				for(ll j=l;j<=r;j++)ans=(ans+query(j,c))%c;
				printf("%lld\n",ans%c);
			}
			else 
			{
				printf("%lld\n",qu_query(l,r,c)%c);
			}
		}
	}
	return 0;
}

數列分塊入門 2

想法就是維護每一個大塊,讓裡面的元素有序,每次詢問就大塊lower_bound,小塊暴力找,注意修改的時候要保持有序性就好啦

需要注意的點就是lower_bound的時候記得一個位置的值應該是它本身的值+區間增量

跟陣列的值有關的需要開long long,一些加減乘除可能會爆int

/****************************
* Author : W.A.R            *
* Date : 2020-08-13-02:20   *
****************************/
/*
*/
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<vector>
#include<algorithm>
using namespace std;
using namespace std;
typedef long long ll;
const ll maxn=1e6+50;

vector<ll>v[1050];
int blocks,n,block[maxn];
ll a[maxn],lazy[maxn];
inline void pushup(int x)
{
	v[x].clear();
	for(int i=x*blocks+1;i<=min(n,blocks*(x+1));i++)v[x].push_back(a[i]);
	sort(v[x].begin(),v[x].end());
}

inline void Add(int x,int y,ll c)
{
	if(block[x]==block[y])
	{
		for(int i=x;i<=y;i++)a[i]+=c;
		pushup(block[x]);
		return;
	}
	for(int i=x;block[i]==block[x];i++)a[i]+=c;pushup(block[x]);
	for(int i=block[x]+1;i<block[y];i++)lazy[i]+=c;
	for(int i=y;block[i]==block[y];i--)a[i]+=c;pushup(block[y]);return;
}

inline ll Ask(int x,int y,ll c)
{
	ll ans=0;
	if(block[x]==block[y])
	{
		for(int i=x;i<=y;i++)if(a[i]+lazy[block[i]]<c)ans++;
		return ans;
	}
	for(int i=x;block[i]==block[x];i++)if(a[i]+lazy[block[i]]<c)ans++;
	for(int i=block[x]+1;i<block[y];i++)
	{
		ll tt=c-lazy[i];ans+=lower_bound(v[i].begin(),v[i].end(),tt)-v[i].begin();
	}
	for(int i=y;block[i]==block[y];i--)if(a[i]+lazy[block[i]]<c)ans++;
	return ans;
}

int main()
{
	int op,l,r;ll c;scanf("%d",&n);blocks=sqrt(n);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]),block[i]=(i-1)/blocks,v[block[i]].push_back(a[i]);
	for(int i=0;i<((n+blocks-1)/blocks);i++)sort(v[i].begin(),v[i].end());
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d%lld",&op,&l,&r,&c);
		if(op)printf("%lld\n",Ask(l,r,c*c));
		else Add(l,r,c);
		
	}
}

數列分塊入門 3

這道題跟上一道題的做法差不多,大塊維護有序,詢問答案lower_bound即可

需要注意的是要判掉這個區間沒有比這個數小的情況這個區間全都比這個數小的情況

其他情況lower_bound找到這個數在塊裡第一次出現的位置即可

還有需要注意的就是

記得一個位置的值應該是它本身的值+區間增量

記得一個位置的值應該是它本身的值+區間增量

記得一個位置的值應該是它本身的值+區間增量

重要的事情說三遍

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<vector>
#include<algorithm>
using namespace std;
using namespace std;
typedef long long ll;
const ll maxn=1e6+50;

vector<ll>v[1050];
int blocks,n,block[maxn];
ll a[maxn],lazy[maxn];
inline void pushup(int x)
{
	v[x].clear();
	for(int i=x*blocks+1;i<=min(n,blocks*(x+1));i++)v[x].push_back(a[i]);
	sort(v[x].begin(),v[x].end());
}

inline void Add(int x,int y,ll c)
{
	if(block[x]==block[y])
	{
		for(int i=x;i<=y;i++)a[i]+=c;
		pushup(block[x]);
		return;
	}
	for(int i=x;block[i]==block[x];i++)a[i]+=c;pushup(block[x]);
	for(int i=block[x]+1;i<block[y];i++)lazy[i]+=c;
	for(int i=y;block[i]==block[y];i--)a[i]+=c;pushup(block[y]);return;
}

inline ll Ask(int x,int y,ll c)
{
	ll ans=-4000000000000000000;
	if(block[x]==block[y])
	{
		for(int i=x;i<=y;i++)if(a[i]+lazy[block[i]]<c)ans=max(ans,a[i]+lazy[block[i]]);
		return ans==-4000000000000000000?-1ll:ans;
	}
	for(int i=x;block[i]==block[x];i++)if(a[i]+lazy[block[i]]<c)ans=max(ans,a[i]+lazy[block[i]]);
	for(int i=block[x]+1;i<block[y];i++)
	{
		int tt=(n+blocks-1)/blocks-1;//總塊數 
		int endp=(i==tt?n-tt*blocks:blocks)-1;//長度 
		
		if(v[i][0]+lazy[i]>=c)continue;
		if(v[i][endp]+lazy[i]<c)ans=max(ans,v[i][endp]+lazy[i]);
		else ans=max(ans,v[i][lower_bound(v[i].begin(),v[i].end(),c-lazy[i])-v[i].begin()-1]+lazy[i]);
	}
	for(int i=y;block[i]==block[y];i--)if(a[i]+lazy[block[i]]<c)ans=max(ans,a[i]+lazy[block[i]]);
	return ans==-4000000000000000000?-1ll:ans;
}

int main()
{
	int op,l,r;ll c;scanf("%d",&n);blocks=sqrt(n);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]),block[i]=(i-1)/blocks,v[block[i]].push_back(a[i]);
	for(int i=0;i<((n+blocks-1)/blocks);i++)sort(v[i].begin(),v[i].end());
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d%lld",&op,&l,&r,&c);
		if(op)printf("%lld\n",Ask(l,r,c));
		else Add(l,r,c);
	}
}

Finding a MEX

一道究極分塊題啦,圖分塊,根據度數把點分為大點和小點

每次是詢問一個點,他的鄰接點的點權中最小的沒有出現過的非負整數Mex

n是1e5,m也是1e5,1e5條邊,度數就是2e5.易得度數超過$$\sqrt{2m}$$的點不會超過$$\sqrt{2m}$$個

因此可以把點根據度數分成大點和小點,大點用資料結構(我用的是樹狀陣列)維護答案,小點暴力(分塊正常思路)。

大點就定義為度數$$>\sqrt{2*m}$$的點,反之則為小點。

現在考慮大點怎麼維護答案。

同樣是用類似之前那個詢問區間不同數個數的思路,用cnt陣列記錄某個數出現的次數,用樹狀陣列維護每個數是否出現過,若出現過則該數這個位置上為1,反之則為0。因此要在點修改的時候判斷一下一個數是不是新出現的,或者是不是被刪光了,據此更新樹狀陣列對應位置的值。

處理詢問的時候,就是二分詢問,看樹狀陣列字首和是不是跟長度相等,如果相等,則說明前面每個位置都是放滿了的說明答案在[mid+1 , r],否則答案在[ l , mid-1 ]。二分查詢到這個答案即可。

小塊的暴力就是放到把這個點的鄰接點都放到一個set裡,一個個扔出來,第一個沒出現過的就是答案。

注意因為這道題包含0,0在樹狀數組裡跑起來可能會有點問題,最好直接+1來處理,免得錯掉啦

注意:有一個小結論,就是一個長度為n的陣列的Mex不會大於n,可以細品一下,比較易得。

就是因為這個結論,所以樹狀陣列的最大值只需要定為這個點的度數即可。

/****************************
* Author : W.A.R            *
* Date : 2020-08-14-16:31   *
****************************/
/*
*/
#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<string>
#define lowbit(x) x&(-x)
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
const ll mod=1e9+7;
namespace Fast_IO{ //orz laofu
    const int MAXL((1 << 18) + 1);int iof, iotp;
    char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
    char Getchar(){
        if (ioiS == ioiT){
            ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
        }else return (*ioiS++);
    }
    void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
    void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
    inline int read(){
        int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
        if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    inline long long read_ll(){
        long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
        if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    template <class Int>void Print(Int x, char ch = '\0'){
        if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
        while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
    }
    void Getstr(char *s, int &l){
        for(ioc=Getchar();ioc==' '||ioc=='\n'||ioc=='\t';)ioc=Getchar();
        if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
    }
    void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
} // namespace Fast_IO 
using namespace Fast_IO;
struct node{int to,nxt;}e[maxn];
int ct=0,head[maxn],a[maxn],du[maxn];bool vis[maxn];
inline void addE(int u,int v){e[++ct].to=v;e[ct].nxt=head[u];head[u]=ct;}
struct Bit{
	vector<int>tree,b;int sz;
    void init(int n){sz=n;tree.clear();b.clear();
        for(int i=0;i<=n;i++)tree.push_back(0),b.push_back(0);
    }
    void add(int x,int val){
        if(x>=sz)return;//一個長度為n的陣列的mex不會大於n,不加這句會RE 
		b[x]+=val;//x這個數字出現了1次或者減少了1次 
        if((b[x]==1&&val==1)||(b[x]==0&&val==-1))
            for(x++;x<=sz;x+=lowbit(x))tree[x]+=val;
    }
	int getsum(int m){
        int sum=0;while(m){sum+=tree[m];m-=lowbit(m);}
		return sum;
    }
    int Mex(){
        int l=0,r=sz,ans=1;
        while(l<=r){
            int mid=(l+r)>>1; 
            if(getsum(mid)==mid)l=mid+1,ans=mid;else r=mid-1;
        }
        return ans;
    }
}tr[maxn];
inline void upd(int u,int x){
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;if(!vis[v])continue;
        tr[v].add(a[u],-1);tr[v].add(x,1);
    }a[u]=x;
}
inline int query(int u){
    if(vis[u])return tr[u].Mex();
    else{
        set<int>S;int ans=0;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;S.insert(a[v]);    
        }
        for(auto i:S)if(i==ans)ans++;else break;
        return ans;
    }
}
signed main(){
    int T=read();
    while(T--){
        int n=read(),m=read(),blocks=sqrt(2*m);ct=0;
        memset(head,0,sizeof(head));memset(vis,0,sizeof(vis));memset(du,0,sizeof(du));
        for(int i=1;i<=n;i++)a[i]=read();
        for(int i=1;i<=m;i++){int u=read(),v=read();addE(u,v);addE(v,u);du[u]++;du[v]++;}
        for(int u=1;u<=n;u++){
            if(du[u]<=blocks)continue;vis[u]=1;tr[u].init(du[u]);
            for(int i=head[u];i;i=e[i].nxt){int v=e[i].to;tr[u].add(a[v],1);}
        }
        int q=read();
        while(q--){
            int op=read(),u=read(),x;
            if(op==1){x=read();upd(u,x);}
            else printf("%d\n",query(u));
        }
    }return 0;
}

總結

我發現分塊的問題主要就是思考一下大塊維護的資訊是什麼

然後我寫的時候bug不斷,但是也沒關係

好像分塊的題目就是自己聽懂的

一段一段地掃過去,反覆的看就是能看出bug的,還是挺開心的,技能點++

分塊2和3是大半夜A的,大半夜寫題甚是興奮哦

莫隊

D-query

經典莫隊模板題

詢問區間不同數的個數(也可以用主席樹做啦/yf講座也有這道題目啦

把詢問讀進來,排個序,左端點屬於同一塊,若塊號為奇數則按右端點從小到大排,若塊號為偶數則按右端點從大到小排,否則按左端點所屬塊號從小到大排。(奇偶優化減少右端點移動距離)

cnt記錄當前區間這個某個數出現的次數,ans維護的是當前區間的答案,每次往外擴一個位置 i 的時候,cnt [ a [ i ] ]++,如果++之後cnt [ a [ i ] ]==1則說明這個數原來沒出現過,現在出現了,所以ans++;每次縮排來一個位置也是同理。

這道題我踩的一個就是,一個int型別的函式沒有返回值會wa掉!

/****************************
* Author : W.A.R            *
* Date : 2020-08-12-14:36   *
****************************/
/*
莫隊求區間有多少不同的數字 
int 型別的函式沒有返回值可能會WA掉 
*/
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<string>
using namespace std;
typedef long long ll;
const int maxn=3e6+10;
const ll mod=1e9+7;
namespace Fast_IO{
    const int MAXL((1 << 18) + 1);int iof, iotp;
    char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
    char Getchar(){
        if (ioiS == ioiT){
            ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
        }else return (*ioiS++);
    }
    void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
    void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
    inline int read(){
        int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    inline long long read_ll(){
        long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    template <class Int>void Print(Int x, char ch = '\0'){
        if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
        while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
    }
    void Getstr(char *s, int &l){
        for(ioc=Getchar();ioc==' '||ioc=='\n'||ioc=='\t';)ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
    }
    void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
} // namespace Fast_IO 
using namespace Fast_IO;
struct Query{int l,r,id;}q[maxn];
int tmpans=0,block[maxn],cnt[maxn],a[maxn],ans[maxn];
inline bool cmp(Query a,Query b){return (block[a.l])^(block[b.l])?a.l<b.l:(((block[a.l])&1)?a.r<b.r:a.r>b.r);}
inline void Add(int x){cnt[a[x]]++;if(cnt[a[x]]==1)tmpans++;}
inline void Del(int x){cnt[a[x]]--;if(cnt[a[x]]==0)tmpans--;}
int Calc(){return tmpans;}
int main(){
	int n=read(),Q,l=0,r=0;
	int blocks=sqrt(n);for(int i=1;i<=n;i++)block[i]=(i-1)/blocks;
	for(int i=1;i<=n;i++)a[i]=read();Q=read();
	for(int i=1;i<=Q;i++)q[i].l=read(),q[i].r=read(),q[i].id=i;
	sort(q+1,q+1+Q,cmp);
	for(int i=1;i<=Q;i++){
		while(l<q[i].l)Del(l++);
		while(l>q[i].l)Add(--l);
		while(r<q[i].r)Add(++r);
		while(r>q[i].r)Del(r--);
		ans[q[i].id]=Calc();
	}
	for(int i=1;i<=Q;i++)printf("%d\n",ans[i]);
	return 0;
}

XOR and Favorite Number

異或區間詢問

詢問給定區間內有多少個區間異或和為k

很巧妙的一個想法

首先算一個字首異或和pre

假設已知當前區間答案為tmpans,若往外擴一個位置 i ,pre[ i ]為當前位置的異或字首和,那麼思考當前位置的增加會增加多少答案呢?

先思考另一個問題,有兩個位置 i 和 j (i<j),問[ i+1, j ]區間的異或和為多少

根據異或和的性質可得,答案是$$pre[i]*pre[j]$$,是不是跟字首和的處理超級像!!!!

所以說,當往外擴一個位置 i 的時候,會增加的答案就是,之前的區間裡面,字首異或和跟當前位置的字首異或和異或起來結果為k的數量,這就是區間要維護的資訊啦。

形象一點就是,j 為之前區間裡的某一個位置,擴充位置 i 會增加的答案數量就是,之前區間裡滿足pre[ i ] ^ pre[ j ] = k 的 j 的數量,也就是cnt[ k ^ pre[ i ] ] (pre[ j ]=k ^ pre[ i ])。答案增量計算完之後記得要把cnt[ pre[ i ] ]++

同理,當要往裡收縮一個位置的時候,要減去的影響是之前的區間裡的cnt[ k ^ pre[ i ] ]

注意:這道題我有一個點琢磨了很久都不明白,後來明白了

這個點就是為什麼在這道題裡Add和Del的cnt改變的語句修改答案的語句是剛好順序反一下的,後來我明白了,這是因為這道題維護的是字首和的內容,字首和是$$ans[l,r]=pre[r]^pre[l-1]$$就算$$l==r$$,那也是$$ans[l]=pre[l]^pre[l-1]$$,所以自己這個位置和自己這個位置是不能對答案產生貢獻的(字首和意義下),所以Add和Del語句的順序都是為了保證這個即將刪除/增加的位置不會自己跟自己產生個答案貢獻什麼的(可以細細品一下

還有一個要注意的點就是,cnt[ 0 ]一開始要初始化為1,因為再一開始,已知的答案區間為空時,字首異或和是為0的,因此cnt[ 0 ]=1。

while迴圈裡 l 的兩個迴圈也要改成$$<q[i].l-1$$和$$>q[i].l-1$$這也是因為字首和啦,自己細品一下!

/****************************
* Author : W.A.R            *
* Date : 2020-08-12-14:36   *
****************************/
/*
莫隊求區間有多少不同的數字 
int 型別的函式沒有返回值可能會WA掉 
*/
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<string>
using namespace std;
typedef long long ll;
const int maxn=3e6+10;
const ll mod=1e9+7;
namespace Fast_IO{
    const int MAXL((1 << 18) + 1);int iof, iotp;
    char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
    char Getchar(){
        if (ioiS == ioiT){
            ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
        }else return (*ioiS++);
    }
    void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
    void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
    inline int read(){
        int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    inline long long read_ll(){
        long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    template <class Int>void Print(Int x, char ch = '\0'){
        if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
        while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
    }
    void Getstr(char *s, int &l){
        for(ioc=Getchar();ioc==' '||ioc=='\n'||ioc=='\t';)ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
    }
    void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
} // namespace Fast_IO 
using namespace Fast_IO;
int block[maxn],cnt[maxn],a[maxn];
struct Query{int l,r,id;}q[maxn];
inline bool cmp(Query a,Query b){return (block[a.l])^(block[b.l])?a.l<b.l:(((block[a.l])&1)?a.r<b.r:a.r>b.r);}


ll pre[maxn],tmpans=0,k,ans[maxn];
inline void Add(int x){tmpans+=cnt[k^pre[x]];cnt[pre[x]]++;}
inline void Del(int x){cnt[pre[x]]--;tmpans-=cnt[k^pre[x]];}
ll Calc(){return tmpans;}

int main(){
	int n=read(),Q=read(),l=0,r=-1;k=read_ll();
	int blocks=sqrt(n);for(int i=1;i<=n;i++)block[i]=(i-1)/blocks;
	for(int i=1;i<=n;i++)a[i]=read(),pre[i]=pre[i-1]^a[i];
	for(int i=1;i<=Q;i++)q[i].l=read(),q[i].r=read(),q[i].id=i;
	sort(q+1,q+1+Q,cmp);
	for(int i=1;i<=Q;i++){
		while(l<q[i].l-1)Del(l++);
		while(l>q[i].l-1)Add(--l);
		while(r<q[i].r)Add(++r);
		while(r>q[i].r)Del(r--);
		ans[q[i].id]=Calc();
	}
	for(int i=1;i<=Q;i++)printf("%lld\n",ans[i]);
	return 0;
}

一個簡單的詢問

這道題直接莫隊不太好維護

所以要把它給的式子展開一下

\[get(l$$~1~$$,r$$~1~$$,x)\cdot$$$$get(l$$~2~$$,r$$~2~$$,x) \]

\[=( get(1$$$$,r$$~1~$$,x)-$$$$get(1$$$$,l$$~1~-1$$,x) )\cdot$$$$( get(1$$$$,r$$~2~$$,x)-$$$$get(1$$$$,l$$~2~-1$$,x) ) \]

\[=(get(1,r$$~1~$$,x)\cdot get(1,r$$~2~$$,x))-(get(1,r$$~1~$$,x)\cdot get(1,l$$~2~$$-1,x))-(get(1,l$$~1~$$-1,x)\cdot(get(1,r$$~2~$$,x))+(get(1,l$$~1~$$-1)\cdot get(1,l$$~2~$$-1,x)) \]

這樣的話就可以把莫隊區間維護的答案轉化成維護以上四個式子的答案

依然用cnt1[x]表示[1,左端點]區間內x出現的次數,如果把左端點往左挪一下,則a[i]這個位置上的數就離開了[1,左端點]這個區間,所以要cnt1[x]--。

以第一部分的式子舉例$$get(1,r$$~1~$$,x)\cdot get(1,r$$~2~$$,x)$$,如果r~1~往左挪一位造成cnt[x]--,那麼對這一整個式子來說就是減少了一個$$get(1,r$$~2~$$,x)$$,因為式子前半部分少了1,相當於少了一個後一項。

其他四個式子也是同理,因此處理的時候只需要把一個詢問拆成四個詢問,然後莫隊分別處理加到對應的詢問裡,最後輸出答案即可。

這道題跟普通莫隊不太一樣的就是

一般左端點往左和右端點往右是擴大區間的

但是由於這道題維護的是字首和,所以左端點往左和右端點往左都是縮小區間的,所以處理的時候用Add還是Del要好好考慮一下

還有一個需要注意的點就是,把一個詢問拆成四個詢問之後,乘號左邊那個變數就是區間左端點,乘號右邊那個變數就是區間右端點,不需要考慮他原來是l還是r什麼亂七八糟的,拆分開來之後他就是一個全新的區間,甚至不需要保證左端點小於右端點。

然後由於一個區間的答案是由左端點的字首和和右端點的字首和決定的,所以需要兩個cnt陣列來分別維護左端點字首資訊和右端點字首資訊。

建議細品

/****************************
* Author : W.A.R            *
* Date : 2020-08-14-18:37   *
****************************/
/*
莫隊求區間表示式的值
https://loj.ac/problem/2254 
這一題區間的收縮與擴張與之前做的題目不太一樣,注意區分
主要是把表示式化簡一下成為可以維護的東西 
*/
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<string>
using namespace std;
typedef long long ll;
const int maxn=3e6+10;
const ll mod=1e9+7;
namespace Fast_IO{
    const int MAXL((1 << 18) + 1);int iof, iotp;
    char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
    char Getchar(){
        if (ioiS == ioiT){
            ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
        }else return (*ioiS++);
    }
    void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
    void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
    inline int read(){
        int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    inline long long read_ll(){
        long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    template <class Int>void Print(Int x, char ch = '\0'){
        if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
        while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
    }
    void Getstr(char *s, int &l){
        for(ioc=Getchar();ioc==' '||ioc=='\n'||ioc=='\t';)ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
    }
    void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
} // namespace Fast_IO 
using namespace Fast_IO;
struct Query{int l,r,id,op;}q[maxn];
int tmpans=0,block[maxn],cnt1[maxn],cnt2[maxn],a[maxn],ans[maxn];
inline bool cmp(Query a,Query b){return (block[a.l])^(block[b.l])?a.l<b.l:(((block[a.l])&1)?a.r<b.r:a.r>b.r);}
inline void Add1(int x){cnt1[a[x]]++;tmpans+=cnt2[a[x]];}
inline void Add2(int x){cnt2[a[x]]++;tmpans+=cnt1[a[x]];}
inline void Del1(int x){cnt1[a[x]]--;tmpans-=cnt2[a[x]];}
inline void Del2(int x){cnt2[a[x]]--;tmpans-=cnt1[a[x]];}
int Calc(int i){return tmpans*q[i].op;}
int main(){
	int n=read(),Q,l=0,r=0;
	int blocks=sqrt(n);for(int i=1;i<=n;i++)block[i]=(i-1)/blocks;
	for(int i=1;i<=n;i++)a[i]=read();Q=read();
	int cnt=0;
	for(int i=1;i<=Q;i++)
	{
		int l1=read(),r1=read(),l2=read(),r2=read();
		q[++cnt].l=r1;q[cnt].r=r2;q[cnt].op=1;q[cnt].id=i;
		q[++cnt].l=r1;q[cnt].r=(l2-1);q[cnt].op=-1;q[cnt].id=i;
		q[++cnt].l=r2;q[cnt].r=(l1-1);q[cnt].op=-1;q[cnt].id=i;
		q[++cnt].l=(l1-1);q[cnt].r=(l2-1);q[cnt].op=1;q[cnt].id=i;
	}
	sort(q+1,q+1+cnt,cmp);
	for(int i=1;i<=cnt;i++){
		while(l<q[i].l)Add1(++l);
		while(l>q[i].l)Del1(l--);
		while(r<q[i].r)Add2(++r);
		while(r>q[i].r)Del2(r--);
		ans[q[i].id]+=Calc(i);
	}
	for(int i=1;i<=Q;i++)printf("%d\n",ans[i]);
	return 0;
}

總結

聽ztc講課的時候說,出題一般不會出那種只有莫隊才能做的題,所以感覺莫隊的題一般都可能有多解,希望有空能用其他寫法也寫寫這幾題啦

感覺莫隊是比較模板的東西,區間排序,四個while迴圈維護區間變化,不同的題目修改一下Add和Del函式就好啦

笛卡爾樹

Largest Rectangle in a Histogram

一個可以用單調棧來做的題目啦,四捨五入約等於最大01子矩陣和,高度滿足笛卡爾樹的性質啦。

做法就是對這個高度陣列維護一個大根笛卡爾樹,這樣的話,一個結點的子孫都是比他高的,也就是說他的子孫都是它可以左右擴充套件的區域,因此答案就是遍歷每一個結點,每個結點答案就是該結點權值*子樹大小,取max即是答案。

#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long ll;

#define ls(p) ch[p][0]
#define rs(p) ch[p][1]

const int MAXN = 100005;
int a[MAXN];
ll sum[MAXN], suf[MAXN];

int val[MAXN];
int ch[MAXN][2], siz[MAXN], tot, root;

inline void Init() {
    root = 0, tot = 0;
}

inline int NewNode(int v) {
    int p = ++tot;
    ch[p][0] = ch[p][1] = 0;
    val[p] = v;
    siz[p] = 1;
    return p;
}

inline void PushUp(int p) {
    siz[p] = siz[ls(p)] + siz[rs(p)] + 1;
}

//O(n)建樹,返回新樹的根
int st[MAXN], stop;
inline int Build(int n) {
    stop = 0;
    for(int i = 1; i <= n; ++i) {
        //實際上笛卡爾樹中tmp就是i
        int tmp = NewNode(a[i]), last = 0;
        //大根
        while(stop && val[st[stop]] > val[tmp]) {
            last = st[stop];
            PushUp(last);
            st[stop--] = 0;
        }
        if(stop)
            rs(st[stop]) = tmp;
        ls(tmp) = last;
        st[++stop] = tmp;
    }
    while(stop)
        PushUp(st[stop--]);
    return st[1];
}

//[L,R]的最值下標
int Query(int root,int L,int R){
    //笛卡爾樹中節點下標=陣列下標(從1開始)
    while(root<L||root>R)
        root=root<L?rs(root):ls(root);
    return root;
}

ll CalcSum(int p) {
    if(!p)
        return 0;
    //p節點管轄的範圍就是其左右子樹,這道題裡面要把根去掉
    return max(CalcSum(ls(p)),max(CalcSum(rs(p)),1ll * val[p] * siz[p]));
}

int n;
int top;

int main() {
#ifdef Yinku
    freopen("Yinku.in", "r", stdin);
#endif // Yinku
    while(~scanf("%d", &n)&&n) 
	{
        Init();
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        root = Build(n);
        printf("%lld\n", CalcSum(root));
    }
}

貼完程式碼才發現這是我還不會寫笛卡爾樹的時候拿別人的板子過的,不管啦,有空再自己寫一遍吧~

Scaffolding

一個難難的笛卡爾樹上dp,聽說是某年香港區域賽防AK題,真的是怕了

題意就是有n個地方要搭腳手架,第i個位置要搭a~i~個腳手架,從低往高這樣搭上去,你每次去搭腳手架呢可以帶上m個腳手架,然後從某一個位置的地面高度出發。

每一次,你的左邊、右邊或者上面如果已經搭好了腳手架,你就可以走過去啦,或者你可以選在在左、右、上這幾個沒搭好腳手架的地方搭上腳手架,你m個腳手架沒用完之前是不能下去的,並且你不能往下面搭腳手架!(超危險,問最少多少趟能搭完腳手架(一趟最多帶m個腳手架)

這題說實話題意是真的不怎麼懂,到現在也是,不怎麼懂!

怎麼dp呢,就先建一棵小根堆笛卡爾樹,然後去dfs這棵笛卡爾樹(感覺是笛卡爾樹的常規操作)

這題是搭腳手架,其實也可以想象成已經搭成了他要求的樣子,最多需要多少趟把全部腳手架都拆完!

dp[i]表示把 i 的子樹(包括 i )都拆到跟 i 的父親一樣的高度需要多少趟

轉移方程就是$$dp[i]=dp[ls[i]]+dp[rs[i]]+v(把自己這個高度拆成父親的高度*size/m向上取整)$$

\[v=(siz[i]*(a[i]-a[fa[i]])-res[ls[i]]-res[rs[i]]+m-1)/m \]

+m-1是為了向上取整

還需要另一個數組res,因為一趟可以拆m個,但是肯定不會每一趟都完美的用完,那麼就會可能有剩下的,這樣就可以先幫自己的父親拆掉一些

會剩下多少呢

\[res[i]=dp[i]*m-sum[i]-size[i]*a[fa[i]] \]

就是用自己用掉的減去自己該拆的,多出來的就是幫父親拆的。

最後dp[root]就是答案啦,因為根結點父親的高度就是0啦,目標就是全部都拆成0啦

/****************************
* Author : W.A.R            *
* Date : 2020-08-05-15:24   *
****************************/
/*
*/
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<string>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
const ll mod=1e9+7;

namespace Fast_IO{ //orz laofu
    const int MAXL((1 << 18) + 1);int iof, iotp;
    char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
    char Getchar(){
        if (ioiS == ioiT){
            ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
        }else return (*ioiS++);
    }
    void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
    void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
    inline int read(){
        int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    inline long long read_ll(){
        long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    template <class Int>void Print(Int x, char ch = '\0'){
        if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
        while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
    }
    void Getstr(char *s, int &l){
        for(ioc=Getchar();ioc==' '||ioc=='\n'||ioc=='\t';)ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
    }
    void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
} // namespace Fast_IO 
using namespace Fast_IO;

ll n,m,fa[maxn],ls[maxn],rs[maxn],st[maxn],a[maxn],siz[maxn],sum[maxn],res[maxn],dp[maxn],top;

void build(){
	for(ll i=1;i<=n;i++){
		bool flag=0;
		while(top&&a[st[top]]>a[i])top--,flag=1;
		if(top)fa[i]=st[top],rs[st[top]]=i;
		if(flag)ls[i]=st[top+1],fa[st[top+1]]=i;
		st[++top]=i;
	}
}

void dfs(int u,int fa)
{
	if(!u)return;
	dfs(ls[u],u);dfs(rs[u],u);
	dp[u]=dp[ls[u]]+dp[rs[u]];
	siz[u]=siz[ls[u]]+siz[rs[u]]+1;
	sum[u]=sum[ls[u]]+sum[rs[u]]+a[u];
	ll v=(a[u]-a[fa])*siz[u]-res[ls[u]]-res[rs[u]];
	if(v>0)dp[u]+=(v+m-1)/m;
	res[u]=dp[u]*m-sum[u]+siz[u]*a[fa];
}

int main(){
	n=read_ll(),m=read_ll();
	for(int i=1;i<=n;i++)a[i]=read_ll();
	build();
	dfs(st[1],0);
	printf("%lld\n",dp[st[1]]);
	return 0;
}

Max answer

這道題就是問一個區間權值定義為區間最小值*區間和。問一個序列的權值最大的區間權值是多少

所以有兩個維度的東西需要維護,一個是區間最值,一個是區間和,既然要最大,我當然是想兩個東西都儘量大

很容易想到,每個值都有可能作為區間最小值,那麼噹噹前位置作為區間最小值的時候,它可以往左往右最遠擴充套件到那裡仍然能夠滿足這個區間是以它為最小值的呢?

啊這就是笛卡爾數呀!!!!是不是是不是!所以搞一顆小根笛卡爾樹,就可以輕易維護以當前結點為最小值的最大區間是哪裡到哪裡。

這個維護好之後就是需要考慮,在這個合法區間裡怎麼讓區間和最大!

但是很快又發現了不對勁

因為每個位置的值可以是負數

所以易得,當一個位置是負數的時候,我應該在他的可行區間裡找一個區間和最小的區間就能使答案越大!(負數越小,乘積越大)

如果這個位置是正數,那麼需要區間和儘量大才能使答案儘可能大!

所以這個區間和怎麼維護呢?

就想到了用字首和+線段樹來維護

就是如果要在合法區間裡找一個區間和最大的話,就在$$[l,i-1]$$裡找一個最小字首和,在$$[i,r]$$區間找一個字首和最大的,那以這兩個點為左右端點的區間一定是這個合法區間裡區間和最大的,找區間和最小也同理,反一反就好。

但是需要注意的就是注意處理一下邊界情況。

最後遍歷每一個位置作為最小值,最小值乘以區間和,取max即可

/****************************
* Author : W.A.R            *
* Date : 2020-08-15-20:40   *
****************************/
/*
建一個小根堆笛卡爾樹 
不能用size會跟某個衝突ce 
https://cn.vjudge.net/problem/Kattis-scaffolding
*/
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<string>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
const ll mod=1e9+7;
const ll LINF = 6e18;


namespace Fast_IO{ //orz laofu
    const int MAXL((1 << 18) + 1);int iof, iotp;
    char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
    char Getchar(){
        if (ioiS == ioiT){
            ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
        }else return (*ioiS++);
    }
    void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
    void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
    inline int read(){
        int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    inline long long read_ll(){
        long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    template <class Int>void Print(Int x, char ch = '\0'){
        if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
        while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
    }
    void Getstr(char *s, int &l){
        for(ioc=Getchar();ioc==' '||ioc=='\n'||ioc=='\t';)ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
    }
    void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
} // namespace Fast_IO 
using namespace Fast_IO;
int n,top,fa[maxn],ls[maxn],rs[maxn],a[maxn],siz[maxn],st[maxn];;
ll mx[maxn*4],mi[maxn*4],b[maxn];
void build(){//笛卡爾樹建樹模板 
	for(ll i=1;i<=n;i++){
		while(top&&a[st[top]]>a[i])ls[i]=st[top--];
		fa[i]=st[top];
		fa[ls[i]]=i;
		if(fa[i])rs[fa[i]]=i;
		st[++top]=i;
	}
}

void build(int root,int l,int r)
{
	if(l==r){mx[root]=mi[root]=b[l];return;}
	int mid=l+r>>1;
	build(root<<1,l,mid);build(root<<1|1,mid+1,r);
	mi[root]=min(mi[root<<1],mi[root<<1|1]);
	mx[root]=max(mx[root<<1],mx[root<<1|1]);
}
ll q_mx(int root,int l,int r,int ql,int qr)
{
	if(l>=ql&&r<=qr)return mx[root];
	int mid=l+r>>1;
	ll MaxL = -LINF,MaxR = -LINF;
	if(ql <= mid) MaxL = max(MaxL,q_mx(root << 1,l,mid,ql,qr)); 
	if(qr > mid) MaxR = max(MaxR,q_mx(root << 1 | 1,mid+1,r,ql,qr));
	return max(MaxL,MaxR);
}

ll q_mi(int root,int l,int r,int ql,int qr)
{
	if(l>=ql&&r<=qr)return mi[root];
	int mid=l+r>>1;
	ll MinL = LINF,MinR = LINF;
	if(ql <= mid) MinL = min(MinL,q_mi(root << 1,l,mid,ql,qr)); 
	if(qr > mid) MinR = min(MinR,q_mi(root << 1 | 1,mid+1,r,ql,qr));
	return min(MinL,MinR);
}
ll ans=-6e18;
void dfs(int l,int r,int root)
{
	if(l==r)
	{
		ans=max(ans,1ll*a[root]*a[root]);
		return;
	}
	if(a[root]<0)
	{
		ll minn=q_mi(1,1,n,root,r),maxx;
		if(root==1)maxx=0;
		else if(l==1)maxx=max(0ll,q_mx(1,1,n,l,root-1));
		else maxx=q_mx(1,1,n,l-1,root-1);
		ans=max(ans,(minn-maxx)*a[root]);
	}
	else
	{
		ll maxx=q_mx(1,1,n,root,r),minn;
		if(root==1)minn=0;
		else if(l==1)minn=min(0ll,q_mx(1,1,n,l,root-1));
		else minn=q_mx(1,1,n,l-1,root-1);
		ans=max(ans,(maxx-minn)*a[root]);
	}
	if(ls[root])dfs(l,root-1,ls[root]);
	if(rs[root])dfs(root+1,r,rs[root]);
}

int main(){
	n=read();
	for(int i=1;i<=n;i++)a[i]=read(),b[i]=a[i]+b[i-1];
	build();build(1,1,n);
	dfs(1,n,st[1]);
	printf("%lld\n",ans);
	return 0;
}

總結

笛卡爾樹感覺是一種比較好用的陣列樹形資料結構,當有些東西需要維護的東西非常高度符合笛卡爾樹的時候,笛卡爾樹就可以很方便地幫你維護出來,就很nice

然後一般笛卡爾樹的題就配合著他的dfs遍歷這棵笛卡爾樹使用

亂七八糟講

第一次AK專題啦,但是感覺以後會AK更多的!衝過去就完事了!雖然好幾題都是看題解寫的,就當是見世面了,衝他的!