250514 模拟赛

分数:\(100+20+30=150\),打的好像是他们去年的某场,即便如此这个得分也是比较的神奇。前 \(1.5h\) 使用假算通过了 T1(?),然后写了 T2 \(O(n^3)\) 暴力拼尽全力无法优化,然后乱想 + 罚坐非常久开 T3;T3 推出来 \(=n\) 的条件之后不会了,最后半小时直接输出 \(n\) 获得了 \(30pts\) 的高分。疑似思路顺序比较对的一场。


T1 雪之下

给出一个长度为 \(n\) 的序列 \(a\),其中 \(a_i\in [1,n]\)\(m\) 次询问,每次给出 \(l,r\),回答有多少长度为 \(n\) 的排列 \(p\) 满足 \(\forall i\in[l,r],p_i\not=a_i\)

tag:dp,容斥,退背包,莫队

一眼容斥。首先考虑 \(n,m\le 20\),每次枚举给出的区间内钦定哪些位置与 \(a_i\) 相等,然后乘上剩余部分的阶乘以及容斥系数就好了,特判如果钦定的位置有重复元素方案数为 \(0\)

写完以上的做法发现其实钦定哪些位置不重要,只需要关注哪些元素在区间内,以及分别有多少个。我们设 \(f_{i,j}\) 表示考虑到当前区间内第 \(i\) 个元素,选择了 \(j\) 种元素钦定每种中的一个位置,有转移方程:

\[f_{i,j}\leftarrow f_{i-1,j}+f_{i-1,j-1}\times num_{i} \]

统计答案的时候依然是乘上剩余部分的阶乘以及容斥系数,也就是

\[ans=\sum_{i=0}^{tot}f_{tot,i}\times(n-i)!\times (-1)^i \]

这样我们就有了一个大概 \(O(n^3)\) 的做法。考场上发现这个东西开大空间就过了,然后就扔那了/汗。

发现每次只对当前的物品种类数做计数 dp,所以考虑用退背包 + 莫队来优化。过程类似以上,时间复杂度为 \(O(n^2\sqrt n)\)

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read()
{
	int t=0;char h=getchar();
	while(!isdigit(h))h=getchar();
	while(isdigit(h))t=(t<<1)+(t<<3)+(h^48),h=getchar();
	return t;
}
void write(int x)
{
	if(x>9)write(x/10);putchar(x%10+'0');
}
const int mod=998244353;
const int N=2010;
int n,m,sz;
int a[N];
struct node{
	int l,r,id;
	friend bool operator<(node a,node b)
	{
		return a.l/sz==b.l/sz?((a.l/sz)&1)?a.r<b.r:a.r>b.r:a.l/sz<b.l/sz;
	}
}q[N];
int num[N];
ll f[N];
int tot=0;
inline void back(int x)
{
	for(int i=1;i<=tot;i++)(f[i]+=mod-f[i-1]*num[x]%mod)%=mod;
}
inline void step(int x)
{
	for(int i=tot;i;i--)(f[i]+=f[i-1]*num[x]%mod)%=mod;
}
inline void add(int x)
{
	if(num[x])back(x);else ++tot;++num[x];step(x);
}
inline void del(int x)
{
	back(x);--num[x];if(num[x])step(x);else --tot;
}
int ans[N];
ll fac[N],p[2];
int main()
{
	// freopen("b.in","r",stdin);
	freopen("yukinoshita.in","r",stdin);
	freopen("yukinoshita.out","w",stdout);

	n=read();m=read();sz=max((int)sqrt(n),1);f[0]=1;fac[0]=1;p[0]=1;p[1]=mod-1;
	for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=m;i++)q[i].l=read(),q[i].r=read(),q[i].id=i;
	sort(q+1,q+m+1);
	int l=1,r=0;
	for(int i=1;i<=m;i++)
	{
		while(r<q[i].r)add(a[++r]);
		while(l>q[i].l)add(a[--l]);
		while(r>q[i].r)del(a[r--]);
		while(l<q[i].l)del(a[l++]);
		ll res=0;
		for(int j=0;j<=tot;j++)(res+=f[j]*fac[n-j]%mod*p[j&1]%mod)%=mod;
		ans[q[i].id]=res;
	}
	for(int i=1;i<=m;i++)write(ans[i]),puts("");
	return 0;
}

