分块+莫队+笛卡尔树专题

这个夏天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{2*m}\)的点不会超过\(\sqrt{2*m}\)

因此可以把点根据度数分成大点和小点,大点用数据结构(我用的是树状数组)维护答案,小点暴力(分块正常思路)。

大点就定义为度数\(>\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(l1,r1,x)\cdot\) $get(l2,r2,x) $

\(=( get(1\) \(,r1,x)-\) \(get(1\) \(,l1-1,x) )\cdot\) \(( get(1\) \(,r2,x)-\) \(get(1\) \(,l2-1,x) )\)

\(=(get(1,r1,x)\cdot get(1,r2,x))-(get(1,r1,x)\cdot get(1,l2-1,x))-(get(1,l1-1,x)\cdot(get(1,r2,x))+(get(1,l1-1)\cdot get(1,l2-1,x))\)

这样的话就可以把莫队区间维护的答案转化成维护以上四个式子的答案

依然用cnt1[x]表示[1,左端点]区间内x出现的次数,如果把左端点往左挪一下,则a[i]这个位置上的数就离开了[1,左端点]这个区间,所以要cnt1[x]--。

以第一部分的式子举例\(get(1,r1\) \(,x)\cdot get(1,r2,x)\),如果r1 往左挪一位造成cnt[x]--,那么对这一整个式子来说就是减少了一个\(get(1,r2,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个位置要搭ai个脚手架,从低往高这样搭上去,你每次去搭脚手架呢可以带上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_mi(1,1,n,l,root-1));
		else minn=q_mi(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更多的!冲过去就完事了!虽然好几题都是看题解写的,就当是见世面了,冲他的!

posted @ 2020-08-17 19:56  AnranWu  阅读(164)  评论(0编辑  收藏  举报