总结
对于以前讲过的题还是要复习吧,比如今天 ( t T3) 就是正睿讲的那个题,但是不会做了。
考场上 (A) 了 ( t T2) 挺不错,只是语言选错了拿了暴力分。
思维不行真的没办法,但是能拿的分都要拿到吧。
盗梦空间
题目描述
(q) 次询问,每次给定树上 (k) 个关键点,求所有点中距离关键点最近距离的最大值。
(n,m,qleq 100000)
解法
对于每次询问建虚树,然后讨论一下答案点落在哪里。
- 落在虚树结点上:树形DP解决。
- 落在空子树内:预处理子树内最远点,将儿子按照该值排序。
- 在虚树边上:二分边上的分界点在哪里,预处理倍增数组查询答案。
这东西除了 ( t Oneindark) 谁写得出来啊!
爱乐之城
题目描述
要不你给我谨慎点,要不你给我常数小点。
提交的时候没选 ( t O_2),直接 ( t T) 飞了。嘤嘤嘤
给定 (n) 个元素 (a_{1..n}),对于 (iin[1,n]),求 (forall iin[1,n],F({a_{1..i}})),其中:
答案对 (998244353) 取模,强制在线,(1leq n,a_ileq 4e5)
解法
你要知道其实是三个不相关的子问题。
对于子问题 (1),也就是求这个:
这个只能在线算,把里面那东西反演一下:
其中 (sum(i)) 表示 (1+2+....i),这个数论分块可以直接 (O(sqrt m)),应该是正确的复杂度。
对于子问题 (2),记 (d=gcd(a_1,a_2...,a_i)),也就是求这个:
额,这个东西可以 (O(m^2)) 预处理,期望得分 (35) 分。
因为是 ( t 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(msqrt m)),真不错!但是好像还是过不了。
第一个子问题可以搞,我们直接把所有 (v) 的都处理出来,还是一个一个增加。
可以考虑成有若干个 (x),如果碰到一个 (x) 的倍数那么会改,所以也可以 (O(mlog m))
都用因数的方式来考虑,都可以 (O(mlog 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
",ans);
else if(op==1) printf("%lld
",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 imes n) 的矩阵个数:
- 给定一个正整数 (k),我们说 (j(1leq j<n)) 是稳定的,当且仅当 (forall 1leq ileq r,a_{i,j}<a_{i,j+1})
- 每一行是一个长度为 (n) 的排列
- 若 (j) 是 (k) 的倍数,则第 (j) 列应该是不稳定的,否则若 (j) 不是 (k) 的倍数,则 (j) 列应该是稳定的。
答案对 (998244353) 取模。
(nleq 1000000,rleq 19891989)
解法
先讲一个 (40) 分的针对 (r=1,nleq2000) 的做法,不会真的有人去打五分的爆搜吧。
就用类似连续段 (dp) 的思想,每次插入一个新的数就会带来一些改变,也就是 (dp) 过程中排列是不确定的。所以就不能存固定的数值,设 (dp[i][j]) 表示填完前 (i) 个位置,(i) 位置的数位前 (i) 个数中的第 (j) 大,转移就看当前位置是不是 (k) 的倍数,用前缀后缀和算一下就行了,时间复杂度 (O(n^2))
正解需要容斥,但是我们是做过类似的题的:不等关系,令 (m=lfloorfrac{n-1}{k} floor),(ans_i) 表示有 (i) 个位置违反的方案数,那么:
考虑 (dp) 维护容斥系数,设 (f_i) 表示考虑到 (i) 的容斥系数,转移就枚举连续的一段放小于号,段的端点一定是不稳定的大于号,但是我们让他是随便放的,所以就会划分出段。对于那些被钦定成了小于号的大于号,会贡献一个 (-1) 的系数,我们再最后乘上 ((-1)^m) 在随便放的那里贡献 (-1)(方便转移):
除以 ([k(i-j)]!) 表示可重集的排列数,因为段内顺序一定,全局又是无须的,那么最后的答案是:
上面讨论的是 (r=1) 的情况,你发现转移算的是一段小于号的情况,这个可以直接扩展到 (r>1) 的情况,因为这样每一行就是独立的了,直接乘法原理即可,直接把所有的阶乘快速幂一下。
暴力算这个 (dp) 是 (O(n^2)) 的,但是可以分治 ( t FFT) 可以 (O(nlog^2n))
众所周知,简单的分治 ( t FFT) 可以用多项式求逆优化,设 (F(x)=sum_{j=1}^infty f_ix^i,G(x)=sum_{j=1}^inftyfrac{1}{(jk)!}):
直接多项式求逆做到 (O(nlog 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
",ans);
}