交完假算之后发现只用了一个半小时。


T2 雪乃酱

\(a,b\) 最初都在网格上的 \((1,1)\),两个人每秒可以选择向下或者向右走一步,最终都要走到 \((n,m)\),对两人途中曼哈顿距离不超过 \(k\) 的路径计数。

tag:dp,反射容斥

考场上写出了 \(O(n^3)\) 暴力。不妨设 \(n<m\)。设 \(f_{t,i,j}\) 表示到时刻 \(t\)\(a\) 在第 \(i\) 行,\(b\) 在第 \(j\) 行,共有多少种合法方案。枚举一下就好了。

	k/=2;f[0][1][1]=1;
	for(int t=1;t<=n+m-2;t++)
		for(int d=0;d<=k;d++)
			for(int i=1,j=i+d,lmt=min(n,t+k);j<=lmt;i++,j++)
				f[t&1][i][j]=(f[t&1^1][i-1][j]+f[t&1^1][i][j-1]+f[t&1^1][i-1][j-1]+f[t&1^1][i][j])%mod,
				f[t&1][j][i]=(f[t&1^1][j-1][i]+f[t&1^1][j][i-1]+f[t&1^1][j-1][i-1]+f[t&1^1][j][i])%mod;
	write(f[(n+m-2)&1][n][n]);

考场上想的是能不能用矩阵乘法之类的东西把上面优化到 \(O(n^2\log n)\) 什么的,但是发现点数非常多,这样做反而不优了。然后打打表,发现所有会经过的 \((i,j)\) 是一个条形,就像:

 0 0 0 0            
 0 0 0 0 0          
 0 0 0 0 0 0        
 0 0 0 0 0 0 0      
   0 0 0 0 0 0 0    
     0 0 0 0 0 0 0  
       0 0 0 0 0 0 0
         0 0 0 0 0 0
           0 0 0 0 0
             0 0 0 0

但是不会了。

后来 %%% klz 老师为我讲解了反射容斥。发现把上面那一堆 dp 柿子弄出来之后就很像一个三维的格路计数, 每次可以走 \((1,0,1),(1,1,0),(1,0,0),(1,1,1)\),设这些方向走的步数分别是 \(a,b,c,d\),那么可以列出方程组:

\[\begin{cases} a+b+c+d=n+m-2\\ a+d=n\\ b+d=n\\ \end{cases} \]

然后化简得到:

\[\begin{cases} b=a\\ c=m-a-2\\ d=n-a \end{cases} \]

考虑枚举 \(a\),然后计算此情况下路径的方案数。但是考虑题目中的限制,(不妨令 \(k\) 为原题中 \(k\) 的一半),我们需要保证 \(|a-b|\le k\)。发现变成了上边那个打表描述的形状,这个东西就可以用反射容斥解决。

