首页 > 其他分享 >2024 Noip 做题记录(四)

2024 Noip 做题记录(四)

时间:2024-10-07 12:24:01浏览次数:8  
标签:le Noip 记录 int dfrac ll 2024 MAXN MOD


\(\text{By DaiRuiChen007}\)



Round #13 - 2024.10.1

A. [ARC165D] Compare

Problem Link

题目大意

判断是否存在一个长度为 \(n\) 的字符串 \(S\),满足 \(m\) 个限制 \((a,b,c,d)\),要求 \(S[a,b]\) 字典序小于 \(S[c,d]\)。

数据范围:\(n,m\le 2000\)。

思路分析

从一些必要条件开始,对每个限制显然有 \(S_a\le S_c\),那么考虑建出拓扑关系图,连边 \(a\to c\)。

在这张拓扑图上,如果有环,那么说明环上元素必须相等,因此可以把每个强联通分量都缩点。

注意到缩点后相当于确定若干 \(S_a=S_c\),那么 \(S[a,b]<S[c,d]\) 的必要条件就是 \(S[a+1,b]<S[c+1,d]\),即找到最小的 \(i\) 使得未钦定 \(S_{a+i}=S_{c+i}\),连边 \(a+i\to c+i\) 即可,容易发现这也是必要的。

如果某个时刻 \(S[c,d]\) 已经变成 \(S[a,b]\) 前缀说明无解,否则图中已经无环说明有解。

每次 Tarjan 缩点至少合并两个位置,因此合并至多进行 \(n+1\) 轮。

