杂题记 2
写在前面:分解大致为 \(15\sim 20\) 题,题目难度高,大部分题个人认为的实际难度不低于洛谷的紫题。
\(\color{lightblue}{\texttt{CF1848F. Vika and Wiki}}\)
\(\text{link}\) 。提示:考虑第 \(i\) 次会变成啥。
$\texttt{solution}$
下边设 \(a_k\) 为实际中的 \(a_{k\bmod n}\)。首先根据样例猜测一定有解。
按照提示,注意到第 \(1\) 次 \(a_i\gets a_i\ \text{xor}\ a_{i+1}\),第二次 \(a_i\gets a_i\ \text{xor}\ a_{i+1}\ \text{xor}\ a_{i+1}\ \text{xor}\ a_{i+2}=a_i\ \text{xor}\ a_{i+2}\)。
但是第三次似乎没啥规律?这时注意到可以倍增,第 \(2^k\) 次 \(a_i\gets a_i\ \text{xor}\ a_{i+2^k}\)。于是 \(k=\log_2n\) 一定是一个解。但是要求最小解。
由于我们可以快速求每 \(2^k\) 次操作数组会变成啥,直接倍增即可,类似倍增 \(\text{lca}\) 求出最大的不变为全 \(0\) 的操作数,最后 \(+1\) 即可。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1<<20|5;
int n,a[N],b[N],ans;
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
if(!*max_element(a,a+n)) return cout<<0,0;
for(int i=n/2;i;i>>=1)
{
for(int j=0;j<n;j++) b[j]=a[j];
for(int j=0;j<n;j++) b[j]^=a[(j+i)&(n-1)];
if(*max_element(b,b+n)){ans+=i;for(int j=0;j<n;j++) a[j]=b[j];}
}
return cout<<ans+1,0;
}
\(\color{lightblue}{\texttt{CF512D. Fox And Travelling}}\)
\(\text{link}\) 。提示:缩点,dp。
$\texttt{solution}$
考虑把题目中的“加点”理解为删点,发现每个点可以被删当且仅当这个点只有一条连边,删点后删去这个点连的所有边。
发现这个性质很严,手模一下,发现对于一个边双,若边双内有 \(\ge 2\) 个点,那么整个边双都不可被删,这样性质就很好了。
考虑缩点后的森林(图不联通),称边双内点为 \(1\) 的为白点,否则为黑点。
考虑森林里一颗全为白点的树,dp 计算。发现钦定一个点为根会漏算,于是把这个树中所有点都当做根算一遍答案。记 \(size\) 为这棵树的大小,发现 \(t<size\) 个点的方案在以不是这 \(t\) 个点为根时都会算一遍,于是要除上 \(size-t\)。当 \(t=size\) 时方案就不重不漏,不用动。
简单说一下 dp 式,大概是 \(f_u=1,\forall v\in son_u,f_{u,i}\times f_{v,j}\times \binom{i+j}{j}\to f_{u,i+j}\),枚举完所有子节点后算上加上这个点的贡献:\(f_{v,sz_u}\gets f_{v,sz_u-1}\),复杂度同树形背包 \(O(n^2)\)。
剩下的树可以拆分成若干以黑点连出的白点为根的树,同上 dp,当发现子节点转移到黑点的时候直接 return,精细实现一下发现是对的。这时候以那个点为根不重不漏。
把每颗树的答案合并起来和上面的 dp 类似,记得 \(0\) 处答案的转移要特别注意,实现精细,还用不懂看代码。
$\texttt{code}$
#include<bits/stdc++.h>
#define P pair<int,int>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=105,M=1e4+5,mod=1e9+9;
int n,m,id[N],low[N],tot=1,head[N],ok[M<<1],cnt,bl[N],c[N],jc[N],inv[N],siz[N],f[N][N],ff[N],F[N],A[N],ans[N];
struct edge{int to,nex;}e[M<<1];P E[M];basic_string<int>g[N],h;bool v[N];
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline int C(int n,int m){return n<m?0:1ll*jc[n]*inv[m]%mod*inv[n-m]%mod;}
inline void add(int u,int v)
{
e[++tot]={v,head[u]};head[u]=tot;
e[++tot]={u,head[v]};head[v]=tot;
}
void dfs(int x,int fa)
{
id[x]=low[x]=++tot;
for(int i=head[x];i;i=e[i].nex)
{
int to=e[i].to;
if(!id[to]) dfs(to,x),low[x]=min(low[x],low[to]),(low[to]>id[x])&&(ok[i]=ok[i^1]=1);
else if(to^fa) low[x]=min(low[x],id[to]);
}
}
void dfs1(int x)
{
v[x]=1;bl[x]=cnt;c[cnt]++;
for(int i=head[x];i;i=e[i].nex){int to=e[i].to;if(!ok[i]&&!v[to]) dfs1(to);}
}//缩成边双
void dfs2(int x){v[x]=1;h+=x;for(int i:g[x]) if(!v[i]) dfs2(i);}//找到全白树的所有点
void dfs3(int x,int fa)
{
if(c[x]>1) return;siz[x]=f[x][0]=1;v[x]=1;//注意这里也要v=1,dfs2的v=1只处理了全白树
for(int i:g[x]) if(i^fa)
{
dfs3(i,x);
for(int j=0;j<=siz[x];j++) ff[j]=f[x][j],f[x][j]=0;
for(int j=0;j<=siz[x];j++) for(int k=0;k<=siz[i];k++)
f[x][j+k]=(f[x][j+k]+1ll*ff[j]*f[i][k]%mod*C(j+k,k))%mod;siz[x]+=siz[i];
}f[x][siz[x]]=f[x][siz[x]-1];
}//对于所有树的dp,注意这里的树形背包实现,要有上界
inline void sol(int x)
{
h.clear();dfs2(x);memset(F,0,sizeof(F));
for(int i:h)
{
memset(f,0,sizeof(f));dfs3(i,0);
for(int j=0;j<=h.size();j++) F[j]=md(F[j]+f[i][j]);
}
for(int i=0;i<=n;i++) A[i]=ans[i];
for(int i=1;i<=h.size();i++)
{
int t=1ll*F[i]*ksm(h.size()-i,mod-2)%mod;if(i==h.size()) t=F[i];
for(int j=i;j<=n;j++) ans[j]=(ans[j]+1ll*t*A[j-i]%mod*C(j,i))%mod;
}
}//对于全白树每个点dp并贡献到答案
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m;
jc[0]=1;for(int i=1;i<=n;i++) jc[i]=1ll*jc[i-1]*i%mod;
inv[n]=ksm(jc[n],mod-2);for(int i=n-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
for(int i=1,u,v;i<=m;i++) cin>>u>>v,add(u,v),E[i]={u,v};tot=0;
for(int i=1;i<=n;i++) if(!id[i]) dfs(i,0);
for(int i=1;i<=n;i++) if(!v[i]) cnt++,dfs1(i);
for(int i=1;i<=m;i++)
{
auto [u,v]=E[i];u=bl[u],v=bl[v];
if((u^v)) g[u]+=v,g[v]+=u;
}memset(v,0,sizeof(v));ans[0]=1;
for(int i=1;i<=cnt;i++) if(!v[i]&&c[i]>1)
for(int j:g[i]) if(!v[j]&&c[j]==1)
{
dfs3(j,i);
for(int I=0;I<=n;I++) A[I]=ans[I];
for(int I=1;I<=n;I++) for(int J=I;J<=n;J++) ans[J]=(ans[J]+1ll*f[j][I]*A[J-I]%mod*C(J,I))%mod;
}//黑点连出的白点为根的树特殊处理,贡献答案的方式略有不同
for(int i=1;i<=cnt;i++) if(!v[i]&&c[i]==1) sol(i);
for(int i=0;i<=n;i++) cout<<ans[i]<<"\n";
return 0;
}
\(\color{lightblue}{\texttt{CF1762E. Tree Sum}}\)
\(\text{link}\) 。提示:奇偶,算贡献。
$\texttt{solution}$
首先看到样例就猜测 \(n\) 为奇数答案为 \(0\),否则为啥剩下三样例个全是偶数?
考虑反证,记 \(F(u)\) 表示 \(\prod\limits_{(u,v,w)\in E} w\)。则由题意 \(\forall u,F(u)=-1\)。则 \(\prod\limits_{u}F(u)=(-1)^n=-1\)。
考虑每条边的贡献,由于每条边会在两个端点贡献一次,于是 \(\prod\limits_{u}F(u)=\prod\limits_{(u,v,w)\in E} w^2=1\),矛盾。
接下来考虑每种形态的树有怎样的填法,注意到类似 Prüfer 序列从叶节点剥,填法唯一。
考虑直接求 \(dis\) 很困难,于是算每条边的贡献。首先边在 \(dis\) 上等价于割掉这条边 \(1,n\) 在不同连通块上。设两个连通块的大小为 \(i,n-i\)。若边权为 \(1\),则两边子树内的 \(F\) 值都不变,于是两边都是合法的子树,\(2\mid i,2\mid n-i\)。否则两边一定都不合法,\(i,n-i\) 都是奇数。
考虑 \(i,n-i\) 的两个连通块有多少种构成树的方案,首先两个子树分别是 \(i^{i-2}\) 和 \((n-i)^{n-i-2}\),然后两边各选一个点连边是 \(i(n-i)\),然后除掉 \(1,n\) 的剩下 \(n-2\) 个点给 \(1\) 的连通块 \(i-1\) 个是 \(\binom{n-2}{i-1}\).
记 \(f_i=i^{i-1}\),则答案为 \(\sum\limits_{i=1}^{n-1} \dbinom{n-2}{i-1}f_if_{n-i}\),经验证:在 \(f_1\) 处这样答案仍是对的,复杂度 \(O(n\log n)\)。
注意到拆开组合数这是一个卷积形式,于是能 \(\texttt{NTT}\) 做到 \(O(N\log N)\) 算出 \(n=1\sim N\) 时的答案,但是在这题是没必要的。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e6+5,mod=998244353;
int n,f[N],jc[N],inv[N],ans;
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline int C(int n,int m){return n<m?0:1ll*jc[n]*inv[m]%mod*inv[n-m]%mod;}
int main()
{
scanf("%d",&n);if(n&1) return puts("0"),0;
jc[0]=1;for(int i=1;i<=n;i++) jc[i]=1ll*jc[i-1]*i%mod;
inv[n]=ksm(jc[n],mod-2);for(int i=n-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
for(int i=1;i<=n;i++) f[i]=ksm(i,i-1);
for(int i=1,t;i<n;i++) t=1ll*C(n-2,i-1)*f[i]%mod*f[n-i]%mod,ans=md(ans+((i&1)?mod-t:t));
return printf("%d",ans),0;
}
\(\color{lightblue}{\texttt{CF1305F. Kuroni and the Punishment}}\)
\(\text{link}\) 。提示:上界,一半,随机化。
$\texttt{solution}$
丢一道类似 trick 的题:CF364D。
首先,容易注意到答案上界为 \(n\),因为一定能在不超过 \(n\) 次操作内变成全偶数。
而后有结论:在最终的答案中,一定存在至少一半的元素,它们最多被操作过一次。
反证:如果有至少一半两次,那么总个操作数就 \(>n\) 了。
考虑这个一半怎么用,发现每次随机一个数组中的数,它们被操作次数 \(\le 1\) 的概率是 \(\ge \frac{1}{2}\) 的,于是直接随机若干个,枚举它被 \(\pm 1\) 或不动,枚举它质因子 check 即可。不会 check 就别做这题了。
设随机 \(C\) 个,\(V=\max(a)\),则复杂度为 \(O(C(n\omega(V)+\sqrt V))\),错误率为 \(\le\dfrac{1}{2^C}\),于是取 \(C\in[20,50]\) 即可通过,具体取值看喜好。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=2e5+5;
mt19937 rnd(time(0));
int n;LL a[N],b[N],ans=1e18,t,p[15];
inline void sol(LL w)
{
t=0;for(int i=2;1ll*i*i<=w;i++) if(w%i==0){p[++t]=i;while(w%i==0) w/=i;}
if(w>1) p[++t]=w;
for(int i=1;i<=t;i++)
{
LL s=0,c;
for(int j=1;j<=n;j++) c=a[j]%p[i],s+=a[j]<p[i]?p[i]-c:min(c,p[i]-c);
ans=min(ans,s);
}
}
int 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];shuffle(b+1,b+1+n,rnd);
for(int i=1;i<=min(20,n);i++) sol(b[i]),sol(b[i]-1),sol(b[i]+1);
return cout<<ans,0;
}
\(\color{lightblue}{\texttt{gym 104639E. Magical Pair}}\)
\(\text{link}\) 。提示:算贡献,离散对数,狄利克雷前缀和。
$\texttt{solution}$
为了简洁下面用 \(/\) 代替分数。
首先特判 \(A,B\) 有 \(n\) 的倍数的情况,发现当且仅当两者均为 \(n\) 倍数时才有贡献,于是这部分答案为 \((n-1)^2\)。
考虑令 \(A=(a,b)\) 表示 \(a=A\bmod n,b=A\bmod n-1\),类似定义 \(B=(c,d)\),则有:\(0\le a,c<n,0\le b,d<n-1\),且 \(n^2-n\) 个数与自然数对 \((a,b)\) 一一对应。
这时 \(A^B\equiv B^A\pmod n\Leftrightarrow a^d\equiv b^c\pmod n\)。
考虑算贡献,记 \(f_c\) 表示 \(a^d\equiv c\pmod n\) 的个数,则答案为 \(\sum\limits_{c=1}^{n-1} f_c^2\)。
设 \(g\) 为原根,\(m=n-1\),则 \(a^d\equiv c\pmod n\Leftrightarrow g^{a'd}\equiv g^{c'}\pmod n\Leftrightarrow a'd\equiv c'\pmod m\)。
此时有 \(0\le a',d,c'<m\),其中 \(a',c'\) 满足 \(g^{a'}\equiv a\pmod m,g^{c'}\equiv c\pmod m\)。
记 \(F_{c'}\) 表示 \(a'd\equiv c'\pmod m,0\le a',d<m\) 的个数,则答案为 \(\sum\limits_{i=0}^{m-1} F_i^2\)。
考虑固定 \(k\),求 \(xy\equiv k\pmod m,0\le x,y<m\) 的数量。
枚举 \((x,m)=d\),那么 \(x\) 的取值只有 \(\varphi(m/d)\) 种,我们需要保证 \(d\mid k\)。
那么有 \((x/d)\times y\equiv (k/d)\pmod {(m/d)},(x/d,m/d)=1\),也就是 \(y\equiv (k/d)\times ((x/d)^{-1})\pmod {(m/d)}\)。
于是 \(y\) 在 \(\bmod {(m/d)}\) 下唯一确定,此时 \(y\) 在 \(\bmod m\) 在有 \(d\) 种取值。于是此时合法的 \((x,y)\) 的方案数为 \(\sum\limits_{d\mid (m,k)} \varphi(m/d)d\)。
于是 \(\sum\limits_{i=0}^{m-1} F_i^2=\sum\limits_{i=0}^{m-1} \left(\sum\limits_{d\mid (m,i)} \varphi(m/d)d\right)^2=\sum\limits_{D\mid m} \varphi(m/D)\left(\sum \limits_{d\mid D} \varphi(m/d)d\right)^2\).
其中第二个等号是枚举 \((m,i)=D\) 时统计 \(i\)。记 \(g_D=\sum \limits_{d\mid D} \varphi(m/d)d,G_D=\varphi(m/D)D\),则 \(g_D=\sum\limits_{d\mid D}G_d\),做狄利克雷前缀和即可。
其中分解质因数要用 \(\texttt{pollard-rho}\)。
考虑计算这时狄利克雷前缀和的复杂度,记 \(m\) 的质因数分解为:\(\prod\limits_{i=1}^k p_i^{\alpha_i}\),则复杂度为 \(\sum\limits_{i=1}^k \sum\limits_{d\mid m,p_i^{\alpha_i}\nmid d}1=\sum\limits_{i=1}^k \alpha_i\prod\limits_{j\neq i}(\alpha_i+1)<\sum\limits_{i=1}^k \prod\limits_{j=1}^k(\alpha_i+1)=d(m)w(n)\),由于我实现不精细要对所有质因数进行一次排序,此时复杂度为 \(d(n)\log (d(n))\)。
质因数分解的复杂度为 \(\sqrt[4]{n}\),于是总复杂度 \(O(T(\sqrt[4]{n}+d(n)(w(n)+\log d(n))))\),查表发现足以通过本题。具体不懂的可以看代码。
注意,写代码时我的式子是:\(\sum\limits_{D\mid m} \varphi(D)\left(\sum \limits_{d\mid (m/D)} \varphi(m/d)d\right)^2\),显然这是等价的。
$\texttt{code}$
#include<bits/stdc++.h>
#include<bits/extc++.h>
#define P pair<LL,LL>
#define fi first
#define se second
#define LL long long
#define int LL
#define bll __int128
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(time(0));
const int N=2e5+5,M=25,mod=998244353;
LL T,n,m,t,p[M],c[M],pp[M],cnt,b[N];P a[N];
basic_string<int>g[M];
gp_hash_table<LL,int>mp;
inline int md(int x){return x>=mod?x-mod:x;}
inline void ad(int &x,int y){x=md(x+y);}
inline int ksm(int x,int p){LL s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
namespace PRHO
{
inline LL ksm(LL x,LL p,LL mod){LL s=1;for(;p;(p&1)&&(s=(bll)s*x%mod),x=(bll)x*x%mod,p>>=1);return s;}
#define mytz __builtin_ctzll
#define Abs(x) ((x)>0?(x):-(x))
inline LL gcd(LL a,LL b)
{
LL az=mytz(a),bz=mytz(b),z=min(az,bz),diff;b>>=bz;
while(a) a>>=az,diff=a-b,az=mytz(diff),b=min(a,b),a=Abs(diff);return b<<z;
}
const LL pr[]={2,3,5,7,11,13,17,19,23,29,31,37};
inline bool check(LL a,LL p)
{
LL d=a-1,t=0;while(~d&1) d>>=1,t++;LL now=ksm(p,d,a);
if(now==1||now==0||now==a-1) return 1;
for(int i=0;i<t;i++)
{
now=(bll)now*now%a;
if(now==1) return 0;
if(now==a-1&&i!=t-1) return 1;
}
return 0;
}
inline bool pd(LL x)
{
if(x==1) return 0;
for(LL i:pr)
{
if(x==i) return 1;
if(x%i==0||!check(x,i)) return 0;
}return 1;
}
#define f(x,c,n) (((bll)(x)*(x)+(c))%(n))
inline LL Find(LL x)
{
LL t1=1ll*rnd()*rnd()%(x-1)+1,c=1ll*rnd()*rnd()%(x-1)+1,t2=f(t1,c,x),d,mul=1;
for(int i=1;;i<<=1,t1=t2,mul=1)
{
for(int j=1;j<=i;j++)
{
t2=f(t2,c,x);
mul=(bll)mul*Abs(t1-t2)%x;
if(j%127==0){d=gcd(mul,x);if(d>1) return d;}
}d=gcd(mul,x);
if(d>1) return d;
}
}
void po(LL x)
{
if(x<=1e9){for(int i=2;i*i<=x;i++) if(x%i==0){p[++t]=i;while(x%i==0) x/=i;}if(x^1) p[++t]=x;return;}
if(x==1) return;
if(pd(x)) return p[++t]=x,void();LL num=Find(x);
while(x%num==0) x/=num;po(x),po(num);
}
inline void bk(LL x){t=c[1]=0;po(x);for(int i=1;i<=t;c[++i]=0) while(x%p[i]==0) c[i]++,x/=p[i];}
}using PRHO::bk;//封装的pollard-rho
void dfs(int x,LL s,LL ph)
{
if(x==t+1){a[++cnt]={s,ph};return;}LL S=1,PH=1;
for(int i=0;i<=c[x];i++) dfs(x+1,s*S,ph*PH),s*=p[x],PH*=(p[x]-(!i));
}//由质因子得到所有因子以及所有因子的phi
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>T;
while(T--)
{
cin>>n;m=n-1;bk(m);cnt=0;dfs(1,1,1);sort(a+1,a+1+cnt);mp.clear();
for(int i=1;i<=cnt;i++) mp[a[i].fi]=i;int x,ans=0,sum=1;//分解质因子部分
for(int i=1;i<=t;i++){pp[i]=1;for(int j=1;j<=c[i];j++) pp[i]*=p[i];}
for(int i=1;i<=t;i++) for(int j=1;j<=cnt;j++) if(a[j].fi%pp[i]) g[i]+=j;//这里就是处理可被转移的位置,自己仔细思考为啥这样做
for(int i=1;i<=cnt;i++) x=a[i].fi,b[i]=a[mp[m/x]].se%mod*(x%mod)%mod;//这行是求 G
for(int i=1;i<=t;i++) for(int j:g[i]) x=a[j].fi,ad(b[mp[x*p[i]]],b[j]);//这行是做狄利克雷前缀和求 g
for(int i=1;i<=cnt;i++) x=b[mp[m/a[i].fi]],ans=(ans+a[i].se%mod*x%mod*x)%mod;//统计答案部分
m%=mod;ans=(ans+m*m)%mod;cout<<ans<<"\n";//加上 (n-1)*(n-1)
for(int i=1;i<=t;i++) g[i].clear();
}
return 0;
}
\(\color{lightblue}{\texttt{ABC331G. Collect Them All}}\)
\(\text{link}\) 。提示:多项式,\(\min-\max\) 反演。
$\texttt{solution}$
锐评:出题人只会 \(\log^2\) 太不牛了!既然出了就得会但 \(\log\) 啊。而且官方题解写的一坨屎,太复杂了(
这题的有个小套路是参考P5860,推荐同时练习。
前置知识:多项式理论(要会多项式 \(\exp\)),概率期望,\(\min-\max\) 反演。
分析复杂度时默认 \(n,m\) 同阶。
考虑设第 \(i\) 个颜色出现的最早时间为 \(t_i\),则要求 \(E(\max(t_i))\)
发现 \(\min\) 比 \(\max\) 好求,进行 \(\min-\max\) 反演,令 \(S=\{a_1,a_2,\dots,a_m\}\):
\(E(\max(t_i))=\sum\limits_{T\subset S,T\neq \varnothing} (-1)^{|T|+1} E(\min(T))\)
考察 \(E(\min(T))\),发现一个位置的颜色属于 \(T\) 的概率为 \(P(x)=\dfrac{\sum\limits_{x\in T} x}{n}\),由于概率期望互为倒数,于是 \(E(\min(T))=\dfrac{n}{\sum\limits_{x\in T} x}\)
于是 \(E(\max(t_i))=n\times \sum\limits_{T\subset S,T\neq \varnothing} \dfrac{(-1)^{|T|+1}}{\sum\limits_{x\in T} x}\)
这时发现分母部分是和的倒数,难刻画,考虑剥离。
考虑 \(\forall s\),求 \(f_s=\sum\limits_{\substack{\sum\limits_{x\in T} x=s \\ T\subset S,T\neq \varnothing }}(-1)^{|T|+1}\),\(E(\max(t_i))=n\sum\limits_{s=1}^n\dfrac{f_s}{s}\)
这东西先考虑朴素 dp:设 \(F_{i,j}\) 表示 \(\sum\limits_{\substack{\sum\limits_{x\in T} x=j \\ T\subset \{1,2,\dots ,i\}}}(-1)^{|T|+1}\),则有转移:
\(F_{i,j}=F_{i-1,j}-F_{i-1,j-a_i}\),初值为 \(F_{0,0}=-1\),欲 \(\forall s\) 求 \(F_{n,s}\)
考虑套路性的设 \(F_i(x)=\sum\limits_{j\ge 0}F_{i,j}x^j\),则 \(F_{i}=(1-x^{a_i})F_{i-1}\)
于是 \(F_{n}=-\prod\limits_{i=1}^m(1-x^{a_i})\),此时大部分人都会分治 \(\text{NTT}\) 做到 \(O(n\log^2n)\)。
考虑求 \(\prod\limits_{i=1}^m(1-x^{a_i})\),限制条件为 \(a_i\ge 1,\sum a_i=n\),记 \(a_i=v\) 的有 \(c_v\) 个,进行 \(\ln-\exp\):
\(\prod\limits_{i=1}^m(1-x^{a_i})=\exp(\sum\limits_{i=1}^m\ln (1-x^{a_i}))=\exp(-\sum\limits_{v=1}^nc_v\sum\limits_{j=1}^{n/v}\dfrac{x^{vj}}{j})\)
里面直接暴力算即可,套多项式 \(\exp\),复杂度为 \(O(n\log n)\),注意不要漏 \(F\) 前的负号和前面 \(n\) 的系数。
$\texttt{code}$
#include<bits/stdc++.h>
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int mod=998244353,N=8e5+5;
int n,m,a[N],b[N],c[N],I[N],w[N],mmax,ans;
inline int rd()
{
int x=0,zf=1;
char ch=getchar();
while(ch<'0'||ch>'9') (ch=='-')and(zf=-1),ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*zf;
}
inline void wr(int x)
{
if(x==0) return putchar('0'),putchar(' '),void();
int num[35],len=0;
while(x) num[++len]=x%10,x/=10;
for(int i=len;i>=1;i--) putchar(num[i]+'0');
putchar(' ');
}
inline int bger(int x){return x|=x>>1,x|=x>>2,x|=x>>4,x|=x>>8,x|=x>>16,x+1;}
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline void dao(int *a,int n){for(int i=1;i<n;i++) a[i-1]=1ll*i*a[i]%mod;a[n-1]=0;}
inline void ji(int *a,int n){for(int i=n-1;i>=1;i--) a[i]=1ll*ksm(i,mod-2)*a[i-1]%mod;a[0]=0;}
inline void init(int mmax)
{
for(int i=1,j,k;i<mmax;i<<=1)
for(w[j=i]=1,k=ksm(3,(mod-1)/(i<<1)),j++;j<(i<<1);j++)
w[j]=1ll*w[j-1]*k%mod;
}
inline void DNT(int *a,int mmax)
{
for(int i,j,k=mmax>>1,L,*W,*x,*y,z;k;k>>=1)
for(L=k<<1,i=0;i<mmax;i+=L)
for(j=0,W=w+k,x=a+i,y=x+k;j<k;j++,W++,x++,y++)
*y=1ll*(*x+mod-(z=*y))* *W%mod,*x=md(*x+z);
}
inline void IDNT(int *a,int mmax)
{
for(int i,j,k=1,L,*W,*x,*y,z;k<mmax;k<<=1)
for(L=k<<1,i=0;i<mmax;i+=L)
for(j=0,W=w+k,x=a+i,y=x+k;j<k;j++,W++,x++,y++)
z=1ll* *W* *y%mod,*y=md(*x+mod-z),*x=md(*x+z);
reverse(a+1,a+mmax);
for(int inv=ksm(mmax,mod-2),i=0;i<mmax;i++) a[i]=1ll*a[i]*inv%mod;
}
inline void NTT(int *a,int *b,int n,int m)
{
mmax=bger(n+m);init(mmax);
DNT(a,mmax);DNT(b,mmax);
for(int i=0;i<mmax;i++) a[i]=1ll*a[i]*b[i]%mod;
IDNT(a,mmax);
}
void INV(int num,int *a,int *b)
{
if(num==1) return b[0]=ksm(a[0],mod-2),void();
INV((num+1)>>1,a,b);
int mmax=bger(num<<1);init(mmax);
static int c[N];
for(int i=0;i<num;i++) c[i]=a[i];for(int i=num;i<mmax;i++) c[i]=0;
DNT(c,mmax);DNT(b,mmax);
for(int i=0;i<mmax;i++) b[i]=1ll*(2-1ll*c[i]*b[i]%mod+mod)%mod*b[i]%mod;
IDNT(b,mmax);
for(int i=num;i<mmax;i++) b[i]=0;
}
inline void Ln(int *a,int n){static int b[N];for(int i=0;i<bger(n<<1);i++) b[i]=0;INV(n,a,b);dao(a,n);NTT(a,b,n,n);ji(a,n);for(int i=n;i<bger(n<<1);i++) a[i]=0;}
inline void Exp(int *a,int *b,int n)
{
if(n==1) return b[0]=1,void();
Exp(a,b,(n+1)>>1);static int c[N];for(int i=0;i<bger(n<<1);i++) c[i]=0;
for(int i=0;i<n;i++) c[i]=b[i];Ln(c,n);
for(int i=0;i<n;i++) c[i]=md(mod-c[i]+a[i]);c[0]=md(c[0]+1);
NTT(b,c,n,n);for(int i=n;i<bger(n<<1);i++) b[i]=0;
}
int main()
{
n=rd(),m=rd();for(int i=1;i<=m;i++) c[rd()]++;
I[1]=1;for(int i=2;i<=n;i++) I[i]=mod-1ll*I[mod%i]*(mod/i)%mod;
for(int i=1;i<=n;i++) for(int j=1;j<=n/i;j++) a[i*j]=(a[i*j]+1ll*c[i]*I[j])%mod;
for(int i=1;i<=n;i++) a[i]=md(mod-a[i]);Exp(a,b,n+1);
for(int i=1;i<=n;i++) ans=(ans+1ll*b[i]*I[i])%mod;
return wr(1ll*(mod-n)*ans%mod),0;
}
\(\color{lightblue}{\texttt{Snuke21 E. Tournament}}\)
\(\text{link}\) 。提示:竞赛图缩点后的性质。
$\texttt{solution}$
前置知识:竞赛图强联通分量性质,只要用到本文的结论 \(1\)。
竞赛图强连通缩点后的 \(\texttt{DAG}\) 呈链状, 前面的所有点向后面的所有点连边。
通常性的,我们考虑应考虑前 \(i\) 个强连通分量,于是我们对所有图缩点后对这样的划线计数:
假设某条线前面有 \(1\le i\le n\) 个点(注意不是强联通分量),后面有 \(n-i\) 个点,则这个竞赛图可以拆成:
前 \(i\) 个点组成的竞赛子图
后 \(n-i\) 个点组成的竞赛子图
所有前 \(i\) 个点向所有后 \(n-i\) 个点连的一条有向边
于是我们要把 \(n\) 个点划分成大小为 \(i,n-i\) 两个集合,两个集合内的点要以某种方式连成竞赛图,而后按照第三个条件再连边。
记 \(s_i=\binom{i}{2}\),则方案数为 \(\dbinom{n}{i}2^{s_i}2^{s_{n-i}}\),于是答案为 \(\sum\limits_{i=1}^n \dbinom{n}{i}2^{s_i}2^{s_{n-i}}\)
复杂度 \(O(n\log n)\sim O(n)\) 不等,取决于实现的精细(我代码是带 \(\log\) 的)
此题很难低于 \(O(n)\) 的原因:
由于 \(s_is_j+ij=s_{i+j}\),于是 \(2^{s_i}2^{s_{n-i}}=2^{n-i(n-i)}=2^{n^2-ni-i^2}\)。此时幂次出现了平方而且要求和,是经典不太可做问题。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e5+5,mod=1e9+7;
int n,ans,I[N];
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;
I[1]=1;for(int i=2;i<=n;i++) I[i]=mod-1ll*I[mod%i]*(mod/i)%mod;
for(int i=1,s=n;i<=n;s=1ll*s*(n-i)%mod*I[i+1]%mod,i++) ans=(ans+1ll*s*ksm(2,(1ll*i*(i-1)+1ll*(n-i)*(n-i-1))/2%(mod-1)))%mod;
return cout<<ans,0;
}
\(\color{lightblue}{\texttt{Snuke Density}}\)
\(\text{link}\) 。提示:分析 \(v_2\)。
$\texttt{solution}$
硬生生把一个困难的蓝想了黑的做法。(下面讲的是蓝的思维难度的做法)
前置知识:Kummer 定理,勒让德定理。
显然特判 \(c<\max(a,b)\) 和 \(c\ge a+b\) 的情况,现在情况是 \(a,b\le c<a+b\)
考虑变形:\(\dfrac{c!}{a!b!}=\dbinom{a+b}{a}\times \left(\dfrac{(a+b)!}{c!}\right)^{-1}\)
若它是整数,根据 Kummer 定理,\(v_2\left(\dbinom{a+b}{a}\right)\) 等于 \(a + b\) 在 \(2\) 进制下进位的次数。
于是根据数据范围有 \(v_2\left(\dbinom{a+b}{a}\right)\le 37\)。
于是 \(v_2\left(\dfrac{(a+b)!}{c!}\right)\le 37\Rightarrow \sum\limits_{i\ge 1} \left\lfloor\dfrac{a+b}{2^i}\right\rfloor-\left\lfloor\dfrac{c}{2^i}\right\rfloor\le 37\)
于是 $ \left\lfloor\dfrac{a+b}{2}\right\rfloor-\left\lfloor\dfrac{c}{2}\right\rfloor\le 37\Rightarrow 37\ge \left\lfloor\dfrac{a+b}{2}\right\rfloor-\left\lfloor\dfrac{c}{2}\right\rfloor\ge \dfrac{a+b}{2}-1-\dfrac{c}{2}\Rightarrow a+b-c\le 2\times 38=76$
于是 \(0<a+b-c\le 76\),否则不是整数。
考虑若它不是整数,则存在素数 \(p\) 使得 \(v_p\left(\dfrac{c!}{a!b!}\right)<0\),即 \(v_p\left(\dbinom{a+b}{a}\times \left(\dfrac{(a+b)!}{c!}\right)^{-1}\right)<0\)。
于是 \(p\mid \dfrac{(a+b)!}{c!}\),于是 \(p\) 必为 \((c,a+b]\) 中一个数的素因子。由于 \(0<a+b-c\le 76\),直接枚举而后分解素因子即可判断。
瓶颈在于分解素因子,设其复杂度为 \(T(V)\),则总复杂度为 \(O(\log V(T(V)+\omega(V)\log V))=O(T(V)\log V+\log^3V)\)
其中 \(V\) 为 \(\max\{a,b,c\}\),而最外层的 $\log $ 为 \(76\),显然由分析这时数始终是 \(\log V\) 级别的。
如果用 pollard-rho 则这题能做 \(V=10^{18}\)。那个黑的想法就不讲了,太蠢了。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e6+5;
LL a,b,c;
inline bool chk(LL p){LL cnt=0;for(LL i=p;i<=c;i*=p) cnt+=c/i-a/i-b/i;return cnt>=0;}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>a>>b>>c;
if(c>=a+b) return cout<<"YES",0;if(max(a,b)>c||a+b>c+80) return cout<<"NO",0;
for(LL i=c+1;i<=a+b;i++)
{
LL x=i;
for(LL j=2;j*j<=x;j++) if(x%j==0)
{
while(x%j==0) x/=j;
if(!chk(j)) return cout<<"NO",0;
}if((x^1)&&!chk(x)) return cout<<"NO",0;
}
return cout<<"YES",0;
}
\(\color{lightblue}{\texttt{Tenka1 F. ModularPowerEquation!!}}\)
\(\text{link}\) 。提示:幂塔。
$\texttt{solution}$
前置知识:扩展欧拉定理,幂塔。前置题目:P4139,一定要先做这个!
沿用记号:记 \(a\uparrow\uparrow b=\begin{matrix} \underbrace{a^{a^{a^{a\dots}}}}\\b \end{matrix}\)
想到幂塔这题就容易多了。感觉第一篇题解是没想清楚欧拉定理的细节写的,那个对较小数的处理太粗糙了!感觉这样直接求比他的递归更容易想到啊。
由前置题目推广:记 \(a_0=1,a_n=A^{a_{n-1}},b_n=a_n\bmod m\),则 \(\exists N\),使得 \(\forall n\ge N\),\(b_n\) 为常数。
于是取充分大的 \(n\),则 \(A\uparrow\uparrow n\) 为一组解。(你可以想象成 \(n\to +\infty\) 理解)
但是 \(R=a\uparrow\uparrow n\) 太大了,考虑 \(r\) 为一组解,想要:\(A^{R}\equiv A^{r}\pmod m,R\equiv r\pmod m\)。
不妨设 \(R>\varphi(m)\),于是由扩展欧拉可以变成构造:\(r>\varphi(m),R\equiv r\pmod {\varphi(m)},R\equiv r\pmod {m}\)。
记 \(L=\text{lcm}(m,\varphi(m))\),则我们容易构造出 \(r=R\bmod L+L\) 为满足条件且 \(\le 2\times 10^{18}\) 的解。
于是要求 \(R\bmod m,R\bmod \varphi(m)\),然后 exgcd 合并。
而求这个显然就转换为了前置题目,直接套上即可,复杂度 \(O(Q\sqrt{m}\log m)\)。注意 exgcd 时要用 __int128。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define bll __int128
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
LL T,a,mod,ph;
inline LL phi(LL x)
{
LL s=x;
for(LL i=2;i*i<=x;i++)
{
if(x%i==0) s=s/i*(i-1);
for(;x%i==0;x/=i);
}
if(x>1) s=s/x*(x-1);return s;
}
inline LL ksm(LL x,LL p,const LL mod){LL s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
LL dfs(LL mod)
{
if(mod==1) return 0;LL t=phi(mod);
return ksm(a,dfs(t)+t,mod);
}//前置题目的过程
void exgcd(LL a,LL b,bll &x,bll &y)
{
if(!b) return x=1,y=0,void();
exgcd(b,a%b,x,y);bll t=x-(a/b)*y;x=y;y=t;
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>T;
while(T--)
{
cin>>a>>mod;ph=phi(mod);LL x=dfs(mod),y=dfs(ph);bll x0,y0;//分别求解 mod m,mod phi(m)
LL X,L=mod/__gcd(mod,ph)*ph,C=(y-x)/__gcd(mod,ph);exgcd(mod,ph,x0,y0);//exgcd合并,记得__int128!
X=((x0%L+L)%L*C%L*mod%L+x)%L;cout<<X+L<<"\n";//记得+L!
}
return 0;
}
\(\color{lightblue}{\texttt{LG P9970. 套娃}}\)
\(\text{link}\) 。提示:极小区间。
$\texttt{solution}$
前置知识:P4137 的在线单次 \(O(\log n)\) 求区间 \(\text{mex}\) 做法,CF1870E 的结论。
赛事做繁了,没冲出来。发现第二篇题解难理解,第一篇题解有个细节繁了,于是我结合了一下。除去主席树板子,代码非常短。
这篇题解是目前第一篇题解和第二篇的结合,由重合请见谅。
如果不想看结论证明直接忽略即可,没啥影响。
称 \([l,r]\) 是极小区间,当且仅当不存在 \([L,R]\varsubsetneq[l,r]\),\(\text{mex}(l,r)=\text{mex}(L,R)\)。则有结论:极小区间只有 \(O(n)\) 个。
- 证明:设 \([l,r]\) 是极小区间,则显然 \(a_l\neq a_r\),不妨设 \(a_l>a_r\),则由于删掉端点 \(\text{mex}\) 要变化,于是 \(\text{mex}(l,r)>a_l>a_r\)。若存在 \(r_1>r\),\([l,r_1]\) 是极小区间,则 \(\text{mex}(l,r)\ge a_l>a_{r_1}\),于是 \(a_{r_1}\) 在 \([l,r]\) 中出现过。于是删去 \(a_{r_1}\),\(\text{mex}\) 不变。于是固定 \(l\),\(a_l>a_r\) 的情况只对应一个 \(r\),所以只有 \(O(n)\) 个了。
考虑如何求所有极小区间。如果直接按证明方法求是非常难写的。于是考虑巧妙方法。
设 \(\text{mex}(l,r)=x\),则称 \([l,r]\) 为 \(\text{mex}_x\) 区间。
考虑一个 \(\text{mex}_x\) 的极小区间,同样不妨设 \(a_l>a_r\),则由于极小性,于是 \(a_r\) 在 \((l,r)\) 中没有出现。
考虑删去 \(a_l\) 之后,\(\text{mex}\) 变为 \(a_l\),不妨设 \(\text{mex}_{a_l}\) 区间 \([l+1,r]\) 对应的极小子区间为 \([L,R]\),则 \(a_r\) 一定在 \([L,R]\) 中出现,于是 \(R=r\)。
- 这说明:\(\text{mex}_x\) 区间必定为一个 \(\text{mex}_{t}\) 区间向一端扩展到第一个数 \(t\) 得到。其中 \(x>t\)。
考虑从 \(x=0\to n+1\) 依次求出所有 \(\text{mex}_x\) 极小区间。对于每个 \(x\) 维护 \(\text{mex}_x\) 极小区间的 vector。
每次先把所有 \(\text{mex}_{x-1}\) 区间进行如上扩展,设一个扩展完后 \(\text{mex}=y\),则把新区间丢到 \(y\) 的 vector 里。这里求扩展完的 \(\text{mex}\) 用上面说的单次 \(O(\log n)\) 的方法。
这时候所有 \(\text{mex}_x\) 极小区间都在 \(x\) 的 vector 里了,但注意扩展完的不一定都是极小的,于是排除掉即可。就不多不少的求出了 \(\text{mex}_x\) 极小区间,一直做下去即可。记得初始化极小 \(\text{mex}_{0/1}\) 区间。
由于极小区间总共只有 \(O(n)\) 个,乘上求区间 \(\text{mex}\) 的 \(\log\),于是复杂度为 \(O(n\log n)\)。
求出所有极小区间后,考虑所有对应 \(\text{mex}_x\) 的极小子区间 \([l,r]\) 的大区间的形态。
设 \(l\) 左侧第一个 \(x\) 的位置为 \(L-1\),\(r\) 右侧第一个 \(x\) 的位置为 \(R+1\)。
则所有对应的大区间为:左端点在 \([L,l]\),右端点在 \([r,R]\) 的所有区间。于是 \(\forall len\in [r-l+1,R-L+1]\),存在长为 \(len\) 的区间 \(\text{mex}=x\)。
于是问题就转化成了:维护 \(n\) 个集合,区间插入一个数,最终对所有集合求 \(\text{mex}\)。把插入操作差分,用一个 set 动态维护 \(\text{mex}\) 即可。具体看代码。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define P pair<int,int>
#define fi first
#define se second
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e5+5;
int n,V,a[N],cnt[N],tot,rt[N];
vector<int>g[N],ad[N],dl[N];vector<P>h[N];
namespace MEX
{
struct node{int ls,rs,x;}b[N*25];
void build(int l,int r,int &wz)
{
b[wz=++tot]={0,0,0};if(l==r) return;
int mid=(l+r)>>1;build(l,mid,b[wz].ls);build(mid+1,r,b[wz].rs);
}
void updata(int l,int r,int &wz,int wz1,int x,int y)
{
b[wz=++tot]=b[wz1];if(l==r) return b[wz].x=y,void();
int mid=(l+r)>>1;
if(x<=mid) updata(l,mid,b[wz].ls,b[wz1].ls,x,y);
else updata(mid+1,r,b[wz].rs,b[wz1].rs,x,y);
b[wz].x=min(b[b[wz].ls].x,b[b[wz].rs].x);
}
int query(int l,int r,int wz,int x)
{
if(l==r) return l;int mid=(l+r)>>1;
if(b[b[wz].ls].x<x) return query(l,mid,b[wz].ls,x);
return query(mid+1,r,b[wz].rs,x);
}
inline int que(int l,int r){return query(0,V,rt[r],l);}
}using MEX::que;
int vis[1005][1005];
inline void add(int l,int r,int L,int R,int x){ad[L-r+1].push_back(x);dl[R-l+2].push_back(x);}
inline void getans()
{
set<int>S;for(int i=0;i<=n;i++) cnt[i]=0,S.insert(i);
for(int i=1;i<=n;i++)
{
for(int j:ad[i]) if(!cnt[j]++) S.erase(j);
for(int j:dl[i]) if(!--cnt[j]) S.insert(j);cout<<*S.begin()<<" ";
}
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;
V=n+3;for(int i=0;i<=V;i++) g[i].push_back(0);
for(int i=1;i<=n;i++) cin>>a[i],g[a[i]].push_back(i);
MEX::build(1,V,rt[0]);for(int i=1;i<=n;i++) MEX::updata(0,V,rt[i],rt[i-1],a[i],i);
for(int i=0;i<=V;i++) g[i].push_back(n+1);
for(int i=1;i<=n;i++) a[i]?h[0].push_back({i,i}):h[1].push_back({i,i});
for(int i=1;i<=V;i++)
{
for(auto [l,r]:h[i-1])
{
#define all g[i-1].begin(),g[i-1].end()
int L=*(--lower_bound(all,l)),R=*upper_bound(all,r);
if(L) h[que(L,r)].push_back({L,r});
if(R<=n) h[que(l,R)].push_back({l,R});
}sort(h[i].begin(),h[i].end(),[](P x,P y){return x.fi==y.fi?x.se<y.se:x.fi>y.fi;});
vector<P>G;int las=2e9;
for(auto [l,r]:h[i]) if(las>r) G.push_back({l,r}),las=r;swap(G,h[i]);
}
for(int i=0;i<=V;i++)
for(auto [l,r]:h[i])
#define all g[i].begin(),g[i].end()
add(*(--lower_bound(all,l))+1,l,r,*upper_bound(all,r)-1,i);
return getans(),0;
}
\(\color{lightblue}{\texttt{LG P9291. Quick sort}}\)
\(\text{link}\) 。提示:逆操作。
$\texttt{solution}$
人类智慧题。需要一些矩阵乘法的知识辅助理解。
首先 \(O(n\log n)\) 次操作时容易的,按顺序把 \(n\to 1\) 依次归位即可。每个数归位由于每次距离减半于是是 \(\log n\) 的。
这时候随机一下会不会很优秀?但是有佬证明了这个算法期望下依然是 \(O(n\log n)\) 次的。不足以通过。
到了人类智慧部分了,看这个结构本来不是很好逆,但是依然要逆。
不妨设初始排列为 \(p\),\(1\sim n\) 的排列为 \(I\)。操作为 \(f_1,f_2,\dots,f_k\),则 \(p\circ f_1\circ f_2\circ\dots\circ f_k=I\)。
考虑左右求逆。操作本质为矩阵乘法,于是类似的做。同时 \(1\sim n\) 的排列可以看做单位矩阵。
于是 \(f_k^{-1}\circ \dots \circ f_2^{-1}\circ f_1^{-1}\circ p^{-1}=I\)。由于矩乘中 \(AB=I\Leftrightarrow BA=I\),于是 \(p^{-1}\circ f_k^{-1}\circ \dots \circ f_2^{-1}\circ f_1^{-1}=I\)。
- 令逆操作 \(f^{-1}=g\),相当于对逆排列 \(p'=p^{-1}\) 进行编号 \(k\to 1\) 的逆操作,最终要变成 \(1\sim n\) 的排列。
同样的,我们依然按顺序把 \(n\to 1\) 依次归位。然后把操作逆序输出。
观察对 \([1,2i]\) 逆操作,则位置 \(i\) 上的数变到位置 \(2i\) 了。同时 \(\forall \frac{i}{2}<w<i\) 的位置 \(w\),对 \([2w-i+1,i]\) 逆操作即可把位置 \(w\) 上的数移动到位置 \(i\) 上。
设 \(i\) 在位置 \(w\le i\)。如果 \(w\neq i\),我们先把 \(w\) 倍增到 \((\frac{i}{2},i]\) 范围,而后进行一次逆操作即可。次数为 \(\log_2(i/w)\le \log_2 i-\log_2 w+1\)。
看似没有优化?但是不妨先进行随机打乱,此时假设 \(p\) 在 \([1,i]\) 中随机选取,则 \(E(\log _2(w))=\dfrac{\log_2(i!)}{i}\sim \log_2\left(\sqrt[2i]{2\pi i}\times \dfrac{i}{e}\right)=\log_2i-\log_2(e)+C\)。
其中 \(C>0\) 是极小的余项,可以用函数图像感受一下。近似部分用了 Stirling 公式估计阶乘。
于是 \(\log_2 i-\log_2 w+1\sim \log_2e+1\),于是总次数估计为 \(2n\sim 3n\) 次。足以通过。事实上次数上界设为 \(6000\) 也能飞快就出结果。
关于随机打乱:方法是随机选常数个区间进行逆操作。直接对 \(p'\) 这样做即可,还原为 \(1\sim n\) 后,最后逆序输出时这样显然不影响正确性。
注意最开始是对逆排列进行上述操作!具体细节看代码,代码很短。
$\texttt{code}$
#include<bits/stdc++.h>
#define P pair<int,int>
#define fi first
#define se second
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
mt19937 rnd(time(0));
const int N=3e3+5,M=1.5e4+5;
int n,a[N],b[N],c[N],tot;P ans[M];
inline void rep(int l,int r)
{
ans[++tot]={l,r};int t=l;
for(int i=l;i<=r;i++) c[i]=b[i];
for(int i=l+1;i<=r;i+=2) b[i]=c[t++];
for(int i=l;i<=r;i+=2) b[i]=c[t++];
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;
for(int i=1,x;i<=n;i++) cin>>x,a[x]=i;
do
{
memcpy(b,a,sizeof(b));tot=0;
for(int i=1;i<=52;i++)
{
int l=rnd()%n+1,r=rnd()%n+1;
if(l>r) swap(l,r);rep(l,r);
}
for(int i=n;i;i--) if(b[i]^i)
{
int w=i;while(b[w]^i) w--;
while((w<<1)<=i) rep(1,w<<=1);
if(w^i) rep(2*w-i+1,i);
}
}while(tot>7500);cout<<tot<<"\n";
for(int i=tot;i;i--) cout<<ans[i].fi<<" "<<ans[i].se<<"\n";
return 0;
}
\(\color{lightblue}{\texttt{loj 6575. LJJ 学二项式定理 二}}\)
\(\text{link}\) 。提示:\(\texttt{GF}\)。
$\texttt{solution}$
前置知识:齐次线性递推。
注意到瓶颈在于组合数上标有 \(+\ i\)。
考虑 \([x^n]\dfrac{1}{(1-x)^{k+1}}=\dbinom{n+k}{k}\),于是容易构造出答案为:\([x^n]\sum\limits_{i\ge 0}\dfrac{x^{i(m-1)}}{(1-x)^{im+1}}\).
\([x^n]\sum\limits_{i\ge 0}\dfrac{x^{i(m-1)}}{(1-x)^{im+1}}=[x^n]\frac{1}{1-x}\sum\limits_{i\ge 0}\left(\dfrac{x^{m-1}}{(1-x)^{m}}\right)^i=[x^n]\frac{1}{(1-x)(1-f(x))}\left(f(x)=\dfrac{x^{m-1}}{(1-x)^{m}}\right)=[x^n]\dfrac{(1-x)^{m}}{(1-x)((1-x)^{m}-x^{m-1})}\)
直接上齐次递推中的 Bostan-mori 算法即可,复杂度 \(O(m^2\log n)\) 或 \(O(m\log m\log n)\),取决于多项式乘法的实现。
在 \(m=5000,p=998244353\) 时需要 \(\texttt{NTT}\) 来加速。否则超时。
一个细节就是最后的分母始终是 \(1\),不需要求逆得出结果。还有不懂可以看代码。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=4e4+5,mod=998244353;
LL n;int m,Mod,a[N],b[N],c[N],w[N],C[N/4][N/4];
inline int bger(int x){return x|=x>>1,x|=x>>2,x|=x>>4,x|=x>>8,x|=x>>16,x+1;}
inline int md(int x){return x>=Mod?x-Mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline void init(int mmax)
{
for(int i=1,j,k;i<mmax;i<<=1)
for(w[j=i]=1,k=ksm(3,(mod-1)/(i<<1)),j++;j<(i<<1);j++)
w[j]=1ll*w[j-1]*k%mod;
}
inline void DNT(int *a,int mmax)
{
for(int i,j,k=mmax>>1,L,*W,*x,*y,z;k;k>>=1)
for(L=k<<1,i=0;i<mmax;i+=L)
for(j=0,W=w+k,x=a+i,y=x+k;j<k;j++,W++,x++,y++)
*y=1ll*(*x+mod-(z=*y))* *W%mod,*x=md(*x+z);
}
inline void IDNT(int *a,int mmax)
{
for(int i,j,k=1,L,*W,*x,*y,z;k<mmax;k<<=1)
for(L=k<<1,i=0;i<mmax;i+=L)
for(j=0,W=w+k,x=a+i,y=x+k;j<k;j++,W++,x++,y++)
z=1ll* *W* *y%mod,*y=md(*x+mod-z),*x=md(*x+z);
reverse(a+1,a+mmax);
for(int inv=ksm(mmax,mod-2),i=0;i<mmax;i++) a[i]=1ll*a[i]*inv%mod;
}
inline void mul(int *a,int *b,int n,int m)
{
static int c[N];
for(int i=0;i<=n;i++) for(int j=0;j<=m;j++) c[i+j]=(c[i+j]+1ll*a[i]*b[j])%Mod;
for(int i=0;i<=n+m;i++) a[i]=c[i],c[i]=0;
}
inline void NTT(int *a,int *b,int n,int m)
{
if(Mod^998244353) return mul(a,b,n,m);
int mmax=bger(n+m);init(mmax);DNT(a,mmax);DNT(b,mmax);
for(int i=0;i<mmax;i++) a[i]=1ll*a[i]*b[i]%mod;IDNT(a,mmax);
}
inline int genf(int *a,int *b,int n,int m,LL k)
{
static int c[N],d[N];
for(;k;k>>=1)
{
memset(c,0,sizeof(c));for(int i=0;i<=m;i++) c[i]=b[i];
for(int i=1;i<=m;i+=2) c[i]=(Mod-c[i])%Mod;
memset(d,0,sizeof(d));for(int i=0;i<=m;i++) d[i]=c[i];
NTT(a,c,n,m);NTT(b,d,m,m);int j=0;
for(int i=(k&1);i<=n+m;i+=2,j++) a[i/2]=a[i];n=j-1;j=0;
for(int i=0;i<=2*m;i+=2,j++) b[i/2]=b[i];m=j-1;
for(int i=n+1;i<=N-5;i++) a[i]=0;
for(int i=m+1;i<=N-5;i++) b[i]=0;
}return a[0];
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m>>Mod;
for(int i=0;i<=m;i++) for(int j=0;j<=i;j++) C[i][j]=!j?1:md(C[i-1][j]+C[i-1][j-1]);
for(int i=0;i<=m;i++) a[i]=b[i]=i&1?(Mod-C[m][i]):C[m][i];b[m-1]=md(b[m-1]-1+Mod);
for(int i=0;i<=m;i++) c[i+1]=md(Mod-b[i]);
for(int i=0;i<=m+1;i++) b[i]=md(b[i]+c[i]);
return cout<<genf(a,b,m,m+1,n),0;
}
\(\color{lightblue}{\texttt{ATC tenka1 2019f. Banned X}}\)
\(\text{link}\) 。提示:消 \(0\),考虑前缀和,组合计数。
$\texttt{solution}$
第一个线性做法题解。下文规定若 \(m<0\) 或 \(n<m\),则 \(\dbinom{n}{m}=0\)。
先不考虑 \(0\),设长度为 \(m\) 的 \(1,2\) 序列满足条件的方案数为 \(f_m\),则答案为 \(\sum\limits_{i=0}^n\dbinom{n}{i}f_m\)。考虑线性求 \(f_m\)。
设总和为 \(s\),分讨:
若 \(s<X-1\),则数量为 \(\sum\limits_{s=m}^{X-2} \dbinom{m}{s-m}\)。
否则,若序列合法一定存在前缀和为 \(X-1\)。
首先总和要 \(\ge X-1\),而又不存在为 \(X\) 的前缀和,且序列中元素在 \(\{1,2\}\) 中,于是存在。
特判 \(m=0\),此时考虑 \(0\le k\le m\) 位置的前缀和为 \(X-1\),发现 \(a_{k+1}=2\),又发现 \(a_1=1\) 不满足条件,于是 \(a_1=2\)。
递推下去,于是 \(a_{1,2,\cdots,m-k},a_{k+1,k+2,\cdots ,m}\) 均为 \(2\),其他位置任意。
再分讨,若 \(m-k\le k\),则对 \((m-k,k]\) 中填数方案计数,否则判断全填 \(2\) 可不可行即可。
后者是容易的,前者是要 \(2k-m\) 个数,和为 \(x-1-2(m-k)\),简单计算后发现为 \(\dbinom{2k-m}{X-1-m}\)。
于是这时方案数为 \(\sum\limits_{k=0}^m \dbinom{2k-m}{X-1-m}+[2\nmid X]\times [(m-1)/2\ge (X-1)/2]\)。
设 \(n,X\) 同阶,复杂度 \(O(n^2)\)。代码。
接下来考虑线性求 \(\sum\limits_{s=m}^{X-2} \dbinom{m}{s-m}=\sum\limits_{i=0}^{X-2-m} \dbinom{m}{i}\),这是容易的。先预处理阶乘逆元。
\(\sum\limits_{i=0}^{X-2-m} \dbinom{m}{i}=\sum\limits_{i=0}^{X-2-(m-1)} \left(\dbinom{m-1}{i}+\dbinom{m-1}{i-1}\right)-\dbinom{m}{X-m+1}=2\sum\limits_{i=0}^{X-2-(m-1)} \dbinom{m-1}{i}-\dbinom{m-1}{X-m+1}-\dbinom{m}{X-m+1}\)。就能每次 \(O(1)\) 递推啦。
最后线性求 \(\sum\limits_{k=0}^m \dbinom{2k-m}{X-1-m}\)。第一步思路来源于官方题解,但最后式子比官方题解简单。
\(\begin{aligned} \sum\limits_{k=0}^m \dbinom{2k-m}{X-1-m}&=\dfrac{1}{2}\left(\sum\limits_{k=0}^m \dbinom{2k-m}{X-1-m}+\dbinom{2k-m}{X-1-m}\right)\\ &=\dfrac{1}{2}\left(\sum\limits_{k=0}^m \dbinom{2k-m}{X-1-m}+\dbinom{2k-(m+1)}{X-1-m}+\dbinom{2k-(m+1)}{X-1-(m+1)}\right)\\ &=\dfrac{1}{2}\left(\sum\limits_{k=0}^{2m} \dbinom{k-m}{X-1-m}+\sum\limits_{k=0}^m\dbinom{2k-(m+1)}{X-1-(m+1)}\right)\\ &=\dfrac{1}{2}\left(\dbinom{m+1}{X-m}+\sum\limits_{k=0}^{m+1}\dbinom{2k-(m+1)}{X-1-(m+1)}-\dbinom{m+1}{X-1-(m+1)}\right) \end{aligned}\)
于是 \(2\sum\limits_{k=0}^m \dbinom{2k-m}{X-1-m}+\dbinom{m+1}{X-1-(m+1)}-\dbinom{m+1}{X-m}=\sum\limits_{k=0}^{m+1}\dbinom{2k-(m+1)}{X-1-(m+1)}\)。一样预处理阶乘线性递推即可。
最终复杂度 \(O(n)\)。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e5+5,M=N-5,mod=998244353;
int n,X,s,S,jc[N],inv[N],f[N];
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline int C(int n,int m){return n<m?0:1ll*jc[n]*inv[m]%mod*inv[n-m]%mod;}
inline int sol(int m)
{
if(!m) return 1;int ans=0;
if(m==1) for(int i=0;i<X-1-m;i++) s=md(s+C(m,i));
else s=md((2ll*s-C(m,X-m-1)-C(m-1,X-m-1))%mod+mod);ans=md(ans+s);
if(X-1>=m)
{
if(m==1) for(int i=0;i<=m;i++) S=md(S+C(2*i-m,X-1-m));
ans=md(ans+S);if(m+1<=X-1) S=md((2ll*S-C(m+1,X-m)+C(m+1,X-m-2))%mod+mod);
}
if(X&1) ans=md(ans+((m-1)/2>=(X-1)/2));return ans;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>X;
jc[0]=1;for(int i=1;i<=M;i++) jc[i]=1ll*jc[i-1]*i%mod;
inv[M]=ksm(jc[M],mod-2);for(int i=M-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
for(int i=0;i<=n;i++) f[i]=sol(i);int ans=0;
for(int i=0;i<=n;i++) ans=(ans+1ll*C(n,i)*f[i])%mod;
return cout<<ans,0;
}
\(\color{lightblue}{\texttt{ATC tenka1 2019e. Polynomial Divisors}}\)
\(\text{link}\) 。提示:拉格朗日定理。
$\texttt{solution}$
前置知识:拉格朗日定理。
设 \(g=\gcd\limits_{i=0}^n a_i\),则 \(|g|\) 的所有素因子都满足条件。
考虑剩下的,若 \(p\) 为满足条件的素数,则多项式 \(A\) 必有因式 \(\prod\limits_{i=0}^{p-1}(x-i)\equiv x^p-x\pmod p\)。同余这步可以参考前置知识中文章。
于是得出 \(\forall k\sum\limits_{i=0}^n [i\equiv k\pmod {p-1}]a_i=0\)。
对于每个素数判一下即可,由拉格朗日定理得 \(p\le n\)。
复杂度 \(O(\dfrac{n^2}{\ln n})\),跑不满。
$\texttt{code}$
//洛谷 AT_tenka1_2019_e
//https://www.luogu.com.cn/problem/AT_tenka1_2019_e
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e4+5;
int n,g,a[N],b[N],pr[N];bool v[N];
basic_string<int>ans;
inline void init(int M)
{
for(int i=2;i<=M;i++)
{
if(!v[i]) pr[++pr[0]]=i;
for(int j=1;j<=pr[0]&&i*pr[j]<=M;j++)
{
v[i*pr[j]]=1;
if(i%pr[j]==0) break;
}
}
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;
for(int i=n;~i;i--) cin>>a[i],g=__gcd(g,a[i]);g=abs(g);
for(int i=2;i*i<=g;i++) if(!(g%i)){ans+=i;while(!(g%i)) g/=i;}
if(g^1) ans+=g;init(n);
for(int i=1;i<=pr[0];i++)
{
int p=pr[i];bool ok=1;if(a[0]%p) continue;
for(int j=0;j<p-1;j++) b[j]=0;
for(int j=0;j<=n;j++) b[j%(p-1)]=(b[j%(p-1)]+a[j]%p+p)%p;
for(int j=0;j<p-1;j++) if(b[j]){ok=0;break;}if(ok) ans+=p;
}sort(ans.begin(),ans.end());ans.erase(unique(ans.begin(),ans.end()),ans.end());
for(int i:ans) cout<<i<<"\n";
return 0;
}
\(\color{lightblue}{\texttt{P9443. Hand of the Free Marked}}\)
\(\text{link}\) 。提示:枚举计数。
$\texttt{solution}$
下文计算复杂度时设 \(k,m\) 同阶。
枚举每种样式选了多少个,设当前考虑的情况为样式 \(1\sim m\) 分别选了 \(c_{1,2,\cdots, m}\) 个,考虑此时猜中的概率。
先计算选出这种情况的方案数。为 \(s=\prod\limits_{i=1}^m \dbinom{a_i}{c_i}\)。
考虑根据桌面上牌表示的信息情况总数。
前 \(k\) 张有 \((k-1)!\) 种排列方式,枚举倒置的牌,计算得:
\((k-1)!\times \sum\limits_{i=1}^ms\times \dfrac{\dbinom{a_i}{c_i-1}}{\dbinom{a_i}{c_i}}=(k-1)!\times \sum\limits_{i=1}^ms\times \dfrac{c_i}{a_i-c_i+1}\)。
于是猜中的概率为 \((k-1)!\times \sum\limits_{i=1}^m\dfrac{c_i}{a_i-c_i+1}\),然后要与 \(1\) 取 \(\min\),直接 \(O(m^2)\) 计算即可。
考虑这种情况出现的概率,发现是 \(\dfrac{s}{\dbinom{n}{k}}\),用你喜欢的写法 \(O(m^2)\) 算即可。
枚举合法的 \(c_{1,2,\cdots, m}\) 的情况直接 dfs 做即可,情况数上界为选若干非负整数和为 \(k\) 的方案数。即 \(\dbinom{k+m-1}{k}\)。
当 \(k,m\) 同阶时,情况数 \(\dbinom{k+m-1}{k}\le \dbinom{2m}{m}\)。
总复杂度就是 \(O\left(m^2\dbinom{2m}{m}\right)\),随便过。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define LD long double
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=15;
int k,m,a[N],b[N];LL n;LD ans=1;
inline void calc()
{
LD s=0;
for(int i=1;i<=m;i++) s+=(LD)b[i]/(a[i]-b[i]+1);
for(int i=1;i<k;i++) s*=i;
if(s>=1) return;s=1-s;
for(int i=1,K=0;i<=m;i++)
for(int j=0;j<b[i];j++,K++) s*=(LD)(a[i]-j)/(b[i]-j)*(k-K)/(n-K);ans-=s;
}
void dfs(int v,int x)
{
if(v==m+1){if(!x) calc();return;}
for(int i=0;i<=min(a[v],x);i++) b[v]=i,dfs(v+1,x-i);
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>k>>m;
for(int i=1;i<=m;i++) cin>>a[i],n+=a[i];
dfs(1,k);printf("%.12Lf",ans);
return 0;
}
\(\color{lightblue}{\texttt{P10254. 口吃}}\)
\(\text{link}\) 。提示:考虑把式子和 \([a_i>a_j]\) 挂上关系。
$\texttt{solution}$
只有写了题解才能明白每一步的原委。
考虑拆 \(i\):
\(\begin{aligned} \sum\limits_{i=1}^n ia_i&=\sum\limits_{i=1}^n a_i\left(\sum\limits_{j<i}[a_i>a_j]+[a_i<a_j]+1\right)\\ &=\sum\limits_{i=1}^n a_i\left(a_i-\sum\limits_{j>i}[a_i>a_j]+\sum\limits_{j<i}[a_i<a_j]\right)\\ &=\sum\limits_{i=1}^n i^2+\sum\limits_{i=1}^n a_i\left(\sum\limits_{j<i}[a_i<a_j]-\sum\limits_{j>i}[a_i>a_j]\right)\\ &=\sum\limits_{i=1}^n i^2+\sum\limits_{i=1}^n a_i\sum\limits_{j<i}[a_i<a_j]-\sum\limits_{i=1}^n a_i\sum\limits_{j>i}[a_i>a_j] \end{aligned}\)
对最后两部分 dp 即可。
首先套路性地记 \(f_{i,j}\) 表示填到 \(i\),逆序对数为 \(j\) 的方案数。
则 \(f_{i,j}=\sum\limits_{0\le k<i} f_{i-1,j-k}\),记下 \(f\) 的前缀和进行前缀和优化即可。
则前半部分的总贡献为 \(f_{n,k}\times \sum\limits_{i=1}^n i^2\)。
记 \(g_{i,j}\) 表示填到 \(i\),逆序对数为 \(j\) 的方案中当前所有 \(\sum\limits_{i=1}^n a_i\sum\limits_{j>i}[a_i>a_j]\) 的和。
则 \(g_{i,j}=\sum\limits_{0\le k<i}g_{i-1,j-k}+i\times k\times f_{i-1,j-k}\)。
类似记 \(h_{i,j}\) 表示填到 \(i\),逆序对数为 \(j\) 的方案中当前所有 \(\sum\limits_{i=1}^n a_i\sum\limits_{j<i}[a_i<a_j]\) 的和。
考虑倒着做,令 \(t=n-i+1\),\(h_{i,j}=\sum\limits_{0\le k<t}h_{i+1,j-k}+i\times k\times f_{i+1,j-k}\)。
此时若正着做,则有:\(h_{i,j}=\sum\limits_{0\le k<i}h_{i-1,j-k}+(n-i+1)\times k\times f_{i-1,j-k}\)。
考虑 \(U=h-g\),则 \(U_{i,j}=\sum\limits_{0\le k<i}U_{i-1,j-k}+(n-2i+1)\times k\times f_{i-1,j-k}\)。
答案为 \(f_{n,k}\times \sum\limits_{i=1}^n i^2+U_{n,k}\)。
记下 \(U\) 的前缀和和 \(jf_{i,j}\) 的前缀和,优化 dp 即可。具体细节看代码。
复杂度 \(O(nk)\),空间复杂度经过优化后为 \(O(k)\)。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=305,M=N*(N-1)/2+5,mod=998244353;
int n,k,f[M],F[M],s[M],g[M],G[M];
inline int md(int x){return x>=mod?x-mod:x;}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>k;
f[0]=1;for(int i=0;i<=k;i++) F[i]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=k;j++) f[j]=md(F[j]-((j>=i)?F[j-i]:0)+mod),
g[j]=md((1ll*(G[j]-((j>=i)?G[j-i]:0))+1ll*(n-2*i+1)*j*f[j]-1ll*(n-2*i+1)*(s[j]-((j>=i)?s[j-i]:0)))%mod+mod);
F[0]=f[0],G[0]=g[0];
for(int j=1;j<=k;j++) F[j]=md(F[j-1]+f[j]),G[j]=md(G[j-1]+g[j]),s[j]=(s[j-1]+1ll*j*f[j])%mod;
}
return cout<<(1ll*n*(n+1)*(n*2+1)/6%mod*f[k]+g[k])%mod,0;
}
\(\color{lightblue}{\texttt{P8140. Trailing Digits}}\)
\(\text{link}\) 。提示:同余方程。
$\texttt{solution}$
目前最优复杂度题解。
显然答案具有可二分性。现在考虑判断 \(k\) 是否满足条件。
令 \(A=10^k,c=d\times\dfrac{10^k-1}{9}\),则我们需要找到最小的非负整数 \(x\) 使得 \(Ax+c\equiv 0\pmod b\)。
这显然是可以通过 exgcd \(O(\log b)\) 求解的,具体细节看代码。
注意:当 \(d=0\) 且解出 \(x=0\) 时,需要把 \(x\) 加到最小正整数。
然后考虑最终数 \(\le a\) 的条件,只需 \(Ax+c\le a\),把 \(Ax+c\) 表示为字符串,进行 \(O(len)\) 比较即可。
瓶颈在于二分加比较,复杂度 \(O(len\log len)\)。
注意到二分不同值时比较的不同位只有 \(\lg b\) 级别,即前几位是解出来的 \(x\),后面大多数为都是 \(d\),于是存在 \(O(len)\) 预处理 \(O(\lg b)\) 比较的做法。留作思考(我懒得写了)。
于是最优复杂度可以看作 \(O(len)\),于是可以加强到 \(a=10^{10^7},b=10^9\) 这样。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e4+5;
int b,d;LL x,y;string a;
inline int ksm(int x,int p,const int mod){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
void exgcd(int a,int b,LL &x,LL &y)
{
if(!b) return x=1,y=0,void();
exgcd(b,a%b,x,y);LL t=x-(a/b)*y;x=y;y=t;
}
inline int eq(int a,int b,int c)
{
int t=__gcd(a,b);if(c%t) return -1;
exgcd(a,b,x,y);b/=t;x=(x%b+b)*(c/t)%b;return (!x&&!d)?b:x;
}//exgcd 求解最小解,注意最小解要对 b/gcd 取模,而不是对 b 取模,注意 d=0 的 Corner Case
inline int pw(int k,const int mod){return ((ksm(10,k,9*mod)-1+9*mod)/9)%mod;}//求 c 的小手段
inline bool cmp(string b,string a){if(b.size()^a.size()) return b.size()<a.size();return b<=a;}//O(len) 比较
inline bool chk(int k)
{
int a=ksm(10,k,b),c=1ll*(b-d)*pw(k,b)%b,t=eq(a,b,c);
if(t==-1) return 0;string s=t?to_string(t):"";
while(k--) s+=(d+'0');return cmp(s,::a);
}//二分判断答案,要注意同余方程无解的情况
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>b>>d>>a;
int l=1,r=a.size(),mid,ans=0;
while(l<=r) mid=(l+r)>>1,(chk(mid))?(ans=mid,l=mid+1):r=mid-1;
return cout<<ans,0;
}
\(\color{lightblue}{\texttt{ABC352G. Socks 3}}\)
\(\text{link}\) 。提示:期望转概率。
$\texttt{solution}$
我起了,一枪秒了。ABC g 就这素质了,感觉好熟悉的题,但一下子又找不到原题。
套路性的,设抽袜子次数为 \(X\)。则 \(\mathbb{E}(X)=\sum\limits_{k=0}^n \mathbb{P}(X>k)\)。
考虑 \(\mathbb{P}(X>k)\) 的意义,即抽 \(k\) 次后袜子仍两两不同的概率。
首先算方案数,设 \(s=\sum\limits_{i=1}^n a_i\),显然是 \(\prod\limits_{i=0}^{k-1}(s-i)=k!\dbinom{s}{k}\)。
考虑两两不同的方案数,颜色 \(i\) 的生成函数为:\(1+a_ix\),即不选有一种方案,选一个有 \(a_i\) 种方案。
而最后 \(k\) 个袜子可以任意排列,于是方案数为:\(k![x^k]\prod\limits_{i=1}^n(1+a_ix)\)。
于是 \(\mathbb{E}(X)=\sum\limits_{k=0}^n \dfrac{k!\binom{s}{k}}{k![x^k]\prod\limits_{i=1}^n(1+a_ix)}=\sum\limits_{k=0}^n \dfrac{\binom{s}{k}}{[x^k]\prod\limits_{i=1}^n(1+a_ix)}\).
于是只需求出 \(f(x)=\prod\limits_{i=1}^n(1+a_ix)\) 的 \(0\sim n\) 次项系数即可。
这是一个经典分治 \(\texttt{NTT}\) 问题,复杂度 \(O(n\log^2 n)\),\(\texttt{NTT}\) 不要太慢就足以通过。
求 \(\dbinom{s}{k}\) 利用 \(\dfrac{s-k}{k+1}\dbinom{s}{k}=\dbinom{s}{k+1}\) 递推即可。
记 \(a_i=k\) 的个数为 \(c_k\),\(m=\max a\),则 \(f(x)=\prod\limits_{i=2}^m(1+ix)^{c_i}=\prod\limits_{i=2}^m g_i(x)\)。
单个 \(g\) 直接二项式定理展开即可,而后对 \(g_2\sim g_m\) 做分治 \(\texttt{NTT}\),此时复杂度应为 \(O(n\log n\log m)\),更优秀一点,但我懒得写啦,自己上面那个做法已经跑飞快了。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int mod=998244353,N=12e5+5;
int n,s,f[N],a[N],w[N],*b[N],mmax,ans;
inline int rd()
{
int x=0,zf=1;
char ch=getchar();
while(ch<'0'||ch>'9') (ch=='-')and(zf=-1),ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*zf;
}
inline void wr(int x)
{
if(x==0) return putchar('0'),putchar('\n'),void();
int num[35],len=0;
while(x) num[++len]=x%10,x/=10;
for(int i=len;i>=1;i--) putchar(num[i]+'0');
putchar('\n');
}
inline int bger(int x){return x|=x>>1,x|=x>>2,x|=x>>4,x|=x>>8,x|=x>>16,x+1;}
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline void init(int mmax)
{
for(int i=1,j,k;i<mmax;i<<=1)
for(w[j=i]=1,k=ksm(3,(mod-1)/(i<<1)),j++;j<(i<<1);j++)
w[j]=1ll*w[j-1]*k%mod;
}
inline void DNT(int *a,int mmax)
{
for(int i,j,k=mmax>>1,L,*W,*x,*y,z;k;k>>=1)
for(L=k<<1,i=0;i<mmax;i+=L)
for(j=0,W=w+k,x=a+i,y=x+k;j<k;j++,W++,x++,y++)
*y=1ll*(*x+mod-(z=*y))* *W%mod,*x=md(*x+z);
}
inline void IDNT(int *a,int mmax)
{
for(int i,j,k=1,L,*W,*x,*y,z;k<mmax;k<<=1)
for(L=k<<1,i=0;i<mmax;i+=L)
for(j=0,W=w+k,x=a+i,y=x+k;j<k;j++,W++,x++,y++)
z=1ll* *W* *y%mod,*y=md(*x+mod-z),*x=md(*x+z);
reverse(a+1,a+mmax);
for(int inv=ksm(mmax,mod-2),i=0;i<mmax;i++) a[i]=1ll*a[i]*inv%mod;
}
inline void NTT(int *a,int *b,int n,int m)
{
mmax=bger(n+m);DNT(a,mmax);DNT(b,mmax);
for(int i=0;i<mmax;i++) a[i]=1ll*a[i]*b[i]%mod;IDNT(a,mmax);
}
void sol(int l,int r,int wz)
{
b[wz]=new int[r-l+2];
if(l==r) return b[wz][0]=1,b[wz][1]=a[l],void();
int mid=(l+r)>>1;sol(l,mid,wz<<1);sol(mid+1,r,wz<<1|1);
static int A[N],B[N];for(int i=0;i<bger(r-l+3);i++) A[i]=B[i]=0;
for(int i=0;i<=mid-l+1;i++) A[i]=b[wz<<1][i];
for(int i=0;i<=r-mid;i++) B[i]=b[wz<<1|1][i];
NTT(A,B,mid-l+2,r-mid+1);for(int i=0;i<=r-l+1;i++) b[wz][i]=A[i];
}
int main()
{
n=rd();init(bger(n<<1));for(int i=1;i<=n;i++) s=md(s+(a[i]=rd()));
f[0]=1;for(int i=0;i<n;i++) f[i+1]=1ll*f[i]*(i+1)%mod*ksm(s-i,mod-2)%mod;
sol(1,n,1);for(int i=0;i<=n;i++) ans=(ans+1ll*b[1][i]*f[i])%mod;
return wr(ans),0;
}
\(\color{lightblue}{\texttt{qoj 5376. xor}}\)
\(\text{link}\) 。提示:分段,自然数幂和。
$\texttt{solution}$
前置知识,线性/线性对数求自然数幂和:CF622F,这里为了实现简洁带了 \(\log\)。
考虑设 \(c_x\) 表示 $0\le i,j,k<n $ 且 \(i \oplus j \oplus k=x\) 的个数,则 \(ans=\sum\limits_{x} c_x x^d\)。
猜测/观察到 \(c_x\) 形成的值相同的连续段个数不多,一个朴素的上界是 \(\log^3 n\) 段。
- PS: 实际执行下来感觉是 \(O(\log n)\) 级别的,不是很会证。
考虑怎么快速求出这些连续段以及其对应的 \(c\) 的值。
套路性的,考虑把 \([0,n)\) 拆成若干 \([x,x+2^k)\) 的合并。
此时维护 \((x,k,c)\) 这样一个类,表示数在 \([x,x+2^k)\) 中,且有 \(c\) 种方案。
考虑 merge \((x_0,k_0,c_0),(x_1,k_1,c_1)\),不妨设 \(k_0\le k_1\),发现形成的新类为:\((2^{k_1}\lfloor (x_0\oplus x_1)/2^{k_1}\rfloor,k_1,2^{k_0}c_0c_1)\)。
枚举 \(i,j,k\) 分别在哪个类里,并自然数幂和计算一个类的答案即可。
复杂度 \(O(\log^3 n+d\times \text{polylog}(n))\)。一个朴素的上界是四只 \(\log\),实际感觉是两只或三只 \(\log\)。
$\texttt{code}$
//qoj 5376
//https://qoj.ac/problem/5376
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e5+15,mod=998244353;
int n,k,m,p2[95],jc[N],inv[N],a[N],ans;
unordered_map<int,int>mp;
struct node{int x,k,c;}b[35];
inline node hb(node A,node B)
{
if(A.k>B.k) swap(A,B);auto [x,k,c]=A;auto [X,K,C]=B;
return {X^(x>>K<<K),K,1ll*c*C%mod*p2[k]%mod};
}//合并类
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline int sol(int n)
{
int s=1,t,ans=0;
if(n<=k+2) return a[n];n%=mod;n+=mod;
if(mp.count(n)) return mp[n];
for(int i=1;i<=k+2;i++) s=1ll*s*(n-i)%mod;
for(int i=1;i<=k+2;i++) t=1ll*a[i]*inv[i-1]%mod*inv[k+2-i]%mod*s%mod*ksm(n-i,mod-2)%mod,ans=md(ans+(((k+2-i)&1)?mod-t:t));
return mp[n]=ans;
}
inline void init()
{
int M=k+1;for(int i=jc[0]=1;i<=M;i++) jc[i]=1ll*jc[i-1]*i%mod;
inv[M]=ksm(jc[M],mod-2);for(int i=M-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
for(int i=1;i<=k+2;i++) a[i]=md(a[i-1]+ksm(i,k));
for(int i=p2[0]=1;i<=50;i++) p2[i]=md(p2[i-1]<<1);
}//自然数幂和
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>k;init();
for(int i=0;i<=__lg(n);i++) if(n>>i&1) b[++m]={n>>(i+1)<<(i+1),i,1};
for(int i=1;i<=m;i++) for(int j=1;j<=m;j++) for(int I=1;I<=m;I++)
{
auto [x,k,c]=::hb(hb(b[i],b[j]),b[I]);int s=md(sol(x+(1<<k)-1)+mod-sol(x-1));
ans=(ans+1ll*c*s)%mod;//计算答案
}//枚举三次类
return cout<<ans,0;
}
\(\color{lightblue}{\texttt{P10855. Gcd with Xor}}\)
\(\text{link}\) 。提示:\(\text{xor}\) 拆 \(\log\) 段区间。
$\texttt{solution}$
记 \(ans\) 表示最终答案。
\[\begin{aligned} ans&=\sum\limits_{i=1}^n\sum\limits_{j=i}^n (i,i\ \text{xor}\ j)^k \\ &=\sum\limits_{i=1}^n\sum\limits_{d\mid i}d^k\sum\limits_{j=i}^n[(i,i\ \text{xor}\ j)=d] \\ &=\sum\limits_{i=1}^n\sum\limits_{d\mid i}d^k\sum\limits_{j=i}^n\sum\limits_{dD\mid i,dD\mid i\ \text{xor}\ j,i\le j\le n}\mu(D) \\ &=\sum\limits_{T=1}^n \left(\sum\limits_{d\mid T}d^k\mu(T/d)\right)\sum\limits_{T\mid i}\sum\limits_{j=i}^n[T\mid (i\ \text{xor}\ j)] \end{aligned} \]此时注意到 \(j\in[i,n],i\ \text{xor}\ j\) 显然能被分成 \(\log\) 段区间,再加上调和级数的 \(\ln\),后半部分的复杂度是 \(O(n\ln n\log n)\)。
至于 \(f(T)=\sum\limits_{d\mid T}d^k\mu(T/d)\) 部分,为了避免 \(\log k\) 的出现,可以线性筛 \(O(n)\) 预处理 \(1^k,2^k,\cdots ,n^k\)。
此时前半部分计算的复杂度就是调和级数的 \(O(n\ln n)\)。
于是复杂度 \(O(n\log^2 n)\),常数别写太劣就能过。
$\texttt{code}$
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=2e5+5,mod=1e9+7;
int T,n,k,pr[N],mu[N],pw[N],s;bool v[N];
basic_string<int>d[N];
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline void init(int M)
{
for(int i=2;i<=M;i++)
{
if(!v[i]) pr[++pr[0]]=i,mu[i]=-1;
for(int j=1;j<=pr[0]&&i*pr[j]<=M;j++)
{
v[i*pr[j]]=1;if(i%pr[j]==0) break;
mu[i*pr[j]]=-mu[i];
}
}mu[1]=1;
for(int i=1;i<=M;i++) for(int j=i;j<=M;j+=i) d[j]+=i;
}//预处理 mu 以及因子
inline void ipw(int M)
{
pr[0]=0;
for(int i=2;i<=M;i++)
{
if(!v[i]) pr[++pr[0]]=i,pw[i]=ksm(i,k);
for(int j=1;j<=pr[0]&&i*pr[j]<=M;j++)
{
v[i*pr[j]]=1;pw[i*pr[j]]=1ll*pw[i]*pw[pr[j]]%mod;
if(i%pr[j]==0) break;
}
}pw[1]=1;
}//O(nln k/ln n) 预处理 1^k,2^k,...,n^k
inline int get(int l,int r,int d){return r/d+1-((l>0)?(l-1)/d+1:0);}
void dfs(int l,int r,int L,int R,int x)
{
if(L<=l&&r<=R)
{
int t=__lg((l^r)+1),y=(L^l)>>t<<t;
s=md(s+get(y,y+(1<<t)-1,x)); return;
}int mid=(l+r)>>1;
if(L<=mid) dfs(l,mid,L,R,x);
if(mid<R) dfs(mid+1,r,L,R,x);
}//拆成 log 段区间算后半部分贡献
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>T;init(N-5);
while(T--)
{
cin>>n>>k;ipw(n);int ans=0,U=1<<(__lg(n)+1);
for(int i=1;i<=n;i++)
{
int A=0;s=0;
for(int j:d[i]) A=(A+1ll*pw[j]*(mod+mu[i/j]))%mod;//前半部分直接枚举
for(int j=i;j<=n;j+=i) dfs(0,U-1,j,n,i);ans=(ans+1ll*A*s)%mod;//后半部分
}cout<<ans<<"\n";
}
return 0;
}