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\) 种元素钦定每种中的一个位置,有转移方程:
统计答案的时候依然是乘上剩余部分的阶乘以及容斥系数,也就是
这样我们就有了一个大概 \(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\),那么可以列出方程组:
然后化简得到:
考虑枚举 \(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)\) 的时间。
推推柿子:
设 \(S(n)=\sum_{i=1}^{n}\phi(i)\) ,那么有:
然后每次递归的时候整除分块求一部分,剩下的在循环里加上去复杂度就比较好了。这样做大概是 \(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;
}
浙公网安备 33010602011771号