20200521模拟赛A. island(笛卡尔树||分治+分类讨论计数)

 

 

题解:

毒瘤分类讨论题

我们先把所有格子纵向互相走的总贡献

2*\sum_{i=1}^nlen_i*\sum_{j=1}^{i-1}len_j

直接记录一下前缀和就O(n)了

设F1(x)表示

\sum_{i=1}^x{i}

设F2(x)表示

\sum_{i=1}^x{i^2}

然后再把跨过0号点的路径的贡献算出来

2*((\sum_{i=1}^nF1(-L[i]-1))*(\sum_{i=1}^n-L[i])+(\sum_{i=1}^nF1(R[i]))*(\sum_{i=1}^nR[i]))

(这里的L[i]是负的)

然后我们需要做的就是计算左边走到左边,右边走到右边的贡献了

这里我们采取分治

为了方便我们把图旋转90°

考虑一个区间 [ l , r ] 的最小值A[x]

我们对于这个最小值,它需要统计的路径有三种类型

1、下边到下边的贡献(绿色线)

2、上到下以及下到上的贡献(蓝色线)

3、左上到右上+右上到左上的贡献(紫色线)

我们可以分别列出式子

1、下到下

由于我们已经算过了横向的贡献,所有我们只需要考虑纵向贡献

(F2(A[x])-F1(A[x]))*(r-l+1)^2

一个1*A[x]的小矩形块中所有路径的贡献 * 选择两个小矩形的方案数

 

2、下到上+上到下

下到上:

我们先计算下方的起点(绿点)走到A[x]高度的贡献和

(r-l+1)*F1(A[x]-1)(小矩形的个数 * 每一个小矩形的贡献)

然后下方每一个点都要走到上方的每一个点(蓝点)

设cnt(l,r,x)表示,区间[l,r]中高于x的格子有多少个,这个可以预处理前缀和来快速计算(因为x为l,r中的最小值)

cnt(l,r,x)=sum1[r]-sum1[l-1]-A[x]*(r-l+1)

所以每一个起点都要到达cnt(l,r,x)个终点,所以每一个起点走到A[x]的贡献和为

(r-l+1)*F1(A[x]-1)*cnt(l,r,A[x])

上到下:

同样的道理,我们计算上方终点倒着走走到A[x]的贡献和

我们把

\sum_{i=l}^rF1(A[i]-x),计作F(l,r,x)

我们把式子展开一下

\sum_{i=l}^r\frac{(A[i]-x)*(A[i]-x+1)}{2}

\frac{1}{2}\sum_{i=l}^r(A[i]-x)^2+(A[i]-x)

\frac{1}{2}\sum_{i=l}^rA[i]^2-2xA[i]+x^2+A[i]-x

\frac{1}{2}\sum_{i=l}^rA[i]^2+(1-2x)A[i]+x(x-1)

发现我们只需要记录一下A[i]^2与A[i]的前缀和sum2[i],sum1[i]即可

\frac{1}{2}(sum2[r]-sum2[l-1]+(1-2x)(sum1[r]-sum1[l-1])+(r-l+1)x(x-1))

有由于我们每一个上方的点都倒着走到(r-l+1)*A[x]个下方的点

所以这一部分的贡献就为

(r-l+1)*A[x]*F(l,r,A[x])

 

总贡献就是把这两个部分的贡献加起来乘个2

 

3、左上到右上+右上到左上

这一部分就是(左上部分到A[x]的路径长度总贡献*右上部分的总点数

再加上右上部分到A[x]的路径长度总贡献*左上部分的总点数)* 2

表达出来就是

F(l,x-1,A[x])*cnt(x+1,r,A[x])+F(x+1,r,A[x])*cnt(l,x-1,A[x]) * 2

 

这样就算完了

分治之后有一个小细节

在计算1、2部分的贡献的时候由于与A[x]以下部分的点有关,而A[x]以下的点有一部分在上一次分治已经算过了

