[多校联考2021] 模拟赛1
总结
对于以前讲过的题还是要复习吧,比如今天 \(\tt T3\) 就是正睿讲的那个题,但是不会做了。
考场上 \(A\) 了 \(\tt T2\) 挺不错,只是语言选错了拿了暴力分。
思维不行真的没办法,但是能拿的分都要拿到吧。
盗梦空间
题目描述
\(q\) 次询问,每次给定树上 \(k\) 个关键点,求所有点中距离关键点最近距离的最大值。
\(n,m,q\leq 100000\)
解法
对于每次询问建虚树,然后讨论一下答案点落在哪里。
- 落在虚树结点上:树形DP解决。
- 落在空子树内:预处理子树内最远点,将儿子按照该值排序。
- 在虚树边上:二分边上的分界点在哪里,预处理倍增数组查询答案。
这东西除了 \(\tt Oneindark\) 谁写得出来啊!
爱乐之城
题目描述
要不你给我谨慎点,要不你给我常数小点。
提交的时候没选 \(\tt O_2\),直接 \(\tt T\) 飞了。嘤嘤嘤
给定 \(n\) 个元素 \(a_{1..n}\),对于 \(i\in[1,n]\),求 \(\forall i\in[1,n],F(\{a_{1..i}\})\),其中:
答案对 \(998244353\) 取模,强制在线,\(1\leq n,a_i\leq 4e5\)
解法
你要知道其实是三个不相关的子问题。
对于子问题 \(1\),也就是求这个:
这个只能在线算,把里面那东西反演一下:
其中 \(sum(i)\) 表示 \(1+2+....i\),这个数论分块可以直接 \(O(\sqrt m)\),应该是正确的复杂度。
对于子问题 \(2\),记 \(d=\gcd(a_1,a_2...,a_i)\),也就是求这个:
额,这个东西可以 \(O(m^2)\) 预处理,期望得分 \(35\) 分。
因为是 \(\tt gcd\),而且 \(a_1\) 是知道的,所以他是 \(a_1\) 的因数,有 \(\sqrt m\) 种取值,如果对于每一种取值能够快速算就好了,会不会 \(ij\) 对数很小呢?别想了,已经试过了,数量级大的一批。
因为不能把 \(\mu\) 筛都不能筛出来,分析一下他的性质,有了,我们先加一些条件再积性函数分拆。
An idea strikes me.
其中 \(zy(x)=\sum_{x|i}\mu(i)\),这个东西很容易预处理,额 \(...\) 所以我们在线算 \(zy(x)\) 不就行了?
凉啦,求的是所有情况的答案,\(100->0\),先不做这题了。
这可能就是命运吧。
不管了我就是要硬刚这个题:
前面那个东西是系数,可以直接算出来。
后面那东西好像可以边做边维护诶,每次就把 \(a_i\) 拿去分解就行了呗,把因数的答案更新下。
但是算 \(zxy_2(d)\) 的时间复杂度又爆掉了啊,现在要 \(O(m^2)\) 来算了,对了,那个东西在 \(zy(x)\) 改的时候顺便修改一下不就行了吗?
时间复杂度 \(O(m\sqrt m)\),真不错!但是好像还是过不了。
第一个子问题可以搞,我们直接把所有 \(v\) 的都处理出来,还是一个一个增加。
可以考虑成有若干个 \(x\),如果碰到一个 \(x\) 的倍数那么会改,所以也可以 \(O(m\log m)\)
都用因数的方式来考虑,都可以 \(O(m\log m)\)!
#include <cstdio>
#include <vector>
using namespace std;
const int M = 400005;
const int MOD = 998244353;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
void write(int x)
{
if(x<0) x=-x,putchar('-');
if(x<=9) {putchar(x+'0');return ;}
write(x/10);putchar(x%10+'0');
}
int n,m,op,cnt,ans,sy,a[M],b[M],c[M],f[M],g[M],zxy[M],now[M];
int cur,mu[M],p[M],vis[M],zy[M],sum[M];vector<int> v[M];
void sieve(int n)
{
mu[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i])
{
mu[i]=-1;
p[++cnt]=i;
}
for(int j=1;j<=cnt && i*p[j]<=n;j++)
{
vis[i*p[j]]=1;
if(i%p[j]==0) break;
mu[i*p[j]]=-mu[i];
}
}
for(int i=1;i<=n;i++)
{
b[i]=mu[i]*i*i%MOD;
c[i]=(c[i-1]+i)%MOD;
}
for(int i=1;i<=n;i++)
c[i]=c[i]*c[i]%MOD;
}
int gcd(int a,int b)
{
return !b?a:gcd(b,a%b);
}
void upd(int x,int y)
{
cur=(cur-mu[x]*zy[x]*zy[x])%MOD;
zy[x]+=y;
cur=(cur+mu[x]*zy[x]*zy[x])%MOD;
}
void fuck(int x,int y)
{
ans=(ans-g[x]*f[x])%MOD;
f[x]=f[x]*(y+1)%MOD;
ans=(ans+g[x]*f[x])%MOD;
}
void add(int x)
{
sy=(sy-b[x]*c[now[x]])%MOD;
now[x]++;
sy=(sy+b[x]*c[now[x]])%MOD;
}
signed main()
{
freopen("lalaland.in","r",stdin);
freopen("lalaland.out","w",stdout);
n=read();m=read();op=read();
sieve(m);
for(int i=1;i<=n;i++)
a[i]=read();
//把每个数的因数预处理出来
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j+=i)
v[j].push_back(i);
for(int i=1;i<=m;i++)
{
//这东西要在线算
for(int j=0;j<v[i].size();j++)
upd(v[i][j],mu[i]);
sum[i]=cur;f[i]=1;
}
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j+=i)
g[j]=(g[j]+mu[i]*sum[j/i])%MOD;
//优化
for(int i=1;i<=m;i++)
{
for(int j=0;j<v[i].size();j++)
add(v[i][j]);
zxy[i]=sy;
}
//在线算呗
for(int i=1;i<=n;i++)
{
if(op==1) a[i]=(19891989*ans+a[i])%m+1;
//出题人死了几个吗这么搞,WDNMD!
//枚举a[i]的所有因数
for(int j=0;j<v[a[i]].size();j++)
fuck(v[a[i]][j],zxy[a[i]]);
ans=(ans+MOD)%MOD;
if(op==0 && i==n) printf("%lld\n",ans);
else if(op==1) printf("%lld\n",ans);
}
}
星际穿越
题目描述
Once you’re a parent, you’re the ghost of your children’s future.
And love is the only thing we’re capable of perceiving that transcends time and space.
求满足下列条件的 \(r\times n\) 的矩阵个数:
- 给定一个正整数 \(k\),我们说 \(j(1\leq j<n)\) 是稳定的,当且仅当 \(\forall 1\leq i\leq r,a_{i,j}<a_{i,j+1}\)
- 每一行是一个长度为 \(n\) 的排列
- 若 \(j\) 是 \(k\) 的倍数,则第 \(j\) 列应该是不稳定的,否则若 \(j\) 不是 \(k\) 的倍数,则 \(j\) 列应该是稳定的。
答案对 \(998244353\) 取模。
\(n\leq 1000000,r\leq 19891989\)
解法
先讲一个 \(40\) 分的针对 \(r=1,n\leq2000\) 的做法,不会真的有人去打五分的爆搜吧。
就用类似连续段 \(dp\) 的思想,每次插入一个新的数就会带来一些改变,也就是 \(dp\) 过程中排列是不确定的。所以就不能存固定的数值,设 \(dp[i][j]\) 表示填完前 \(i\) 个位置,\(i\) 位置的数位前 \(i\) 个数中的第 \(j\) 大,转移就看当前位置是不是 \(k\) 的倍数,用前缀后缀和算一下就行了,时间复杂度 \(O(n^2)\)
正解需要容斥,但是我们是做过类似的题的:不等关系,令 \(m=\lfloor\frac{n-1}{k}\rfloor\),\(ans_i\) 表示有 \(i\) 个位置违反的方案数,那么:
考虑 \(dp\) 维护容斥系数,设 \(f_i\) 表示考虑到 \(i\) 的容斥系数,转移就枚举连续的一段放小于号,段的端点一定是不稳定的大于号,但是我们让他是随便放的,所以就会划分出段。对于那些被钦定成了小于号的大于号,会贡献一个 \(-1\) 的系数,我们再最后乘上 \((-1)^m\) 在随便放的那里贡献 \(-1\)(方便转移):
除以 \([k(i-j)]!\) 表示可重集的排列数,因为段内顺序一定,全局又是无序的,那么最后的答案是:
上面讨论的是 \(r=1\) 的情况,你发现转移算的是一段小于号的情况,这个可以直接扩展到 \(r>1\) 的情况,因为这样每一行就是独立的了,直接乘法原理即可,直接把所有的阶乘快速幂一下。
暴力算这个 \(dp\) 是 \(O(n^2)\) 的,但是可以分治 \(\tt FFT\) 可以 \(O(n\log^2n)\)
众所周知,简单的分治 \(\tt FFT\) 可以用多项式求逆优化,设 \(F(x)=\sum_{j=1}^\infty f_ix^i,G(x)=\sum_{j=1}^\infty\frac{1}{(jk)!}\):
直接多项式求逆做到 \(O(n\log n)\)
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 4000005;
const int MOD = 998244353;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,r,k,ans,fac[M],inv[M],a[M],f[M],g[M];
namespace poly
{
int len,A[M],B[M],rev[M];
int qkpow(int a,int b)
{
int r=1;
while(b>0)
{
if(b&1) r=r*a%MOD;
a=a*a%MOD;
b>>=1;
}
return r;
}
void NTT(int *a,int len,int op)
{
for(int i=0;i<len;i++)
{
rev[i]=(rev[i>>1]>>1)|((i&1)*(len/2));
if(i<rev[i]) swap(a[i],a[rev[i]]);
}
for(int s=2;s<=len;s<<=1)
{
int t=s/2,w=(op==1)?qkpow(3,(MOD-1)/s):qkpow(3,MOD-1-(MOD-1)/s);
for(int i=0;i<len;i+=s)
for(int j=0,x=1;j<t;j++,x=x*w%MOD)
{
int fe=a[i+j],fo=a[i+j+t];
a[i+j]=(fe+x*fo)%MOD;
a[i+j+t]=((fe-x*fo)%MOD+MOD)%MOD;
}
}
if(op==1) return ;
int inv=qkpow(len,MOD-2);
for(int i=0;i<len;i++) a[i]=a[i]*inv%MOD;
}
void work(int n,int *a,int *b)
{
len=1;while(len<2*n) len<<=1;
for(int i=0;i<len;i++) A[i]=B[i]=0;
for(int i=0;i<n;i++) A[i]=a[i];
for(int i=0;i<n/2;i++) B[i]=b[i];
NTT(A,len,1);NTT(B,len,1);
for(int i=0;i<len;i++)
A[i]=((2*B[i]-B[i]*B[i]%MOD*A[i])%MOD+MOD)%MOD;
NTT(A,len,-1);
for(int i=0;i<n;i++) b[i]=A[i];
}
void inv(int n,int *a,int *b)
{
b[0]=qkpow(a[0],MOD-2);
int cur=1;
while(cur<n)
{
cur<<=1;
work(cur,a,b);
}
}
void mul(int n,int *a,int *b)
{
len=1;while(len<2*n) len<<=1;
for(int i=0;i<len;i++) A[i]=B[i]=0;
for(int i=0;i<n;i++) A[i]=a[i],B[i]=b[i];
NTT(A,len,1);NTT(B,len,1);
for(int i=0;i<len;i++) A[i]=A[i]*B[i]%MOD;
NTT(A,len,-1);
//只需要n项
for(int i=0;i<n;i++) b[i]=A[i];
}
};
void init(int n)
{
fac[0]=inv[0]=inv[1]=1;
for(int i=2;i<=n;i++) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
for(int i=1;i<=n;i++)
{
inv[i]=poly::qkpow(inv[i],r);
fac[i]=poly::qkpow(fac[i],r);
}
}
signed main()
{
freopen("interstellar.in","r",stdin);
freopen("interstellar.out","w",stdout);
n=read();r=read();k=read();m=(n-1)/k+1;
init(1e6);
for(int i=1;i<m;i++)
g[i]=MOD-inv[i*k],a[i]=inv[i*k];
a[0]=1;
poly::inv(m,a,f);
poly::mul(m,g,f);
f[0]=1;//这里别忘记了哦
for(int i=0;i<m;i++)
ans=(ans+f[i]*inv[n-i*k])%MOD;
if((m-1)&1) ans=MOD-ans;
ans=ans*fac[n]%MOD;
printf("%lld\n",ans);
}