2026.2.4 模拟赛

https://oj.gxyzh.com/d/hzoj/contest/696b314c6599a4befbbd0617

T1


题解

考虑简单一些的情况:从 \((1,1)\) 出发。
显然对于这种情况可以用 \(dp\) 层层转移,得到最优答案。
\(设 dp_{i,0/1}\) 为从 \((1,1)\) 到第 \(i\) 层 \(0/1\) 门的最短距离。
这道题同理,只是起始位置变成了任意一层。
\(设 dp_{i,0/1,j,0/1}\) 为从第 \(i\) 层 \(0/1\) 门到第 \(j\) 层 \(0/1\) 门的最短距离。
发现答案不具有前缀性质,但可以合并,因此使用 \(ST\) 表/线段树/分块 维护。
线段树时间复杂度为 \(O(n+mlogn)\)。

调了两个小时没有调出来……

代码

#include<iostream>
#define  abs(x)  (((x)>0)?(x):(-(x)))
#define  int  long long
#define  lch  (x<<1)
#define  rch  (x<<1|1)
using namespace std;
constexpr int N=1e5+5;
int n,m;
struct D{ int x,y; };
D d[N][2],d2[N][2];
int dis(D x,D y){
	return abs(x.x-y.x)+abs(x.y-y.y);
}int dep(D x){
	return max(x.x,x.y);
}
struct State{ int a[2][2],L,R; };
struct Segment_Tree{
	State dp[N<<2];
	State merge(State x,State y){
		State ans; int L=x.L,mid=x.R,R=y.R;
		ans.L=L,ans.R=R;
		ans.a[0][0]=ans.a[0][1]=ans.a[1][0]=ans.a[1][1]=1e17;
		ans.a[0][0]=min(ans.a[0][0],x.a[0][0]+y.a[0][0]);
		ans.a[0][0]=min(ans.a[0][0],x.a[0][1]+y.a[1][0]);
		ans.a[0][1]=min(ans.a[0][1],x.a[0][0]+y.a[0][1]);
		ans.a[0][1]=min(ans.a[0][1],x.a[0][1]+y.a[1][1]);
		ans.a[1][0]=min(ans.a[1][0],x.a[1][0]+y.a[0][0]);
		ans.a[1][0]=min(ans.a[1][0],x.a[1][1]+y.a[1][0]);
		ans.a[1][1]=min(ans.a[1][1],x.a[1][0]+y.a[0][1]);
		ans.a[1][1]=min(ans.a[1][1],x.a[1][1]+y.a[1][1]);
		return ans;
	}void build(int x,int L,int R){
		if(L==R){
			dp[x].a[0][0]=dis(d2[L-1][0],d[L][0])+1;
			dp[x].a[0][1]=dis(d2[L-1][0],d[L][1])+1;
			dp[x].a[1][0]=dis(d2[L-1][1],d[L][0])+1;
			dp[x].a[1][1]=dis(d2[L-1][1],d[L][1])+1;		
			dp[x].L=L,dp[x].R=L+1;
			return;
		}int mid=(L+R)>>1; if(L>R)return;
		build(lch,L,mid),build(rch,mid+1,R);
		dp[x]=merge(dp[lch],dp[rch]);
	}State query(int x,int l,int r,int L,int R){
		if(l==L&&r==R)return dp[x]; int mid=(L+R)>>1;
		if(r<=mid)return query(lch,l,r,L,mid);
		else if(mid+1<=l)return query(rch,l,r,mid+1,R);
		else return merge(query(lch,l,mid,L,mid),query(rch,mid+1,r,mid+1,R));
	}
}tr;
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>d[i][0].x>>d[i][0].y;
		cin>>d[i][1].x>>d[i][1].y;
		d2[i][0].x=d[i][0].x+1,d2[i][0].y=d[i][0].y;
		d2[i][1].x=d[i][1].x,d2[i][1].y=d[i][1].y+1;
	}tr.build(1,2,n-1),cin>>m;
	for(int i=1;i<=m;i++){
		D x,y; cin>>x.x>>x.y>>y.x>>y.y;
		int dp1=dep(x),dp2=dep(y),ans=1e17;
		if(dp1>dp2)swap(x,y),swap(dp1,dp2);
		if(dp1==dp2)cout<<dis(x,y)<<'\n';
		else if(dp2-dp1==1){
			ans=min(ans,dis(x,d[dp1][0])+1+dis(d2[dp1][0],y));
			ans=min(ans,dis(x,d[dp1][1])+1+dis(d2[dp1][1],y));
			cout<<ans<<'\n';
		}else {
			State dp=tr.query(1,dp1+1,dp2-1,2,n-1);
			ans=min(ans,dis(x,d[dp1][0])+1+dp.a[0][0]+dis(d2[dp2-1][0],y));
			ans=min(ans,dis(x,d[dp1][1])+1+dp.a[1][0]+dis(d2[dp2-1][0],y));
			ans=min(ans,dis(x,d[dp1][0])+1+dp.a[0][1]+dis(d2[dp2-1][1],y));
			ans=min(ans,dis(x,d[dp1][1])+1+dp.a[1][1]+dis(d2[dp2-1][1],y));
			cout<<ans<<'\n';
		}
	}return 0;
}

