2026.1.14 题解

35+0+15=50pts,rk23。

T1 降智 + T2 读错题 + 没时间仔细想 T3,我能说什么呢。

还是没有找到适合自己的比赛节奏和解题技巧。

A. Easy Restrictions

考场直接降智,疯狂想容斥,唐成啥了。

实际上很简单。考虑先将问题转化成插板,然后发现只有 \(\bmod c\) 同余的位置才会相关。这个问题容易直接大力 \(dp\) 解决,最后暴力合并即可。

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

#include<bits/stdc++.h>
using namespace std;
const int N=5005,p=998244353;
int n,m,c,f[N][N][2],g[N][N][2],ck[N],ans[N],tmp[N<<1];
inline void add(int &x,int y){
	x+=y;
	if(x>=p) x-=p;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m>>c;
	f[0][0][0]=g[0][0][1]=1;
	for(int i=0;i<=n/c+1;i++) for(int j=0;j<=i;j++){
		add(f[i+1][j][0],f[i][j][0]),add(f[i+1][j][0],f[i][j][1]);
		add(f[i+1][j][1],f[i][j][0]),add(f[i+1][j+1][1],f[i][j][1]);
		add(g[i+1][j][0],g[i][j][0]),add(g[i+1][j][0],g[i][j][1]);
		add(g[i+1][j][1],g[i][j][0]),add(g[i+1][j+1][1],g[i][j][1]);
	}
	if(n%c) for(int i=0;i<=n/c;i++)
		add(ans[i]=g[n/c][i][0],g[n/c][i][1]);
	else for(int i=0;i<=n/c;i++) ans[i]=g[n/c][i][1];
	for(int i=1;i<c;i++){
		int cc=n/c+(i<=n%c);
		if(n%c==i) for(int j=0;j<=cc;j++) ck[j]=f[cc][j][1];
		else for(int j=0;j<=cc;j++) add(ck[j]=f[cc][j][1],f[cc][j][0]);
		for(int j=0;j<=cc;j++) for(int k=0;k<=n;k++)
			add(tmp[j+k],1ll*ck[j]*ans[k]%p);
		for(int j=0;j<=n;j++) ans[j]=tmp[j],tmp[j]=0;
	}
	for(int i=0;i<=m;i++) cout<<ans[i]<<" ";
	return 0;
}

B. Bubble Sort

T2 把题读错了,以为每次只换一对位置,关键问题是我还真找到了符合这种情况的排序方式,那我能说啥呢。

显然设以 \(i\) 为较小值的逆序对数量 \(b_i\) 每次变成 \(\max(b_i-1,0)\),那么假如最终 \(b'_i=0\),那么 \(b_i\in[0,\min(n-i,m)]\),所以后 \(m\) 个位置和数都是固定的。

显然,除却后 \(m\) 位以外,其他情况答案都只和前缀最大值数量有关,所以可以考虑 \(dp\)

我们设 \(dp_{i,j}\) 表示当前在第 \(i\) 个位置,前 \(i\) 个数中最大值为 \(j\) 的情况下贡献是多少。考虑分类讨论:

  1. 当前位固定。
    1. \(j'>a_i\)。那么 \(a_i\) 就不产生新贡献,即 \(dp_{i,j}\gets dp_{i-1,j'}\)
    2. \(j'<a_i\)。那么新的最大值就是 \(a_i\),则有 \(dp_{i,a_i}\gets (m+1)dp_{i-1,j'}\)
  2. 当前位不固定。
    1. \(j'>j\)。那么 \(j\) 就不产生新贡献,但是 \(j\) 有不同的取值。我们设 \(i\) 之后的固定位置中 \(\le j'\) 的有 \(c_{i,j}\) 个,则有 \(dp_{i,j'}\gets (i-j-c_{i,j})dp_{i-1,j'}\)
    2. \(j'<j\)。那么新的最大值就是 \(j\)。假如 \(j\) 这个元素没有被固定,则有 \(dp_{i,j}\gets (m+1)dp_{i-1,j'}\)

整个实际上只有 2.2 有点小问题,但是前缀和优化一下就能做到 \(O(n^2)\) 了。

答案即为 \(dp_{n-m,n-m}m!\)

#include<bits/stdc++.h>
using namespace std;
const int N=5005,p=998244353;
int n,m,ans=1,a[N],dp[N][N],C[N][N],vis[N];
inline void add(int &x,int y){
	x+=y,x-=(x>=p?p:0);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m,dp[0][0]=1;
	for(int i=1;i<=n;i++) cin>>a[i],vis[a[i]]=1;
	for(int j=1;j<=n;j++) for(int i=n;i;i--) C[j][i]=C[j][i+1]+(a[i]&&a[i]<=j);
	for(int i=1,c=0;i<=n-m;i++,c++){
		if(!a[i]) for(int j=0;j<=n-m;j++){
			add(dp[i][j+1]=dp[c][j],dp[i][j]);
			if(j) add(dp[i][j]=dp[i][j]*(vis[j]?0:m+1ll)%p,1ll*dp[c][j]*max(j-c-C[j][i],0)%p);
		}
		else for(int j=0;j<=n-m;j++){
			if(a[i]>j) add(dp[i][a[i]],dp[c][j]*(m+1ll)%p);
			else add(dp[i][j],dp[c][j]);
		}
	}
	for(int i=1;i<=m;i++) ans=1ll*ans*i%p;
	cout<<1ll*ans*dp[n-m][n-m]%p;
	return 0;
}

C. Softball