反射容斥的时间复杂度是 \(O(\frac{n}{k})\),整体复杂度为 \(O(\frac{n^2}{k})\)。但是要是 \(k\) 比较小的话不就寄了!考虑 \(k\le S\)\(S\) 是某一个另外决定的值)的时候直接 dp,\(f_{i,j}\) 为求出 \(a+b\) 一共走了 \(i\) 步,\(a-b\)\(j\) 的方案数,时间复杂度为 \(O(nk)\)。发现最优的情况下 \(S=\sqrt n\),两种的复杂度都是 \(O(n\sqrt n)\)

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
inline int read()
{
	int t=0;char h=getchar();
	while(!isdigit(h))h=getchar();
	while(isdigit(h))t=(t<<1)+(t<<3)+(h^48),h=getchar();
	return t;
}
void write(int x)
{
	if(x>9)write(x/10);putchar(x%10+'0');
}
const int mod=998244353;
const int N=4e5+10;
inline ll quikp(ll a,int b)
{
	ll res=1;
	while(b)(b&1)&&((res*=a)%=mod),(a*=a)%=mod,b>>=1;
	return res;
}
ll fac[N],inv[N];
ll f[2][1000];
int n,m,k;
void init()
{
	f[0][k]=1;
	// for(int i=1;i<=n*2;i++)
	// 	for(int j=k+k;j>=0;j--)f[i&1][j]=(1ll*f[i&1^1][j-1]+f[i&1^1][j+1])%mod;
}
bool fl=false;
int a,b,c,d;
ll ans=0;
inline ll solve(int x,int y)
{
	return (x<0||y<0)?0:fac[x+y]*inv[x]%mod*inv[y]%mod;
}
inline void tu(int&x,int&y)
{
	swap(x,y);x-=k+1;y+=k+1;
}
inline void td(int&x,int&y)
{
	swap(x,y);x+=k+1;y-=k+1;
}
inline ll calc(int x,int y,int k)
{
	int a=x;
	ll res=solve(x,y);
	while(x>=0&&y>=0)
	{
		tu(x,y);res+=mod-solve(x,y);(res>=mod)&&(res-=mod);
		td(x,y);res+=solve(x,y);(res>=mod)&&(res-=mod);
	}
	x=a,y=a;
	while(x>=0&&y>=0)
	{
		td(x,y);res+=mod-solve(x,y);(res>=mod)&&(res-=mod);
		tu(x,y);res+=solve(x,y);(res>=mod)&&(res-=mod);
	}
	return res;
}
int main()
{
	// freopen("b.in","r",stdin);
	// freopen("b.out","w",stdout);
	freopen("yukino.in","r",stdin);
	freopen("yukino.out","w",stdout);

	n=read();m=read();k=read()/2;if(n>m)swap(n,m);fac[0]=1;
	for(int i=1,lmt=n+m;i<=lmt;i++)fac[i]=fac[i-1]*i%mod;
	inv[n+m]=quikp(fac[n+m],mod-2);
	for(int i=n+m-1;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;
	if(k<=sqrt(n))fl=true,init();
	for(int a=0;a<=n;a++)
	{
		ll res=0;
		b=a;c=m-a-1;d=n-a-1;if(c<0)break;
		if(fl)
		{
			for(int i=1;i>=0&&a;i--)
			{
				memset(f[i],0,sizeof f[i]);
				for(int j=k+k;j>=0;j--)
					f[i][j]=(f[i^1][j-1]+f[i^1][j+1])%mod;
			}
			res=f[0][k];
		}
		else res=calc(a,a,k);
		(res*=inv[a+b]*inv[c]%mod*inv[d]%mod)%=mod;
		(ans+=res);(ans>=mod)&&(ans-=mod);
	}
	write(ans*fac[n+m-2]%mod);
	return 0;
}

T3 可爱捏

\(w(l,r)=\sum_{i=l}^{r}\sum_{j=i}^{r}[gcd(i,j)\ge l]\)\(T\) 组询问,每次给出 \(n,k\),找到 \(0=x_0<x_1<x_2...<x_k=n\),最小化 \(\sum_{i=1}^{k}w(x_{i-1}+1,x_i)\)

tag:dp,决策单调性,整除分块,推推柿子

发现 \(k>\log_{2}n\) 的时候答案为 \(n\)。然后我输出 \(n\) 获得了\(T\le 5\)\(30\) 分。然后就不会了。

打表发现 \(w\) 具有决策单调性,然后就可以设 \(f_{i,j}\) 为划分到 \(j\),共划分了 \(i\) 段的最小代价之和。根据上一行的结论此时段数是 \(O(\log n)\) 的,所以我们只需要预处理出 dp 树组然后 \(O(1)\) 输出就好了。

分治求 \(f\) 数组,复杂度为 \(O(nt\log^2n)\),其中 \(t\) 为求一次 \(w(l,r)\) 的时间。

推推柿子:

\[\begin{aligned} w(l,r)&=\sum_{i=l}^{r}\sum_{j=i}^{r}[gcd(i,j)\ge l]\\ &=\sum_{k=l}^r\sum_{i=l}^r\sum_{j=l}^r[gcd(i,j)=k]\\ &=\sum_{k=l}^r\sum_{i=\lceil \frac{l}{k}\rceil}^{\lfloor\frac{r}{k}\rfloor}\sum_{j=i}^{\lfloor\frac{r}{k}\rfloor}[gcd(i,j)=1]\\ &=\sum_{k=l}^r\sum_{i=1}^{\lfloor\frac{r}{k}\rfloor}\sum_{j=i}^{\lfloor\frac{r}{k}\rfloor}[gcd(i,j)=1]\\ &=\sum_{k=l}^r\sum_{i=1}^{\lfloor\frac{r}{k}\rfloor}\phi(i) \end{aligned} \]

\(S(n)=\sum_{i=1}^{n}\phi(i)\) ,那么有:

\[w(l,r)=\sum_{k=l}^{r}S(\lfloor\frac{r}{k}\rfloor) \]

然后每次递归的时候整除分块求一部分,剩下的在循环里加上去复杂度就比较好了。这样做大概是 \(O(n\log^2n)\) 的。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read()
{
	int t=0;char h=getchar();
	while(!isdigit(h))h=getchar();
	while(isdigit(h))t=(t<<1)+(t<<3)+(h^48),h=getchar();
	return t;
}
void write(int x)
{
	if(x>9)write(x/10);putchar(x%10+'0');
}
const int N=1e5+10;
int pri[N],cnt=0;
ll phi[N];
inline ll calc(int l,int r)
{
	ll res=0;
	for(int i=l;l<=r;l=i+1)
	{
		i=min(r/(r/l),r);res+=phi[r/i]*(i-l+1);
	}
	return res;
}
ll f[20][N];
int k;
void solve(int l,int r,int lf,int rt)
{
	if(lf>rt)return;
	int mid=((lf+rt)>>1),pos=0;ll sum=calc(r+1,mid),minn=0x3f3f3f3f3f3f3f3f;
	for(int i=min(mid,r);i>=l;i--)
	{
		sum+=phi[mid/i];
		if(sum+f[k-1][i-1]<minn)minn=f[k-1][i-1]+sum,pos=i;
	}
	f[k][mid]=minn;
	solve(l,pos,lf,mid-1);solve(pos,r,mid+1,rt);
}
bool vis[N];
int lg[N];
void init()
{
	int n=1e5;
	for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
	phi[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])pri[++cnt]=i,phi[i]=i-1;
		for(int j=1;i*pri[j]<=n;j++)
		{
			vis[i*pri[j]]=true;
			if(i%pri[j])phi[i*pri[j]]=phi[i]*(pri[j]-1);
			else
			{
				phi[i*pri[j]]=phi[i]*pri[j];break;
			}
		}
	}
	for(int i=1;i<=n;i++)phi[i]+=phi[i-1];
	for(ll i=1;i<=n;i++)f[1][i]=i*(i+1)/2;
	for(k=2;k<=17;k++)solve(1,n,1,n);
	return;
}
int n;
int main()
{
	freopen("kawaii.in","r",stdin);
	freopen("kawaii.out","w",stdout);

	init();
	int t=read();
	while(t--)
	{
		n=read();k=read();write(k>lg[n]?n:f[k][n]);puts("");
	}
	return 0;
}
posted @ 2025-05-15 09:57  baiguifan  阅读(31)  评论(0)    收藏  举报