所以还要记录一下上一次分治的A[x']

代码:(区间最小值的位置查询应该是可以用笛卡尔树的,但是作者并不会笛卡尔树,于是就写了ST表)

#include<cstdio>
#include<cstring>
#include<algorithm>
//#include<ctime>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
#define N 1000005
#define LOG 20
const int mod=998244353;
const int inv2=499122177;
const int inv6=166374059;
int n,L[N],R[N],A[N],ans;
int F1(int n)
{
	return 1ll*n*(n+1)/2%mod;
}
int F2(int n)
{
	return 1ll*n*(n+1)%mod*(2*n+1)%mod*inv6%mod;
}
int sum1[N],sum2[N];
int st[LOG][N],lg[N];
int getmin(int l,int r)
{
	int k=lg[r-l+1];
	return A[st[k][l]]<A[st[k][r-(1<<k)+1]]?st[k][l]:st[k][r-(1<<k)+1];
}
int F(int l,int r,int x)
{
	if(l>r) return 0;
	return 1ll*(sum2[r]-sum2[l-1]+1ll*(1-2*x)*(sum1[r]-sum1[l-1])+1ll*x*(x-1)%mod*(r-l+1))%mod*inv2%mod;
}
void solve(int l,int r,int pre)
{
	if(l>r) return;
	int len=r-l+1,x=getmin(l,r);
	int cntl=(sum1[x]-sum1[l-1]-1ll*A[x]*(x-l+1))%mod;
	int cntr=(sum1[r]-sum1[x]-1ll*A[x]*(r-x))%mod;
	ans=(ans+1ll*(F2(A[x]-pre)-F1(A[x]-pre))*len%mod*len)%mod;
	ans=(ans+2*(1ll*F1(A[x]-pre-1)*len%mod*(cntl+cntr)+1ll*(A[x]-pre)*len%mod*F(l,r,A[x])))%mod;
	ans=(ans+2*(1ll*F(l,x-1,A[x])*cntr+1ll*F(x+1,r,A[x])*cntl))%mod;
	solve(l,x-1,A[x]);solve(x+1,r,A[x]);
}
void work(int B[])
{
	int i,j,t;
	memcpy(A,B,sizeof(A));
	for(i=1;i<=n;i++){
		sum1[i]=(sum1[i-1]+A[i])%mod;
		sum2[i]=(sum2[i-1]+1ll*A[i]*A[i])%mod;
	}
	lg[0]=-1;
	for(i=1;i<=n;i++)lg[i]=lg[i>>1]+1,st[0][i]=i;
	//double c1=clock();
	for(j=1;j<LOG;j++)
		for(i=1,t=(1<<(j-1));i+(t<<1)-1<=n;i++)
			st[j][i]=A[st[j-1][i]]<A[st[j-1][i+t]]?st[j-1][i]:st[j-1][i+t];
	solve(1,n,0);
	//printf("%.3fs\n",(clock()-c1)/1000);
}
char ch[3];
int main()
{
	//freopen("1.in","r",stdin);
	int i,ss=0,s=0;
	n=gi();scanf("%s",ch);
	for(i=1;i<=n;i++)L[i]=gi(),R[i]=gi();
	for(i=1;i<=n;i++){
		ans=(ans+2ll*ss*(R[i]-L[i]))%mod;
		s=(1ll*s+R[i]-L[i])%mod;
		ss=(ss+s)%mod;
	}
	int suml=0,sumr=0,cntl=0,cntr=0;
	for(i=1;i<=n;i++){
		suml=(suml+F1(-L[i]-1))%mod;
		sumr=(sumr+F1(R[i]))%mod;
		cntl=(cntl-L[i])%mod;
		cntr=(cntr+R[i])%mod;
	}
	ans=(ans+2ll*(1ll*suml*cntr+1ll*sumr*cntl)%mod)%mod;
	work(R);
	for(i=1;i<=n;i++)L[i]=-L[i];
	work(L);
	printf("%d\n",(ans+mod)%mod);
}

 

常数极大。。。其实复杂度也不对,O(nlogn)

然而同一份代码换了C++ -O2的语言之后会快一倍

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值