題解:
毒瘤分類討論題
我們先把所有格子縱向互相走的總貢獻
直接記錄一下前綴和就O(n)了
設F1(x)表示
設F2(x)表示
然後再把跨過0號點的路徑的貢獻算出來
(這裏的L[i]是負的)
然後我們需要做的就是計算左邊走到左邊,右邊走到右邊的貢獻了
這裏我們採取分治
爲了方便我們把圖旋轉90°
考慮一個區間 [ l , r ] 的最小值A[x]
我們對於這個最小值,它需要統計的路徑有三種類型
1、下邊到下邊的貢獻(綠色線)
2、上到下以及下到上的貢獻(藍色線)
3、左上到右上+右上到左上的貢獻(紫色線)
我們可以分別列出式子
1、下到下
由於我們已經算過了橫向的貢獻,所有我們只需要考慮縱向貢獻
一個1*A[x]的小矩形塊中所有路徑的貢獻 * 選擇兩個小矩形的方案數
2、下到上+上到下
下到上:
我們先計算下方的起點(綠點)走到A[x]高度的貢獻和
(小矩形的個數 * 每一個小矩形的貢獻)
然後下方每一個點都要走到上方的每一個點(藍點)
設cnt(l,r,x)表示,區間[l,r]中高於x的格子有多少個,這個可以預處理前綴和來快速計算(因爲x爲l,r中的最小值)
所以每一個起點都要到達cnt(l,r,x)個終點,所以每一個起點走到A[x]的貢獻和爲
上到下:
同樣的道理,我們計算上方終點倒着走走到A[x]的貢獻和
我們把
,計作F(l,r,x)
我們把式子展開一下
發現我們只需要記錄一下A[i]^2與A[i]的前綴和sum2[i],sum1[i]即可
有由於我們每一個上方的點都倒着走到(r-l+1)*A[x]個下方的點
所以這一部分的貢獻就爲
總貢獻就是把這兩個部分的貢獻加起來乘個2
3、左上到右上+右上到左上
這一部分就是(左上部分到A[x]的路徑長度總貢獻*右上部分的總點數
再加上右上部分到A[x]的路徑長度總貢獻*左上部分的總點數)* 2
表達出來就是
* 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的語言之後會快一倍