时间复杂度 \(\mathcal O(n(n+m))\)。

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005;
int n,m,a[MAXN],b[MAXN],c[MAXN],d[MAXN],dsu[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
vector <int> G[MAXN];
int dfn[MAXN],low[MAXN],dcnt,stk[MAXN],tp;
bool ins[MAXN],ok;
void tarjan(int u) {
	dfn[u]=low[u]=++dcnt,ins[stk[++tp]=u]=true;
	for(int v:G[u]) {
		if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
		else if(ins[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]) {
		ok&=stk[tp]==u;
		while(ins[u]) dsu[find(u)]=find(stk[tp]),ins[stk[tp--]]=false;
	}
}
signed main() {
	scanf("%d%d",&n,&m),iota(dsu+1,dsu+n+1,1);
	for(int i=1;i<=m;++i) scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
	while(true) {
		for(int i=1;i<=n;++i) G[i].clear(),dfn[i]=low[i]=0;
		for(int i=1;i<=m;++i) {
			while(a[i]<=b[i]&&c[i]<=d[i]&&find(a[i])==find(c[i])) ++a[i],++c[i];
			if(c[i]>d[i]) return puts("No"),0;
			if(a[i]<=b[i]) G[find(a[i])].push_back(find(c[i]));
		}
		ok=true,dcnt=0;
		for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
		if(ok) break;
	}
	puts("Yes");
	return 0;
}



B. [ARC169D] Modify

Problem Link

题目大意

给定 \(a_1\sim a_n\),每次操作可以给 \(m\) 个不同的 $ a_i$ 加一(在 \(\bmod n\) 意义下),求至少几次操作能让 \(a\) 变成 \(0\sim n-1\) 的排列。

数据范围:\(n\le 2.5\times 10^5\)。

思路分析

考虑在不 \(\bmod n\) 意义下确定最终的 \(b_i\),要满足如下条件:

  • \(a_i\le b_i\)。
  • \(b_i\bmod n\) 互不相同。
  • 记 \(S=\sum b_i-a_i\),那么 \(m\mid S\)。
  • \(\max \{b_i-a_i\}\le \dfrac Sm\)。

首先只有第一个条件和 \(b\) 的相对顺序有关,为了满足这个条件,显然同时将 \(a,b\) 升序排列最优。

然后考虑第四个条件,对于一组合法解 \(b_1\sim b_n\),如果 \(b_n-b_1\ge n\),那么调整 \(b_1\gets b_n-n,b_n\gets b_1+n\)。

显然不影响条件一、二、三的合法性,且此时 \(b_n-a_n\) 会变小,且 \(b'_1-a_1=b_n-a_1-n<b_n-a_n\),因此 \(\max\{b_i-a_i\}\) 也会变小。

因此这样调整一定更优,最终一定有 \(b_n-b_1<n\),即 \(b_i\) 是连续的一段,存在一个 \(x\) 使得 \(b_i=x+i-1\)。

那么逐个满足条件即可,先找到 \(\max a_i-i+1\),然后调整到最近的 \(x\) 使得 \(\dfrac{n(2x+n-1)}2\equiv \sum a_i\pmod m\)。

找到此时的 \(k=\max\{b_i-a_i\}\),如果 \(k> \dfrac Sm\),那么接下来就会进行若干次调整,每次会令 \(x\) 加上 \(d=\dfrac{m}{\gcd(n,m)}\)。

此时 \(k\) 加上 \(\dfrac m{\gcd(n,m)}\),且 \(\dfrac Sm\) 加上 \(\dfrac{n}{\gcd(n,m)}\),由于 \(n>m\) 因此这个调整总能结束。

时间复杂度 \(\mathcal O(n\log n)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2.5e5+5;
int n,m,a[MAXN],g;
ll sum,k=0;
signed main() {
	scanf("%d%d",&n,&m),g=__gcd(n,m);
	for(int i=0;i<n;++i) scanf("%d",&a[i]),sum+=a[i];
	if((1ll*n*(n-1)/2-sum)%g) return puts("-1"),0;
	sort(a,a+n);
	for(int i=0;i<n;++i) k=max(k,(ll)a[i]-i);
	sum=(2*k+n-1)*n/2-sum;
	while(sum%m) ++k,sum+=n;
	ll mx=0,d=m/__gcd(n,m),c=n*d/m;
	for(int i=0;i<n;++i) mx=max(mx,k+i-a[i]);
	if(mx>sum/m) {
		ll cur=(mx-sum/m-1)/(c-d)+1;
		printf("%lld\n",sum/m+cur*c);
	} else printf("%lld\n",sum/m);
	return 0;
}



C. [ARC168E] Devide

Problem Link

题目大意

给定 \(a_1\sim a_n\),将其分成 \(k\) 个非空区间,最大化元素和 \(\ge s\) 的区间个数。

数据范围:\(n\le 2.5\times 10^5\)。

思路分析

作为一道数列分段题,先观察代价函数,发现 \(w(l,r)=[\sum_{i=l}^r a_i\ge s]\) 不具有四边形不等式,答案也没有凸性。

但我们可以进行一些观察,对于元素和 \(<s\) 的区间,很显然只要长度为 \(1\) 保证非空即可。

因此可以二分,假设有 \(x\) 个区间 \(\ge s\),那么他们占据的总长度不超过 \(n-(k-x)\)。

我们只要计算分 \(x\) 个 \(\ge s\) 的段时,区间总长的最小值,发现最小总长关于 \(x\) 有凸性,此时就可以 wqs 二分了。

时间复杂度 \(\mathcal O(n\log^2n)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2.5e5+5;
int n,k,pre[MAXN];
ll S,sum[MAXN];
array <ll,2> dp[MAXN];
array<ll,2> wqs(ll p) {
	dp[0]={0,0};
	for(int i=1;i<=n;++i) {
		dp[i]=dp[i-1];
		if(~pre[i]) dp[i]=min(dp[i],{dp[pre[i]][0]+i-pre[i]-p,dp[pre[i]][1]+1});
	}
	return dp[n];
}
bool chk(int x) {
	int l=1,r=n,z=0;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(wqs(mid)[1]<=x) z=mid,l=mid+1;
		else r=mid-1;
	}
	return wqs(z)[0]+1ll*x*z<=n-(k-x);
}
signed main() {
	scanf("%d%d%lld",&n,&k,&S);
	for(int i=1;i<=n;++i) scanf("%lld",&sum[i]),sum[i]+=sum[i-1];
	for(int i=1,j=-1;i<=n;++i) {
		while(sum[i]-sum[j+1]>=S) ++j;
		pre[i]=j;
	}
	int l=1,r=k,z=0;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(chk(mid)) z=mid,l=mid+1;
		else r=mid-1;
	}
	printf("%d\n",z);
	return 0;
}



D. [ARC170E] Expand

Problem Link

题目大意

在一个长度为 \(n\) 的环进行 bfs,初始在点 \(1\),每次取出当前点相邻的所有未访问过的点,按编号大小顺序更新最短路,并依次加入队列,其中每个点有 \(p\) 的概率加入队首,有 \(1−p\) 的概率加入队尾。求所有点的距离之和的期望。

数据范围:\(n\le 10^{18}\)。

思路分析

注意到任何时候队里只有两个元素,每次会更新队首元素的后继,然后以 \(1-p\) 的概率交换队首队尾,进行这样的操作 \(n-1\) 次。

那么可以 dp,设 \(f_i/g_i\) 表示拓展 \(i\) 步后队首或队尾元素距离的期望,答案就加上 \(f_i+1\),更新就是 \(f_{i+1}=p(f_i+1)+(1-p)g_i,g_{i+1}=(1-p)(f_i+1)+pg_i\)。

不难用一个 \(4\times 4\) 的矩阵描述 \(f_i,g_i,1,ans\) 的转移,矩阵快速幂优化 dp 即可。

时间复杂度 \(\mathcal O(\delta^3\log n)\),其中 \(\delta=4\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef array<array<ll,4>,4> Mat;
const int MOD=998244353,inv=828542813;
inline Mat operator *(const Mat &u,const Mat &v) {
	Mat w; w.fill({0,0,0,0});
	for(int i:{0,1,2,3}) for(int k:{0,1,2,3}) if(u[i][k]) for(int j:{0,1,2,3}) {
		w[i][j]=(w[i][j]+u[i][k]*v[k][j])%MOD;
	}
	return w;
}
void solve() {
	ll n,p;
	scanf("%lld%lld",&n,&p),--n,p=inv*p%MOD;
	Mat I,X; //f g 1 ans
	I.fill({0,0,0,0}),X.fill({0,0,0,0});
	I[0][0]=p,I[0][1]=1+MOD-p,I[0][3]=1;
	I[1][0]=1+MOD-p,I[1][1]=p;
	I[2][0]=p,I[2][1]=1+MOD-p,I[2][2]=1,I[2][3]=1;
	I[3][3]=1,X[0][2]=1;
	for(;n;n>>=1,I=I*I) if(n&1) X=X*I;
	printf("%lld\n",X[0][3]);
}
signed main() {
	int T; scanf("%d",&T);
	while(T--) solve();
	return 0;
}



*E. [ARC169F] Table

Problem Link

题目大意

给定 \(1\sim 2n\) 排列 \(a_1\sim a_n,b_1\sim b_n\),定义 \(f_{i,j}=\begin{cases}f_{i,j-1}+x_i&a_i<b_j\\f_{i-1,j}+y_j&\mathrm{otherwise}\end{cases}\),其中 \(f_{1,1}=0\),求 \(\sum_{i,j} f_{i,j}\)。

数据范围:\(n\le 2.5\times 10^5\)。

思路分析

题目相当于给出一个 \(n\times n\) 的转移网格,每个点从左侧或上侧转移并附带一个额外权值。

考虑 \(x_i\) 在 \((i,j)\) 处的贡献,如果 \(a_i<b_j\),那么我们就是要数 \((i,j)\) 右下方有多少个点和 \((i,j)\) 连通。

不难发现如果 \((i,j)\) 和 \((p,q)\) 不连通,一定是存在一行或一列的边都不存在。

分析发现如果某一行 \(a_k\) 满足 \(a_k<\min b(j,q]\),那么 \(k\) 这一行就不会和之前的点连通,如果某一列 \(b_k\) 满足 \(b_k<\min a(i,p]\),那么 \(k\) 这一列就不会和之前的点连通。

因此 \(a[i,p],b[j,q]\) 中的最小值一定是 \(a_i\) 或 \(b_j\),由于钦定了 \(a_i<b_j\),因此 \(x_i\) 在 \((i,j)\) 处对 \((p,q)\) 有贡献当且仅当 \(a_i<\min b[j,q]<\min a(i,p]\)。

我们要对这样的区间计数,设 \(f_w\) 表示 \(\min b[j,q]\le w\) 的元素个数,可以用单调栈预处理。

然后就是要求 \(x_i\sum_p f_{\min a(i,p]}-f_{a_i}\),注意到 \(\min a(i,p]\) 一定是 \(a[i,n]\) 的后缀单调栈上的元素,并且因为 \(\min a(i,p]>a_i\),因此一定是后缀单调栈上被 \(a_i\) 弹出的元素,答案是容易计算的,加上 \(p=i\) 的贡献即可。

时间复杂度 \(\mathcal O(n)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2.5e5+5,MOD=998244353;
int n,stk[MAXN],tp;
void solve(int *a,int *l,int *r,ll *f) {
	stk[tp=0]=0;
	for(int i=1;i<=n;++i) {
		while(tp&&a[stk[tp]]>a[i]) r[stk[tp--]]=i;
		l[i]=stk[tp],stk[++tp]=i;
	}
	while(tp) r[stk[tp--]]=n+1;
	for(int i=1;i<=n;++i) f[a[i]]=1ll*(r[i]-i)*(i-l[i])%MOD;
	for(int i=2*n;i;--i) f[i]=(f[i]+f[i+1])%MOD;
}
int a[MAXN],b[MAXN],la[MAXN],ra[MAXN],lb[MAXN],rb[MAXN],wa[MAXN],wb[MAXN];
ll fa[MAXN<<1],fb[MAXN<<1];
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=1;i<=n;++i) scanf("%d",&b[i]);
	for(int i=1;i<=n;++i) scanf("%d",&wa[i]);
	for(int i=1;i<=n;++i) scanf("%d",&wb[i]);
	solve(a,la,ra,fa),solve(b,lb,rb,fb);
	ll ans=0;
	stk[tp=0]=n+1;
	for(int i=n;i>=1;--i) {
		for(;tp&&a[stk[tp]]>a[i];--tp) {
			ans+=wa[i]*(fb[a[i]]-fb[a[stk[tp]]])%MOD*(stk[tp-1]-stk[tp])%MOD;
		}
		ans+=wa[i]*fb[a[i]]%MOD,stk[++tp]=i;
	}
	stk[tp=0]=n+1;
	for(int i=n;i>=1;--i) {
		for(;tp&&b[stk[tp]]>b[i];--tp) {
			ans+=wb[i]*(fa[b[i]]-fa[b[stk[tp]]])%MOD*(stk[tp-1]-stk[tp])%MOD;
		}
		ans+=wb[i]*fa[b[i]]%MOD,stk[++tp]=i;
	}
	ans-=1ll*n*n%MOD*wa[1]%MOD;
	printf("%lld\n",(ans%MOD+MOD)%MOD);
	return 0;
}




Round #14 - 2024.10.2

A. [ARC172D] Distance

Problem Link

题目大意

给定 \(n\) 个 \(n\) 维空间中的点,已知他们两两之间距离的大小关系(不存在相等的距离),在 \([1,10^8]\) 范围内构造一组可能的方案。

数据范围:\(n\le 20\)。

思路分析

先考虑如何构造若干个距离两两相等的点,可以设 \(x_{i,i}=1\),其他 \(x_{i,j}=0\),这样两两距离都为 \(1\)。

然后考虑调整,如果给某个 \(x_{i,j}\) 加上 \(\epsilon\),那么 \(\mathrm{dis}(x_i,x_j)\) 减去 \(2\epsilon-\epsilon^2\),其他 \(\mathrm{dis}(x_i,x_k),\mathrm{dis}(x_j,x_k)\) 变化量都是 \(\epsilon^2\)。

因此我们发现当 \(\epsilon\) 足够小的时候,\(\epsilon^2\) 可以完全忽略不计,此时就相当于让 \(\mathrm{dis}(x_i,x_j)\) 小于其他的距离。

我们可以取 \(x_{i,i}=10^8\),然后距离最大的一对点 \((i,j)\) 取 \(x_{i,j}=1\),次大的取 \(x_{i,j}=2\),其他的以此类推。

容易发现 \(\epsilon^2\) 项的累计误差最大值接近 \(\dfrac {n^6}6\),小于 \(10^8\),因此这样的构造是合法的。

时间复杂度 \(\mathcal O(n^2)\)。

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int K=1e8;
int n,a[25][25];
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) a[i][i]=K;
	for(int i=n*(n-1)/2,x,y;i;--i) scanf("%d%d",&x,&y),a[x][y]=i;
	for(int i=1;i<=n;++i,puts("")) for(int j=1;j<=n;++j) printf("%d ",a[i][j]);
	return 0;
}



*B. [ARC139E] Grid

Problem Link

题目大意

给定 \(n\times m\) 循环网格(第 \(1,n\) 行、第 \(1,m\) 列相邻),求有多少种选出格子数最多的方案,使得选出的格子互不相邻。

数据范围:\(n\le 10^5,m\le 10^{10}\)。

思路分析

如果 \(n,m\) 都是偶数,那么显然只有 \(2\) 种方案(黑白间隔染色)。

否则不妨设 \(n\) 是偶数 \(m\) 是奇数,此时最多选 \(n\times \dfrac{m-1}2\) 个格子,如果都是奇数。

那么设 \(n\le m\),选出格子最多也是 \(n\times \dfrac {m-1}2\) 个。

这是显然的,因为每一行 \(m\) 个格子至多选 \(\dfrac{m-1}2\) 个。

然后考虑如何数方案数,注意到每一行的未选格子都是间隔排列,但是有两个位置是相邻的,即未被选的格子排列形如:$\dots,x-4,x-2,x,x+1,x+3,x+5,\dots $。

不妨设第 \(i\) 行未选格子包含 \(x_i,x_i+1\),那么我们发现一组方案合法当且仅当 \(\forall i\in[1,n]\) 都有 \(|x_i-x_{i+1}|=1\)。

因此我们只要数有多少长度为 \(n\) 的 \(\pm 1\) 序列元素总和是 \(m\) 的倍数。

由于我们钦定 \(n\) 是偶数,因此不能保证 \(n\le 10^5\)。

  • 如果 \(n>10^5\),此时 \(m\le 10^5\),所求相当于 \([z^0](z+z^{-1})^n\bmod{z^m-1}\),直接多项式快速幂计算。
  • 如果 \(n\le 10^5\),枚举序列中 \(+1\) 个数,组合数计算即可。

时间复杂度 \(\mathcal O(n\log n\log m)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MOD=998244353;
namespace P {
const int N=1<<18,G=3;
int rev[N],inv[N],fac[N],ifac[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 namespace P;
int f[N],g[N],h[N];
signed main() {
	ll n,m; scanf("%lld%lld",&n,&m),poly_init();
	if(n%2==0&&m%2==0) return puts("2"),0;
	if(m%2==0||(n%2==1&&m%2==1&&n<m)) swap(n,m);
	if(n<MAXN) {
		int ans=0;
		for(int i=0;i<=n;++i) if(i%m==(n-i)%m) ans=(ans+1ll*fac[n]*ifac[i]%MOD*ifac[n-i])%MOD;
		return printf("%lld\n",m%MOD*ans%MOD),0;
	}
	g[1]=g[m-1]=f[0]=1;
	for(;n;n>>=1) {
		ntt(g,0,N);
		if(n&1) {
			ntt(f,0,N);
			for(int i=0;i<N;++i) h[i]=1ll*f[i]*g[i]%MOD;
			ntt(h,1,N);
			memset(f,0,sizeof(f));
			for(int i=0;i<N;++i) f[i%m]=(f[i%m]+h[i])%MOD;
		}
		memset(h,0,sizeof(h));
		for(int i=0;i<N;++i) h[i]=1ll*g[i]*g[i]%MOD;
		ntt(h,1,N);
		memset(g,0,sizeof(g));
		for(int i=0;i<N;++i) g[i%m]=(g[i%m]+h[i])%MOD;
	}
	printf("%lld\n",m%MOD*f[0]%MOD);
	return 0;
}



C. [ABC370G] Product

Problem Link

题目大意

求有多少长度为 \(m\) 的序列元素乘积 \(S\) 不超过 \(n\),且 \(S\) 的因数个数和是 \(3\) 的倍数。

数据范围:\(n\le 10^{10},m\le 10^5\)。

思路分析

假设 \(S\) 已知,考虑有多少这样的序列,很显然只需要分配每个质因子的质数到 \(m\) 个位置即可。

设答案为 \(f(S)\),那么 \(f(S)\) 是积性函数且 \(f(p^c)=\binom{c+m-1}{m-1}\)。

我们要求的就是积性函数 \(f(S)\) 在 \([1,n]\) 处的前缀和,注意到 \(f(p)=m\),因此可以用 Min25 筛计算。

预处理出 \(1\sim \lfloor n/t\rfloor\) 中的质数个数,即可知道 \(f(p)\) 在 \(\lfloor n/t\rfloor\) 处的质数前缀和。

然后要考虑 \(S\) 的因数个数和是 \(3\) 的倍数,只要存在 \(S\) 的一个质因子 \(p^c\) 满足 \(3\mid 1+p^1+\cdots+p^c\) 即可。

这并不好维护,但我们可以反面考虑计算因数个数和不是 \(3\) 倍数的 \(f'(S)\),那么 \(f'(p^c)=[3\nmid 1+p^1+\cdots+p^c]f(p^c)\)。

还是要处理 \(f'(p)\) 在 \(\lfloor n/t\rfloor\) 处的质数前缀和,也就是 \(1\sim \lfloor n/t\rfloor\) 中有多少 \(\bmod 3\ne 2\) 的质数。

直接设 \(g(p)=[p\bmod 3\ne 2]\) 的话,构造出的完全积性函数不好维护前缀和。

考虑作差,即计算 \(1\sim \lfloor n/t\rfloor\) 中 \(\bmod 3=1\) 和 \(\bmod 3=2\) 质数个数之差,\(\bmod 3= 0\) 的质数显然只有 \(3\),容易处理。

那么 \(g(p)\) 在 \(p\bmod 3= 2\) 的时候为 \(-1\),其他时候为 \(p\bmod 3\),容易发现构造出完全积性函数后也满足这个规律,维护前缀和是容易的,然后就能带入 Min25 筛的过程求解了。

时间复杂度 \(\mathcal O\left(\dfrac{n^{3/4}}{\log n}\right)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=998244353,r[3]={0,1,-1},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; }
bool isc[MAXN];
int p[MAXN],tot,idx1[MAXN],idx2[MAXN],m;
ll n,q,B,v[MAXN],fp2[MAXN],g1[MAXN],g2[MAXN],h[45];
int id(ll x) { return x<=B?idx1[x]:idx2[n/x]; }
ll S1(ll N,int k) {
	if(p[k]>N) return 0;
	ll s=(g1[id(N)]+MOD-k)%MOD*q%MOD;
	for(int i=k+1;i<=tot&&1ll*p[i]*p[i]<=N;++i) for(ll c=1,w=p[i];w<=N;++c,w*=p[i]) {
		s=(s+h[c]*((c>1)+S1(N/w,i)))%MOD;
	}
	return s;
}
ll S2(ll N,int k) {
	if(p[k]>N) return 0;
	ll s=(g2[id(N)]+MOD-fp2[k])%MOD*q%MOD;
	for(int i=k+1;i<=tot&&1ll*p[i]*p[i]<=N;++i) for(ll c=1,w=p[i];w<=N;++c,w*=p[i]) {
		if((w*p[i]-1)/(p[i]-1)%3) s=(s+h[c]*((c>1)+S2(N/w,i)))%MOD;
	}
	return s;
}
signed main() {
	scanf("%lld%lld",&n,&q),B=sqrt(n);
	for(int i=h[0]=1;i<=40;++i) h[i]=h[i-1]*(q+i-1)%MOD*ksm(i)%MOD;
	for(int i=2;i<=B;++i) {
		if(!isc[i]) p[++tot]=i;
		for(int j=1;j<=tot&&i*p[j]<=B;++j) {
			isc[i*p[j]]=true;
			if(i%p[j]==0) break;
		}
	}
	for(int i=1;i<=tot;++i) fp2[i]=fp2[i-1]+r[p[i]%3];
	for(ll l=1;l<=n;l=n/v[m]+1) {
		v[++m]=n/l;
		if(v[m]<=B) idx1[v[m]]=m;
		else idx2[n/v[m]]=m;
	}
	for(int i=1;i<=m;++i) {
		g1[i]=(v[i]-1)%MOD;
		g2[i]=((v[i]-1)/3+MOD-(v[i]+1)/3)%MOD;
	}
	for(int k=1;k<=tot;++k) for(int i=1;i<=m&&1ll*p[k]*p[k]<=v[i];++i) {
		g1[i]=(g1[i]+(k-1)+MOD-g1[id(v[i]/p[k])])%MOD;
		g2[i]=(g2[i]+MOD+r[p[k]%3]*(fp2[k-1]+MOD-g2[id(v[i]/p[k])]))%MOD;
	}
	for(int i=1;i<=m;++i) g2[i]=(g1[i]+g2[i]+(v[i]>=3))*i2%MOD;
	for(int i=1;i<=tot;++i) fp2[i]=fp2[i-1]+(p[i]%3!=2);
	printf("%lld\n",(S1(n,0)+MOD-S2(n,0))%MOD);
	return 0;
}



*D. [ARC138E] Decrease

Problem Link

题目大意

给定 \(n,k\),定义一个序列 \(a_1\sim a_n\) 是好的当且仅当:

  • \(a_i\in[0,i]\)。
  • 如果 \(a_i,a_j>0\),那么 \(a_i\ne a_j\)。

求所有好的序列 \(a\) 的长度为 \(k\) 且不包含 \(0\) 的下降子序列个数之和。

数据范围:\(n\le 5000\)。

思路分析

考虑对于 \(a_i>0\) 的元素连边 \(i\to a_{i}-1\),由于 \(a_i,a_j>0\) 时一定有 \(a_i\ne a_j\),因此不存在 \(i,j\) 前驱相同。

那么连边之后会把 \(0\sim n\) 分成若干条链,而我们要求的下降子序列就是若干条起点递增,终点递减的边。

很显然这些边不可能来自于同一条链,并且这些边存在两两包含关系,此时最内层的边就会把这 \(k\) 条链分成左右两部分。

枚举这 \(k\) 条链左右两侧的大小 \(i,j\),方案数可以用第二类斯特林数计算得到:

\[\sum_{i,j}\binom{n+1}{i+j}\begin{Bmatrix}i\\k\end{Bmatrix}\begin{Bmatrix}j\\k\end{Bmatrix}\times w_{n+1-i-j} \]

其中 \(w_i\) 是 \(i\) 个点划分成若干条链的方案数,即第二类斯特林数第 \(i\) 行的和。

时间复杂度 \(\mathcal O(n^2)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5005,MOD=1e9+7;
ll S[MAXN][MAXN],w[MAXN],C[MAXN][MAXN];
signed main() {
	int n,k;
	scanf("%d%d",&n,&k);
	for(int i=0;i<=n+1;++i) {
		w[i]=S[i][0]=!i,C[i][0]=1;
		for(int j=1;j<=i;++j) {
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
			S[i][j]=(S[i-1][j-1]+S[i-1][j]*j)%MOD;
			w[i]=(w[i]+S[i][j])%MOD;
		}
	}
	ll ans=0;
	for(int i=k;i<=n+1;++i) for(int j=k;i+j<=n+1;++j) {
		ans=(ans+C[n+1][i+j]*S[i][k]%MOD*S[j][k]%MOD*w[n+1-i-j])%MOD;
	}
	printf("%lld\n",ans);
	return 0;
}



*E. [ARC138F] Split

Problem Link

题目大意

给定 \(n\) 个点 \((1,a_1)\sim (n,a_n)\),\(a\) 是 \(1\sim n\) 排列,你要对这个点集进行若干次操作,得到一个序列。

如果 \(n=1\),序列中只含有这个点,否则选择如下两种操作之一:

  • 选择一个 \(x\),把 \(i<x\) 的点操作得到的序列和 \(x\le i\) 的点操作得到的序列拼起来。
  • 选择一个 \(y\),把 \(a_i<y\) 的点操作得到的序列和 \(y\le a_i\) 的点操作得到的序列拼起来。

求有能得到多少种不同的序列。

数据范围:\(n\le 30\)。

思路分析

计数操作序列是容易的,直接 \(f_{l,r,u,d}\) 表示 \(x,y\) 两维的范围,求出这个范围内的点列有多少种划分方式。

但我们无法把操作序列和结果序列建立对应,考虑对一个若干等价的操作序列定义代表元。

对于一组结果序列,我们定义其代表元为每次操作贪心地取字典序最小的一个时的操作序列,其中所有操作的字典序按如下顺序排列:\(x=2,y=2,x=3,\dots,x=n,y=n\)。

考虑 \(f_{1,n,1,n}\) 如何计算。

那么我们 dp 时枚举最小操作 \(x=i\),那么就会递归成 \(f_{1,i-1,1,n}\) 和 \(f_{i,n,1,n}\) 两部分,但我们要在 \(f_{1,i-1,1,n}\) 范围内钦定 \(x=i\) 是最小字典序操作,设方案数为 \(g_i\)。

不妨容斥,设 \(x=j\) 是实际最小字典序操作,那么最终的结果序列一定会被分成 \(x\in [1,j),x\in [j,i),x\in[i,n]\) 三部分,\(g_i\) 就要减去 \(f_{1,j-1,1,n}\times f_{j,n,1,n}\)。

同理如果 \(y=j\) 是最小字典序操作,此时一定有 \(j<i\),那么 \(y=j\) 时划分出的左部点集大小小于 \(x=i\) 时的左部点集。

如果想让得到的结果序列相同,则 \(y=j\) 时划分出的左部点集必须是 \(x=i\) 时划分出的左部点集的子集,类似可以划分子问题计算。

注意我们枚举的操作一定是非平凡操作,即 \(x/y\) 恰好是某个点的横坐标或纵坐标。

不断递归子问题即可,可以用记忆化搜索实现,用状压记录状态比较好写。

时间复杂度 \(\mathcal O(n^6)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=1e9+7;
int n,a[35];
unordered_map <int,ll> Q;
ll dp(int s) {
	if(Q.count(s)) return Q[s];
	if(__builtin_popcount(s)<=1) return 1;
	vector <int> xi,yi;
	for(int i=0;i<n;++i) if(s>>i&1) xi.push_back(i),yi.push_back(i);
	sort(yi.begin(),yi.end(),[&](int i,int j){ return a[i]<a[j]; });
	int k=xi.size();
	vector <int> st;
	for(int i=0,t=0;i<k-1;++i) {
		st.push_back(t|=1<<xi[i]);
	}
	for(int i=0,t=0;i<k-1;++i) {
		st.push_back(t|=1<<yi[i]);
	}
	vector <ll> f(2*k-2);
	ll ans=0;
	for(int i=0;i<2*k-2;++i) {
		f[i]=dp(st[i]);
		for(int j=0;j<i;++j) if((st[j]&st[i])==st[j]) f[i]=(f[i]-f[j]*dp(st[i]^st[j]))%MOD;
		if(f[i]<0) f[i]+=MOD;
		ans=(ans+f[i]*dp(s^st[i]))%MOD;
	}
	return Q[s]=ans;
}
signed main() {
	scanf("%d",&n);
	for(int i=0;i<n;++i) scanf("%d",&a[i]);
	printf("%lld\n",dp((1<<n)-1));
	return 0;
}



Round #15 - 2024.10.3

A. [ARC136E] Coprime

Problem Link

题目大意

构造一个 DAG,点集 \(1\sim n\),\(x\to y\in E\) 当且仅当 \(x<y\) 且 \(\gcd(x,y)>1\),求最大带权反链。

数据范围:\(n\le 10^6\)。

思路分析

注意到任意两个偶数之间都有连边,因此一个奇数 \(x\) 可以走到一个偶数 \(y\) 只需要把 \(x\) 移动到最接近的偶数上,两个奇数只要分别走到最近的偶数上即可。

形式化来说,\(x\) 能走到 \(y\) 当且仅当:

  • \(x,y\) 均为偶数,一定可以。
  • \(x\) 为奇数,\(y\) 为偶数,\(x+p_x\le y\)。
  • \(x\) 为偶数,\(y\) 为奇数,\(x\le y-p_y\).
  • \(x\) 为奇数,\(y\) 为奇数,\(x+p_x\le y-p_y\)。

其中 \(p_x\) 是 \(x\) 的最小质因子。

可以把限制统一看成 \(r_x<l_y\) 的形式,其中对于偶数 \(x\),\(l_x=r_x=x\),对于奇数 \(x\),\(l_x=x-p_x+1,r_x=x+p_x-1\),特判 \(x=1\)。

那么一组反链就是若干个 \([l_x,r_x]\) 有交的 \(x\) 构成的,对每个 \(i\) 计算 \(i\in[l_x,r_x]\) 的所有 \(x\) 的权值和即可。

时间复杂度 \(\mathcal O(n)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e6+5;
int n,m,a[MAXN],p[MAXN],d[MAXN];
bool isc[MAXN];
ll f[MAXN],ans=0;
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=2;i<=n;++i) {
		if(!isc[i]) p[++m]=i,d[i]=i;
		for(int j=1;j<=m&&i*p[j]<=n;++j) {
			isc[i*p[j]]=true,d[i*p[j]]=p[j];
			if(i%p[j]==0) break;
		}
	}
	f[1]+=a[1];
	for(int i=2;i<=n;++i) {
		if(i&1) f[i-d[i]+1]+=a[i],f[i+d[i]]-=a[i];
		else f[i]+=a[i],f[i+1]-=a[i];
	}
	for(int i=1;i<=2*n;++i) f[i]+=f[i-1],ans=max(ans,f[i]);
	printf("%lld",ans);
	return 0;
}



B. [ARC137E] Profit

Problem Link

题目大意

给定 \(n\) 个点,\(m\) 个操作,每个操作可以花费 \(c_i\) 的代价令 \([l_i,r_i]\) 区间内的 \(x_j\) 加一,收益为 \(d\times \sum_j \min(x_j,a_j)\),求最大收益减代价。

数据范围:\(n,m\le 2000\)。

思路分析

考虑费用流。

类似志愿者招募那题的思路,连一条 \(0\to n\) 的链,然后对于一个操作,连 \(l-1\to r\)。

这题我们考虑先给每个点选满,即答案记为 \(d\sum a_j\),然后求出最小费用最大流减掉。

然后对于 \(i-1\to i\) 的边,这条边每流过 \(1\) 的流量,就说明 \(x_i\) 减一了,连一条容量为 \(a_i\) 费用为 \(d\) 的边,再连一条容量为 \(m-a_i\) 费用为 \(0\) 的边,此时当流过的流量 \(>m-a_i\) 后,每 \(1\) 流量就会 \(-d\) 收益。

对于一条 \(l-1\to r\) 的边,流量为 \(1\),费用为 \(c\),表示选了这条边后可以让 \(l-1\to l\sim r-1\to r\) 这些边少被流一次,即 \(x_j\) 加一。

答案就是 \(d\sum a_j\) 减去这个网络上大小为 \(m\) 的最小费用流。

本题需要用用原始对偶算法求费用流。

时间复杂度 \(\mathcal O((n+m)^2\log m)\)。

代码呈现

#include<bits/stdc++.h>
#include<atcoder/mincostflow>
#define ll long long
using namespace std;
signed main() {
	int n,m,d;
	scanf("%d%d%d",&n,&m,&d);
	atcoder::mcf_graph <int,ll> G(n+1);
	ll ans=0;
	for(int i=1,A;i<=n;++i) {
		scanf("%d",&A),ans+=1ll*A*d;
		G.add_edge(i-1,i,A,d);
		G.add_edge(i-1,i,m-A,0);
	}
	for(int i=1,l,r,c;i<=m;++i) {
		scanf("%d%d%d",&l,&r,&c);
		G.add_edge(l-1,r,1,c);
	}
	printf("%lld\n",ans-G.flow(0,n,m).second);
	return 0;
}



*C. [ABC367G] Subset

Problem Link

题目大意

定义一个集合 \(S\) 的权值为其内部元素的异或和的 \(k\) 次方。

给定 \(a_1\sim a_n\),求其所有大小为 \(m\) 的倍数的子集的权值和。

数据范围:\(n\le 2\times 10^5,a_i<2^{20},m\le 100\)。

思路分析

考虑用生成函数的语言描述,很显然我们同时关心集合大小和元素异或和,因此可以用 \(F(x,z)=\prod (1+x^{a_i}z)\) 描述,其中 \(z\) 是形式幂级数,\(x\) 是集合幂级数。

不妨把 \(z\) 看成参数,那么这就是若干个关于 \(x\) 的集合幂级数做异或卷积。

考虑 FWT,那么 \(\mathrm{FWT}(x^{\varnothing}+x^{a_i}z)=\sum_s\left(1+(-1)^{\mathrm{popcount}(a_i\operatorname{AND}s)}z\right)x^s\)。

那么 \([x^s]\mathrm{FWT}(F)\) 就是若干 \(1+z\) 和 \(1-z\) 的乘积,具体来说就是 \(\sum_sx^s(1+z)^{w_s}(1-z)^{n-w_s}\)。

我们只要求出这些 \(w_s\),直接对 \(\sum x^{a_i}\) FWT 即可得到 \(w_s-(n-w_s)\) 然后就能算出 \(w_s\)。

然后我们要求 \([z^{km}]\mathrm{IFWT}(\sum_sx^s(1+z)^{w_s}(1-z)^{n-w_s})\)。

不难发现这就等于 \(\mathrm{IFWT}(\sum_sx^s[z^{km}](1+z)^{w_s}(1-z)^{n-w_s})\)。

那么只要对每个 \(w\) 求出 \([z^0](1+z)^{w}(1-z)^{n-w}\bmod{z^m-1}\) 然后 IFWT 回去,暴力处理即可。

时间复杂度 \(\mathcal O(nm+V\log V)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,N=1<<20,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; }
int n,m,pw,c[N],f[MAXN][105],g[MAXN][105];
ll ans=0,w[MAXN],d[N]; //[z^0](1+z)^i(1-z)^n-i
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%d%d",&n,&m,&pw);
	for(int i=1,x;i<=n;++i) scanf("%d",&x),++c[x];
	for(int k=1;k<N;k<<=1) for(int i=0;i<N;i+=k<<1) for(int j=i;j<i+k;++j) {
		int x=c[j],y=c[j+k]; c[j]=x+y,c[j+k]=x-y;
	}
	f[0][0]=g[0][0]=1;
	for(int i=1;i<=n;++i) {
		memcpy(f[i],f[i-1],sizeof(f[i]));
		memcpy(g[i],g[i-1],sizeof(g[i]));
		for(int j=0;j<m;++j) {
			add(f[i][(j+1)%m],f[i-1][j]);
			sub(g[i][(j+1)%m],g[i-1][j]);
		}
	}
	for(int i=0;i<=n;++i) {
		for(int j=0;j<m;++j) w[i]=(w[i]+1ll*f[i][j]*g[n-i][(m-j)%m])%MOD;
	}
	for(int s=0;s<N;++s) d[s]=w[(n+c[s])>>1];
	for(int k=1;k<N;k<<=1) for(int i=0;i<N;i+=k<<1) for(int j=i;j<i+k;++j) {
		ll x=d[j],y=d[j+k]; d[j]=(x+y)*i2%MOD,d[j+k]=(x-y)*i2%MOD;
	}
	for(int s=0;s<N;++s) ans=(ans+d[s]*ksm(s,pw))%MOD;
	printf("%lld\n",(ans+MOD)%MOD);
	return 0;
}



*D. [ARC136F] Flip

Problem Link

题目大意

给定 \(h\times w\) 01 网格,每次操作随机翻转一个位置,求期望多少次操作后首次满足网格第 \(i\) 行恰有 \(i\) 个 \(1\)。

数据范围:\(h,w\le 50\)。

思路分析

记 \(n=h\times w\)。

对于这种首次出现的问题,可以用 PGF 刻画:设 \([x^i]f(x)\) 表示从初始状态 \(i\) 次操作后和目标相等的概率,\([x^i]g(x)\) 表示从一个目标状态 \(i\) 次操作后回到目标状态的概率, \(h(x)\) 表示答案的生成函数。

那么答案就是 \(h'(1)\),而根据组合意义 \(f\) 中的过程可以看成 \(h\) 中的一个过程加上 \(g\) 的一个过程,即 \(f(x)=g(x)h(x)\),即 \(h(x)=\dfrac{f(x)}{g(x)}\)。

因此我们只要求 \(h'(1)=\dfrac{f'(1)g(1)-f(1)g'(1)}{g'(1)^2}\)。

注意到 \(f,g\) 本质相同,以求 \(f\) 为例。

那么我们注意到 \(f(x)\) 可以看成每个格子上的 PGF 乘积,但由于这些操作之间要分先后顺序,因此要用 EGF 表示,设 \(\hat f(x)=\mathrm{EGF} f(x)\)。

那么 dp 预处理出 \(c_i\) 表示有多少种方案在恰好翻转 \(i\) 个不同的网格后得到目标状态。

记 \(p_r(x)=\sum_{i}\left(\dfrac{x}n\right)^i[i\bmod 2=r]\) 表示单个格子被翻转或不翻转的 EGF,那么 \(\hat f(x)=\sum_ic_i p_1^i(x)p^{n-i}_0(x)\)。

不难用单位根反演计算出 \(p_0(x)=\dfrac{e^{x/n}+e^{-x/n}}{2},p_1(x)=\dfrac{e^{x/n}-e^{-x/n}}{2}\),那么带入 \(\hat f(x)\) 得到:

\[\hat f(x)=\dfrac 1{2^n}\sum_{i=0}^nc_i(e^{x/n}-e^{-x/n})^i(e^{x/n}+e^{-x/n})^{n-i} \]

二项式定理展开并化简得到:

\[\hat f(x)=\dfrac 1{2^n}\sum_{s=0}^n e^{(2s-n)x/n}\sum_{i=0}^nc_i\sum_{j=0}^i(-1)^{i-j}\binom ij\binom{n-i}{s-j} \]

把后面的式子用 OGF 表示得到:

\[\hat f(x)=\dfrac 1{2^n}\sum_{s=0}^{n}e^{(2s-n)x/n}[x^s]\sum_{i=0}^nc_i(x-1)^i(x+1)^{n-i} \]

预处理出所有的 \(c_i(x-1)^i(x+1)^{n-i}\) 即可对每个 \(s\) 求出系数 \(w_s\),再把 EGF 转回 OGF 的形式:

\[f(x)=\dfrac 1{2^n}\sum_{s=0}^n\dfrac{w_s}{1-\dfrac{(2s-n)x}{n}} \]

我们只要求 \(f(1),f'(1)\),但是在 \(s=n\) 的时候会在分母处产生 \(\dfrac 1{1-x}\),无法带入 \(x=1\)。

因此可以考虑求 \(F(x)=(1-x)f(x),G(x)=(1-x)g(x)\),此时依然有 \(h(x)=\dfrac{F(x)}{G(x)}\),所求仍为 \(F(1),F'(1)\)。

记 \(k=\dfrac {2s-n}n\),我们只要计算每个 \(w_x\times \dfrac{1-x}{1-kx}\) 对 \(F(1),F'(1)\) 的贡献。

  • \(s<n\) 时:\(k\ne 1\),\(x=1\) 时 \(\dfrac{1-x}{1-kx}=0\),此时对 \(F(1)\) 无贡献,对 \(F'(1)\) 的贡献是 \(w_s\times \dfrac{k-1}{(1-kx)^2}\mid_{x=1}\),即 \(\dfrac{w_s}{k-1}\)。
  • \(s=n\) 时:\(k=1\),\(\dfrac{1-x}{1-kx}=1\),对 \(F(1)\) 贡献为 \(w_s\),对 \(F'(1)\) 贡献为 \(0\)。

类似求出 \(G(1),G'(1)\) 即可。

时间复杂度 \(\mathcal O(h^2w^2)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2505,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; } 
int n,H,W;
int a[MAXN],b[MAXN];
ll C[MAXN][MAXN],cf[MAXN],cg[MAXN],wf[MAXN],wg[MAXN],p[MAXN],q[MAXN];
//ans=f/g
void solc(int *s,int *t,ll *c) {
	c[0]=1;
	for(int i=1;i<=H;++i) {
		memset(p,0,sizeof(p));
		memset(q,0,sizeof(q));
		for(int j=0;j<=s[i];++j) {
			int k=t[i]-s[i]+j;
			if(0<=k&&k<=W-s[i]) q[j+k]=(q[j+k]+C[s[i]][j]*C[W-s[i]][k])%MOD;
		}
		for(int j=0;j<=W;++j) for(int k=0;j+k<=n;++k) {
			p[j+k]=(p[j+k]+q[j]*c[k])%MOD;
		}
		memcpy(c,p,sizeof(p));
	}
}
void solw(ll *c,ll *w) {
	memset(p,0x3f,sizeof(p));
	for(int i=0;i<=n;++i) p[i]=C[n][i];
	ll inv=ksm(i2,n);
	for(int i=0;i<=n;++i) {
		if(i) {
			for(int j=1;j<=n;++j) p[j]=(p[j]+MOD-p[j-1])%MOD;
			for(int j=n;j;--j) p[j]=(p[j-1]+MOD-p[j])%MOD;
			p[0]=MOD-p[0];
		}
		for(int s=0;s<=n;++s) w[s]=(w[s]+c[i]*inv%MOD*p[s])%MOD;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>H>>W,n=H*W;
	for(int i=0;i<=n;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	for(int i=1;i<=H;++i) {
		string s; cin>>s,a[i]=count(s.begin(),s.end(),'1');
	}
	for(int i=1;i<=H;++i) cin>>b[i];
	solc(a,b,cf),solc(b,b,cg);
	solw(cf,wf),solw(cg,wg);
	ll f1=wf[n],g1=wg[n],fd=0,gd=0;
	for(int i=0;i<n;++i) {
		ll k=1ll*n*i2%MOD*ksm(i+MOD-n)%MOD;
		fd=(fd+wf[i]*k)%MOD;
		gd=(gd+wg[i]*k)%MOD;
	}
	printf("%lld\n",((g1*fd-f1*gd)%MOD+MOD)*ksm(g1*g1%MOD)%MOD);
	return 0;
}



*E. [ARC137F] Cover

Problem Link

题目大意

\(n\) 次操作,每次在 \([0,1]\) 范围内随机选两个点 \(x,y\),然后覆盖 \([\min(x,y),\max(x,y)]\),求最终每个位置被覆盖次数 \(\le k\) 的概率。

数据范围:\(n\le 2\times 10^5,k\le 10^5\)。

思路分析

由于我们是在实数范围内随机,因此选出重复元素的概率为 \(0\),我们只要看成在 \(2n\) 个位置中随机一组匹配即可,总方案数就是 \(1\times 3\times\cdots\times(2n-1)\),我们只要对合法操作序列计数即可。

如果把每次操作覆盖的线段看成一对括号的话,那么一组操作方案就对应一个长度为 \(2n\) 的合法括号序列,但每个括号序列可以对应多个操作方案。

求出括号序列的 \(\pm 1\) 前缀和,覆盖次数 \(\le k\) 就相当于前缀和值域 \([0,k]\)。

设 \(a_i\) 表示第 \(i\) 个右括号前的前缀和,很显然整个格括号序列的最大前缀和一定在 \(a_i\) 处取到。

那么要求就是 \(a_i\in[1,k]\) 且 \(a_n=1\)。

由于 \(a_i\) 和 \(a_{i+1}\) 中间没有其他右括号,因此 \(a_{i+1}\ge a_{i}-1\)。

不难证明每组 \(a_1\sim a_n\) 唯一对应一个括号序列。

并且第 \(i\) 个右括号可以匹配的左括号恰好有 \(a_i\) 个,因此一个括号序列对应的操作序列恰好有 \(\prod a_i\) 个。

那么只需要对所有合法的 \(a_i\) 求 \(\prod a_i\) 即可。

比较难处理的条件是 \(a_{i+1}\ge a_i-1\),因此考虑容斥,即钦定若干 \(a_{i+1}<a_i-1\),那么整个序列可以看成若干 \(a_{i+1}<a_i-1\) 的连续段拼接而成。

我们可以求出每个连续段的 OGF \(f\),然后答案就是 \(\dfrac 1{1-f}\)。

因此我们要求出所有满足 \(a_{i+1}<a_i-1\) 的序列 \(a_1\sim a_m\) 的 \(\prod a_i\) 值加进 \([x^m]f(x)\)。

不难用分治 NTT 维护,用 \(f_{l,r,0/1,0/1}(x)\) 表示考虑值域在 \([l,r]\) 之间的元素,是否选了 \(l/r\) 进入 \(a_i\) 时的 OGF。

转移时排除 \(f_{*,1}\times f_{1,*}\) 即可。

求出最终的 \(f(x)\) 后要把偶数项系数 \(\times -1\),因为长度为偶数说明钦定了奇数个 \(a_{i+1}<a_i-1\)。

我们还要求 \(a_n=1\),因此求出 \(g(x)=f_{1,k,1,0}+f_{1,k,1,1}\) 表示强制选 \(1\) 的连续段。

答案就是 \([x^n]\dfrac g{1-f}\),多项式求逆计算即可。

时间复杂度 \(\mathcal O(k\log^2 k+n\log n)\)。

代码呈现

#include<bits/stdc++.h>
using namespace std;
namespace P {
const int MOD=998244353,N=1<<19,G=3;
int rev[N],inv[N],fac[N],ifac[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_inv(const int *f,int *g,int n) {
	static int a[N];
	g[0]=ksm(f[0]);
	int k=2;
	for(;k<(n<<1);k<<=1) {
		for(int i=0;i<k;++i) a[i]=f[i];
		ntt(g,0,k<<1),ntt(a,0,k<<1);
		for(int i=0;i<(k<<1);++i) {
			g[i]=(2-1ll*a[i]*g[i]%MOD)*g[i]%MOD;
			if(g[i]<0) g[i]+=MOD;
		}
		ntt(g,1,k<<1);
		memset(g+k,0,sizeof(int)*k);
	}
	memset(g+n,0,sizeof(int)*(k-n));
	memset(a,0,sizeof(int)*k);
}
}
using P::ntt;
const int N=1<<19,MOD=998244353;
vector <int> f[N][2][2];
int n,k,a[2][2][N],b[2][2][N],c[N];
void cdq(int l,int r) {
	if(l==r) {
		f[l][0][0]={1,0};
		f[l][0][1]={0,0};
		f[l][1][0]={0,0};
		f[l][1][1]={0,l};
		return ;
	}
	int mid=(l+r)>>1,len=P::plen(r-l+2);
	cdq(l,mid),cdq(mid+1,r);
	for(int x:{0,1}) for(int y:{0,1}) {
		memset(a[x][y],0,sizeof(int)*len);
		memset(b[x][y],0,sizeof(int)*len);
		for(int i=0;i<=mid-l+1;++i) a[x][y][i]=f[l][x][y][i];
		for(int i=0;i<=r-mid;++i) b[x][y][i]=f[mid+1][x][y][i];
		ntt(a[x][y],0,len),ntt(b[x][y],0,len);
	}
	for(int x:{0,1}) for(int y:{0,1}) {
		memset(c,0,sizeof(int)*len);
		for(int p:{0,1}) for(int q:{0,1}) if(!p||!q) {
			for(int i=0;i<len;++i) c[i]=(c[i]+1ll*a[x][p][i]*b[q][y][i])%MOD;
		}
		ntt(c,1,len);
		f[l][x][y].resize(r-l+2);
		for(int i=0;i<=r-l+1;++i) f[l][x][y][i]=c[i];
	}
}
int u[N],v[N],w[N];
signed main() {
	scanf("%d%d",&n,&k),P::poly_init();
	cdq(1,k);
	for(int i=1;i<=k;++i) {
		for(int x:{0,1}) for(int y:{0,1}) {
			u[i]=(u[i]+f[1][x][y][i])%MOD;
			if(x) v[i]=(v[i]+f[1][x][y][i])%MOD;
		}
		if(i&1) u[i]=(MOD-u[i])%MOD;
		else v[i]=(MOD-v[i])%MOD;
	}
	++u[0];
	P::poly_inv(u,w,n+1);
	int ans=0;
	for(int i=0;i<=n;++i) ans=(ans+1ll*v[i]*w[n-i])%MOD;
	for(int i=1;i<=n;++i) ans=1ll*ans*P::inv[2*i-1]%MOD;
	printf("%d\n",ans);
	return 0;
}



Round #16 - 2024.10.4

A. [AGC055C] Delete

Problem Link

题目大意

对于一个排列 \(p\),定义 \(a_i\) 表示 \(p_1,\dots,p_{i-1},p_{i+1},\dots,p_n\) 的 LIS,求共能生成有多少个值域 \([2,m]\) 的序列 \(a_1\sim a_n\)。

数据范围:\(n\le 5000\)。

思路分析

很显然 \(a\) 的极差 \(\le 1\),并且大部分情况下最大值就是 \(p\) 的 LIS 长度,除非 \(a_1=\cdots=a_n=n-1\),此时 \(p_i=i\)。

先考虑 \(a\) 中元素全部相等的情况,此时每个 \(a_i\) 都是 LIS 长度,即每个 \(p_i\) 都可以找到 LIS 外的替换元素。

很显然此时 LIS 长度 \(k\le \lfloor n/2\rfloor\),因为每个元素的替换元素不可能相同。

并且这样的序列总能构造,一组合法的构造形如: \(n,n-1,\dots,2k+1,2,1,4,3,\dots,2k,2k-1\)。

那么对于一般的情况,会有一些 \(a_i=k-1\) 的元素,表示他们是 LIS 的必经点。

设这些必经点为 \(x_1\sim x_q\),那么很显然 \(k\ge q\),并且对于每个 \((x_i,x_{i+1})\) 中间的段,对 LIS 的贡献至多为 \(\left\lfloor\dfrac{x_{i+1}-x_i-1}2\right\rfloor\)(设 \(x_0=0,x_{q+1}=n+1\))。

那么一个必要条件就是 \(q\le k\le q+\sum \left\lfloor\dfrac{x_{i+1}-x_i-1}2\right\rfloor\),手玩发现这样的序列也总是能构造出来的。

不妨设 \(s=\sum \left\lfloor\dfrac{x_{i+1}-x_i-1}2\right\rfloor\),那么 \(k\) 的取值区间就是 \(\max(q,3)\sim\min(m,q+s)\),并且确定 \(q,s\) 后,直接分配 \(s\) 个元素进入 \(q+1\) 个空,还剩 \(n-q-2s\) 个元素作为除以二的余数分进 \(q+1\) 个空(每个空至多一个)。

那么我们只要枚举 \(q,s\) 就能计算出对应方案数。

时间复杂度 \(\mathcal O(n^2)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e4+5;
int n,m,MOD;
ll fac[MAXN],ifac[MAXN];
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 C(int x,int y) {
	if(x<0||y<0||y>x) return 0;
	return fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
signed main() {
	scanf("%d%d%d",&n,&m,&MOD);
	for(int i=fac[0]=ifac[0]=1;i<MAXN;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
	ll ans=1;
	if(n>3&&(m==n-1)) ++ans;
	for(int k=0;k<=n;++k) for(int s=0;2*s+k<=n;++s) {
		ans=(ans+C(s+k,k)*C(k+1,n-k-2*s)%MOD*max(min(m,k+s)-max(k,3)+1,0))%MOD;
	}
	printf("%lld\n",ans);
	return 0;
}



B. [AGC053C] Stack

Problem Link

题目大意

给定 \(a_1\sim a_n,b_1\sim b_n\),所有元素是 \(1\sim 2n\) 的一个随机排列,每次操作选定一个 \(k\),比较 \(a_k,b_k\) 的大小:

  • 如果 \(a_k<b_k\) 那么删除 \(a_k\) 并把 \(a_{k+1}\sim a_n\) 平移到 \(a_k\sim a_{n-1}\)。
  • 否则删除 \(b_k\) 并做相同操作。

求清空其中一个序列的最小操作次数的期望。

数据范围:\(n\le 10^6\)。

思路分析

先考虑如何刻画最小操作次数,不妨设 \(2n\) 在 \(a\) 中,那么一定要清空 \(b\)。

考虑每个 \(b_i\) 被删除至少要删掉几个 \(a\),设 \(p_i\) 为最小的 \(a_{p_i}>b_i\) 的点,那么至少需要 \(\max(0,p_i-i)\) 次操作才能删掉 \(b_i\)。

因此答案有下界 \(n+\max\{p_i-i\}\),下面给出构造:如果存在 \(a_i>b_i\),那么直接操作 \(i\) 删除一个 \(b\) 中元素。

否则找到第一个 \(p_i>i\) 的 \(i\) 并操作,此时所有 \(p_i>i\) 的 \(p_i\) 都会 \(-1\)。

因此答案就是 \(n+\max\{p_i-i\}\),不难把答案转成统计每个 \(\max\{p_i-i\}\le k\) 的概率。

考虑什么时候 \(\max\{p_i-i\}\le k\),那么只要求出第一个 \(p_i-i>k\) 的概率,此时 \(b_i\) 大于 \(a_1\sim a_{\min(n,i+k)}\)。

并且对于 \(b_1\sim b_{i-1}\),由于 \(i\) 是第一个非法位置,因此 \(b_1\sim b_{i-1}\) 都要小于 \(a_1\sim a_{\min(n,i+k)}\)。

因此 \(i\) 是第一个非法位置的概率就是 \(\dfrac1{i+\min(i+k,n)}\),总概率就是 \(\prod\limits_{i=1}^n\left(1- \dfrac{1}{i+\min(i+k,n)}\right)\),预处理前后缀积即可。

概率最终要 \(\times 2\) 因为可以交换 \(a,b\)。

时间复杂度 \(\mathcal O(n\log P)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e6+5,MOD=1e9+7;
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;
ll fac[MAXN],ifac[MAXN];
signed main() {
	scanf("%d",&n);
	fac[0]=ifac[0]=fac[1]=ifac[1]=1;
	for(int i=2;i<MAXN;++i) ifac[i]=ksm(fac[i]=i*fac[i-2]%MOD);
	ll ans=2*n;
	for(int k=0;k<n;++k) {
		//prod 1-1/min(i+k,n)+i
		//i=1~n-k: 2i+k-1/2i+k, else n+i-1/n+i
		ll w=(2*n-k)*ksm(2*n)%MOD*fac[2*n-k-1]%MOD*ifac[2*n-k]%MOD;
		if(k) w=w*ifac[k-1]%MOD*fac[k]%MOD;
		ans=(ans+2*(MOD-w))%MOD;
	}
	printf("%lld\n",ans);
	return 0;
}



*C. [AGC057D] Sum

Problem Link

题目大意

给定 \(n\),定义一个 \([1,n)\) 的子集 \(S\) 是好的当且仅当不能从 \(S\) 中选出若干元素求和得到 \(n\)(可以重复选元素)。

取所有好的集合中最大的一个,有多个取从小到大排序后字典序最小的集合 \(S\),求出其中的第 \(k\) 个元素。

数据范围:\(n\le 10^{18}\)。

思路分析

显然集合最大大小就是 \(\left\lfloor\dfrac{n-1}2\right\rfloor\),因为所有 \((1,n-1),(2,n-2),\dots\) 的匹配只能每对选一个,即 \(i\in S\) 和 \(n-i\in S\) 恰有一个成立。

不妨设 \(S\) 在 \(1\sim \left\lfloor\dfrac{n-1}2\right\rfloor\) 范围内的集合是 \(A\),那么我们知道 \(A\) 就能还原 \(S\),并且只要最小化 \(A\) 的字典序即可。

我们发现如果 \(x,y\in A\) 且 \(x+y\le \left\lfloor\dfrac{n-1}2\right\rfloor\),那么必须有 \(x+y\in A\),否则 \(n-x-y\in S\setminus A\),则 \(x,y,n-x-y\) 加起来得到 \(n\)。

其次,如果 \(A\) 中元素加起来得不到 \(n\),那么 \(S\) 中元素加起来也得不到 \(n\)。

考虑反证法,如果存在一种选元素的方法选了 \(S\setminus A\) 中元素,显然至多选一个,因为选两个的和就 \(>n\) 了。

那么设选出了 \(x\in S\setminus A\),则 \(A\) 中能选出若干元素和为 \(n-x\),但 \(n-x\le \left\lfloor\dfrac{n-1}2\right\rfloor\) 且能被 \(A\) 中元素表示,则根据刚才的结论,一定有 \(n-x\in A\),从而导出矛盾。

那么求 \(A\) 的过程就可以贪心,从小到大考虑每个元素,如果加入后依然不能表示出 \(n\) 就加入,并且把 \(\le \left\lfloor\dfrac{n-1}2\right\rfloor\) 的子集和加入 \(A\)。

考虑 \(A\) 中最小的元素,即第一个 \(p\) 满足 \(p\nmid n\),此时 \(\mathrm{lcm}(1,2,\dots,p-1)\mid n\),因此 \(p\le 43\)。

那么加入 \(p\) 后会加入所有 \(\le \left\lfloor\dfrac{n-1}2\right\rfloor\) 的倍数。

加入某个 \(x\) 后又会加入所有 \(x+p,x+2p,\dots\),因此可以考虑同余最短路。

设 \(f_i\) 表示当前 \(A\) 中元素能组合出的最小的 \(\bmod p=i\) 的数。

加入一个 \(x\) 就会让 \(f_i\gets f_{(i-kx)\bmod n}+kx\),可以转两圈更新。

一个元素加入后合法当且仅当加入后 \(f_{n\bmod p}>n\),因此对于所有 \(i\),都有 \(x\ge \left\lfloor\dfrac{n-f_{(n-ix)\bmod p}}{i}\right\rfloor+1\)。

枚举每个 \(x\bmod p\) 算出最小的 \(x\),取出所有可能的 \(x\) 中最小的一个加入。

求答案可以二分,求出 \(A\) 中 \(\le k\) 的元素个数,即 \(\sum\limits_{i=0}^{p-1} \left\lfloor\dfrac{k-f_i}{p}\right\rfloor+[i>0]\)。

时间复杂度 \(\mathcal O(p^3+p\log n)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1e18;
ll n,m,p,f[45];
ll cnt(ll x) {
	ll s=0;
	for(int i=0;i<p;++i) if(f[i]<=x) s+=(x-f[i])/p+(i>0);
	return s;
}
void solve() {
	scanf("%lld%lld",&n,&m);
	if(m>(n-1)/2) return puts("-1"),void();
	for(p=1;n%p==0;++p);
	f[0]=0,fill(f+1,f+p,inf);
	while(true) {
		ll v=inf;
		for(int x=1;x<p;++x) {
			ll c=0;
			for(int i=1;i<p;++i) c=max(c,(n-f[(n+(p-x)*i)%p])/i+1);
			while(c%p!=x) ++c;
			if(c<f[x]) v=min(v,c);
		}
		if(v>(n-1)/2) break;
		f[v%p]=v;
		for(int r=0;r<__gcd(v,p);++r) {
			for(int i=r,c=0;c<2;i=(i+v)%p,c+=(i==r)) {
				f[(i+v)%p]=min(f[(i+v)%p],f[i]+v);
			}
		}
	}
	if(m<=cnt((n-1)/2)) {
		ll l=1,r=(n-1)/2,z=(n-1)/2;
		while(l<=r) {
			ll mid=(l+r)>>1;
			if(cnt(mid)>=m) z=mid,r=mid-1;
			else l=mid+1;
		}
		printf("%lld\n",z);
	} else {
		ll l=(n-1)/2+1,r=n-1,z=n-1;
		while(l<=r) {
			ll mid=(l+r)>>1;
			if((n-1)/2-(n-mid-1-cnt(n-mid-1))>=m) z=mid,r=mid-1;
			else l=mid+1;
		}
		printf("%lld\n",z);
	}
}
signed main() {
	int T; scanf("%d",&T);
	while(T--) solve();
	return 0;
}



*D. [AGC056D] Game

Problem Link

题目大意

给定 \(a_1\sim a_n\),两个人 A 和 B 轮流取数,最终如果 A 取出的数总和 \(\in[L,R]\) 就胜利,问谁有必胜策略。

数据范围:\(n\le 5000\),\(n\) 为偶数。

思路分析

先考虑 \(n\) 是奇数怎么做,那么枚举 A 第一次取出的数,剩下的就变成一个 B 先取的问题。

先考虑 B 能把取出的数最小化到什么程度,我们发现 A 可以选定一组匹配,B 选其中的某个元素,A 就选其匹配点,那么 A 最终能得到每组匹配中较小的数。

为了最大化这个数,A 肯定会把排序后相邻的元素结成匹配,即得到的最小值就是排序后奇数位上的值,这个和 \(\ge L\) 时合法。

如果 B 想要最大化取出的数,那么 A 依然会选定匹配并取出每组匹配中较大的数,此时 A 的最优方案依然是把排序后相邻的元素结成匹配,得到排序后偶数位上的值,总和 \(\le R\) 时合法。

回到原问题。

此时枚举 A 第一步的操作,那么会剩余奇数个元素并轮到 B 先手。

A 依然考虑把元素进行匹配,但是此时两两匹配后会在剩余一个元素在最后一次操作给 B。

并且这个元素也是由 A 控制的,枚举这个元素后再判定排序后奇数位和偶数位上的和是否合法即可。

时间复杂度 \(\mathcal O(n^2)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5005;
ll L,R,a[MAXN],b[MAXN];
signed main() {
	int n;
	scanf("%d%lld%lld",&n,&L,&R);
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
	sort(a+1,a+n+1);
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n;++j) if(i^j) b[j-(j>i)]=a[j];
		array <ll,2> sl{0,0},sr{0,0};
		for(int j=1;j<n;++j) sr[j&1]+=b[j];
		for(int j=1;j<n;++j) {
			sr[j&1]-=b[j];
			if(L<=sl[1]+sr[0]+a[i]&&sl[0]+sr[1]+a[i]<=R) return puts("Alice"),0;
			sl[j&1]+=b[j];
		}
	}
	puts("Bob");
	return 0;
}



*E. [AGC056E] Ball

Problem Link

题目大意

给定 \(n\) 个洞排成一圈,位置为 \(0\sim n-1\),随机 \(n-1\) 次,每次取出一个球,以 \(a_i\) 的概率放在 \(i\) 上,然后从 \(i\) 出发,每到一个还没有球洞口,就以 \(\dfrac 12\) 的概率放进这个洞口,否则继续考虑下一个洞口。

对于每个洞口 \(i\),求最后没有球的洞口恰好是 \(i\) 的概率。

数据范围:\(n\le 40\)。

思路分析

用 \(C(x,y)\) 表示判定第 \(x\) 个球能否扔进第 \(y\bmod n\) 个洞,设第 \(i\) 个球的起点是 \(p_i\),那么题目中的过程形如:

\[\begin{aligned} &C(1,p_1),C(1,p_1+1),\dots\\ &C(2,p_2),C(2,p_2+1),\dots\\ &\vdots\\ &C(n-1,p_{n-1}),C(n-1,p_{n-1}+1),\dots \end{aligned} \]

显然直接用 dp 维护这个判定过程是很难的,考虑重排这些判定事件。

首先我们要求 \(C(x,y)\) 比 \(C(x,y+1)\) 先判定,这个是不能改变的。

那么我们只能考虑交换 \(C(x_1,y)\) 和 \(C(x_2,y)\),事实上这是可以的,如果 \(C(x_2,y)\) 成功,那么我们可以交换 \(C(x_2,y+k),C(x_1,y+k)\),然后就等价于 \(C(x_1, y)\) 成功了。

因此我们可以按 \(y\) 从小到大判定,对于若干 \(C(x_1,y)\sim C(x_q,y)\),如果有一个成功,钦定为 \(C(x_1,y)\) 成功。

很显然我们只要求出第 \(0\) 个洞最终没有球的概率,其他的旋转 \(a\) 后等价。

我们只需要处理 \(y=1\sim n-1\) 的情况并记录剩余的球数,不妨设还有 \(k\) 个球等待判定 \(C(x_1,n)\sim C(x_k,n)\)。

此时所有球一起判定比较不优,考虑把判定顺序换回先判定 \(C(x_1,n),C(x_1,n+1)\cdots\)。

对于 \(x_1\),如果在 \(C(x_1,n+1)\sim C(x_1,2n-1)\) 中成功判定,概率为 \(\dfrac 1{2^2}+\dots+\dfrac 1{2^{k+1}}\),因为 \(C(x_1,n)\) 必须失败,然后在剩余的 \(k\) 个洞中进入其中的一个。

并且 \(x_1\),有 \(\dfrac 1{2^{k+1}}\) 的概率回到 \(C(x_1,2n)\),因此 \(x_1\) 最终未进入 \(0\) 的概率为 \(\left(\dfrac 1{2^2}+\dots+\dfrac 1{2^{k+1}}\right)\times \dfrac 1{1-2^{-(k+1)}}\),即 \(\dfrac{2^k-1}{2^{k+1}-1}\)。

那么对于 \(x_1\sim x_k\) 都未进入 \(0\) 的概率就是 \(\prod\limits_{i\le k}\dfrac{2^i-1}{2^{i+1}-1}=\dfrac1{2^{k+1}-1}\)。

因此我们只要求出还剩 \(k\) 个球判定到 \(C(x_1,n)\sim C(x_k,n)\) 的概率。

考虑 dp,设 \(f_{i,j,k}\) 表示当前判定到 \(y=i\),一共加入 \(j\) 个球,还剩 \(k\) 个球没有判定成功的概率。

那么转移时先加入若干 \(p_x=i\) 的球,由于球之间有顺序,因此转移时要乘组合数:

\[f_{i,j,k}\gets f_{i-1,j-t,k-1}\times (a_i)^t\times \binom{j}{t} \]

然后要考虑是否存在某个 \(C(x,i)\) 判定成功,都不成功的概率是 \(2^{-k}\),成功一个的概率就是 \(1-2^{-k}\)。

因此有转移 \(f_{i,j,k}\gets f_{i,j,k}\times 2^{-k}\) 和 \(f_{i,j,k}\gets f_{i,j,k+1}\times (1-2^{-k})\)。

最终答案是 \(\sum\dfrac{f_{n-1,n-1,k}}{2^{k+1}-1}\)。

时间复杂度 \(\mathcal O(n^6)\)。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=45,MOD=998244353,inv=828542813;
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 f[MAXN][MAXN],g[MAXN][MAXN],a[MAXN],pw[MAXN],fac[MAXN],ifac[MAXN];
int n;
inline void add(ll &x,const ll &y) { x=(x+y)%MOD; }
ll solve() {
	memset(f,0,sizeof(f)),f[0][0]=1;
	for(int i=1;i<=n;++i) {
		memcpy(g,f,sizeof(g)),memset(f,0,sizeof(f));
		for(int j=0;j<n;++j) for(int k=0;k<=j;++k) {
			ll pr=1;
			for(int x=0;j+x<n;++x) {
				add(f[j+x][k+x],g[j][k]*pr%MOD*ifac[x]);
				pr=pr*a[i]%MOD;
			}
		}
		if(i==n) break;
		memcpy(g,f,sizeof(g)),memset(f,0,sizeof(f));
		for(int j=0;j<n;++j) for(int k=0;k<=j;++k) {
			ll pr=ksm(pw[k]);
			add(f[j][k],g[j][k]*pr);
			if(k) add(f[j][k-1],g[j][k]*(1+MOD-pr));
		}
	}
	ll s=0;
	for(int i=0;i<n;++i) add(s,f[n-1][i]*fac[n-1]%MOD*ksm(pw[i+1]-1));
	return s;
}
signed main() {
	for(int i=pw[0]=fac[0]=ifac[0]=1;i<MAXN;++i) {
		pw[i]=pw[i-1]*2%MOD,ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
	}
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]),a[i]=a[i]*inv%MOD;
	for(int i=1;i<=n;++i) rotate(a+1,a+2,a+n+1),printf("%lld ",solve());
	puts("");
	return 0;
}

标签:le,Noip,记录,int,dfrac,ll,2024,MAXN,MOD
From: https://www.cnblogs.com/DaiRuiChen007/p/18449902

相关文章

  • 2024熵密杯wp
    第一部分:初始谜题这一部分算是开胃菜,形式也更像平时见到的CTF题目,三个题目都是python加密的,做出其中任意一个就可以进入第二部分,也就是一个更类似真实情境的大型密码渗透系统。但每个初始谜题都是有分数的,所以就算开了第二部分也当然要接着做。每个题目也都有前三血的加成,一血5%,二......
  • 2024高校网络安全管理运维赛 wp
    0x00前言本文是关于“2024高校网络安全管理运维赛”的详细题解,主要针对Web、Pwn、Re、Misc以及Algorithm等多方向题目的解题过程,包含但不限于钓鱼邮件识别、流量分析、SQLite文件解析、ssrf、xxe等等。如有错误,欢迎指正。0x01Misc签到给了一个gif,直接在线分帧得到synt{fvtava-dh......
  • 20241006
    BacktoSchool'24P1-Kicking按照题意模拟即可#include<bits/stdc++.h>usingnamespacestd;#defineintlonglongconstintN=1e5+5;intn,m,k;charc;vector<int>sum[2][N];vector<int>a[N];signedmain(){cin>>......
  • # 2024-2025-1 20241301 《计算机基础与程序设计》第二周学习总结
    这个作业属于哪个课程<2024-2025-1-计算机基础与程序设计>这个作业要求在哪里<2024-2025-1计算机基础与程序设计第一周作业>这个作业的目标<概括性地了解计算机系统,解决为什么要学习编程的问题>作业正文https://www.cnblogs.com/HonJo/p/18449597教材学习内......
  • 2024-2025-1计算机程序与设计第二周学习总结20241420姚金济
    <班级链接>(2024-2025-1-计算机基础与程序设计<作业要求链接>2024-2025-1计算机基础与程序设计第二周作业|作业正文|本博客链接教材学习内容总结(前者AI完成,后者自己完成,AI参考:kimi智能助手)计算机科学概论:由于文件内容较多,我将列出核心内容的概要:第1章全景图计算机系统:计算......
  • 中国农业大学公布2024级研究生新生大数据
    来源:中国农大......
  • 2024.10.6训练记录
    下午cfA到!B签到题,考场还是写挂了,今天码力差。挂在while动指针的时候没有判右边界,似。唐诗程度不亚于数组开小。C1猜出来结论是第一次出现需要按照一开始的顺序就能过。C2把一开始的排列映射到[1,n]。修改时用set动态维护每个数第一次出现的位置。把第一次出现位置的......
  • 2024-2025-1 20241428 《计算机基础与程序设计》第二周学习总结
    学期(2024-2025-1)学号20241428《计算机基础与程序设计》第2周学习总结作业信息|这个作业属于哪个课程|<班级的链接>(如2024-2025-1-计算机基础与程序设计)||这个作业要求在哪里||https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP/homework/13276))||这个作业的目标|数......
  • 2024-2025-1 20241320 《计算机基础与程序设计》第2周学习总结
    2024-2025-120241320《计算机基础与程序设计》第2周学习总结作业信息|这个作业属于哪个课程|https://www.cnblogs.com/rocedu/p/9577842.html|这个作业要求在哪里|https://www.cnblogs.com/rocedu/p/9577842.html#WEEK02|这个作业的目标|数字化信息安全自学教材《计算......
  • 2024-2025-1 20241316 《计算机基础与程序设计》第二周学习总结
    2024-2025-120241316《计算机基础与程序设计》第二周学习总结作业信息这个作业属于哪个课程2024-2025-1-计算机基础与程序设计这个作业要求在哪里2024-2025-1计算机基础与程序设计第二周作业这个作业的目标*自学教材计算机科学概论(第七版)第1章并完成云班课测试*......