T2

题解

赛时尝试 组合方法求概率 推了一个小时,还是没有推出答案。
实际上正解是用 方程求概率。
设 \(f_i\) 为第 \(i\) 层向上光线的概率,\(g_i\) 为第 \(i\) 层向下光线的概率。
依题意,有 \(g_0=1,f_n=0\)。
易有:

\[\begin{aligned} f_i =& g_ib_{i+1}+f_{i+1}a_{i+1}\\ g_i =& f_ib_i+g_{i-1}a_i \end{aligned} \]

从而

\[\begin{aligned} f_i =& g_ib_{i+1}+f_{i+1}a_{i+1}\\ g_i =& \frac{g_{i+1}-f_{i+1}b_{i+1}}{a_{i+1}} \end{aligned} \]

得出 \(f_{n-1}=\frac{b_n}{a_n}g_n\),\(g_{n-1}=\frac{1}{a_n}g_n\)。
(用 \(g_n\) 表示出 所有的 \(f_i,g_i\))
因此可以从后向前不断求解,最终得出 \(g_0=xg_n\)。
时间复杂度为 \(O(n)\)。

代码

#include<iostream>
#define  int  long long
using namespace std;
constexpr int N=5e5+5,p=1e9+7;
int n,a[N],b[N];
int f[N],g[N];
int qpow(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans=ans*a%p;
		a=a*a%p,b>>=1;
	}return ans;
}int inv(int x){
	if(x==100)return 570000004;
	else return qpow(x,p-2);
}signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i]>>b[i],
		a[i]=a[i]*inv(100)%p,
		b[i]=b[i]*inv(100)%p;
	g[n-1]=inv(a[n]),f[n-1]=g[n-1]*b[n]%p;
	for(int i=n-2;i>=0;i--)
		g[i]=(g[i+1]-f[i+1]*b[i+1])%p*inv(a[i+1])%p,
		f[i]=(g[i]*b[i+1]+f[i+1]*a[i+1])%p;
	cout<<inv((g[0]+p)%p)<<'\n';
	return 0;
}

T3

题解

简单计数 \(dp\),但是开始做 \(T3\) 时只剩 \(10\) 分钟。
易于设计出状态 \(dp_{i,j}\),表示长度为 \(i\)、最大值为 \(j\) 的方案数(计数 \(dp\) 状态设计一般都涉及值域)。
易于得出边界 \(dp_{1,j}=1\);
易于得出转移方程:\(dp_{i,j}=\sum_{k=1}^{cnt_j}dp_{i-1,k}\);
其中 \(cnt_j\) 表示小于等于 \(j\) 的数字个数。
前缀和优化,可以达到 \(O(n^2)\) 的时间复杂度。

代码

