2024 Noip 做題記錄(五)

DaiRuiChen007發表於2024-10-20

\(\text{By DaiRuiChen007}\)



Round #17 - 2024.10.8

A. [ARC135D] Square

Problem Link

題目大意

給定 \(n\times m\) 矩陣,每次操作可以把 \(2\times 2\) 子矩形中的每個元素 \(\pm 1\),若干次操作後最小化所有元素的絕對值和,給出構造。

資料範圍:\(n,m\le 500\)

思路分析

給矩陣黑白間隔染色,並把白格取反,這樣不改變答案。

此時所有的操作都不改變每行每列的元素和,因此答案的下界是每行元素和的絕對值之和以及每列元素和的絕對值之和。

不難發現這個下界是可以取到的,下給出構造性證明:

  • 不妨設每行元素的絕對值之和大於每列元素的絕對值之和,那麼一組解取到下界當且僅當每個元素的符號和其行和符號相等。
  • 如果有一行一列同為正,那麼在交點處填行和與列和的較小值(同為負同理)。
  • 由於行和列和總和相等,因此該過程停止時所有列和一定為 \(0\)
  • 此時找到一正一負的兩行,任取一列填絕對值較小值。

每次填數至少使得一行或一列和為 \(0\),操作次數 \(\mathcal O(n+m)\)

時間複雜度 \(\mathcal O((n+m)^2)\)

思路分析

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=505;
int n,m;
ll a[MAXN][MAXN],R[MAXN],C[MAXN],ans=0;
void add(int i,int j,ll x) { a[i][j]+=x,R[i]-=x,C[j]-=x; }
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1,w;i<=n;++i) for(int j=1;j<=m;++j) {
		scanf("%d",&w),w*=(i+j)&1?-1:1,R[i]+=w,C[j]+=w;
	}
	while(true) {
		int ip=0,in=0,jp=0,jn=0;
		for(int i=1;i<=n;++i) ip=(R[i]>0?i:ip),in=(R[i]<0?i:in);
		for(int j=1;j<=m;++j) jp=(C[j]>0?j:jp),jn=(C[j]<0?j:jn);
		if(ip&&jp) add(ip,jp,min(R[ip],C[jp]));
		else if(in&&jn) add(in,jn,max(R[in],C[jn]));
		else if(ip&&in) {
			ll z=min(R[ip],-R[in]);
			add(ip,1,z),add(in,1,-z);
		} else if(jp&&jn)  {
			ll z=min(C[jp],-C[jn]);
			add(1,jp,z),add(1,jn,-z);
		} else break;
	}
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) ans+=abs(a[i][j]);
	printf("%lld\n",ans);
	for(int i=1;i<=n;++i,puts("")) for(int j=1;j<=m;++j) printf("%lld ",a[i][j]*((i+j)&1?-1:1));
	return 0;
}



B. [ARC173E] Adjacent

Problem Link

題目大意