考场想出了大部分性质,考后听到了一点信息,又想了想就会了,不过实际上考试的时候 AC 概率不大。

下设 \(f(x)=\lfloor\log_2x\rfloor\)

显然不能写非常值域的东西,不然一分都拿不了。发现假如 \(f(x)<f(y)\),那么 \(x<y\)。那么我们一定可以取到所有 \(f(a_i )<f(w)\) 的位置,而且所有满足 \(f(a_i)>f(w)\) 的位置我们都取不到,而且满足 \(f(a_i)=f(w)\) 的位置我们至多能取到一个。

考虑什么时候能取到。假如我们要取等于的位置,我们就必须得保证不能使得任何一个小于的位置因为这个操作而无法取到。假如我们选择第 \(i\) 个位置作为等于的位置,那么合法性判断可以看作一些形如 \(x\oplus c_i\ge d_i\) 的式子。这些式子顶多有 \(O(n)\) 个,所以我们就得到了 \(O(n^2m)\) 的做法。

我们发现,这样的公式的解一定可以表示成 \(O(\log V)\) 个区间,假如我们能进一步减少公式的数量,我们就可以暴力求出区间交,然后用线段树得到答案。

稍加思考,我们就可以得到一个性质:

一个位置对应的判定方程组一定可以转化成只有 \(O(\log V)\) 个式子的判定方程组。

考虑证明。首先所有有效的判定方程一定是通过只考虑 \(f(a_i)\) 的位置的情况下,后缀最大(没有比它更大的)\(f(a_i)\) 的所有位置。

这些位置的 \(f(x)\) 一共只有 \(O(\log V)\) 种,但是假如我们经过了两个数 \(a,b\),满足 \(f(a)=f(b)\),那么假如经过它们两个还没死,那就说明当前的 \(f(w')>f(a)\),那你也就不用判断后面的情况了,否则一定会死。所以实际上只会有 \(O(\log V)\) 个方程式。

然后直接线段树乱搞就行。时间复杂度 \(O(n\log^3V+m\log V)\),但是严重跑不满。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,M=(1<<30)-1,K=N*200;
int n,m,tot,rt,a[N],lg[N],sf[N],mx[N],xr[N];
int e[N],cnt,tp,vs[N],sum[N],sg[K],ls[K],rs[K];
struct qj{
	int l,r,id;
}c[N];
inline void add(int &x,int l,int r,int L,int R,int v){
	if(!x) x=++tot;
	if(L<=l&&r<=R){
		sg[x]|=v;
		return;
	}
	int mid=(l+r)>>1;
	if(L<=mid) add(ls[x],l,mid,L,R,v);
	if(R>mid) add(rs[x],mid+1,r,L,R,v);
}
inline int num(int x,int l,int r,int k){
	if(!x||l==r) return sg[x];
	int mid=(l+r)>>1;
	if(k<=mid) return num(ls[x],l,mid,k)|sg[x];
	return num(rs[x],mid+1,r,k)|sg[x];
}
inline void cql(int x,int y,int ln,int bj){
	int now=0;
	for(int i=ln;~i;i--){
		int cc=(x&(1<<i))^(1<<i);
		if(y&(1<<i)) now|=cc;
		else{
			c[++tp]={now|cc,(now|cc)+(1<<i),1<<bj};
			e[++cnt]=c[tp].l,e[++cnt]=c[tp].r;
			now|=x&(1<<i);
		}
	}
	c[++tp]={now,now+1,1<<bj};
	e[++cnt]=now,e[++cnt]=now+1;
}
inline void solve(int ln){
	for(int i=n;i;i--){
		sf[i]=sf[i+1],mx[i]=mx[i+1];
		if(ln<=lg[i]) continue;
		sum[ln]++;
		if(lg[i]>mx[i]) mx[i]=lg[i];
		if(lg[i]==mx[i]) sf[i]=i;
	}
	for(int i=1;i<=n;i++) xr[i]=xr[i-1]^(lg[i]<ln?a[i]:0);
	for(int i=1,x,num;i<=n;i++) if(lg[i]==ln){
		x=sf[i+1],tp=cnt=num=0,cql(xr[i-1],a[i],ln,num++);
		while(x<=n){
			if(lg[x]==lg[sf[x+1]])
				cql(xr[x-1]^a[i],(1<<(lg[x]+1)),ln,num++),x=n+1;
			else cql(xr[x-1]^a[i],a[x],ln,num++),x=sf[x+1];
		}
		sort(e+1,e+cnt+1),cnt=unique(e+1,e+cnt+1)-e-1;
		for(int j=1;j<=tp;j++){
			vs[c[j].l=lower_bound(e+1,e+cnt+1,c[j].l)-e]+=c[j].id;
			vs[c[j].r=lower_bound(e+1,e+cnt+1,c[j].r)-e]-=c[j].id;
		}
		for(int j=1,sm=0;j<=cnt;j++){
			sm+=vs[j],vs[j]=0;
			if(sm==(1<<num)-1)
				add(rt,0,M,e[j],e[j+1]-1,1<<ln);
		}
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m,mx[sf[n+1]=n+1]=-1;
	for(int i=1;i<=n;i++) cin>>a[i],lg[i]=log2(a[i]);
	for(int ln=0;ln<30;ln++) solve(ln);
	while(m--){
		int w,ln;
		cin>>w,ln=log2(w);
		cout<<sum[ln]+((num(1,0,M,w)>>ln)&1)<<"\n";
	}
	return 0;
}
posted @ 2026-01-15 14:38  white_tiger  阅读(6)  评论(0)    收藏  举报