2024 Noip 做题记录(四)


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,m2000

思路分析

从一些必要条件开始,对每个限制显然有 SaSc,那么考虑建出拓扑关系图,连边 ac

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

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

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

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

时间复杂度 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

题目大意

给定 a1an,每次操作可以给 m 个不同的 ai 加一(在 modn 意义下),求至少几次操作能让 a 变成 0n1 的排列。

数据范围:n2.5×105

思路分析

考虑在不 modn 意义下确定最终的 bi,要满足如下条件:

  • aibi
  • bimodn 互不相同。
  • S=biai,那么 mS
  • max{biai}Sm

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

然后考虑第四个条件,对于一组合法解 b1bn,如果 bnb1n,那么调整 b1bnn,bnb1+n

显然不影响条件一、二、三的合法性,且此时 bnan 会变小,且 b1a1=bna1n<bnan,因此 max{biai} 也会变小。

因此这样调整一定更优,最终一定有 bnb1<n,即 bi 是连续的一段,存在一个 x 使得 bi=x+i1

那么逐个满足条件即可,先找到 maxaii+1,然后调整到最近的 x 使得 n(2x+n1)2ai(modm)

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

此时 k 加上 mgcd(n,m),且 Sm 加上 ngcd(n,m),由于 n>m 因此这个调整总能结束。

时间复杂度 O(nlogn)

代码呈现

#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

题目大意

给定 a1an,将其分成 k 个非空区间,最大化元素和 s 的区间个数。

数据范围:n2.5×105

思路分析

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

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

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

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

时间复杂度 O(nlog2n)

代码呈现

#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 的概率加入队首,有 1p 的概率加入队尾。求所有点的距离之和的期望。

数据范围:n1018

思路分析

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

那么可以 dp,设 fi/gi 表示拓展 i 步后队首或队尾元素距离的期望,答案就加上 fi+1,更新就是 fi+1=p(fi+1)+(1p)gi,gi+1=(1p)(fi+1)+pgi

不难用一个 4×4 的矩阵描述 fi,gi,1,ans 的转移,矩阵快速幂优化 dp 即可。

时间复杂度 O(δ3logn),其中 δ=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

题目大意

