「CF549F Yura and Developers」

题目大意

给出一个序列,问又多少子串的和(\(sum\))减去最大值(\(max\))除 \(k\) 后的余数等于 \(0\)(\((sum-max)\bmod k=0\)).

分析

可以先用单调栈处理出每个位置为最大值的区间,那么可以计算出前缀和之后在这个最大值的两边各枚举一下这个子串的起点和终点,利用前缀和可以做到 \(\mathcal{O}(1)\) 判断这个子串是否合法.当然,如果只枚举一边,另外一边可以用一些奇怪的数据结构等东西维护一下前缀和对 \(k\) 取模后的结果也是可以的.但一个点到这个点为最大值的区间两边的距离最大是可以到 \(n\) 级别的,直接枚举的复杂度就是 \(\mathcal{O}(n^2)\) 了,显然不行.但如果是枚举较短的一边呢?发现居然是可以过的!!!

现在来非常不科学得证明一下(因为对于上面这个问题是可以转化为每个数都不同的,所以下面考虑的序列是一个排列):

显然可以写出如下 \(\operatorname{dp}\)(\(f_i\) 表示长度为 \(i\) 的序列通过以上方法枚举的最多枚举次数)

f[0]=0;
f[1]=1;
f[2]=1;
REP(i,3,n)
{
    //一个长度为 i 的排列有一个唯一的最大值,并且对于除这个最大值对应的区间以外,其他数的区间是不可能经过这个位置的,所以可以枚举这个最大值出现的位置,那么就变成了两个长度较短的排列,可以直接使用前面计算出来的 dp 值
	REP(j,0,i-1)
	{
		f[i]=max(f[i],f[j]+f[i-1-j]+min(j,i-1-j));
	}
}

\(n=10000\) 时,通过以上代码计算出来的 \(f_n=59613\),在 \(n=100000\) 时,通过以上代码计算出来的 \(f_n=765030\),在 \(n=300000\) 时,通过以上代码计算出来的 \(f_n=2528456\),据此,我们可以大胆猜测这个 \(f_i\)\(n\log_2n\) 级别,且在 \(n\) 较大时(此题数据范围)比 \(n\log_2n\) 小很多.

当然,通过这个代码也不难看出 \(f_i\) 的转移显然是从中间断开时最大(类似快速排序等分治算法)且每一层的距离的和是 \(n\) 级别,妹子区间的长度都会初二,所以层数是 \(log_2n\) 级别(因为中间为最大值的数不可以被计算到距离里,所以最终能计算结果与小于 \(n\log_2n\)).

代码

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
namespace IO
//快读模板
using namespace IO;
using namespace std;
const int MAXN=3e5+20;
int n,k;
struct Number
{
	int num,l,r,sum;
}arr[MAXN];
int st[MAXN],top=0;
int root_[MAXN];
int *root=root_+10;
namespace SGT//因为闲得无聊就直接用可持久化线段树维护区间前缀和模 k 后的结果个数了(可以用 vector+二分来维护)
{
	struct SegmentTree
	{
		int sum,lson,rson;
	}sgt[MAXN<<6];
	int sgt_cnt=0;
	#define LSON sgt[now].lson
	#define RSON sgt[now].rson
	#define MIDDLE ((left+right)>>1)
	#define LEFT LSON,left,MIDDLE
	#define RIGHT RSON,MIDDLE+1,right
	#define NEW_LSON sgt[new_tree].lson
	#define NEW_RSON sgt[new_tree].rson
	void Updata(int place,int &new_tree,int now,int left=0,int right=k)
	{
		if(place<left||right<place)
		{
			new_tree=now;
			return;
		}
		new_tree=++sgt_cnt;
		if(left==right)
		{
			sgt[new_tree].sum=sgt[now].sum+1;
			return;
		}
		Updata(place,NEW_LSON,LEFT);
		Updata(place,NEW_RSON,RIGHT);
	}
	int Query(int place,int cut,int now,int left=0,int right=k)
	{
		if(place<left||right<place)
		{
			return 0;
		}
		if(left==right)
		{
			return sgt[now].sum-sgt[cut].sum;
		}
		return Query(place,sgt[cut].lson,LEFT)+Query(place,sgt[cut].rson,RIGHT);
	}
}
int main()
{
	Read(n,k);
	SGT::Updata(0,root[0],root[-1]);
	int sum=0;
	REP(i,1,n)//单调栈处理出每个数为最大值的区间
	{
		Read(arr[i].num);
		arr[i].sum=(arr[i-1].sum+arr[i].num)%k;
		SGT::Updata(arr[i].sum,root[i],root[i-1]);
		while(top&&arr[st[top]].num<arr[i].num)
		{
			top--;
		}
		arr[i].l=st[top]+1;
		st[++top]=i;
	}
	top=0;
	DOW(i,n,1)
	{
		while(top&&arr[st[top]].num<=arr[i].num)
		{
			top--;
		}
		arr[i].r=st[top]?st[top]-1:n;
		st[++top]=i;
	}
	long long answer=-n;
	REP(i,1,n)
	{
		if(i-arr[i].l<arr[i].r-i)//枚举较短的一边
		{
			REP(j,arr[i].l,i)
			{
				answer+=SGT::Query((arr[j-1].sum+arr[i].num)%k/*需要用到的前缀和的数值可以通过简单计算得出,这里不展开*/,root[i-1],root[arr[i].r]);
			}
		}
		else//下面同理
		{
			REP(j,i,arr[i].r)
			{
				answer+=SGT::Query((arr[j].sum-arr[i].num%k+k)%k,root[arr[i].l-2],root[i-1]);
			}
		}
	}
	Writeln(answer);
	return 0;
}
posted @ 2020-10-06 20:52  SxyLimit  阅读(98)  评论(0编辑  收藏  举报