給定 \(a_1\sim a_n\),一次操作可以把所有 \(a\) 重排,然後把 \(a\) 替換成 \(a'_i=a_i\oplus a_{i+1}(1\le i<n)\),最大化 \(n-1\) 次操作後序列中剩下的元素。

資料範圍:\(n\le100,a_i<2^{60}\)

思路分析

考慮最終的元素是可能 \(a\) 的哪些子集 \(S\) 的異或和。

由於我們可以進行任意重排,因此一個子集是否合法只和 \(|S|\) 有關。

由於異或不改變 \(|S|\) 奇偶性,且我們至少進行一次操作,因此 \(|S|\) 一定是偶數。

考慮如何構造,設 \(S=a_1\sim a_{2k}\)

  • 如果 \(k\) 是偶數,那麼直接用原排列,我們就要在 \(n-1\) 個元素中匯出 \(a'_1,a'_3\sim a'_{2k-1}\)\(k\) 個元素的異或和。
  • 如果 \(k\) 是奇數,那麼構造 \(a_1\sim a_{2k-1},a_n,a_{2k},\dots\),我們要在 \(n-1\) 個元素中匯出 \(a'_1,a'_3\sim a'_{2k-1},a'_{2k}\)\(k+1\) 個元素的異或和。

容易發現無法構造當且僅當 \(k\) 是奇數時 \(2k=n\),且 \(n>2\)

因此 \(4\mid n\)\(n=2\) 時,\(S\) 可以是所有 \(a_1\sim a_n\) 大小為偶數的子集,把所有 \(a_1\oplus a_i\) 插入線性基後查詢即可。

否則 \(S\) 是所有 \(a_1\sim a_n\) 大小為偶數且不為全集的子集,列舉一個 \(a_i\) 刪除,然後做上一部分的過程。

可以預處理 \(a_1\oplus a_i\) 的前字尾線性基,這樣只要做 \(\mathcal O(n)\) 次線性基合併。

時間複雜度 \(\mathcal O(n\log^2V)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=105;
struct LB {
	ll a[60];
	LB() { memset(a,0,sizeof(a)); }
	void ins(ll x) {
		for(int k=59;~k;--k) if(x>>k&1) {
			if(!a[k]) return a[k]=x,void();
			else x^=a[k];
		}
	}
	ll qry() {
		ll x=0;
		for(int k=59;~k;--k) x=max(x,a[k]^x);
		return x;
	}
	friend LB operator +(LB x,LB y) {
		for(int k=59;~k;--k) if(y.a[k]) x.ins(y.a[k]);
		return x;
	}
}	B,e[MAXN];
ll a[MAXN];
signed main() {
	int n; scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
	if(n==2||n%4==0) {
		for(int i=2;i<=n;++i) B.ins(a[1]^a[i]);
		return printf("%lld\n",B.qry()),0;
	}
	for(int i=3;i<=n;++i) B.ins(a[i]^a[2]);
	ll ans=B.qry();
	B=LB();
	for(int i=n;i>=2;--i) e[i]=e[i+1],e[i].ins(a[1]^a[i]);
	for(int i=2;i<=n;++i) {
		ans=max(ans,(B+e[i+1]).qry()),B.ins(a[1]^a[i]);
	}
	printf("%lld\n",ans);
	return 0;
}



C. [ARC118E] Obstacle

Problem Link

題目大意

定義 \(n\) 階排列 \(a\) 的權值為從 \((0,0)\)\((n+1,n+1)\) 且不經過任何 \((i,a_i)\) 的路徑數。

給定不完整排列 \(a\),求每種補全方案中 \(a\) 的權值和。

資料範圍:\(n\le 200\)

思路分析

考慮如何計算單個排列的權值,可以考慮容斥,欽定 \(k\)\((i,a_i)\) 必須經過,求對應格路數乘 \((-1)^k\) 求和。

那麼對於一般的情況,我們可以 dp 格路,對每條格路在其中選出 \(k\) 個障礙並以 \((-1)^k\) 貢獻到答案、

\(f_{i,j,0/1,0/1}\) 表示走到 \((i,j)\) 的格路,第 \(i\) 行或第 \(j\) 列是否有欽定過障礙的貢獻總和。

然後轉移時考慮能否在 \((i+1,j)\)\((i,j+1)\) 上放障礙並容斥即可。

但最終的方案數還要記錄未確定位置的障礙個數,因此再加一維 \(k\) 表示確定了多少個原先未知的 \(a_i\)

時間複雜度 \(\mathcal O(n^3)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=205,MOD=998244353;
int n,a[MAXN],cnt,fac[MAXN],f[MAXN][MAXN][MAXN][2][2];
bool vis[MAXN];
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
inline void sub(int &x,const int &y) { x=(x>=y)?x-y:x+MOD-y; }
signed main() {
	scanf("%d",&n);
	for(int i=fac[0]=1;i<=n;++i) fac[i]=1ll*i*fac[i-1]%MOD;
	for(int i=1;i<=n;++i) {
		scanf("%d",&a[i]);
		if(~a[i]) vis[a[i]]=true;
		else ++cnt;
	}
	f[0][0][0][0][0]=1;
	for(int i=0;i<=n+1;++i) for(int j=0;j<=n+1;++j) for(int k=0;k<=cnt;++k) {
		for(int x:{0,1}) for(int y:{0,1}) {
			const int &w=f[i][j][k][x][y]; if(!w) continue;
			if(i<=n) {
				add(f[i+1][j][k][0][y],w);
				if(!y&&i<n&&1<=j&&j<=n&&(a[i+1]==j||(a[i+1]==-1&&!vis[j]))) {
					sub(f[i+1][j][k+(a[i+1]==-1)][1][1],w);
				}
			}
			if(j<=n) {
				add(f[i][j+1][k][x][0],w);
				if(!x&&j<n&&1<=i&&i<=n&&(a[i]==j+1||(a[i]==-1&&!vis[j+1]))) {
					sub(f[i][j+1][k+(a[i]==-1)][1][1],w);
				}
			}
		}
	}
	int ans=0;
	for(int k=0;k<=cnt;++k) ans=(ans+1ll*fac[cnt-k]*f[n+1][n+1][k][0][0])%MOD;
	printf("%d\n",ans);
	return 0;
}



D. [ARC135E] Multiple

Problem Link

題目大意

給定 \(n,x\),求出長度為 \(n\) 的嚴格遞增序列 \(a\) 滿足 \(i\mid a_i\)\(a_1=x\),最小化 \(\sum a_i\)

資料範圍:\(n,x\le 10^{18}\)

思路分析

很顯然我們只需要貪心填最小的 \(a_i\),因此有 \(a_i=i\times \left(\left\lfloor\dfrac{a_{i-1}}i\right\rfloor+1\right)\)

\(b_i=\dfrac{a_i}i\),那麼 \(b_i=\left\lfloor\dfrac{(i-1)b_{i-1}}i\right\rfloor+1\),則 \(b_{i+1}-b_{i}=1-\left\lceil\dfrac{b_{i}}{i+1}\right\rceil\le 0\),因此 \(b_i\) 嚴格遞減。

最壞情況下 \(a_i<x+\sum_{j=2}^i j<x+\dfrac{i(i+1)}2\),因此 \(b_i<\dfrac{x}i+\dfrac{i+1}2\)

考慮本質不同的 \(b_{i+1}-b_{i}\) 數量的上界,取 \(B=\sqrt[3] x\)

  • 那麼 \(i\le B\) 時只有 \(\mathcal O(B)\)\(b_{i+1}-b_{i}\)
  • \(i>B\)\(b_{i+1}-b_i=1-\left\lceil\dfrac{b_{i}}{i+1}\right\rceil>1-\left\lceil\dfrac 12+\dfrac x{i(i+1)}\right\rceil\),因此本質不同的 \(b_{i+1}-b_i\) 大約是 \(\mathcal O\left(\dfrac{x}{B^2}\right)=\mathcal O(B)\) 級別的。

因此本質不同的 \(b_i-b_{i-1}\) 只有 \(\mathcal O(\sqrt[3]x)\) 個,對於同一段,我們要求的就是形如 \(\sum_{x=l}^r x\times (kx+b)\) 的式子,拆開 \(x\) 的一次二次字首和即可 \(\mathcal O(1)\) 計算。

我們只要考慮對每個 \(l\) 求出極大的 \(r\) 使得 \(i\in [l,r]\) 中的 \(b_{i}-b_{i-1}\) 相等。

那麼 \(r\) 就是最大的 \(x=\left\lceil\dfrac{b_l+(r-l)(1-x)}{r+1}\right\rceil\)\(r\),其中 \(x=\left\lceil\dfrac{b_{i}}{i+1}\right\rceil\),用整除分塊類似的技巧可以 \(\mathcal O(1)\) 計算。

時間複雜度 \(\mathcal O(\sqrt[3]x)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=998244353,i2=(MOD+1)/2,i6=(MOD+1)/6;
ll s1(ll x) { return x*(x+1)%MOD*i2%MOD;}
ll s2(ll x) { return x*(x+1)%MOD*(2*x+1)%MOD*i6%MOD; }
ll F(ll l,ll r,ll s,ll d) {
	l%=MOD,r%=MOD,s%=MOD,d%=MOD;
	return (d*(s2(r)-s2(l-1))%MOD+(s-d*l)%MOD*(s1(r)-s1(l-1))%MOD)%MOD;
}
void solve() {
	ll n,s,ans=0;
	scanf("%lld%lld",&n,&s);
	for(ll l=1,r;l<=n;l=r+1) {
		ll d=(s+l)/(l+1);
		r=(d==1)?n:min(n,max(l,(s+(l-1)*(d-1)-1)/(2*d-2)));
		ans=(ans+F(l,r,s,1-d))%MOD;
		s+=(r-l+1)*(1-d);
	}
	printf("%lld\n",(ans+MOD)%MOD);
}
signed main() {
	int T; scanf("%d",&T);
	while(T--) solve();
	return 0;
}



E. [ARC133E] Median

Problem Link

題目大意

給定 \(n,m,k,x\),對所有值域 \([1,k]\) 的序列對 \(a_n,b_m\)\(f(x,a,b)\) 的和。

其中 \(f(x,a,b)\) 定義為對於 \(i\in[0,nm)\),依次將 \(x\) 變成 \(a_{i\bmod n+1},b_{i\bmod m+1}\) 的中位數後,最終的 \(x\)

資料範圍:\(n,m,k,x\le 2\times 10^5\)

思路分析

很顯然考慮轉 \(01\) 序列,對於所有 \(i\in [0,k]\),將 \(>i\) 的看成 \(1\),其餘看成 \(0\) 得到 \(x',a'_i,b'_i\)

對每個 \(i\),求 \(f(x',a',b')\) 只和即可得到答案。

那麼我們發現如果 \(a'_{i}=b'_i\),那麼 \(x'\) 經過後一定會變成 \(a'_i\),否則保持不變。

如果 \(x'\) 被某個 \(a'_i\) 覆蓋,那麼我們把所有原本的 \(a,b,i\) 在值域內翻轉,此時恰好反轉所有 \(a',b'\),那麼就能得到一個相反的 \(f\)

因此所有被覆蓋過的 \(x'\) 對答案的貢獻是對稱的。

我們只要對每個 \(i\) 算出沒被覆蓋的 \(a',b'\) 有多少個,剩餘的所有 \(a',b'\) 對答案的貢獻就是 \(\dfrac{k+1}2\)

一對 \(a',b'\) 沒覆蓋過 \(i\),就要求所有 \(a'_{i\bmod n+1}\ne b'_{i\bmod m+1}\),設 \(d=\gcd (n,m)\),那麼所有 \(i\equiv j\pmod d\)\(a'_i,b'_j\) 都不能相等。

那麼對於若干 \(\bmod d\) 同餘的 \(i,j\),所有 \(a'_i\) 都一定相等,\(b'_j\) 都一定相等,且 \(a',b'\) 不同,那麼總方案數就是 \((i^{n/d}(k-i)^{m/d}+i^{m/d}(k-i)^{n/d})^d\)

\(i\le x\) 時把這個貢獻也加進答案裡即可。

時間複雜度 \(\mathcal O(k\log P)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=998244353,i2=(MOD+1)/2;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll n,m,w,x;
signed main() {
	scanf("%lld%lld%lld%lld",&n,&m,&w,&x);
	ll g=__gcd(n,m);
	ll tot=ksm(w,n+m)*(w+1)%MOD,ans=0;
	for(int i=1;i<w;++i) {
		ll t=(ksm(i,n/g)*ksm(w-i,m/g)+ksm(i,m/g)*ksm(w-i,n/g))%MOD;
		t=ksm(t,g);
		if(i<x) ans=(ans+t)%MOD;
		tot=(tot+MOD-t)%MOD;
	}
	printf("%lld\n",(tot*i2+ans)%MOD);
	return 0;
}



*F. [AGC054D] Swap

Problem Link

題目大意

給定長度為 \(n\) 的字串 \(S\),包含 (,),x,o 四種字元,交換若干對相鄰元素後,把 o 換成 (),把 x 換成 )(,最小化交換次數使得得到的串是合法括號序列。

資料範圍:\(n\le 8000\)

思路分析

我們只要求出最終的 \(S_i\) 來自哪裡,得到一個排列,最小化該排列的逆序對數即可。

考慮 \(S\) 中不包含 x,o 的情況,那麼對於一個分界點 \((i,i+1)\),如果 \(S[1,i]\) 中右括號比左括號多 \(x\) 個,我們就要在 \(S[i+1,n]\) 中選至少 \(x\) 個右括號放到 \(S[1,i]\) 中,產生 \(x\) 的貢獻。

這個下界是可以取到的,按如下方式構造:從左到右依次加入字元,如果右括號多於左括號,就把當前的右括號和後面最近的一個左括號交換。

不難證明如下方式構造出來的排列逆序對數恰好取到下界。

回到原題,我們發現 o 可以放在任何位置,而 x 至少要被一對括號包住。

那麼為了使一對 x 合法,交換一對已匹配的括號 (,) 是完全沒有意義的。

並且我們只要把 x 放進括號中間,那麼透過交換 o,xx 放進去不優於把括號給交換出來。

因此 (,)o,x 內部都不會產生交換,預處理出 (,) 的最優排列,然後維護 dp,設 \(f_{i,j}\) 表示當前填了 \(i\)(,)\(j\)o,x 的最小代價,轉移前預處理一些逆序對數即可。

時間複雜度 \(\mathcal O(n^2)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=8005;
char s[MAXN];
int n,m,sz,a[MAXN],b[MAXN],w[MAXN],dp[MAXN][MAXN],fa[MAXN][MAXN],fb[MAXN][MAXN];
inline void chkmin(int &x,const int &y) { x=x<y?x:y; }
signed main() {
	scanf("%s",s+1),sz=strlen(s+1);
	for(int i=1;i<=sz;++i) {
		if(s[i]=='('||s[i]==')') a[++n]=i;
		else b[++m]=i;
	}
	for(int i=1;i<=n;++i) {
		w[i]=w[i-1]+(s[a[i]]=='('?1:-1);
		if(w[i]<0) {
			for(int j=i+1;;++j) if(s[a[j]]=='(') {
				rotate(a+i,a+j,a+j+1); break;
			}
			w[i]+=2;
		}
	}
	memset(dp,0x3f,sizeof(dp)),dp[0][0]=0;
	for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) dp[0][0]+=a[j]<a[i];
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
		fa[i][j]=fa[i][j-1]+(a[i]<b[j]);
		fb[i][j]=fb[i-1][j]+(b[j]<a[i]);
	}
	for(int i=0;i<=n;++i) for(int j=0;j<=m;++j) {
		if(i<=n) chkmin(dp[i+1][j],dp[i][j]+fa[i+1][j]);
		if(j<=m&&(w[i]||s[b[j+1]]=='o')) chkmin(dp[i][j+1],dp[i][j]+fb[i][j+1]);
	}
	printf("%d\n",dp[n][m]);
	return 0;
}



*G. [AGC055D] Fill

Problem Link

題目大意

定義一個長度為 \(3n\) 的字串 \(S\) 是好的,當且僅當其可以劃分為 \(n\)\(\texttt{ABC,BCA,CAB}\) 序列。

給定字串的一部分,求有多少種補全方式使得該串是好的。

資料範圍:\(n\le 15\)

思路分析

我們設 \(f_{A}(i)\) 表示 \(S[1,i]\)\(\texttt B\) 的個數減去 \(\texttt A\) 的個數,並記 \(\max f_{A}(i)=f_A\),同理定義 \(f_B,f_C\)

那麼我們發現 \(S\) 是好的當且僅當 \(f_A+f_B+f_C\le n\),下面給出證明:

先證充分性:

我們觀察字串 \(S+S\),由於每個 \(S\)\(\texttt{A,B,C}\) 個數相等,因此 \(S\to 2S\)\(f_A,f_B,f_C\) 不變。

考慮第 \(i\)\(\texttt A\) 所在位置 \(p_i\),由於 \(f_A(p_i-1)\le f_A\),因此 \(S[1,p_i-1]\)\(\texttt B\) 的個數不超過 \(i-1+f_A\)

因此第 \(i+f_A\)\(\texttt B\) 在第 \(i\)\(\texttt A\) 右邊,同理第 \(i+f_A+f_B\)\(\texttt C\) 在第 \(i+f_A\)\(\texttt B\) 右邊,第 \(i+f_A+f_B+f_C\)\(\texttt A\) 在第 \(i+f_A+f_B\)\(\texttt C\) 右邊。

由於 \(f_A+f_B+f_C\le n\),那麼 \(\texttt C\) 的位置在第 \(i+n\)\(\texttt{A}\) 左邊,因此這三個 \(\texttt{A,B,C}\)\(S+S\) 範圍內,且距離 \(<|S|\)

因此我們取出這三個 \(\texttt{A,B,C}\) 匹配成子序列(\(>|S|\) 的減去 \(|S|\)),由於他們之間的距離 \(<|S|\),只會把一段字尾提到字首上,因此劃分出來的一定是 \(\texttt{ABC,BCA,CAB}\) 中的一個。

再證必要性:

對於一個合法的 \(S\),我們設 \(\texttt{ABC,BCA,CAB}\) 數量分別為 \(g_A,g_B,g_C\)

注意到一個 \(\texttt{ABC,CAB}\) 只會讓 \(f_{A}(i)\) 區間 \(-1\),只有 \(\texttt{BCA}\) 會讓 \(f_A(i)\) 區間 \(+1\),因此顯然有 \(f_A\le g_B\)

同理可知 \(f_B\le g_C,f_C\le g_A\),因此 \(f_A+f_B+f_C\le g_A+g_B+g_C\)

所以我們 dp 記錄 \(f_{a,b,c,fa,fb,fc}\) 表示當前 \(\texttt{A,B,C}\) 個數,以及當前 \(f_A,f_B,f_C\) 即可,轉移列舉下一個字元。

時間複雜度 \(\mathcal O(n^6)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353;
int n,f[16][16][16][16][16][16],ans;
char s[55];
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
signed main() {
	scanf("%d%s",&n,s+1);
	f[0][0][0][0][0][0]=1;
	for(int i=0;i<=n;++i) for(int j=0;j<=n;++j) for(int k=0;k<=n;++k) {
		for(int x=0;x<=n;++x) for(int y=0;x+y<=n;++y) for(int z=0;x+y+z<=n;++z) {
			const int &w=f[i][j][k][x][y][z];
			if(!w) continue;
			int t=i+j+k+1;
			if(t>3*n) { add(ans,w); continue; }
			if((s[t]=='?'||s[t]=='A')&&i<n) add(f[i+1][j][k][x][y][max(z,i-k+1)],w);
			if((s[t]=='?'||s[t]=='B')&&j<n) add(f[i][j+1][k][max(x,j-i+1)][y][z],w);
			if((s[t]=='?'||s[t]=='C')&&k<n) add(f[i][j][k+1][x][max(y,k-j+1)][z],w);
		}
	}
	printf("%d\n",ans);
	return 0;
}



*H. [ARC174F] Nim

Problem Link

題目大意

有一堆石子,兩個人輪流從中取石子,共取 \(n\) 次,第 \(i\) 次要取 \([l_i,r_i]\) 個石子,無法操作的人輸,\(q\) 次詢問如果初始有 \(c\) 個石子,誰會獲勝。

資料範圍:\(n,q\le 3\times 10^5\)

思路分析

如果 \(n=1\),那麼 \([0,l_i)\) 必敗,\([l_i,+\infty)\) 平局。

然後考慮加入 \([l_{n-1},r_{n-1}]\),此時對於每個必敗區間 \([x,y]\),其會變成 \([x+l_{n-1},y+r_{n-1}]\) 並取並,然後翻轉必勝必敗情況,最後設 \([0,0]\) 必敗。

我們要動態維護這些操作,可以只維護所有差分位置,即 \(c=i-1\)\(c=i\) 時結果不同的位置。

那麼奇數項差分位置就是必敗區間的左端點,偶數項差分位置就是必敗區間的右端點。

給不同奇偶的差分位置分別加上 \(l_{n-1}/r_{n-1}\),如果兩個差分位置相交,說明兩個必敗區間相交,把這兩個差分位置都刪除。

用堆維護所有奇偶差分位置的距離,每次把會相遇的差分位置彈出並暴力刪除即可,雙向連結串列維護所有差分位置即可。

而翻轉必勝必敗情況只要在最開頭加入一個差分位置 \(0\)

\(n\)\(1\) 加入所有線段後處理出每段區間的勝負情況,查詢時只需要二分。

時間複雜度 \(\mathcal O((n+q)\log n)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
using namespace std;
const int MAXN=3e5+5;
ll l[MAXN],r[MAXN],x[MAXN],tl,tr,ans[MAXN];
int n,q,pr[MAXN],sf[MAXN];
set <pair<ll,int>> ql,qr;
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%lld%lld",&l[i],&r[i]);
	int hd=1,tot=2;
	sf[1]=2,pr[2]=1,x[2]=l[n];
	ql.insert({l[n],1});
	for(int o=n-1;o;--o) {
		tl+=l[o],tr+=r[o];
		while(qr.size()&&qr.begin()->fi<=tr-tl) {
			int i=qr.begin()->second,j=sf[i]; qr.erase(qr.begin());
			if(!sf[j]) { sf[i]=0; continue; }
			if(!pr[i]) hd=sf[j];
			sf[pr[i]]=sf[j],pr[sf[j]]=pr[i];
			if(pr[i]) ql.erase({x[i]-x[pr[i]],pr[i]});
			if(sf[j]) ql.erase({x[sf[j]]-x[j],j});
			if(pr[i]&&sf[j]) ql.insert({x[sf[j]]-x[pr[i]],pr[i]});
		}
		swap(tl,tr),swap(ql,qr);
		++tot,pr[hd]=tot,sf[tot]=hd;
		x[tot]=-tl,ql.insert({x[hd]-x[tot],tot}),hd=tot;
	}
	int m=0;
	for(int i=hd;i;i=sf[i]) ++m,ans[m]=x[i]+(m&1?tl:tr);
	for(int i=1;i<=m;++i) cerr<<ans[i]<<" \n"[i==m];
	scanf("%d",&q);
	for(ll z;q--;) {
		scanf("%lld",&z);
		int i=upper_bound(ans+1,ans+m+1,z)-ans-1;
		puts(i==m?"Draw":(i&1?"Bob":"Alice"));
	}
	return 0;
}



*I. [ARC176F] Cover

Problem Link

題目大意

給定一棵 \(nm+1\) 個點的樹,由 \(n\) 條長度為 \(m\) 的鏈連到一個根節點上構成。

\(i\) 條鏈上節點顏色為 \(i-1\),根節點顏色為 \(0\),每次操作可以把一個點的顏色設成其某個鄰居的顏色,求能得到多少種顏色序列。

資料範圍:\(n,m\le 2\times 10^5\)

思路分析

倒序維護所有操作,一次操作會把一對相鄰的同色節點中的一個任意染成其他顏色。

稱這種可以任意染色的節點為自由節點,那麼如果一次操作使用的是一個自由節點和一個顏色為 \(c\) 的節點,實際上相當於交換兩個節點的顏色。

因此我們可以把自由節點看成空位,節點的顏色看成一枚該顏色的棋子,那麼操作就是把一枚棋子移動到相鄰的空位上,或者把兩枚相鄰同色棋子中的一枚刪掉。

我們最終的目的是讓每種同色棋子都在一條鏈上。

手玩發現當空位 \(\ge 3\) 時,我們可以把他們聚集在根節點附近,並且無論如何都能找到一步進行操作。

我們只需要考慮空位 \(\le 2\) 的情況,反面考慮減去這種終態數量。

  • 空位 \(=0\),只要所有點和父親不同色即可,方案數 \(n(n-1)^{nm}\)

  • 空位 \(=1\),設空位在根節點上,那麼要求每個兒子不同色。

    先給每個兒子填色,再列舉一對點顏色相同,剩餘的和父親不同色即可,方案數 \(n!\times nm\times(n-1)^{(m-1)n}\)

  • 空位 \(=2\),設空位在根節點和其兒子上,那麼剩餘的 \(n-1\) 個兒子必須不同色,且所有深度 \(=2\) 的點都是剩下的那種顏色。

    如果初始就能生成兩個空位,列舉產生位置,類似得到方案數 \(n!\times\binom{nm}2\times (n-1)^{(m-2)n}\)

    否則相當於把第一個空位移動到根節點後,有兩個兒子顏色相同,方案數 \(n!\times nm\times\binom{n-1}2\times (n-1)^{(m-2)n}\)

特判 \(n\le 2,m\le 1\) 的情況。

時間複雜度 \(\mathcal O(n)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=998244353;
ll ksm(ll a,ll b) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll C(ll x) { return x*(x-1)/2%MOD; }
signed main() {
	ll n,m,fac=1;
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;++i) fac=fac*i%MOD;
	if(n==1) return puts("1"),0;
	if(n==2) return printf("%lld\n",n*m+2),0;
	if(m==1) {
		ll ans=ksm(n,n+1);
		ans-=n*ksm(n-1,n)%MOD;
		ans-=n*(fac-1)%MOD;
		printf("%lld\n",(ans%MOD+MOD)%MOD);
		return 0;
	}
	ll ans=ksm(n,n*m+1);
	ans-=n*ksm(n-1,n*m)%MOD;
	ans-=n*m%MOD*fac%MOD*ksm(n-1,n*(m-1))%MOD;
	ans-=C(n*m%MOD)*fac%MOD*ksm(n-1,n*(m-2))%MOD;
	ans-=n*m%MOD*C(n-1)%MOD*fac%MOD*ksm(n-1,n*(m-2))%MOD;
	printf("%lld\n",(ans%MOD+MOD)%MOD);
	return 0;
}




Round #18 - 2024.10.10

A. [ARC126E] Exchange

Problem Link

題目大意

給定長度為 \(n\) 的序列 \(a\),定義一次操作為 \(a_i\gets a_i-x,a_j\gets a_j+x\),產生 \(x\) 的收益,需要保證 \(a_i-2x\ge a_j\)

支援 \(q\) 次單點修改,動態維護最大收益。

資料範圍:\(n,q\le 3\times 10^5\)

思路分析

猜測答案為 \(\dfrac 12\sum_{i<j}|a_i-a_j|\),這是可以證明的:

每次操作 \((i,j)\) 的貢獻都會減小 \(x\),而 \(a_k>a_{i}+x\) 就會讓 \((i,k)\) 貢獻 \(-x\),可以抵消 \((j,k)\) 貢獻增量,同理 \(a_k<a_j-x\) 的貢獻會抵消。

因此一次代價為 \(x\) 的操作至少會讓上式 \(-x\)

構造方案是容易的,每次取出排序後相鄰的兩個元素操作即可。

維護 \(\sum |a_i-a_j|\) 直接主席樹維護值域區間元素數量和元素和,合併左右區間時計算跨越區間的 \((i,j)\) 對貢獻。

時間複雜度 \(\mathcal O((n+q)\log V)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5,MOD=998244353,V=1e9,i2=(MOD+1)/2;
struct Segt {
	int tot,ls[MAXN*60],rs[MAXN*60],siz[MAXN*60];
	ll sum[MAXN*60],val[MAXN*60];
	void psu(int p) {
		siz[p]=siz[ls[p]]+siz[rs[p]];
		sum[p]=(sum[ls[p]]+sum[rs[p]])%MOD;
		val[p]=(val[ls[p]]+val[rs[p]]+sum[rs[p]]*siz[ls[p]]-sum[ls[p]]*siz[rs[p]])%MOD;
	}
	void ins(int x,int op,int l,int r,int &p) {
		if(!p) p=++tot;
		if(l==r) return siz[p]+=op,sum[p]=(sum[p]+op*x)%MOD,void();
		int mid=(l+r)>>1;
		x<=mid?ins(x,op,l,mid,ls[p]):ins(x,op,mid+1,r,rs[p]);
		psu(p);
	}
}	T;
int n,q,a[MAXN],rt;
signed main() {
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),T.ins(a[i],1,1,V,rt);
	for(int x,y;q--;) {
		scanf("%d%d",&x,&y);
		T.ins(a[x],-1,1,V,rt);
		T.ins(a[x]=y,1,1,V,rt);
		printf("%lld\n",(T.val[rt]+MOD)*i2%MOD);
	}
	return 0;
}



B. [ARC128E] Different

Problem Link

題目大意

給出 \(m\) 個值域 \([1,n]\) 的元素,其中有 \(a_i\)\(i\),求一組字典序最小的排列使得每個長度為 \(k\) 的子區間中都沒有相同元素。

資料範圍:\(n\le 500,m\le 2.5\times 10^5\)

思路分析

容易發現同一種顏色的元素最多填 \(\left\lceil\dfrac nk\right\rceil\) 個,並且這樣的元素至多 \(n\bmod k\) 個。

可以遞迴證明這個條件是充分的。

構造方案時從小到大找到第一個可以填並且上一次填入位置距離當前位置 \(\ge k\) 的元素即可。

時間複雜度 \(\mathcal O(nm)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=505,MAXL=2e5+5;
int n,m,k,a[MAXN],mx,cnt[MAXL],pre[MAXN];
void del(int x) {
	--cnt[a[x]];
	if(!cnt[mx]) --mx;
	++cnt[--a[x]];
}
void add(int x) {
	--cnt[a[x]];
	++cnt[++a[x]];
	if(cnt[mx+1]) ++mx;
}
bool judge() {
	if(!n) return true;
	if(n%k) return mx<n/k+1||(mx==n/k+1&&cnt[mx]<=n%k);
	return mx<n/k||(mx==n/k&&cnt[mx]<=k);
}
signed main() {
	scanf("%d%d",&m,&k);
	for(int i=1;i<=m;++i) {
		scanf("%d",&a[i]),n+=a[i];
		mx=max(mx,a[i]),++cnt[a[i]];
	}
	while(n--) {
		for(int i=1;i<=m;++i) if(a[i]&&(!pre[i]||pre[i]>=n+k)) {
			del(i);
			if(judge()) { printf("%d ",i),pre[i]=n; goto fd; }
			add(i);
		}
		return puts("-1"),0;
		fd:;
	}
	return 0;
}



C. [ARC132F] Strategy

Problem Link

題目大意

給定三個人玩石頭剪刀布,進行 \(k\) 輪,已知前兩個人會選擇的策略集合。

對於第三個人的每種策略,求出前兩個人有多少種選擇策略的方法使得第三個人至少獲勝一輪。

資料範圍:\(k\le 12\)

思路分析

考慮列舉第 \(i\) 輪並欽定第三個人在這一輪獲勝。

我們就要計算有多少第一個人和第二個人的策略對,使得他們在第 \(i\) 位相等。

回到原問題,進行容斥,欽定第三個人獲勝了其中 \(x\) 輪,容斥係數 \((-1)^{x-1}\)

我們就要對於 \(3^x\) 個可能的決策 \(s\),求第一個人和第二個人在這 \(x\) 位恰好是 \(s\) 的數量,可以四進位制 FWT,某一位 \(=3\) 說明不欽定這一位取值。

然後做一個類似的過程,把每一位 \(=3\) 的值加給這一位 \(=0/1/2\) 的值。

注意輸出的時候要輪換一下(第三個人要出石頭才能贏另外兩個人出剪刀)。

時間複雜度 \(\mathcal O(k4^k)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,N,Z,X,Y;
ll f[1<<24],g[1<<24];
int read() {
	string o; cin>>o;
	int s=0;
	for(int i=0;i<n;++i) s=s<<2|(o[i]=='P'?0:(o[i]=='R'?1:2));
	return s;
}
void out(int i,int s) {
	if(i==n) return cout<<f[s]<<"\n",void();
	out(i+1,s<<2|1);
	out(i+1,s<<2|2);
	out(i+1,s<<2);
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>n>>X>>Y,N=1<<(n*2);
	for(int i=0;i<n;++i) Z=Z<<2|1;
	for(int i=0;i<X;++i) ++f[read()];
	for(int i=0;i<Y;++i) ++g[read()];
	for(int k=1;k<N;k<<=2) for(int i=0;i<N;i+=k<<2) for(int j=i;j<i+k;++j) {
		f[j+3*k]+=f[j]+f[j+k]+f[j+2*k];
		g[j+3*k]+=g[j]+g[j+k]+g[j+2*k];
	}
	for(int s=0;s<N;++s) {
		f[s]*=g[s];
		if((n&1)^__builtin_parity(s&(s>>1)&Z)^1) f[s]*=-1;
	}
	f[N-1]=0;
	for(int k=1;k<N;k<<=2) for(int i=0;i<N;i+=k<<2) for(int j=i;j<i+k;++j) {
		f[j]+=f[j+3*k],f[j+k]+=f[j+3*k],f[j+2*k]+=f[j+3*k];
	}
	out(0,0);
	return 0;
}



D. [ARC130E] Minimum

Problem Link

題目大意

給定序列 \(b_1\sim b_m\),構造一個長度為 \(n\) 的序列 \(a_1\sim a_n\),使得第 \(i\) 次操作前 \(a_{b_i}=\min a_{1\sim n}\),操作後給 \(a_{b_i}\) 加一,求字典序最小解。

資料範圍:\(n,m\le 3\times 10^5\)

思路分析

觀察 \(b\) 的操作過程,把 \(a_{b_i}\) 相同的若干操作劃分成一段,那麼每次操作會讓所有最小值 \(+1\)

對於同一段的操作,不可能有 \(b_i\) 相等,並且上一段的 \(b_i\) 會被下一段的 \(b_i\) 包含。

不難發現滿足這兩個條件的劃分方式一定是合法的,並且我們只要最小化劃分輪數即可求出字典序最小解。

\(f_i\) 表示 \(b[1,i]\) 的最小劃分輪數,容易發現左端點 \(l\) 有一個下界,並且要求 \(b[1,l)\) 的數被 \(b[l,i]\) 包含,顯然 \(l\) 越小越好,因此可以直接貪心。

注意最後一輪可以不劃分滿,即在倒數第二輪所有可能的終止點 \(i\) 中取 \(f_i\) 最小的一個,重複時取 \(i\) 最大的一個。

時間複雜度 \(\mathcal O(n+m)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5;
int n,m,a[MAXN],pre[MAXN],dp[MAXN],ans[MAXN];
signed main() {
	scanf("%d%d",&m,&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	int lst=0,cnt=0;
	for(int i=1;i<=n;++i) {
		lst=max(lst,pre[a[i]]),cnt+=!pre[a[i]],pre[a[i]]=i;
		if(i-lst==cnt&&~dp[lst]) dp[i]=dp[lst]+1;
		else dp[i]=-1;
	}
	int o=-1;
	for(int i=n;i>=lst;--i) if(~dp[i]&&(o==-1||dp[i]<dp[o])) o=i;
	if(o==-1) return puts("-1"),0;
	for(int i=1;i<=m;++i) ans[i]=dp[o]+1;
	for(int i=1;i<=o;++i) --ans[a[i]];
	for(int i=1;i<=m;++i) printf("%d ",ans[i]); puts("");
	return 0;
}



E. [ARC129E] Cost

Problem Link

題目大意

給定 \(n\) 個變數 \(x_i\),每個變數有 \(m\) 種取值,每種取值有代價,並且會產生 \(\sum w_{i,j}|x_i-x_j|\) 的額外代價,最小化總代價。

資料範圍:\(n\le 50,m\le 5\)

思路分析

先考慮所有變數取值範圍都相等怎麼做,設取值範圍為 \(v_1\sim v_m\),可以拆每對 \((v_k,v_{k+1})\) 的貢獻。

\(x_i\le v_k,x_{j}>v_k\) 的時候產生 \(w_{i,j}\times (v_{k+1}-v_k)\) 的代價,用切糕模型刻畫之。

但是原問題中每個點就會有 \(nm\) 個取值,取不到的點設為 \(+\infty\) 代價,建圖網路流難以接受。

注意到每個點網路流圖上的鏈只會有 \(\mathcal O(m)\) 條非平凡邊,剩餘的全是 $+\infty $ 鏈,把這些鏈縮成一條邊即可。

時間複雜度 \(\mathcal O(\mathrm{Flow}(nm,n^2m))\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
namespace F {
const int MAXV=365,MAXE=1e5+5;
const ll inf=1e18;
struct Edge {
	int v,lst; ll f;
}	G[MAXE];
int S,T,tot=1,hd[MAXV],cur[MAXV],dep[MAXV];
void init() { tot=1,memset(hd,0,sizeof(hd)); }
void adde(int u,int v,ll w) { G[++tot]={v,hd[u],w},hd[u]=tot; }
void link(int u,int v,ll w) { adde(u,v,w),adde(v,u,0); }
bool BFS() {
	memcpy(cur,hd,sizeof(cur)),memset(dep,-1,sizeof(dep));
	queue <int> Q;
	Q.push(S),dep[S]=0;
	while(!Q.empty()) {
		int u=Q.front(); Q.pop();
		for(int i=hd[u];i;i=G[i].lst) if(G[i].f&&dep[G[i].v]==-1) {
			dep[G[i].v]=dep[u]+1,Q.push(G[i].v);
		}
	}
	return ~dep[T];
}
ll dfs(int u,ll f) {
	if(u==T) return f;
	ll r=f;
	for(int i=cur[u];i;i=G[i].lst) {
		int v=G[cur[u]=i].v;
		if(G[i].f&&dep[v]==dep[u]+1) {
			ll g=dfs(v,min(r,G[i].f));
			if(!g) dep[v]=-1;
			G[i].f-=g,G[i^1].f+=g,r-=g;
		}
		if(!r) return f;
	}
	return f-r;
}
ll Dinic() {
	ll f=0;
	while(BFS()) f+=dfs(S,inf);
	return f;
}
}
using F::link;
const ll inf=1e18;
int n,m,q=0,k,a[55][10],id[55][10];
ll c[55][10],w[55][55],v[15];
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) scanf("%d%lld",&a[i][j],&c[i][j]);
	for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) scanf("%lld",&w[i][j]);
	int s=F::S=++q,t=F::T=++q;
	for(int i=1;i<=n;++i) {
		for(int j=0;j<=m;++j) id[i][j]=++q;
		for(int j=1;j<=m;++j) link(id[i][j-1],id[i][j],c[i][j]);
		link(s,id[i][0],inf);
		link(id[i][m],t,inf);
	}
	for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) {
		k=0;
		for(int o=1;o<=m;++o) v[++k]=a[i][o],v[++k]=a[j][o];
		sort(v+1,v+k+1),k=unique(v+1,v+k+1)-v-1;
		for(int o=1;o<k;++o) {
			ll z=w[i][j]*(v[o+1]-v[o]);
			int x=upper_bound(a[i]+1,a[i]+m+1,v[o])-a[i]-1;
			int y=upper_bound(a[j]+1,a[j]+m+1,v[o])-a[j]-1;
			link(id[j][y],id[i][x],z);
			link(id[i][x],id[j][y],z);
		}
	}
	printf("%lld\n",F::Dinic());
	return 0;
}



*F. [ARC129F] Catch

Problem Link

題目大意

給定 \(n\) 個負半軸上的動點和 \(m\) 個正半軸的動點,初始位置為 \(l_1\sim l_n,r_1\sim r_m\),每個點以 \(1\) 的速度遠離原點。

一個人從原點次以 \(2\) 的速度抓捕最近的正半軸或負半軸上的點,對於每種抓捕順序求出花費時間之和。

資料範圍:\(n,m\le 2.5\times 10^5\)

思路分析

事實上我們只關心所有抓捕後轉向的點 \(a_1\sim a_k\)(包含最後一個抓到的點)。

那麼設抓到 \(a_i\) 的時刻是 \(t_i\),由於 \(a_{i+1}\) 一定和 \(a_i\) 不同方向,因此當前兩點距離為 \(|a_i|+|a_{i+1}|+2t_i\),因此 \(t_{i+1}=t_i+|a_i|+|a_{i+1}|+2t_i\)

化簡得到 \(t_k=|a_k|+\sum_{i<k}(3^{k-i-1}+3^{k-i})|a_i|\)

我們只考慮 \(a\)\(l_i\) 的情況,剩餘情況是對稱的。

列舉 \(k\),並列舉 \(l_{1}\sim l_{i-1},l_{i+1}\sim l_n\) 選了幾個進入 \(a\),以及 \(r_1\sim r_{m-1}\) 選了幾個進入 \(a\)

因為 \(l_n,r_{m}\) 一定是 \(a_k,a_{k-1}\) 之一,因此特殊討論,\(l_1\sim l_{n-1}\) 的答案為:

\[\dfrac 43\sum_{i=1}^{n-1}l_i\sum_{k=1}^n\left(\binom{m-1}{k-2}+4\binom{m-1}{k-1}+3\binom{m-1}k\right)\sum_{j=1}^{n-1}9^j\binom{n-i-1}{j-1}\binom{i-1}{k-j-1} \]

\(l_n\) 的貢獻為:

\[l_n\sum_{k=1}^{n-1}\binom{m-1}{k-2}+5\binom{m-1}{k-1}+4\binom{m-1}k \]

下面的式子容易算,我們只要快速計算上式,考慮單個 \(\binom{m-1}{k-s}\) 的貢獻,其中 \(s\in\{0,1,2\}\),交換求和順序後把 \(k\) 的列舉用範德蒙德卷積替換得到:

\[\sum_{k=1}^n\binom{m-1}{k-s}\sum_{j=1}^{n-1}9^j\binom{n-i-1}{j-1}\binom{i-1}{k-j-1}=\sum_{j=1}^{n-1}9^j\binom{n-i-1}{j-1}\binom{m+i-2}{m-j+s-2} \]

不難用 NTT 最佳化計算。

時間複雜度 \(\mathcal O((n+m)\log n)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353,N=1<<19,G=3;
int fac[N],ifac[N];
namespace P {
int rev[N],inv[N],w[N<<1];
int ksm(int a,int b=MOD-2) {
	int ret=1;
	for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) ret=1ll*ret*a%MOD;
	return ret;
}
void poly_init() {
	inv[1]=1;
	for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
	fac[0]=ifac[0]=1;
	for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
	for(int k=1;k<=N;k<<=1) {
		int x=ksm(G,(MOD-1)/k); w[k]=1;
		for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
	}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y;  }
void ntt(int *f,bool idft,int n) {
	for(int i=0;i<n;++i) {
		rev[i]=(rev[i>>1]>>1);
		if(i&1) rev[i]|=n>>1;
	}
	for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
	for(int k=2,x,y;k<=n;k<<=1) {
		for(int i=0;i<n;i+=k) {
			for(int j=i;j<i+k/2;++j) {
				x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
				f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
			}
		}
	}
	if(idft) {
		reverse(f+1,f+n);
		for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
	}
}
}
using P::ntt;
int C(int x,int y) {
	if(x<0||y<0||y>x) return 0;
	return 1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
int n,m,w[N],a[N],b[N],pw[N];
const int cof[3]={3,4,1},i3=(MOD+1)/3; //cof of C(m-1,x-k)
int solve() {
	int ans=0;
	memset(a,0,sizeof(a));
	for(int i=1;i<n;++i) a[i]=1ll*w[i]*fac[m+i-2]%MOD*fac[n-i-1]%MOD;
	ntt(a,0,N);
	for(int k:{0,1,2}) {
		memset(b,0,sizeof(b));
		for(int j=1;j<n&&m-j+k-2>=0;++j) b[j]=1ll*pw[j]*ifac[j-1]%MOD*ifac[m-j+k-2]%MOD;
		ntt(b,0,N);
		for(int i=0;i<N;++i) b[i]=1ll*a[i]*b[i]%MOD;
		ntt(b,1,N);
		for(int s=k;s<=n;++s) ans=(ans+1ll*cof[k]*b[s]*ifac[n-s]%MOD*ifac[s-k])%MOD;
	}
	ans=4ll*ans*i3%MOD;
	for(int i=1;i<=n;++i) ans=(ans+(5ll*C(m-1,i-1)+4ll*C(m-1,i)+C(m-1,i-2))%MOD*w[n]%MOD*C(n-1,i-1))%MOD;
	return ans;
}
signed main() {
	P::poly_init();
	for(int i=pw[0]=1;i<N;++i) pw[i]=9ll*pw[i-1]%MOD;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&w[i]);
	int ans=solve();
	for(int i=1;i<=m;++i) scanf("%d",&w[i]);
	swap(n,m);
	printf("%d\n",(ans+solve())%MOD);
	return 0;
}



*G. [ARC131F] Pattern

Problem Link

題目大意

給定長度為 \(n\) 的字串 \(S\),字符集 \(\texttt {A,R,C}\),進行 \(\le k\) 次操作得到 \(T\):每次把一個長度為 \(3\) 的子串替換成 \(\texttt{ARC}\)

已知 \(T\),求有多少個可能的 \(S\)

資料範圍:\(n\le 5000,k\le 10^4\)

思路分析

時光倒流,一次操作會把 \(T\) 中的 \(\texttt{ARC}\) 子串變成三個任意字元,設為 \(\texttt{???}\)

那麼每次操作會把 \(\texttt{ARC,AR?,A??,?RC,??C,?R?}\) 中的一個變成 \(\texttt{???}\),這個過程中顯然不可能產生 \(\texttt{A?C}\)

考慮 \(k=\infty\) 時怎麼做,按 \(\texttt{ARC,AR?,A??,?RC,??C,?R?}\) 的順序把 \(T\) 中若干元素變成 \(\texttt{?}\),顯然只有 \(\texttt ?\) 上的元素能修改。

回到原問題,此時我們把 \(T\) 分成若干 \(\texttt{ARC,AR,A,RC,C,R,X}\) 子串(\(\texttt X\) 表示未匹配字元),每個子串可以變成 \(\texttt ?\) 但有些子串會依賴其前驅、後繼。

考慮 dp,設 \(f_{i,j,0/1}\) 表示決策了前 \(i\) 個子串,最小操作次數為 \(j\),是否欽定第 \(i,i+1\) 個子串必須是 \(\texttt ?\)

一個子串可以不變成 \(\texttt ?\),不產生貢獻,如果選擇了變成 \(\texttt ?\),就有 \(3^{|o|}\) 貢獻(其中 \(o\) 是當前子串),有依賴關係的子串就會改變 \(0/1\) 符號位。

但如果 \(o\) 的前驅後繼都不依賴 \(o\),那麼實際上沒必要操作 \(o\),因此我們欽定 \(S\) 中的這個位置不等於 \(o\),貢獻變為 \(3^{|o|}-1\)

時間複雜度 \(\mathcal O(nk)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e4+5,MOD=998244353,pw[]={1,3,9,27};
int n,m; string s;
ll f[MAXN][2];
struct info {
	int l,r;
	vector <string> q;
};
signed main() {
	ios::sync_with_stdio(false);
	cin>>s>>m,n=s.size();
	vector <info> Q;
	for(int i=0;i<n;++i) if(s.substr(i,3)=="ARC") {
		info o{i,i+3};
		while(true) {
			if(o.l>=1&&s.substr(o.l-1,1)=="A") o.q.push_back("A"),--o.l;
			else if(o.l>=2&&s.substr(o.l-2,2)=="AR") o.q.push_back("AR"),o.l-=2;
			else break;
		}
		reverse(o.q.begin(),o.q.end());
		o.q.push_back("ARC");
		while(true) {
			if(o.r<=n-1&&s.substr(o.r,1)=="C") o.q.push_back("C"),++o.r;
			else if(o.r<=n-2&&s.substr(o.r,2)=="RC") o.q.push_back("RC"),o.r+=2;
			else break;
		}
		Q.push_back(o);
	}
	vector <string> A;
	for(int i=0;i<(int)Q.size();++i) {
		if(i>=1) {
			A.push_back({Q[i-1].r+1==Q[i].l&&s[Q[i-1].r]=='R'?"R":" "});
		}
		A.insert(A.end(),Q[i].q.begin(),Q[i].q.end());
	}
	f[0][0]=1;
	for(auto o:A) {
		int w=pw[o.size()];
		for(int i=m-1;~i;--i) {
			if(o=="ARC") for(int x:{0,1}) for(int y:{0,1}) f[i+1][y]+=f[i][x]*(x||y?w:w-1);
			else if(o=="AR"||o=="A") for(int x:{0,1}) f[i+1][1]+=f[i][x]*(x?w:w-1);
			else if(o=="RC"||o=="C") for(int y:{0,1}) f[i+1][y]+=f[i][1]*(y?w:w-1);
			else if(o=="R") f[i+1][1]+=2*f[i][1];
			f[i][1]=0;
		}
		for(int i=0;i<=m;++i) for(int x:{0,1}) f[i][x]%=MOD;
	}
	ll ans=0;
	for(int i=0;i<=m;++i) ans=(ans+f[i][0])%MOD;
	cout<<ans<<"\n";
	return 0;
}



*H. [ARC118F] Ratio

Problem Link

題目大意

給定 \(a_1\sim a_n\),求有多少值域 \([1,m]\) 的序列 \(x_1\sim x_{n+1}\) 滿足 \(x_{i+1}\ge a_i\times x_i\)

資料範圍:\(n\le 1000,m\le 10^{18}\)

思路分析

正序 dp 較難維護,考慮倒序 dp,設 \(f_{i,j}\) 表示決策 \(x[i,n+1]\)\(x_i=j\) 的方案數。

轉移時 \(f_{i,j}=\sum_{k\ge a_i\times j} f_{i+1,k}\),設 \(g_{i,j}\) 表示 \(f_{i,j}\) 的字首和,那麼有 \(f_{i,j}=g_{i+1,m}-g_{i+1,a_i\times j-1}\)

初始 \(f_{n+1,i}=1,g_{n+1,i}=i\),猜測 \(f_{i,j}\) 是某個低次多項式在 \(j\) 處的點值。

發現 \(f_i\)\(g_{i+1}\) 次數相同,都是 \(n-i+1\) 次多項式的點值。

因此可以拉格朗日插值,每次維護 \(g_{i,1\sim n}\),轉移時直接插值求 \(g\)

我們可以 \(\mathcal O(n)\) 線性插值,但是此時要求 \(\mathcal O(n^2)\)\(g\),難以接受。

由於 \(\prod a_i\le m\),因此 \(a_i\) 中非 \(1\) 項只有 \(\mathcal O(\log m)\) 個,對於 \(a_i=1\) 的問題,我們求的是 \(g_{i+1,j-1}\),可以 \(\mathcal O(1)\) 回答,只要插值求 \(g_{i+1,m}\) 即可。

因此插值次數是 \(\mathcal O(n\log m)\) 的。

時間複雜度 \(\mathcal O(n^2\log m)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1005,MOD=998244353;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int n,T;
ll m,a[MAXN],f[MAXN],inv[MAXN],g[MAXN],pl[MAXN],pr[MAXN];
ll larg(ll x) {
	if(x<=T) return f[x];
	pl[0]=pr[T+1]=1;
	for(int i=1;i<=T;++i) pl[i]=(x-i)%MOD*pl[i-1]%MOD;
	for(int i=T;i>=1;--i) pr[i]=(x-i)%MOD*pr[i+1]%MOD;
	ll ans=0;
	for(int i=1;i<=T;++i) ans=(ans+f[i]*pl[i-1]%MOD*pr[i+1]%MOD*inv[i])%MOD;
	return ans;
}
signed main() {
	scanf("%d%lld",&n,&m),T=n+2;
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
	for(int i=1;i<=T;++i) {
		inv[i]=1;
		for(int j=1;j<=T;++j) if(i!=j) inv[i]=inv[i]*(i+MOD-j)%MOD;
		inv[i]=ksm(inv[i]);
	}
	for(int i=1;i<=T&&i<=m;++i) f[i]=i;
	for(int i=n;i>=1;--i) {
		ll S=larg(m); m/=a[i];
		memset(g,0,sizeof(g));
		for(int j=1;j<=T&&j<=m;++j) g[j]=(S+MOD-larg(j*a[i]-1))%MOD;
		for(int j=1;j<=T&&j<=m;++j) g[j]=(g[j-1]+g[j])%MOD;
		memcpy(f,g,sizeof(f));
	}
	printf("%lld\n",larg(m));
	return 0;
}



*I. [ARC119F] Distance

Problem Link

題目大意

給定節點 \(0\sim n\),用一條 \(i,i+1\) 的鏈連線。

\(1\sim n-1\) 有黑白兩種顏色,每個點會向其同色前驅後繼連雙向邊(不存在設為 \(0/n\))。

已知一些點的顏色,求有多少種染色方法使得 \(0,n\) 之間最短路長度 \(\le m\)

資料範圍:\(n,m\le 4000\)

思路分析

\(f_{i,j,k,0/1}\) 表示前 \(i\) 個點,到最後一個白色,黑色點的最短距離為 \(j,k\),第 \(i\) 個點的顏色為白或黑的方案數。

設當前顏色為白,那麼再加入一個白點會令 \(j\gets j+1\),加入一個黑點會令 \(j\gets \min(k+2,j),k\gets \min(j+1,k+1)\)

暴力轉移複雜度 \(\mathcal O(nm^2)\),考慮最佳化狀態數。

依然設當前顏色為白,那麼我們發現 \(j\) 應該會 \(\ge k\),取出最後一個黑點的下一個白點,設其最短距離為 \(x\),那麼此時 \(k\le x+1\) 因為可以從該點往回走一步,並且由於要面對連續的一段白點,那麼 \(j\ge x\)

因此 \(j\ge k-1\)

我們又發現如果 \(j\)\(k\) 大很多的時候,計算答案時不太可能用 \(j\) 更新最短距離。

如果 \(j>k+2\),那麼填一個黑色格子後 \(j\) 就會被 \(k+2\) 覆蓋,如果不填黑色格子那麼 \(j\) 始終 \(>k\)

因此我們只要維護 \(j'=\min(k+2,j)\)

那麼我們此時維護的狀態 \(j',k'\) 就有 \(|j'-k'|\le 2\),狀態數 \(\mathcal O(m)\)

時間複雜度 \(\mathcal O(nm)\)

程式碼呈現

#include<bits/stdc++.h> 
using namespace std;
const int MAXN=4005,MOD=1e9+7;
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y;}
int f[MAXN][5][2],g[MAXN][5][2];
char s[MAXN];
signed main() {
	int n,m;
	scanf("%d%d%s",&n,&m,s+1);
	f[0][2][0]=1;
	for(int i=1;i<n;++i) {
		memset(g,0,sizeof(g));
		auto upd=[&](int j,int k,int o,const int &w) {
			j=min(j,k+2),k=min(k,j+2),add(g[j][k-j+2][o],w);
		};
		for(int j=0;j<=m+2;++j) for(int k=j-2;k<=j+2;++k) {
			const int &w0=f[j][k-j+2][0],&w1=f[j][k-j+2][1];
			if(s[i]!='B') {
				upd(j+1,k,0,w0);
				upd(min(j+1,k+1),min(j+2,k),0,w1);
			}
			if(s[i]!='A') {
				upd(min(j,k+2),min(j+1,k+1),1,w0);
				upd(j,k+1,1,w1);
			}
		}
		memcpy(f,g,sizeof(f));
	}
	int ans=0;
	for(int j=0;j<=m+2;++j) for(int k=j-2;k<=j+2;++k) if(min(j,k)+1<=m) {
		add(ans,f[j][k-j+2][0]),add(ans,f[j][k-j+2][1]);
	}
	printf("%d\n",ans);
	return 0;
}



*J. [ARC127F] Express

Problem Link

題目大意

給定 \(a,b,v,m\),每次操作可以給 \(v\) 加上或減去 \(a/b\),任何時候要在 \([0,m]\) 範圍內,求能得到多少 \(v\)

資料範圍:\(a,b,v,m\le 10^9,\gcd(a,b)=1\)

思路分析

我們發現當 \(a+b\le m+1\) 時,我們能得到任何一個 \(x\)

\(x=v+pa+qb\),其中 \(p\in[0,b),a\le b\),此時我們不斷進行 \(+a\) 直到進行 \(p\) 次或 \(p+a>m\)

如果進行了 \(p\) 次,直接進行 \(q\)\(+b\) 即可。

否則由於 \(b\le m+1-a\),因此 \(p-b>m-a-b\ge -1\),即 \(p-b\ge 0\),我們總能進行 \(-b\) 後再 \(+a\) 的操作。

否則 \(a+b\ge m+2\),如果我們第一次操作是 \(+a\),很顯然下一次操作不會是 \(-a\),且 \(p+a+b>m\),因此也不會是 \(+b\),所以我們只能操作 \(+a,-b\),並且每次操作都是這兩個之一。

由於 \(a+b\ge m+2\),所以每次 \(+a\)\(-b\) 至多有一個可以使用。

那麼我們就能得到生成所有 \(v\) 的方式:

  • 每次選擇 \(v+a,v-b\) 中合法的一個直到無法操作。
  • 每次選擇 \(v-a,v+b\) 中合法的一個直到無法操作。

那麼所有的 \(v\) 構成兩條鏈,我們首先可以證明每條鏈不成環:

設一個極小的環由 \(p\)\(+a\)\(q\)\(-b\) 構成,那麼 \(pa=qb\),由於 \(\gcd(a,b)=1\),因此 \(p=kb,q=ka,k\in\mathbb{Z^+}\)

因此 \(p+q\ge a+b\ge m+2\),那麼這個環經過了 \(\ge m+2\) 個不同的點,顯然匯出矛盾。

並且我們還能證明兩條鏈不相交:

取出第一個交點 \(x\),我們可以透過 \(+a,-b\)\(-a,+b\) 操作同時得到 \(v\to x\)

那麼我們發現 \(-a,+b\)\(v\to x\) 路徑等價於 \(+a,-b\)\(x\to v\) 路徑,那麼我們就找到了 \(+a,-b\) 操作做構成的簡單環,匯出矛盾。

那麼我們只要計算兩條鏈的長度。

先考慮 \(+a,-b\) 的鏈,顯然我們能把 \(v\) 不斷對 \(b\) 取模再 \(+a\),那麼鏈上 \(+a\) 次數 \(p\) 就是第一個 \((v+pa)\bmod b+a>m\)\(p\)

確定 \(p\) 後鏈上 \(-b\) 次數就是 \(\left\lfloor\dfrac{v+ap}{b}\right\rfloor\)

考慮把 \(v\)\(\bmod b\) 中提出來,這是可以做到的,設 \(v_0=v\bmod b\),那麼 \((v+pa)\bmod b=v_0+pa\bmod b\) 當且僅當 \(pa\bmod b\le b-1-v_0\)

我們發現 \(pa\bmod b>b-1-v_0\)\((v+(p-1)a)\bmod b+a\ge a+b\ge m+2\),因此此時的 \(p\) 一定不合法。

那麼我們只要考慮 \(pa\bmod b\le b-1-v_0\)\(v_0+pa\bmod b+a>m\) 的第一個 \(p\)

即求出最小的 \(p\) 使得 \(pa\bmod b\in[m+1-v_0-a,b-1-v_0]\),這就是 ARC166E 那題的技巧,類似輾轉相除地做即可。

時間複雜度 \(\mathcal O(\log \max(a,b))\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll solve(ll l,ll r,ll a,ll p) { //ax mod p in [l,r]
	if(!l) return 0;
	if((l+a-1)/a*a<=r) return (l+a-1)/a;
	// l <= ax-kp <= r
	// x >= (kp+l)/a
	// -r%a <= pk%a <= -l%a
	ll k=solve((a-r%a)%a,(a-l%a)%a,p%a,a);
	return (k*p+l+a-1)/a;
}
ll calc(ll a,ll b,ll v,ll m) { //+a -b
	ll p=0,v0=v%b;
	if(v0+a<=m) p=solve(m-a-v0+1,b-v0-1,a,b);
	return p+(v+a*p)/b;
}
signed main() {
	ios::sync_with_stdio(false);
	int T; cin>>T;
	while(T--) {
		ll a,b,v,m;
		cin>>a>>b>>v>>m;
		if(a+b<=m+1) cout<<m+1<<"\n";
		else cout<<calc(a,b,v,m)+calc(b,a,v,m)+1<<"\n";
	}
	return 0;
}




Round #19 - 2024.10.11

A. [ARC125E] Box

Problem Link

題目大意

\(n\) 個物品,第 \(i\) 種物品有 \(a_i\) 個,\(m\) 盒子,每個盒子每種物品 \(\le b_i\) 個,總數 \(\le c_i\),求放入盒子的物品總數最大值。

資料範圍:\(n,m\le 2\times 10^5\)

思路分析

直接建立網路流模型,左部點為物品,\(S\to i\) 流量 \(a_i\),右部點為盒子,\(j\to T\) 流量 \(c_j\)\(i\to j\) 流量 \(b_j\)

考慮最小割最大流轉化,用組合意義計算最小割。

\(S\to i\) 切掉了 \(k\) 個,那麼對於每個盒子,不存在 \(S\to j\to T\) 的最小代價為 \(\min(c_j,(n-k)\times b_j)\)

注意這個和值具體切掉了哪 \(k\) 條邊沒有關係,直接切掉最大的 \(k\) 條,列舉 \(k\),動態維護 \(\min(c_j,(n-k)\times b_j)\),維護 \(j\) 決策切換到 \(b_j\) 的時刻即可。

時間複雜度 \(\mathcal O(n\log n+m)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,m;
ll a[MAXN],b[MAXN],c[MAXN],A,B,C,ans;
vector <int> s[MAXN];
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]),A+=a[i];
	for(int i=1;i<=m;++i) scanf("%lld",&b[i]),B+=b[i];
	for(int i=1;i<=m;++i) {
		scanf("%lld",&c[i]);
		if(b[i]*n>c[i]) s[c[i]/b[i]+1].push_back(i);
	}
	sort(a+1,a+n+1),ans=A;
	for(int k=0;k<=n;++k) { //min(kb,c)
		for(int j:s[k]) B-=b[j],C+=c[j];
		ans=min(ans,A+k*B+C),A-=a[n-k];
	}
	printf("%lld\n",ans);
	return 0;
}



B. [ARC124E] Pass

Problem Link

題目大意

\(n\) 個人站成環,第 \(i\) 個人手裡有 \(a_i\) 個球,每個人選擇 \(x_i\in[0,a_i]\),同時把 \(x_i\) 個球傳給下一個人。

求所有可能得到的局面中每個人手中球數乘積之和。

資料範圍:\(n\le 10^5\)

思路分析

首先我們對每種局面刻畫一組唯一的 \(\{x_i\}\),如果所有 \(x_i>0\),可以把他們全部 \(-1\),容易證明每一種終態都唯一對應一組解。

因此我們計算所有操作序列的結果之和,減去所有 \(x_i>0\) 的操作序列結果之和。

只考慮所有操作序列的結果之和,那麼乘積等價於在每個人手中取一個球的方案數,列舉每個人手中的球來自自己還是前一個人。

\(f_{i,0/1}\) 表示考慮了前 \(i\) 個人的 \(x\),第 \(i\) 個人選擇的球來自自己還是前一個人,並且球來自 \(1\sim i-1\) 的貢獻已經計算完畢。

轉移時把原來第 \(i\) 個人手中球的貢獻計算掉,初始值列舉第 \(1\) 個人的狀態後斷環為鏈即可。

欽定 \(x_i>0\) 只需要簡單改變一些轉移貢獻的計算。

時間複雜度 \(\mathcal O(n)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MOD=998244353,i6=(MOD+1)/6;
ll s1(ll x) { return x*(x+1)/2%MOD; }
ll s2(ll x) { return x*(x+1)%MOD*(2*x+1)%MOD*i6%MOD; }
int n,a[MAXN];
ll f[MAXN][2];
ll dp(int o,int c) {
	memset(f,0,sizeof(f)),f[1][c]=1;
	for(int i=1;i<=n;++i) {
		f[i+1][0]=(f[i][0]*s1(a[i]-o)+f[i][1]*(a[i]+1-o))%MOD;
		f[i+1][1]=(f[i][0]*(s1(a[i])*a[i]%MOD+MOD-s2(a[i]))+f[i][1]*s1(a[i]))%MOD;
	}
	return f[n+1][c];
}
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	printf("%lld\n",(dp(0,0)+dp(0,1)+MOD*2-dp(1,0)-dp(1,1))%MOD);
	return 0;
}



C. [ARC123F] Insert

Problem Link

題目大意

對於一個序列 \(a\),一次操作會在所有 \(a_i,a_{i+1}\) 中間插入一個 \(a_i+a_{i+1}\),初始 \(a=[x,y]\),求 \(a^{\infty}\)\(\le n\) 的元素中的第 \(l\sim r\) 個。

資料範圍:\(n\le 3\times 10^5\)

思路分析

序列中的每個元素都是 \(ux+vy\),並且 \(\dfrac{u}v\) 就是 Stern-Brocot Tree。

根據 Stern-Brocot Tree 的結論,其子樹中會出現所有最簡真分數恰好一次,即所有 \(\gcd(u,v)=1\)\(ux+vy\) 恰好一次。

那麼我們就可以快速計運算元樹大小,求有多少 \(ux+vy\le n\)\(\gcd(u,v)=1\),莫比烏斯反演得到:

\[\sum_{d=1}^n\mu(d)\sum_{i=1}^{n/d/x}\left\lfloor\dfrac{\lfloor n/d\rfloor-ix}{y}\right\rfloor \]

可以用整除分塊配合擴充歐幾里得做到 \(\mathcal O(\sqrt n\log n)\) 計算。

求 Stern-Brocot Tree 上第 \(l\) 個元素的過程可以倍增地跳方向相同的鏈,這樣的轉向次數是 \(\mathcal O(\log n)\) 的,複雜度 \(\mathcal O(n+\sqrt n\log ^3n)\)

時間複雜度 \(\mathcal O(n+\sqrt n\log^3n)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5;
ll fs(ll a,ll b,ll c,ll n) { //i=0~n[ai+b/c]
	if(!a) return (b/c)*(n+1);
	if(a*n+b<c) return 0;
	if(a>=c||b>=c) return fs(a%c,b%c,c,n)+(a/c)*n*(n+1)/2+(b/c)*(n+1);
	return (a*n+b)/c*(n+1)-fs(c,c-b+a-1,a,(a*n+b)/c-1);
}
ll Fs(ll a,ll b,ll c,ll n) {
	ll s=0;
	if(a<0) {
		ll ta=(a%c+c)%c;
		s+=n*(n+1)/2*((a-ta)/c);
		a=ta;
	}
	if(b<0) {
		ll tb=(b%c+c)%c;
		s+=(n+1)*((b-tb)/c);
		b=tb;
	}
	return s+fs(a,b,c,n);
}
int n,mu[MAXN],smu[MAXN];
vector <int> PR;
bool isc[MAXN];
inline ll calc(ll a,ll b) {
	ll ans=0;
	for(ll l=1,r;l<=n;l=r+1) {
		r=n/(n/l);
		if(n/l<a+b) break;
		ans+=(smu[r]-smu[l-1])*Fs(-a,n/l-a,b,n/l/a-1);
	}
	return ans;
}
ll A,B;
typedef array<ll,2> pii;
inline pii operator +(const pii &x,const pii &y) { return {x[0]+y[0],x[1]+y[1]}; }
inline pii operator *(const pii &x,ll y) { return {x[0]*y,x[1]*y}; }
inline ll val(const pii &x) { return x[0]*A+x[1]*B; }
ll rem=0;
void out(pii x,pii y,ll l,ll r) {
	if(rem<=0) return ;
	ll mid=val(x+y);
	if(mid>n) return ;
	out(x,x+y,l,r);
	if(rem) printf("%lld ",mid),--rem;
	out(x+y,y,l,r);
}
void dfs(pii x,pii y,ll l,ll r) {
	if(rem<=0) return ;
	ll a=val(x),b=val(y),mid=val(x+y);
	if(mid>n) return ;
	ll cnt=calc(a,mid)+1;
	if(l==cnt) {
		printf("%lld ",mid),--rem;
		return out(x+y,y,l,r);
	}
	if(l<cnt) {
		int d=0;
		for(int k=19;~k;--k) {
			if(l<=calc(a,val(x*(d|1<<k)+y))) d|=1<<k;
		}
		dfs(x,x*d+y,l,r);
		for(int i=d;i&&rem>0;--i) {
			printf("%lld ",val(x*i+y)),--rem;
			out(x*i+y,x*(i-1)+y,l,r);
		}
	} else {
		ll all=calc(a,b);
		int d=0;
		for(int k=19;~k;--k) {
			if(all-calc(val(x+y*(d|1<<k)),b)<l) d|=1<<k;
		}
		ll sz=all-calc(val(x+y*d),b);
		dfs(x+y*d,y,l-sz,r-sz);
	}
}
signed main() {
	ll l,r;
	scanf("%lld%lld%d%lld%lld",&A,&B,&n,&l,&r);
	mu[1]=1;
	for(int i=2;i<=n;++i) {
		if(!isc[i]) PR.push_back(i),mu[i]=-1;
		for(int p:PR) {
			if(i*p>n) break;
			isc[i*p]=true,mu[i*p]=-mu[i];
			if(i%p==0) { mu[i*p]=0; break; }
		}
	}
	for(int i=1;i<=n;++i) smu[i]=smu[i-1]+mu[i];
	ll all=calc(A,B);
	if(l==1) printf("%lld ",A),--r;
	else --l,--r;
	if(l>r) return 0;
	if(l<=all) rem=r-l+1,dfs({1,0},{0,1},l,r);
	if(r>all) printf("%lld ",B);
	return 0;
}



*D. [ARC125F] Backpack

Problem Link

題目大意

給定 \(n\) 個點的樹,求有多少 \((s,i)\) 滿足 \(s\) 可以表示成樹上 \(i\) 個不同節點的度數和。

資料範圍:\(n\le 2\times 10^5\)

思路分析

很顯然樹只是限定度數序列 \(d_i\) 滿足 \(\sum d_i=2n-2\),給所有 \(d_i\) 減去一得到 \(\sum d_i=n-2\) 的自然數序列。

對於每個 \(s\),設 \(L_s,R_s\) 表示 \(\sum d=s\) 時最少、最多分別能選出多少個點。

設有 \(z\)\(d_i=0\),那麼 \(L_s\) 對應方案不包含 \(0\)\(R_s\) 對應方案一定包含所有 \(0\)

因此透過修改 \(0\) 數量能得到選出節點數量 \([L_s,L_s+z]\cup [R_s-z,R_s]\) 的所有方案。

觀察發現 \(R_s-L_s\le 2z+1\)

對於任何一種方案,設選出 \(i\) 個物品和為 \(s\),一定有 \(s-i\ge -z\),因為只有 \(d_i=0\) 的點會有 \(-1\) 貢獻。

考慮取出補集得到 \(n-i\) 個物品和為 \(n-2-s\),那麼 \(s-i\le z-2\)

因此 \(s-L_s,s-R_s\in[-z,z-2]\),原結論得證。

我們只要求 \(\sum R_s-L_s+1\),由於本質不同的 \(d_i\) 只有 \(\mathcal O(\sqrt n)\) 個,單調佇列最佳化多重揹包即可。

時間複雜度 \(\mathcal O(n\sqrt n)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,d[MAXN],c[MAXN],f[MAXN],g[MAXN],mx[MAXN],q[MAXN];
signed main() {
	scanf("%d",&n);
	for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),++d[u],++d[v];
	for(int i=1;i<=n;++i) ++c[d[i]-1];
	memset(f,-0x3f,sizeof(f)),f[0]=c[0];
	for(int x=1;x<=n;++x) if(c[x]) {
		memcpy(g,f,sizeof(g));
		for(int r=0;r<x;++r) {
			int hd=1,tl=0;
			for(int i=0;i*x+r<=n;++i) {
				int k=i*x+r;
				while(hd<=tl&&q[hd]<i-c[x]) ++hd;
				if(hd<=tl) g[k]=max(g[k],f[q[hd]*x+r]+i-q[hd]);
				while(hd<=tl&&f[q[hd]*x+r]-q[hd]<=f[k]-i) --tl;
				q[++tl]=i;
			}
		}
		memcpy(f,g,sizeof(f));
	}
	memcpy(mx,f,sizeof(mx));
	memset(f,0x3f,sizeof(f)),f[0]=0;
	for(int x=1;x<=n;++x) if(c[x]) {
		memcpy(g,f,sizeof(g));
		for(int r=0;r<x;++r) {
			int hd=1,tl=0;
			for(int i=0;i*x+r<=n;++i) {
				int k=i*x+r;
				while(hd<=tl&&q[hd]<i-c[x]) ++hd;
				if(hd<=tl) g[k]=min(g[k],f[q[hd]*x+r]+i-q[hd]);
				while(hd<=tl&&f[q[hd]*x+r]-q[hd]>=f[k]-i) --tl;
				q[++tl]=i;
			}
		}
		memcpy(f,g,sizeof(f));
	}
	long long ans=0;
	for(int i=0;i<=n;++i) if(f[i]<=mx[i]) ans+=mx[i]-f[i]+1;
	printf("%lld\n",ans);
	return 0;
}



*E. [AGC054E] Delete

Problem Link

題目大意

對於一個 \(n\) 階排列 \(a\),一次操作可以刪除 \(a_i>\max(a_{i+1},a_{i-1})\)\(a_i<\min(a_{i+1},a_{i-1})\)\(a_i\)

求有多少 \(a_1=x\)\(n\) 階排列可以透過操作刪成 \(2\) 個元素。

資料範圍:\(n\le 10^6\)

思路分析

不妨設 \(a_1<a_n\)\(a_1>a_n\) 等價於統計 \(a_1=n-x+1\) 的方案數。

一個排列能被刪成兩個元素的充分必要條件是存在 \(i\) 使得 \(a_i\le a_1<a_n\le a_{i+1}\)

考慮充分性:不斷刪除 \((1,i)\)\((i+1,n)\) 內部元素,最終得到的序列一定有 \(a_1>a_2>\cdots>a_i<a_{i+1}>a_{i+2}>\cdots>a_n\),先刪 \(a_i\sim a_2\),再刪 \(a_{i+1}\sim a_{n-1}\) 就是一組方案。

必要性可以歸納法證明。

計算不合法排列數,相當於 \([1,a_1]\) 的元素後面不能是 \([a_n,n]\) 的元素,設 \(a_n=a_1+k\),得到方案數 \((k-1)!(k-1)^{\overline{k-2}}(k-1)^{\overline{x-1}}\)

化簡得到 \((n-x-2)!(x-1)!\sum_{k=0}^{n-x-2}\binom{x+k-1}{k}(k+1)\),把 \(\binom{x+k-1}{k}(k+1)\) 看成 \(\binom{x+k-1}{k}+x\binom{x+k-1}{k-1}\)

分別用組合數上指標字首和得到答案為 \((n-2)!\left(\dfrac 1x+\dfrac{n-x-2}{x+1}\right)\)

時間複雜度 \(\mathcal O(1)\)

程式碼呈現

#include<bits/stdc++.h> 
#define ll long long
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
ll fac[MAXN],inv[MAXN];
ll solve(int n,int p) {
	if(p>=n-1) return 0;
	return (inv[p+1]*(n-p-2)+inv[p])%MOD*fac[n-2]%MOD;
}
signed main() {
	for(int i=fac[0]=1;i<MAXN;++i) fac[i]=fac[i-1]*i%MOD;
	inv[1]=1;
	for(int i=2;i<MAXN;++i) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
	int T; scanf("%d",&T);
	for(int n,p;T--;) {
		scanf("%d%d",&n,&p);
		printf("%lld\n",(fac[n-1]+MOD-solve(n,p)+MOD-solve(n,n-p+1))%MOD);
	}
	return 0;
}




Round #20 - 2024.10.12

A. [ARC119D] Color

Problem Link

題目大意

給定 \(n\times m\) 矩陣,每個格子是紅或藍,每次可以選擇一個紅格子將其所在行或列塗白。

構造一組方案最大化白格子數量。

資料範圍:\(n,m\le 1000\)

思路分析

對於一個 \((i,j)\) 上的紅格子看成左部第 \(i\) 個點對右部第 \(j\) 個點的連邊。

一次染色就是選擇一個非孤立點,將其鄰邊全部刪除。

對於一個連通塊,顯然無論怎麼刪都會留下一個點,並且可以構造方案:以該點為根求出 dfs 樹,每次刪葉子即可。

那麼設有 \(k\) 個連通塊,直接列舉有多少個連通塊剩下的點是行,非孤立的行、列分別有 \(r,c\) 個,那麼答案就是 \(\max_{i=0}^k(n-r+i)(m-c+k-i)\)

構造方案是簡單的,直接 dfs。

時間複雜度 \(\mathcal O(nm)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2505;
char a[MAXN][MAXN];
int n,m,c=0,sl=0,sr=0;
vector <int> G[MAXN<<1];
vector <int> ver[MAXN<<1];
bool vis[MAXN<<1];
void dfs(int u) {
	vis[u]=true,++(u<=n?sl:sr),ver[c].push_back(u);
	for(int v:G[u]) if(!vis[v]) dfs(v);
}
void out(int u,int fz) {
	vis[u]=true;
	for(int v:G[u]) if(!vis[v]) out(v,u);
	if(fz) {
		if(u<=n) printf("X %d %d\n",u,fz-n);
		else printf("Y %d %d\n",fz,u-n);
	}
}
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) {
		scanf("%s",a[i]+1);
		for(int j=1;j<=m;++j) if(a[i][j]=='R') {
			G[i].push_back(j+n),G[j+n].push_back(i);
		}
	}
	for(int i=1;i<=n+m;++i) if(!vis[i]&&!G[i].empty()) ++c,dfs(i);
	int x=0;
	for(int i=1;i<=c;++i) if((n-sl+x)*(m-sr+c-x)>(n-sl+i)*(m-sr+c-i)) x=i;
	printf("%d\n",sl+sr-c);
	memset(vis,false,sizeof(vis));
	for(int i=1;i<=x;++i) {
		for(int z:ver[i]) if(z<=n) { out(z,0); break;}
	}
	for(int i=x+1;i<=c;++i) {
		for(int z:ver[i]) if(z>n) { out(z,0); break; }
	}
	return 0;
}



B. [ARC121F] Shrink

Problem Link

題目大意

給定 \(n\) 個點的樹,每個點上填 \(0,1\),邊上填 \(\mathrm{AND},\mathrm{OR}\)

一次操作可以選定一條邊 \((u,v)\),將 \(u,v\) 合併成一個新的點,權值就是 \(u,v\) 兩個點的權值經過 \((u,v)\) 上的運算後得到的樹。

對於一種填樹方法,其是好的當且僅當能透過若干次操作得到一個節點,且其權值為 \(1\)

資料範圍:\(n\le 10^5\)

思路分析

對於每條 \(\mathrm{AND}\) 邊,優先縮掉一定不劣,因此一棵樹是好的當且僅當至少存在一個 \(\mathrm{AND}\) 連通塊中不包含 \(0\)

反面考慮相當於計數所有 \(\mathrm{AND}\) 連通塊中都有 \(0\) 的方案數,\(f_{u,0/1}\) 表示 \(u\) 子樹連通塊中是否有 \(0\) 的方案數。

轉移時先從子樹 \(\mathrm{AND}\) 卷積,然後分討 \((u,fa_u)\) 的選擇情況。

時間複雜度 \(\mathcal O(n)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MOD=998244353;
int n;
ll f[MAXN][2];
vector <int> G[MAXN];
void dfs(int u,int fz) {
	array <ll,2> g{0,1};
	for(int v:G[u]) if(v^fz) {
		dfs(v,u);
		g[0]=(g[0]*(f[v][0]+f[v][1])+g[1]*f[v][0])%MOD;
		g[1]=g[1]*f[v][1]%MOD;
	}
	if(fz) {
		f[u][1]=(g[1]*2+g[0]*2)%MOD;
		f[u][0]=(g[1]+g[0]*2)%MOD;
	} else f[u][0]=g[0],f[u][1]=g[1];
}
signed main() {
	scanf("%d",&n);
	for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	ll ans=1;
	for(int i=1;i<2*n;++i) ans=ans*2%MOD;
	dfs(1,0);
	ans=(ans-f[1][0]*2-f[1][1])%MOD;
	printf("%lld\n",(ans+MOD)%MOD);
	return 0;
}



C. [ARC120F] Independ

Problem Link

題目大意

給定 \(a_1\sim a_n\),求所有大小為 \(k\) 的鏈上獨立集的 \(a\) 元素之和。

資料範圍:\(n\le 3\times 10^5\)

思路分析

設獨立集個數為 \(f(n,k)=\binom{n-k+1}k\)

注意到鏈上獨立集並不具有對稱性,而環上獨立集有較好的性質,每個點被選入獨立集的方案都相同,都是 \(f(n-2,k-1)\)

回到原問題,未計算的情況一定同時選擇了 \(a_1,a_n\)

這種獨立集一共有 \(f(n-4,k-2)\) 個,\(a_1,a_n\) 的係數也是這個。

我們只要考慮這種獨立集中 \(a_3\sim a_{n-2}\) 中的 \(a_i\) 貢獻,容易發現所有選擇 \(a_1,a_n\)\(k\) 元獨立集和 \(a_3\sim a_{n-2}\) 中的 \(k-2\) 元獨立集一一對應。

因此我們只要遞迴 \(a_3\sim a_{n-2},k-2\) 遞迴算一遍原問題答案即可。

時間複雜度 \(\mathcal O(n)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5,MOD=998244353;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll a[MAXN],s[MAXN],fac[MAXN],ifac[MAXN];
ll g(int n,int m) {
	if(m<0||n<2*m-1) return 0;
	return fac[n+1-m]*ifac[m]%MOD*ifac[n+1-2*m]%MOD;
}
ll f(int l,int r,int k) {
	if(k<=1) return k*(s[r]+MOD-s[l-1])%MOD;
	return ((s[r]-s[l-1])*g(r-l-2,k-1)+(a[l]+a[r])*g(r-l-3,k-2)+f(l+2,r-2,k-2))%MOD;
}
signed main() {
	for(int i=fac[0]=ifac[0]=1;i<MAXN;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
	int n,k,d;
	scanf("%d%d%d",&n,&k,&d);
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]),s[i]=(s[i-1]+a[i])%MOD;
	printf("%lld\n",f(1,n,k));
	return 0;
}



*D. [ARC117F] Cycle

Problem Link

給定 \(x_0\sim x_{2n-1}\) 排成一個環,限定 \(x_i\sim x_{i+n-1}\) 的和 \(\ge a_i\),最小化 \(\sum x_i\)

資料範圍:\(n\le 1.5\times 10^5\)

思路分析

顯然答案具有可二分性,二分 \(X=\sum x_i\) 後,設 \(s_i\)\(x_i\) 的字首和,那麼所有的限制都是對 \(s_i\) 的差分約束:

  • \(s_i\le s_{i+1}\)
  • \(s_i+a_i\le s_{i+n}\)
  • \(s_{i+n}+a_{i+n}-X\le s_i\)
  • \(s_{2n-1}-X\le s_{0}\)

我們發現對 \((i,i+n)\) 分層,按 \(i\) 從小到大轉移,只有 \(n\to n-1\)\(2n-1\to 0\) 兩條邊是逆序轉移的。

因此只需要常數輪增廣就能得到最短路。

如果常數輪增廣後最短路過程仍未結束,說明圖上有負環。

時間複雜度 \(\mathcal O(n\log V)\).

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5;
const ll inf=3e14;
int n;
ll a[MAXN],f[MAXN];
bool chkmax(ll &x,const ll&y) { return x<y?(x=y,1):0; }
bool chk(ll x) {
	memset(f,0,sizeof(f));
	bool fg;
	for(int Q=0;Q<5;++Q) {
		fg=0;
		for(int i=0;i<n;++i) {
			fg|=chkmax(f[i+n],f[i]+a[i]);
			fg|=chkmax(f[i],f[i+n]+a[i+n]-x);
			fg|=chkmax(f[i+1],f[i]);
			if(i<n) chkmax(f[i+n+1],f[i+n]);
		}
		chkmax(f[0],f[2*n-1]-x);
	}
	return !fg&&f[0]>=0;
}
signed main() {
	scanf("%d",&n);
	for(int i=0;i<2*n;++i) scanf("%lld",&a[i]);
	ll l=0,r=inf,w=r;
	for(int i=0;i<n;++i) l=max(l,a[i]+a[i+n]);
	while(l<=r) {
		ll mid=(l+r)>>1;
		if(chk(mid)) r=mid-1,w=mid;
		else l=mid+1;
	}
	printf("%lld\n",w);
	return 0;
}



E. [ARC124F] Meet

Problem Link

題目大意

\(n\times m\) 網格中,A 要從 \((1,1)\) 走到 \((n,m)\),B 要從 \((n,1)\) 走到 \((1,m)\)

每次操作可以讓 A 往下或往右,B 往上或往右,求有多少種操作序列使得 A,B 恰有一個時刻重合。

資料範圍:\(n,m\le 2\times 10^5\)

思路分析

先考慮兩個人在 \((i,j)\) 重合的方案數,同時走到 \((i,j)\) 的方案數就是 \(\binom{n-1+2(j-1)}{i-1,n-i,j-1,j-1}\),從 \((i,j)\) 出發同時走到終點的方案數就是 \(\binom{n-1+2(m-j)}{i-1,n-i,m-j,m-j}\)

然後考慮容斥,先求出至少重合一次的方案數,那麼在相鄰的兩次相遇點之間連邊,答案再減去邊數即可。

顯然兩次相遇一定在同一行,那麼列舉 \((i,j),(i,k)\),從 \(j\to k\) 不重合的方案數相當於 \((j,j)\to (k,k)\) 不經過 \(y=x\) 直線,即 \(2\mathrm{Cat}_{k-j-1}\)

答案還要乘以 \((i,j),(i,k)\) 分別作為起點、終點的方案數。

容易發現 \(i\) 對答案無影響,最後乘以 \(\dfrac{1}{(i-1)!^2(n-i)!^2}\) 即可。

然後我們要容斥掉重合次數 \(>1\) 的方案,即至少存在一條邊的方案數,先對每條邊產生 \(-1\) 的貢獻,在對長度為 \(2\) 的鏈 \(j\to x\to k\) 的方案計數再減掉。

對於這種情況,\(j\to x\to k\) 的方案數就是 \(2\mathrm{Cat}_{i-1}\) 自卷積的結果。

我們最後能求出所有 \((i,j)\to (i,k)\) 總的貢獻係數是一個和 \(k-j\) 有關的值,卷積一次即可。

時間複雜度 \(\mathcal O(n+m\log m)\)

程式碼呈現

#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353,N=1<<20;
int fac[N],ifac[N];
namespace P {
const int G=3;
int rev[N],inv[N],w[N<<1];
int ksm(int a,int b=MOD-2) {
	int ret=1;
	for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) ret=1ll*ret*a%MOD;
	return ret;
}
void poly_init() {
	inv[1]=1;
	for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
	fac[0]=ifac[0]=1;
	for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
	for(int k=1;k<=N;k<<=1) {
		int x=ksm(G,(MOD-1)/k); w[k]=1;
		for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
	}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y;  }
void ntt(int *f,bool idft,int n) {
	for(int i=0;i<n;++i) {
		rev[i]=(rev[i>>1]>>1);
		if(i&1) rev[i]|=n>>1;
	}
	for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
	for(int k=2,x,y;k<=n;k<<=1) {
		for(int i=0;i<n;i+=k) {
			for(int j=i;j<i+k/2;++j) {
				x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
				f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
			}
		}
	}
	if(idft) {
		reverse(f+1,f+n);
		for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
	}
}
void poly_mul(const int *f,const int *g,int *h,int n,int m) {
	static int a[N],b[N];
	for(int i=0;i<n;++i) a[i]=f[i];
	for(int i=0;i<m;++i) b[i]=g[i];
	int len=plen(n+m-1);
	ntt(a,0,len),ntt(b,0,len);
	for(int i=0;i<len;++i) h[i]=1ll*a[i]*b[i]%MOD;
	ntt(h,1,len);
	memset(a,0,sizeof(int)*len);
	memset(b,0,sizeof(int)*len);
}
}
int n,m,f[N],g[N],h[N],c[N],d[N];
signed main() {
	P::poly_init();
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i) c[i]=2ll*fac[2*i-2]*ifac[i]%MOD*ifac[i-1]%MOD;
	P::poly_mul(c,c,d,m+1,m+1);
	f[0]=1;
	for(int i=1;i<=m;++i) f[i]=(d[i]+2ll*(MOD-c[i]))%MOD;
	for(int j=1;j<=m;++j) g[j]=1ll*fac[n-1+2*(j-1)]*ifac[j-1]%MOD*ifac[j-1]%MOD;
	P::poly_mul(f,g,h,m+1,m+1);
	int ans=0,sum=0;
	for(int i=1;i<=n;++i) sum=(sum+1ll*ifac[i-1]*ifac[i-1]%MOD*ifac[n-i]%MOD*ifac[n-i])%MOD;
	for(int j=1;j<=m;++j) ans=(ans+1ll*h[j]*fac[n-1+2*(m-j)]%MOD*ifac[m-j]%MOD*ifac[m-j])%MOD;
	printf("%lld\n",1ll*ans*sum%MOD);
	return 0;
}



*F. [AGC057E] Sort

Problem Link

題目大意

給定一個 \(n\times m\) 矩陣 \(B\),求有多少個矩陣 \(A\) 同時滿足:

  • 依次對 \(A\) 每一行、每一列排序最終得到 \(B\)
  • 依次對 \(A\) 每一列、每一行排序最終得到 \(B\)

資料範圍:\(n,m\le 1500,B_{i,j}\le 9\)

思路分析

考慮 \(B_{i,j}\in\{0,1\}\) 如何做。

那麼對 \(A\) 每行每列排序,相當於把 \(A\) 的每一列按 \(0\) 的個數升序排列,每列每行同理。

因此 \(A\) 合法當且僅當 \(A\) 每行每列 \(0\) 的個數構成的可重集和 \(B\) 相同。

進一步一定存在排列 \(p_n,q_m\) 使得 \(B_{p_i,q_i}=A_{i,j}\)(把 \(B\) 的每行每列根據 \(0\) 的個數一一對應到 \(A\) 的行列上)。

回到原問題,可以證明 \(B\) 合法當且僅當對於每個 \(k\),把 \(\le k\) 的看成 \(0\),剩餘看成 \(1\) 後合法。

設我們每次取出的排列為 \(p^k_n,q^k_m\),那麼我們要求 \(B(p^k_i,q^k_j)\le k\implies B(p^{k+1}_i,q_{j}^{k+1})\le k+1\)

然後對所有 \(p,q\) 計數,但是對於每個 \(k\),行和相同的列是本質相同的,要除以對應的排列數。

我們計數 \(p^k\) 可以轉成計數 \(p^k\times \mathrm{inv}(p^{k-1})\),因此要求變成 \(B(i,j)\le k\implies B(p^k_i,q^k_j)\le k+1\),這樣每層的方案都是獨立的。

由於 \(B\) 一定是楊表,因此設 \(r^k_{i},c^k_j\) 表示此時第 \(i\) 行、第 \(j\) 列的 \(0\) 的個數,那麼 \(B(i,j)\le k\iff j\le r^k_i\iff i\le r^k_j\)

那麼對於 \(j\in[1,r_i^k]\) 都有 \(p_i^{k}\le c(q_j^k)\),由於 \(c\) 單調遞減,因此等價於 \(p_{i}^k\le c(\max_{j=1}^{r_i^k}q_j^k)\)

因此可以 dp,設 \(f_{i,j}\) 表示 \(\max q^k_1\sim q^k_i=j\) 的方案數,轉移時考慮 \(q_i\) 是否是最大值,並且把 \(r_x^k=i\)\(p_x^k\) 確定取值,由於每個 \(p\) 的取值範圍單調減小,因此當前 \(p_x^k\) 方案數就是 \(\max(0,j-x+1)\)

時間複雜度 \(\mathcal O(nmB)\)

程式碼呈現

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1505,MOD=998244353;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int n,m,r[10][MAXN],c[10][MAXN],a[MAXN][MAXN],cnt[MAXN];
ll f[MAXN][MAXN],fac[MAXN],ifac[MAXN];
signed main() {
	for(int i=fac[0]=ifac[0]=1;i<MAXN;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
		scanf("%d",&a[i][j]);
		for(int k=a[i][j];k<10;++k) ++r[k][i],++c[k][j];
	}
	ll ans=1;
	for(int k=0;k<9;++k) {
		memset(f,0,sizeof(f)),f[0][0]=1;
		int p=n;
		for(;p&&!r[k][p];--p);
		for(int i=1;i<=m;++i) {
			ll s=f[i-1][0];
			for(int j=1;j<=m;++j) {
				f[i][j]=(f[i-1][j]*(j-i+1)+s)%MOD;
				s=(s+f[i-1][j])%MOD;
			}
			for(;p&&r[k][p]==i;--p) {
				for(int j=1;j<=m;++j) f[i][j]=f[i][j]*max(0,c[k+1][j]-p+1)%MOD;
			}
		}
		ans=ans*f[m][m]%MOD;
		for(int i=1;i<=n;++i) if(r[k][i]) ++cnt[r[k][i]];
		for(int i=1;i<=m;++i) ans=ans*ifac[cnt[i]]%MOD,cnt[i]=0;
		for(int i=1;i<=m;++i) ++cnt[c[k][i]];
		for(int i=0;i<=n;++i) ans=ans*ifac[cnt[i]]%MOD,cnt[i]=0;
	}
	printf("%lld\n",ans);
	return 0;
}