给定 12n 排列 a1an,b1bn,定义 fi,j={fi,j1+xiai<bjfi1,j+yjotherwise,其中 f1,1=0,求 i,jfi,j

数据范围:n2.5×105

思路分析

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

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

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

分析发现如果某一行 ak 满足 ak<minb(j,q],那么 k 这一行就不会和之前的点连通,如果某一列 bk 满足 bk<mina(i,p],那么 k 这一列就不会和之前的点连通。

因此 a[i,p],b[j,q] 中的最小值一定是 aibj,由于钦定了 ai<bj,因此 xi(i,j) 处对 (p,q) 有贡献当且仅当 ai<minb[j,q]<mina(i,p]

我们要对这样的区间计数,设 fw 表示 minb[j,q]w 的元素个数,可以用单调栈预处理。

然后就是要求 xipfmina(i,p]fai,注意到 mina(i,p] 一定是 a[i,n] 的后缀单调栈上的元素,并且因为 mina(i,p]>ai,因此一定是后缀单调栈上被 ai 弹出的元素,答案是容易计算的,加上 p=i 的贡献即可。

时间复杂度 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

题目大意

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

数据范围:n20

思路分析

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

然后考虑调整,如果给某个 xi,j 加上 ϵ,那么 dis(xi,xj) 减去 2ϵϵ2,其他 dis(xi,xk),dis(xj,xk) 变化量都是 ϵ2

因此我们发现当 ϵ 足够小的时候,ϵ2 可以完全忽略不计,此时就相当于让 dis(xi,xj) 小于其他的距离。

我们可以取 xi,i=108,然后距离最大的一对点 (i,j)xi,j=1,次大的取 xi,j=2,其他的以此类推。

容易发现 ϵ2 项的累计误差最大值接近 n66,小于 108,因此这样的构造是合法的。

时间复杂度 O(n2)

代码呈现

#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×m 循环网格(第 1,n 行、第 1,m 列相邻),求有多少种选出格子数最多的方案,使得选出的格子互不相邻。

数据范围:n105,m1010

思路分析

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

否则不妨设 n 是偶数 m 是奇数,此时最多选 n×m12 个格子,如果都是奇数。

那么设 nm,选出格子最多也是 n×m12 个。

这是显然的,因为每一行 m 个格子至多选 m12 个。

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

不妨设第 i 行未选格子包含 xi,xi+1,那么我们发现一组方案合法当且仅当 i[1,n] 都有 |xixi+1|=1

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

由于我们钦定 n 是偶数,因此不能保证 n105

  • 如果 n>105,此时 m105,所求相当于 [z0](z+z1)nmodzm1,直接多项式快速幂计算。
  • 如果 n105,枚举序列中 +1 个数,组合数计算即可。

时间复杂度 O(nlognlogm)

代码呈现

#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 的倍数。

数据范围:n1010,m105

思路分析

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

设答案为 f(S),那么 f(S) 是积性函数且 f(pc)=(c+m1m1)

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

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

然后要考虑 S 的因数个数和是 3 的倍数,只要存在 S 的一个质因子 pc 满足 31+p1++pc 即可。

这并不好维护,但我们可以反面考虑计算因数个数和不是 3 倍数的 f(S),那么 f(pc)=[31+p1++pc]f(pc)

还是要处理 f(p)n/t 处的质数前缀和,也就是 1n/t 中有多少 mod32 的质数。

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

考虑作差,即计算 1n/tmod3=1mod3=2 质数个数之差,mod3=0 的质数显然只有 3,容易处理。

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

时间复杂度 O(n3/4logn)

代码呈现

#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,定义一个序列 a1an 是好的当且仅当:

  • ai[0,i]
  • 如果 ai,aj>0,那么 aiaj

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

数据范围:n5000

思路分析

考虑对于 ai>0 的元素连边 iai1,由于 ai,aj>0 时一定有 aiaj,因此不存在 i,j 前驱相同。

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

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

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

i,j(n+1i+j){ik}{jk}×wn+1ij

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

时间复杂度 O(n2)

代码呈现

#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,a1)(n,an)a1n 排列,你要对这个点集进行若干次操作,得到一个序列。

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

  • 选择一个 x,把 i<x 的点操作得到的序列和 xi 的点操作得到的序列拼起来。
  • 选择一个 y,把 ai<y 的点操作得到的序列和 yai 的点操作得到的序列拼起来。

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

数据范围:n30

思路分析

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

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

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

考虑 f1,n,1,n 如何计算。

那么我们 dp 时枚举最小操作 x=i,那么就会递归成 f1,i1,1,nfi,n,1,n 两部分,但我们要在 f1,i1,1,n 范围内钦定 x=i 是最小字典序操作,设方案数为 gi

不妨容斥,设 x=j 是实际最小字典序操作,那么最终的结果序列一定会被分成 x[1,j),x[j,i),x[i,n] 三部分,gi 就要减去 f1,j1,1,n×fj,n,1,n

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

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

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

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

时间复杂度 O(n6)

代码呈现

#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,点集 1nxyE 当且仅当 x<ygcd(x,y)>1,求最大带权反链。

数据范围:n106

思路分析

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

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

  • x,y 均为偶数,一定可以。
  • x 为奇数,y 为偶数,x+pxy
  • x 为偶数,y 为奇数,xypy.
  • x 为奇数,y 为奇数,x+pxypy

其中 pxx 的最小质因子。

可以把限制统一看成 rx<ly 的形式,其中对于偶数 xlx=rx=x,对于奇数 xlx=xpx+1,rx=x+px1,特判 x=1

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

时间复杂度 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 个操作,每个操作可以花费 ci 的代价令 [li,ri] 区间内的 xj 加一,收益为 d×jmin(xj,aj),求最大收益减代价。

数据范围:n,m2000

思路分析

考虑费用流。

类似志愿者招募那题的思路,连一条 0n 的链,然后对于一个操作,连 l1r

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

然后对于 i1i 的边,这条边每流过 1 的流量,就说明 xi 减一了,连一条容量为 ai 费用为 d 的边,再连一条容量为 mai 费用为 0 的边,此时当流过的流量 >mai 后,每 1 流量就会 d 收益。

对于一条 l1r 的边,流量为 1,费用为 c,表示选了这条边后可以让 l1lr1r 这些边少被流一次,即 xj 加一。

答案就是 daj 减去这个网络上大小为 m 的最小费用流。

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

时间复杂度 O((n+m)2logm)

代码呈现

#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 次方。

给定 a1an,求其所有大小为 m 的倍数的子集的权值和。

数据范围:n2×105,ai<220,m100

思路分析

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

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

考虑 FWT,那么 FWT(x+xaiz)=s(1+(1)popcount(aiANDs)z)xs

那么 [xs]FWT(F) 就是若干 1+z1z 的乘积,具体来说就是 sxs(1+z)ws(1z)nws

我们只要求出这些 ws,直接对 xai FWT 即可得到 ws(nws) 然后就能算出 ws

然后我们要求 [zkm]IFWT(sxs(1+z)ws(1z)nws)

不难发现这就等于 IFWT(sxs[zkm](1+z)ws(1z)nws)

那么只要对每个 w 求出 [z0](1+z)w(1z)nwmodzm1 然后 IFWT 回去,暴力处理即可。

时间复杂度 O(nm+VlogV)

代码呈现

#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×w 01 网格,每次操作随机翻转一个位置,求期望多少次操作后首次满足网格第 i 行恰有 i1

数据范围:h,w50

思路分析

n=h×w

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

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

因此我们只要求 h(1)=f(1)g(1)f(1)g(1)g(1)2

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

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

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

pr(x)=i(xn)i[imod2=r] 表示单个格子被翻转或不翻转的 EGF,那么 f^(x)=icip1i(x)p0ni(x)

不难用单位根反演计算出 p0(x)=ex/n+ex/n2,p1(x)=ex/nex/n2,那么带入 f^(x) 得到:

f^(x)=12ni=0nci(ex/nex/n)i(ex/n+ex/n)ni

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

f^(x)=12ns=0ne(2sn)x/ni=0ncij=0i(1)ij(ij)(nisj)

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

f^(x)=12ns=0ne(2sn)x/n[xs]i=0nci(x1)i(x+1)ni

预处理出所有的 ci(x1)i(x+1)ni 即可对每个 s 求出系数 ws,再把 EGF 转回 OGF 的形式:

f(x)=12ns=0nws1(2sn)xn

我们只要求 f(1),f(1),但是在 s=n 的时候会在分母处产生 11x,无法带入 x=1

因此可以考虑求 F(x)=(1x)f(x),G(x)=(1x)g(x),此时依然有 h(x)=F(x)G(x),所求仍为 F(1),F(1)

k=2snn,我们只要计算每个 wx×1x1kxF(1),F(1) 的贡献。

  • s<n 时:k1x=11x1kx=0,此时对 F(1) 无贡献,对 F(1) 的贡献是 ws×k1(1kx)2x=1,即 wsk1
  • s=n 时:k=11x1kx=1,对 F(1) 贡献为 ws,对 F(1) 贡献为 0

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

时间复杂度 O(h2w2)

代码呈现

#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)],求最终每个位置被覆盖次数 k 的概率。

数据范围:n2×105,k105

思路分析

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

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

求出括号序列的 ±1 前缀和,覆盖次数 k 就相当于前缀和值域 [0,k]

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

那么要求就是 ai[1,k]an=1

由于 aiai+1 中间没有其他右括号,因此 ai+1ai1

不难证明每组 a1an 唯一对应一个括号序列。

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

那么只需要对所有合法的 aiai 即可。

比较难处理的条件是 ai+1ai1,因此考虑容斥,即钦定若干 ai+1<ai1,那么整个序列可以看成若干 ai+1<ai1 的连续段拼接而成。

我们可以求出每个连续段的 OGF f,然后答案就是 11f

因此我们要求出所有满足 ai+1<ai1 的序列 a1amai 值加进 [xm]f(x)

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

转移时排除 f,1×f1, 即可。

求出最终的 f(x) 后要把偶数项系数 ×1,因为长度为偶数说明钦定了奇数个 ai+1<ai1

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

答案就是 [xn]g1f,多项式求逆计算即可。

时间复杂度 O(klog2k+nlogn)

代码呈现

#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,定义 ai 表示 p1,,pi1,pi+1,,pn 的 LIS,求共能生成有多少个值域 [2,m] 的序列 a1an

数据范围:n5000

思路分析

很显然 a 的极差 1,并且大部分情况下最大值就是 p 的 LIS 长度,除非 a1==an=n1,此时 pi=i

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

很显然此时 LIS 长度 kn/2,因为每个元素的替换元素不可能相同。

并且这样的序列总能构造,一组合法的构造形如: n,n1,,2k+1,2,1,4,3,,2k,2k1

那么对于一般的情况,会有一些 ai=k1 的元素,表示他们是 LIS 的必经点。

设这些必经点为 x1xq,那么很显然 kq,并且对于每个 (xi,xi+1) 中间的段,对 LIS 的贡献至多为 xi+1xi12(设 x0=0,xq+1=n+1)。

那么一个必要条件就是 qkq+xi+1xi12,手玩发现这样的序列也总是能构造出来的。

不妨设 s=xi+1xi12,那么 k 的取值区间就是 max(q,3)min(m,q+s),并且确定 q,s 后,直接分配 s 个元素进入 q+1 个空,还剩 nq2s 个元素作为除以二的余数分进 q+1 个空(每个空至多一个)。

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

时间复杂度 O(n2)

代码呈现

#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

题目大意

给定 a1an,b1bn,所有元素是 12n 的一个随机排列,每次操作选定一个 k,比较 ak,bk 的大小:

  • 如果 ak<bk 那么删除 ak 并把 ak+1an 平移到 akan1
  • 否则删除 bk 并做相同操作。

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

数据范围:n106

思路分析

先考虑如何刻画最小操作次数,不妨设 2na 中,那么一定要清空 b

考虑每个 bi 被删除至少要删掉几个 a,设 pi 为最小的 api>bi 的点,那么至少需要 max(0,pii) 次操作才能删掉 bi

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

否则找到第一个 pi>ii 并操作,此时所有 pi>ipi 都会 1

因此答案就是 n+max{pii},不难把答案转成统计每个 max{pii}k 的概率。

考虑什么时候 max{pii}k,那么只要求出第一个 pii>k 的概率,此时 bi 大于 a1amin(n,i+k)

并且对于 b1bi1,由于 i 是第一个非法位置,因此 b1bi1 都要小于 a1amin(n,i+k)

因此 i 是第一个非法位置的概率就是 1i+min(i+k,n),总概率就是 i=1n(11i+min(i+k,n)),预处理前后缀积即可。

概率最终要 ×2 因为可以交换 a,b

时间复杂度 O(nlogP)

代码呈现

#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 个元素。

数据范围:n1018

思路分析

显然集合最大大小就是 n12,因为所有 (1,n1),(2,n2), 的匹配只能每对选一个,即 iSniS 恰有一个成立。

不妨设 S1n12 范围内的集合是 A,那么我们知道 A 就能还原 S,并且只要最小化 A 的字典序即可。

我们发现如果 x,yAx+yn12,那么必须有 x+yA,否则 nxySA,则 x,y,nxy 加起来得到 n

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

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

那么设选出了 xSA,则 A 中能选出若干元素和为 nx,但 nxn12 且能被 A 中元素表示,则根据刚才的结论,一定有 nxA,从而导出矛盾。

那么求 A 的过程就可以贪心,从小到大考虑每个元素,如果加入后依然不能表示出 n 就加入,并且把 n12 的子集和加入 A

考虑 A 中最小的元素,即第一个 p 满足 pn,此时 lcm(1,2,,p1)n,因此 p43

那么加入 p 后会加入所有 n12 的倍数。

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

fi 表示当前 A 中元素能组合出的最小的 modp=i 的数。

加入一个 x 就会让 fif(ikx)modn+kx,可以转两圈更新。

一个元素加入后合法当且仅当加入后 fnmodp>n,因此对于所有 i,都有 xnf(nix)modpi+1

枚举每个 xmodp 算出最小的 x,取出所有可能的 x 中最小的一个加入。

求答案可以二分,求出 Ak 的元素个数,即 i=0p1kfip+[i>0]

时间复杂度 O(p3+plogn)

代码呈现

#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

题目大意

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

数据范围:n5000n 为偶数。

思路分析

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

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

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

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

回到原问题。

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

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

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

时间复杂度 O(n2)

代码呈现

#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 个洞排成一圈,位置为 0n1,随机 n1 次,每次取出一个球,以 ai 的概率放在 i 上,然后从 i 出发,每到一个还没有球洞口,就以 12 的概率放进这个洞口,否则继续考虑下一个洞口。

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

数据范围:n40

思路分析

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

C(1,p1),C(1,p1+1),C(2,p2),C(2,p2+1),C(n1,pn1),C(n1,pn1+1),

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

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

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

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

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

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

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

对于 x1,如果在 C(x1,n+1)C(x1,2n1) 中成功判定,概率为 122++12k+1,因为 C(x1,n) 必须失败,然后在剩余的 k 个洞中进入其中的一个。

并且 x1,有 12k+1 的概率回到 C(x1,2n),因此 x1 最终未进入 0 的概率为 (122++12k+1)×112(k+1),即 2k12k+11

那么对于 x1xk 都未进入 0 的概率就是 ik2i12i+11=12k+11

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

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

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

fi,j,kfi1,jt,k1×(ai)t×(jt)

然后要考虑是否存在某个 C(x,i) 判定成功,都不成功的概率是 2k,成功一个的概率就是 12k

因此有转移 fi,j,kfi,j,k×2kfi,j,kfi,j,k+1×(12k)

最终答案是 fn1,n1,k2k+11

时间复杂度 O(n6)

代码呈现

#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;
}
posted @   DaiRuiChen007  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示