CF549F Yura and Developers

思路

对于每个值 \(a_i\) 我们求出一个管理区间 \(\left(l_i,r_i\right)\),表示它是 \(\left(l_i,r_i\right)\) 里最大的数。

\(l_i \le L \le i \le r \le r_i\),则我们可以抛弃 \(a_i\),我们记 \(sum_i\) 为前缀和,问题转化为快速求一段区间使得 \(\left(sum_r-sum_{l-1}-a_i\right)\bmod k = 0\),这个式子可以移项得到下面两个式子:

  1. \(sum_r\bmod k = \left(sum_{l-1}+a_i\right)\bmod k\)
  2. \(sum_{l-1}\bmod k = \left(sum_{r}-a_i\right)\bmod k\)

我们考虑笛卡尔树分治,先用桶存一下 \(sum_i\),得到每个值出现了几次,每次找到一个最大值,然后选出小的一边计算贡献,左右分别对应上面的一式二式,算完后递归两边即可,如果对实现有疑惑的可以看一下代码。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
#define getchar() (p1 == p2 && (p2 = (p1 = buf1) + fread(buf1, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf1[1 << 23], *p1 = buf1, *p2 = buf1, ubuf[1 << 23], *u = ubuf;
namespace IO
{
	template<typename T>
	void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
	template<typename T,typename... Args>
	void read(T &_x,Args&...others){Read(_x);Read(others...);}
	const int BUF=20000000;char buf[BUF],to,stk[32];int plen;
	#define pc(x) buf[plen++]=x
	#define flush(); fwrite(buf,1,plen,stdout),plen=0;
	template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++to]=48+x%10;while(to) pc(stk[to--]);}
}
using namespace IO;
const int N = 1e6+10;
int n,k,o,a[N],sum[N],ans,t[N],lg[N],st[N][20]; 
inline int Max(int l,int r)
{
	if(a[l] > a[r]) return l;
	return r;
}
void solve(int l,int r)
{
	if(l == r)
	{
		t[sum[l]]--,ans++;//我们先抛开len>=2的要求,最后减n即可,注意此时也需要删去贡献 
		return;
	}
	if(l>r) return;
	o = lg[r-l+1];
	int mid = Max(st[l][o],st[r-(1<<o)+1][o]);
	if(mid-l <= r-mid)
	{
		for(int i = l;i < mid;i++) t[sum[i]]--;
		for(int i = l;i <= mid;i++) ans += t[(sum[i-1]+a[mid])%k];
		t[sum[mid]]--; 
		solve(mid+1,r);//递归完右边,右边的贡献都被删完了,把左边的加入递归即可 
		for(int i = l;i < mid;i++) t[sum[i]]++;
		solve(l,mid-1);
	} 
	else
	{
		for(int i = r;i > mid;i--) t[sum[i]]--;
		t[sum[l-1]]++,t[sum[mid]]--;//由于要计算sum_{l-1}的,会有所改变 
		for(int i = r;i >= mid;i--) ans += t[(sum[i]-a[mid]%k+k)%k];
		t[sum[l-1]]--; //删去这里的贡献,不过sum[mid]的贡献本来就要删,就不用恢复了 
		solve(l,mid-1);
		for(int i = r;i > mid;i--) t[sum[i]]++;
		solve(mid+1,r);
	}
}
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	read(n),read(k);
	for(int i = 1;i <= n;i++) read(a[i]),sum[i] = sum[i-1]+a[i],sum[i]%=k,st[i][0] = i,t[sum[i]]++;
	for(int i = 2;i <= n;i++) lg[i] = lg[i/2]+1;
	for(int i = 1;i <= lg[n];i++)//st表找区间最大值下标 
		for(int j = 1;j+(1<<i)-1 <= n;j++)
			st[j][i] = Max(st[j][i-1],st[j+(1<<(i-1))][i-1]);
	solve(1,n); 
	print(ans-n); flush();
	return 0;
}
/*
对于每个值a_i我们求出一个管理区间(l_i,r_i),表示它是(l_i,r_i)里最大的数
若 l_i <= L <= i <= r <= r_i && r != l,则我们可以抛弃a_i,问题转化为快速求一段区间%k=0
sum_i为前缀和,sum_r%k = (sum_{l-1}+a_i)%k 则%k=0
右边就是(sum_r-a_i)%k = sum_{l-1}%k 
优先考虑用桶存sum_r%k的值,然后选小的一边删去贡献,然后直接计算,然后加入贡献,递归完事 
*/ 
posted @ 2025-02-03 16:33  kkxacj  阅读(10)  评论(0)    收藏  举报