#include<iostream>
#define  int  long long
using namespace std;
constexpr int N=3005,p=1e9+7;
int n,cnt[N],dp[N][N],sum[N][N];//选i个,最大值为 j 
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1,x;i<=n;i++)
		cin>>x,cnt[x]++;
	for(int i=1;i<=n;i++)
		cnt[i]+=cnt[i-1];
	for(int j=1;j<=n;j++){
		if(cnt[j-1]==cnt[j]){
			for(int i=1;i<=n;i++)
				sum[i][j]=(sum[i][j-1]+dp[i][j])%p;
			continue;
		}dp[1][j]=1;
		sum[1][j]=(sum[1][j-1]+dp[1][j])%p;
		for(int i=2;i<=cnt[j];i++){
			dp[i][j]=sum[i-1][j];
			sum[i][j]=(sum[i][j-1]+dp[i][j])%p;
		}
	}for(int i=1;i<=n;i++)cout<<sum[i][n]<<'\n';
	return 0;
}

T4

题目描述

长度为 \(n\) 的排列,若满足对于所有的 \(i\) 都有 $$ ,则称他为 \(k\) 排列
现在给定 \(n,K\) ,你需要求出所有的 \(K\) 排列,答案对 \(924844033\) (质数)取模

数据范围

\(n,k\le2000\)

题解

正难则反,统计所有不合法的情况。
发现依然不好做,考虑容斥原理。

\[ans=n!+\sum_{i=1}^n (-1)^i\times f_i\times (n-i) \]

其中 \(f_i\) 表示至少有 \(i\) 个不合法的情况。

将 值域 与 数组下标 看成两个维度。
则不合法的点对为 \((i,k+i)\) 与 \((i-k,i)\)。

将这些点对分成一组:
\((r,k+r),(2k+r,k+r),(2k+r,3k+r),\dots\)
可以发现 相邻两个点对不能同时取到。
发现这样不够优美。
由于点对具有对称性,可以将这些点对分成一组:
\((r,k+r),(k+r,2k+r),(2k+r,3k+r),\dots\)
(十分优美)
一组的长度为 $\left \lfloor \frac{n-r}{k} \right \rfloor $,在 \(n\) 个连续元素中选取 \(m\) 个不相邻元素的方案数为 \(C_{n-m+1}^{m}\)。

现在我们可以轻松求出每组中选 \(m\) 个的方案数
如何得到最终的 \(f_i\)?
考虑卷积,将每组方案相乘即可。

时间复杂度为 \(O(n\times \frac{n}{k} \times k)\),即为 \(O(n^2)\)。

代码

#include<iostream>
#define  int  long long
using namespace std;
constexpr int N=2005,p=924844033;
int n,k,ans,a[N],b[N],c[N];
int fac[N],inv[N],fac_inv[N];
void init(){
	fac[0]=inv[0]=fac_inv[0]=1;
	fac[1]=inv[1]=fac_inv[1]=1;
	for(int i=2;i<=n;i++)
		fac[i]=fac[i-1]*i%p,
		inv[i]=(p-p/i)*inv[p%i]%p,
		fac_inv[i]=fac_inv[i-1]*inv[i]%p;
}int C(int n,int m){
	return fac[n]*fac_inv[n-m]%p*fac_inv[m]%p;
}signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>k,init();
	for(int j=0,len=(n-1)/k;len+1>=2*j;j++)b[j]=C(len-j+1,j);
	for(int i=2;i<=k;i++){
		if((n-i)/k<1)break;
		for(int j=0,len=(n-i)/k;len+1>=2*j;j++)c[j]=C(len-j+1,j);
		for(int j=0;j<=n;j++)
			for(int l=0;c[l]&&j+l<=n;l++)
				a[j+l]=(a[j+l]+b[j]*c[l]%p)%p;
		for(int j=0;j<=n;j++)b[j]=a[j],a[j]=c[j]=0;
	}for(int i=0;i<=n;i++)
		for(int j=0;j<=n&&i+j<=n;j++)
			a[i+j]=(a[i+j]+b[i]*b[j]%p)%p;
	for(int i=1;i<=n;i++)
		if(i&1)ans=(ans+a[i]*fac[n-i]%p)%p;
		else ans=(ans-a[i]*fac[n-i]%p)%p;
	ans=((fac[n]-ans)%p+p)%p;
	cout<<ans<<'\n';
	return 0;
} 
posted @ 2026-02-04 21:31  Zhou_Lemon  阅读(19)  评论(0)    收藏  举报