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的語言之後會快一倍

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章