杂题记 2

杂题记 1杂题记 2

写在前面:分解大致为 1520 题,题目难度高,大部分题个人认为的实际难度不低于洛谷的紫题。

CF1848F. Vika and Wiki#

link 。提示:考虑第 i 次会变成啥。

solution

下边设 ak 为实际中的 akmodn。首先根据样例猜测一定有解。

按照提示,注意到第 1aiai xor ai+1,第二次 aiai xor ai+1 xor ai+1 xor ai+2=ai xor ai+2

但是第三次似乎没啥规律?这时注意到可以倍增,第 2kaiai xor ai+2k。于是 k=log2n 一定是一个解。但是要求最小解。

由于我们可以快速求每 2k 次操作数组会变成啥,直接倍增即可,类似倍增 lca 求出最大的不变为全 0 的操作数,最后 +1 即可。

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;
}

CF512D. Fox And Travelling#

link 。提示:缩点,dp

solution

考虑把题目中的“加点”理解为删点,发现每个点可以被删当且仅当这个点只有一条连边,删点后删去这个点连的所有边。

发现这个性质很严,手模一下,发现对于一个边双,若边双内有 2 个点,那么整个边双都不可被删,这样性质就很好了。


考虑缩点后的森林(图不联通),称边双内点为 1 的为白点,否则为黑点。

考虑森林里一颗全为白点的树,dp 计算。发现钦定一个点为根会漏算,于是把这个树中所有点都当做根算一遍答案。记 size 为这棵树的大小,发现 t<size 个点的方案在以不是这 t 个点为根时都会算一遍,于是要除上 sizet。当 t=size 时方案就不重不漏,不用动。

简单说一下 dp 式,大概是 fu=1,vsonu,fu,i×fv,j×(i+jj)fu,i+j,枚举完所有子节点后算上加上这个点的贡献:fv,szufv,szu1,复杂度同树形背包 O(n2)

剩下的树可以拆分成若干以黑点连出的白点为根的树,同上 dp,当发现子节点转移到黑点的时候直接 return,精细实现一下发现是对的。这时候以那个点为根不重不漏。

把每颗树的答案合并起来和上面的 dp 类似,记得 0 处答案的转移要特别注意,实现精细,还用不懂看代码。

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;
}

CF1762E. Tree Sum#

link 。提示:奇偶,算贡献。

solution

首先看到样例就猜测 n 为奇数答案为 0,否则为啥剩下三样例个全是偶数?

考虑反证,记 F(u) 表示 (u,v,w)Ew。则由题意 u,F(u)=1。则 uF(u)=(1)n=1

考虑每条边的贡献,由于每条边会在两个端点贡献一次,于是 uF(u)=(u,v,w)Ew2=1,矛盾。

接下来考虑每种形态的树有怎样的填法,注意到类似 Prüfer 序列从叶节点剥,填法唯一。

考虑直接求 dis 很困难,于是算每条边的贡献。首先边在 dis 上等价于割掉这条边 1,n 在不同连通块上。设两个连通块的大小为 i,ni。若边权为 1,则两边子树内的 F 值都不变,于是两边都是合法的子树,2i,2ni。否则两边一定都不合法,i,ni 都是奇数。

考虑 i,ni 的两个连通块有多少种构成树的方案,首先两个子树分别是 ii2(ni)ni2,然后两边各选一个点连边是 i(ni),然后除掉 1,n 的剩下 n2 个点给 1 的连通块 i1 个是 (n2i1).

fi=ii1,则答案为 i=1n1(n2i1)fifni,经验证:在 f1 处这样答案仍是对的,复杂度 O(nlogn)

注意到拆开组合数这是一个卷积形式,于是能 NTT 做到 O(NlogN) 算出 n=1N 时的答案,但是在这题是没必要的。

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;
}

CF1305F. Kuroni and the Punishment#

link 。提示:上界,一半,随机化。

solution

丢一道类似 trick 的题:CF364D

首先,容易注意到答案上界为 n,因为一定能在不超过 n 次操作内变成全偶数。

而后有结论:在最终的答案中,一定存在至少一半的元素,它们最多被操作过一次。

反证:如果有至少一半两次,那么总个操作数就 >n 了。

考虑这个一半怎么用,发现每次随机一个数组中的数,它们被操作次数 1 的概率是 12 的,于是直接随机若干个,枚举它被 ±1 或不动,枚举它质因子 check 即可。不会 check 就别做这题了。

设随机 C 个,V=max(a),则复杂度为 O(C(nω(V)+V)),错误率为 12C,于是取 C[20,50] 即可通过,具体取值看喜好。

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;
}

gym 104639E. Magical Pair#

link 。提示:算贡献,离散对数,狄利克雷前缀和。

solution

为了简洁下面用 / 代替分数。

首先特判 A,Bn 的倍数的情况,发现当且仅当两者均为 n 倍数时才有贡献,于是这部分答案为 (n1)2

考虑令 A=(a,b) 表示 a=Amodn,b=Amodn1,类似定义 B=(c,d),则有:0a,c<n,0b,d<n1,且 n2n 个数与自然数对 (a,b) 一一对应

这时 ABBA(modn)adbc(modn)

考虑算贡献,记 fc 表示 adc(modn) 的个数,则答案为 c=1n1fc2

g 为原根,m=n1,则 adc(modn)gadgc(modn)adc(modm)

此时有 0a,d,c<m,其中 a,c 满足 gaa(modm),gcc(modm)

Fc 表示 adc(modm),0a,d<m 的个数,则答案为 i=0m1Fi2


考虑固定 k,求 xyk(modm),0x,y<m 的数量。

枚举 (x,m)=d,那么 x 的取值只有 φ(m/d) 种,我们需要保证 dk

那么有 (x/d)×y(k/d)(mod(m/d)),(x/d,m/d)=1,也就是 y(k/d)×((x/d)1)(mod(m/d))

于是 ymod(m/d) 下唯一确定,此时 ymodm 在有 d 种取值。于是此时合法的 (x,y) 的方案数为 d(m,k)φ(m/d)d

于是 i=0m1Fi2=i=0m1(d(m,i)φ(m/d)d)2=Dmφ(m/D)(dDφ(m/d)d)2.

其中第二个等号是枚举 (m,i)=D 时统计 i。记 gD=dDφ(m/d)d,GD=φ(m/D)D,则 gD=dDGd,做狄利克雷前缀和即可。

其中分解质因数要用 pollard-rho


考虑计算这时狄利克雷前缀和的复杂度,记 m 的质因数分解为:i=1kpiαi,则复杂度为 i=1kdm,piαid1=i=1kαiji(αi+1)<i=1kj=1k(αi+1)=d(m)w(n),由于我实现不精细要对所有质因数进行一次排序,此时复杂度为 d(n)log(d(n))

质因数分解的复杂度为 n4,于是总复杂度 O(T(n4+d(n)(w(n)+logd(n)))),查发现足以通过本题。具体不懂的可以看代码。

注意,写代码时我的式子是:Dmφ(D)(d(m/D)φ(m/d)d)2,显然这是等价的。

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;
}

ABC331G. Collect Them All#

link 。提示:多项式,minmax 反演。

solution

锐评:出题人只会 log2 太不牛了!既然出了就得会但 log 啊。而且官方题解写的一坨屎,太复杂了(

这题的有个小套路是参考P5860,推荐同时练习。

前置知识:多项式理论(要会多项式 exp),概率期望,minmax 反演。

分析复杂度时默认 n,m 同阶。


考虑设第 i 个颜色出现的最早时间为 ti,则要求 E(max(ti))

发现 minmax 好求,进行 minmax 反演,令 S={a1,a2,,am}

E(max(ti))=TS,T(1)|T|+1E(min(T))

考察 E(min(T)),发现一个位置的颜色属于 T 的概率为 P(x)=xTxn,由于概率期望互为倒数,于是 E(min(T))=nxTx

于是 E(max(ti))=n×TS,T(1)|T|+1xTx


这时发现分母部分是和的倒数,难刻画,考虑剥离

考虑 s,求 fs=xTx=sTS,T(1)|T|+1E(max(ti))=ns=1nfss

这东西先考虑朴素 dp:设 Fi,j 表示 xTx=jT{1,2,,i}(1)|T|+1,则有转移:

Fi,j=Fi1,jFi1,jai,初值为 F0,0=1,欲 sFn,s

考虑套路性的设 Fi(x)=j0Fi,jxj,则 Fi=(1xai)Fi1

于是 Fn=i=1m(1xai),此时大部分人都会分治 NTT 做到 O(nlog2n)

考虑求 i=1m(1xai),限制条件为 ai1,ai=n,记 ai=v 的有 cv 个,进行 lnexp

i=1m(1xai)=exp(i=1mln(1xai))=exp(v=1ncvj=1n/vxvjj)

里面直接暴力算即可,套多项式 exp,复杂度为 O(nlogn),注意不要漏 F 前的负号和前面 n 的系数。

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;
}

Snuke21 E. Tournament#

link 。提示:竞赛图缩点后的性质。

solution

前置知识:竞赛图强联通分量性质,只要用到本文的结论 1

竞赛图强连通缩点后的 DAG 呈链状, 前面的所有点向后面的所有点连边。

通常性的,我们考虑应考虑前 i 个强连通分量,于是我们对所有图缩点后对这样的划线计数:

假设某条线前面有 1in(注意不是强联通分量),后面有 ni 个点,则这个竞赛图可以拆成:

  • i 个点组成的竞赛子图

  • ni 个点组成的竞赛子图

  • 所有前 i 个点向所有后 ni 个点连的一条有向边

于是我们要把 n 个点划分成大小为 i,ni 两个集合,两个集合内的点要以某种方式连成竞赛图,而后按照第三个条件再连边。

si=(i2),则方案数为 (ni)2si2sni,于是答案为 i=1n(ni)2si2sni

复杂度 O(nlogn)O(n) 不等,取决于实现的精细(我代码是带 log 的)


此题很难低于 O(n) 的原因:

由于 sisj+ij=si+j,于是 2si2sni=2ni(ni)=2n2nii2。此时幂次出现了平方而且要求和,是经典不太可做问题。

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;
}

Snuke Density#

link 。提示:分析 v2

solution

硬生生把一个困难的蓝想了黑的做法。(下面讲的是蓝的思维难度的做法)

前置知识:Kummer 定理,勒让德定理

显然特判 c<max(a,b)ca+b 的情况,现在情况是 a,bc<a+b

考虑变形:c!a!b!=(a+ba)×((a+b)!c!)1

若它是整数,根据 Kummer 定理,v2((a+ba)) 等于 a+b2 进制下进位的次数。

于是根据数据范围有 v2((a+ba))37

于是 v2((a+b)!c!)37i1a+b2ic2i37

于是 a+b2c23737a+b2c2a+b21c2a+bc2×38=76

于是 0<a+bc76,否则不是整数。

考虑若它不是整数,则存在素数 p 使得 vp(c!a!b!)<0,即 vp((a+ba)×((a+b)!c!)1)<0

于是 p(a+b)!c!,于是 p 必为 (c,a+b] 中一个数的素因子。由于 0<a+bc76,直接枚举而后分解素因子即可判断。

瓶颈在于分解素因子,设其复杂度为 T(V),则总复杂度为 O(logV(T(V)+ω(V)logV))=O(T(V)logV+log3V)

其中 Vmax{a,b,c},而最外层的 log76,显然由分析这时数始终是 logV 级别的。

如果用 pollard-rho 则这题能做 V=1018。那个黑的想法就不讲了,太蠢了。

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;
}

Tenka1 F. ModularPowerEquation!!#

link 。提示:幂塔。

solution

前置知识:扩展欧拉定理,幂塔。前置题目:P4139,一定要先做这个!

沿用记号:记 a↑↑b=aaaab

想到幂塔这题就容易多了。感觉第一篇题解是没想清楚欧拉定理的细节写的,那个对较小数的处理太粗糙了!感觉这样直接求比他的递归更容易想到啊。

前置题目推广:记 a0=1,an=Aan1,bn=anmodm,则 N,使得 nNbn 为常数。

于是取充分大的 n,则 A↑↑n 为一组解。(你可以想象成 n+ 理解)

但是 R=a↑↑n 太大了,考虑 r 为一组解,想要:ARAr(modm),Rr(modm)

不妨设 R>φ(m),于是由扩展欧拉可以变成构造:r>φ(m),Rr(modφ(m)),Rr(modm)

L=lcm(m,φ(m)),则我们容易构造出 r=RmodL+L 为满足条件且 2×1018 的解。

于是要求 Rmodm,Rmodφ(m),然后 exgcd 合并。

而求这个显然就转换为了前置题目,直接套上即可,复杂度 O(Qmlogm)。注意 exgcd 时要用 __int128

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;
}

LG P9970. 套娃#

link 。提示:极小区间。

solution

前置知识:P4137 的在线单次 O(logn) 求区间 mex 做法,CF1870E 的结论。

赛事做繁了,没冲出来。发现第二篇题解难理解,第一篇题解有个细节繁了,于是我结合了一下。除去主席树板子,代码非常短。

这篇题解是目前第一篇题解和第二篇的结合,由重合请见谅。

如果不想看结论证明直接忽略即可,没啥影响。


[l,r] 是极小区间,当且仅当不存在 [L,R][l,r]mex(l,r)=mex(L,R)。则有结论:极小区间只有 O(n) 个。

  • 证明:设 [l,r] 是极小区间,则显然 alar,不妨设 al>ar,则由于删掉端点 mex 要变化,于是 mex(l,r)>al>ar。若存在 r1>r[l,r1] 是极小区间,则 mex(l,r)al>ar1,于是 ar1[l,r] 中出现过。于是删去 ar1mex 不变。于是固定 lal>ar 的情况只对应一个 r,所以只有 O(n) 个了。

考虑如何求所有极小区间。如果直接按证明方法求是非常难写的。于是考虑巧妙方法。

mex(l,r)=x,则称 [l,r]mexx 区间。

考虑一个 mexx 的极小区间,同样不妨设 al>ar,则由于极小性,于是 ar(l,r) 中没有出现。

考虑删去 al 之后,mex 变为 al,不妨设 mexal 区间 [l+1,r] 对应的极小子区间为 [L,R],则 ar 一定在 [L,R] 中出现,于是 R=r

  • 这说明:mexx 区间必定为一个 mext 区间向一端扩展到第一个t 得到。其中 x>t

考虑从 x=0n+1 依次求出所有 mexx 极小区间。对于每个 x 维护 mexx 极小区间的 vector

每次先把所有 mexx1 区间进行如上扩展,设一个扩展完后 mex=y,则把新区间丢到 yvector 里。这里求扩展完的 mex 用上面说的单次 O(logn) 的方法。

这时候所有 mexx 极小区间都在 xvector 里了,但注意扩展完的不一定都是极小的,于是排除掉即可。就不多不少的求出了 mexx 极小区间,一直做下去即可。记得初始化极小 mex0/1 区间。

由于极小区间总共只有 O(n) 个,乘上求区间 mexlog,于是复杂度为 O(nlogn)


求出所有极小区间后,考虑所有对应 mexx 的极小子区间 [l,r] 的大区间的形态。

l 左侧第一个 x 的位置为 L1r 右侧第一个 x 的位置为 R+1

则所有对应的大区间为:左端点在 [L,l],右端点在 [r,R] 的所有区间。于是 len[rl+1,RL+1],存在长为 len 的区间 mex=x

于是问题就转化成了:维护 n 个集合,区间插入一个数,最终对所有集合求 mex。把插入操作差分,用一个 set 动态维护 mex 即可。具体看代码。

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;
}

LG P9291. Quick sort#

link 。提示:逆操作。

solution

人类智慧题。需要一些矩阵乘法的知识辅助理解。

首先 O(nlogn) 次操作时容易的,按顺序把 n1 依次归位即可。每个数归位由于每次距离减半于是是 logn 的。

这时候随机一下会不会很优秀?但是有佬证明了这个算法期望下依然是 O(nlogn) 次的。不足以通过。


到了人类智慧部分了,看这个结构本来不是很好逆,但是依然要逆。

不妨设初始排列为 p1n 的排列为 I。操作为 f1,f2,,fk,则 pf1f2fk=I

考虑左右求逆。操作本质为矩阵乘法,于是类似的做。同时 1n 的排列可以看做单位矩阵。

于是 fk1f21f11p1=I。由于矩乘中 AB=IBA=I,于是 p1fk1f21f11=I

  • 令逆操作 f1=g,相当于对逆排列 p=p1 进行编号 k1逆操作,最终要变成 1n 的排列。

同样的,我们依然按顺序把 n1 依次归位。然后把操作逆序输出。

观察对 [1,2i] 逆操作,则位置 i 上的数变到位置 2i 了。同时 i2<w<i 的位置 w,对 [2wi+1,i] 逆操作即可把位置 w 上的数移动到位置 i 上。

i 在位置 wi。如果 wi,我们先把 w 倍增到 (i2,i] 范围,而后进行一次逆操作即可。次数为 log2(i/w)log2ilog2w+1

看似没有优化?但是不妨先进行随机打乱,此时假设 p[1,i] 中随机选取,则 E(log2(w))=log2(i!)ilog2(2πi2i×ie)=log2ilog2(e)+C

其中 C>0 是极小的余项,可以用函数图像感受一下。近似部分用了 Stirling 公式估计阶乘。

于是 log2ilog2w+1log2e+1,于是总次数估计为 2n3n 次。足以通过。事实上次数上界设为 6000 也能飞快就出结果。


关于随机打乱:方法是随机选常数个区间进行逆操作。直接对 p 这样做即可,还原为 1n 后,最后逆序输出时这样显然不影响正确性。

注意最开始是对逆排列进行上述操作!具体细节看代码,代码很短。

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;
}

loj 6575. LJJ 学二项式定理 二#

link 。提示:GF

solution

前置知识:齐次线性递推

注意到瓶颈在于组合数上标有 + i

考虑 [xn]1(1x)k+1=(n+kk),于是容易构造出答案为:[xn]i0xi(m1)(1x)im+1.

[xn]i0xi(m1)(1x)im+1=[xn]11xi0(xm1(1x)m)i=[xn]1(1x)(1f(x))(f(x)=xm1(1x)m)=[xn](1x)m(1x)((1x)mxm1)

直接上齐次递推中的 Bostan-mori 算法即可,复杂度 O(m2logn)O(mlogmlogn),取决于多项式乘法的实现。

m=5000,p=998244353 时需要 NTT 来加速。否则超时。

一个细节就是最后的分母始终是 1,不需要求逆得出结果。还有不懂可以看代码。

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;
}

ATC tenka1 2019f. Banned X#

link 。提示:消 0,考虑前缀和,组合计数。

solution

第一个线性做法题解。下文规定若 m<0n<m,则 (nm)=0

先不考虑 0,设长度为 m1,2 序列满足条件的方案数为 fm,则答案为 i=0n(ni)fm。考虑线性求 fm

设总和为 s,分讨:

s<X1,则数量为 s=mX2(msm)

否则,若序列合法一定存在前缀和为 X1

首先总和要 X1,而又不存在为 X 的前缀和,且序列中元素在 {1,2} 中,于是存在。

特判 m=0,此时考虑 0km 位置的前缀和为 X1,发现 ak+1=2,又发现 a1=1 不满足条件,于是 a1=2

递推下去,于是 a1,2,,mk,ak+1,k+2,,m 均为 2,其他位置任意。

再分讨,若 mkk,则对 (mk,k] 中填数方案计数,否则判断全填 2 可不可行即可。

后者是容易的,前者是要 2km 个数,和为 x12(mk),简单计算后发现为 (2kmX1m)

于是这时方案数为 k=0m(2kmX1m)+[2X]×[(m1)/2(X1)/2]

n,X 同阶,复杂度 O(n2)代码


接下来考虑线性求 s=mX2(msm)=i=0X2m(mi),这是容易的。先预处理阶乘逆元。

i=0X2m(mi)=i=0X2(m1)((m1i)+(m1i1))(mXm+1)=2i=0X2(m1)(m1i)(m1Xm+1)(mXm+1)。就能每次 O(1) 递推啦。


最后线性求 k=0m(2kmX1m)。第一步思路来源于官方题解,但最后式子比官方题解简单。

k=0m(2kmX1m)=12(k=0m(2kmX1m)+(2kmX1m))=12(k=0m(2kmX1m)+(2k(m+1)X1m)+(2k(m+1)X1(m+1)))=12(k=02m(kmX1m)+k=0m(2k(m+1)X1(m+1)))=12((m+1Xm)+k=0m+1(2k(m+1)X1(m+1))(m+1X1(m+1)))

于是 2k=0m(2kmX1m)+(m+1X1(m+1))(m+1Xm)=k=0m+1(2k(m+1)X1(m+1))。一样预处理阶乘线性递推即可。

最终复杂度 O(n)

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;
}

ATC tenka1 2019e. Polynomial Divisors#

link 。提示:拉格朗日定理

solution

前置知识:拉格朗日定理

g=gcdi=0nai,则 |g| 的所有素因子都满足条件。

考虑剩下的,若 p 为满足条件的素数,则多项式 A 必有因式 i=0p1(xi)xpx(modp)。同余这步可以参考前置知识中文章。

于是得出 ki=0n[ik(modp1)]ai=0

对于每个素数判一下即可,由拉格朗日定理得 pn

复杂度 O(n2lnn),跑不满。

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;
}

P9443. Hand of the Free Marked#

link 。提示:枚举计数。

solution

下文计算复杂度时设 k,m 同阶。

枚举每种样式选了多少个,设当前考虑的情况为样式 1m 分别选了 c1,2,,m 个,考虑此时猜中的概率。

先计算选出这种情况的方案数。为 s=i=1m(aici)

考虑根据桌面上牌表示的信息情况总数。

k 张有 (k1)! 种排列方式,枚举倒置的牌,计算得:

(k1)!×i=1ms×(aici1)(aici)=(k1)!×i=1ms×ciaici+1

于是猜中的概率为 (k1)!×i=1mciaici+1,然后要与 1min,直接 O(m2) 计算即可。

考虑这种情况出现的概率,发现是 s(nk),用你喜欢的写法 O(m2) 算即可。


枚举合法的 c1,2,,m 的情况直接 dfs 做即可,情况数上界为选若干非负整数和为 k 的方案数。即 (k+m1k)

k,m 同阶时,情况数 (k+m1k)(2mm)

总复杂度就是 O(m2(2mm)),随便过。

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;
}

P10254. 口吃#

link 。提示:考虑把式子和 [ai>aj] 挂上关系。

solution

只有写了题解才能明白每一步的原委。

考虑拆 i

i=1niai=i=1nai(j<i[ai>aj]+[ai<aj]+1)=i=1nai(aij>i[ai>aj]+j<i[ai<aj])=i=1ni2+i=1nai(j<i[ai<aj]j>i[ai>aj])=i=1ni2+i=1naij<i[ai<aj]i=1naij>i[ai>aj]

对最后两部分 dp 即可。


首先套路性地记 fi,j 表示填到 i,逆序对数为 j 的方案数。

fi,j=0k<ifi1,jk,记下 f 的前缀和进行前缀和优化即可。

则前半部分的总贡献为 fn,k×i=1ni2

gi,j 表示填到 i,逆序对数为 j 的方案中当前所有 i=1naij>i[ai>aj] 的和。

gi,j=0k<igi1,jk+i×k×fi1,jk

类似记 hi,j 表示填到 i,逆序对数为 j 的方案中当前所有 i=1naij<i[ai<aj] 的和。

考虑倒着做,令 t=ni+1hi,j=0k<thi+1,jk+i×k×fi+1,jk

此时若正着做,则有:hi,j=0k<ihi1,jk+(ni+1)×k×fi1,jk

考虑 U=hg,则 Ui,j=0k<iUi1,jk+(n2i+1)×k×fi1,jk

答案为 fn,k×i=1ni2+Un,k

记下 U 的前缀和和 jfi,j 的前缀和,优化 dp 即可。具体细节看代码。

复杂度 O(nk),空间复杂度经过优化后为 O(k)

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;
}

P8140. Trailing Digits#

link 。提示:同余方程。

solution

目前最优复杂度题解。

显然答案具有可二分性。现在考虑判断 k 是否满足条件。

A=10k,c=d×10k19,则我们需要找到最小的非负整数 x 使得 Ax+c0(modb)

这显然是可以通过 exgcd O(logb) 求解的,具体细节看代码。

注意:当 d=0 且解出 x=0 时,需要把 x 加到最小正整数。

然后考虑最终数 a 的条件,只需 Ax+ca,把 Ax+c 表示为字符串,进行 O(len) 比较即可。

瓶颈在于二分加比较,复杂度 O(lenloglen)


注意到二分不同值时比较的不同位只有 lgb 级别,即前几位是解出来的 x,后面大多数为都是 d,于是存在 O(len) 预处理 O(lgb) 比较的做法。留作思考(我懒得写了)。

于是最优复杂度可以看作 O(len),于是可以加强到 a=10107,b=109 这样。

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;
}

ABC352G. Socks 3#

link 。提示:期望转概率。

solution

我起了,一枪秒了。

ABC g 就这素质了,感觉好熟悉的题,但一下子又找不到原题。

套路性的,设抽袜子次数为 X。则 E(X)=k=0nP(X>k)

考虑 P(X>k) 的意义,即抽 k 次后袜子仍两两不同的概率。

首先算方案数,设 s=i=1nai,显然是 i=0k1(si)=k!(sk)

考虑两两不同的方案数,颜色 i 的生成函数为:1+aix,即不选有一种方案,选一个有 ai 种方案。

而最后 k 个袜子可以任意排列,于是方案数为:k![xk]i=1n(1+aix)

于是 E(X)=k=0nk!(sk)k![xk]i=1n(1+aix)=k=0n(sk)[xk]i=1n(1+aix).

于是只需求出 f(x)=i=1n(1+aix)0n 次项系数即可。

这是一个经典分治 NTT 问题,复杂度 O(nlog2n)NTT 不要太慢就足以通过。

(sk) 利用 skk+1(sk)=(sk+1) 递推即可。


ai=k 的个数为 ckm=maxa,则 f(x)=i=2m(1+ix)ci=i=2mgi(x)

单个 g 直接二项式定理展开即可,而后对 g2gm 做分治 NTT,此时复杂度应为 O(nlognlogm),更优秀一点,但我懒得写啦,自己上面那个做法已经跑飞快了。

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;
}

qoj 5376. xor#

link 。提示:分段,自然数幂和。

solution

前置知识,线性/线性对数求自然数幂和:CF622F,这里为了实现简洁带了 log

考虑设 cx 表示 0i,j,k<nijk=x 的个数,则 ans=xcxxd

猜测/观察到 cx 形成的值相同的连续段个数不多,一个朴素的上界是 log3n 段。

  • PS: 实际执行下来感觉是 O(logn) 级别的,不是很会证。

考虑怎么快速求出这些连续段以及其对应的 c 的值。

套路性的,考虑把 [0,n) 拆成若干 [x,x+2k) 的合并。

此时维护 (x,k,c) 这样一个类,表示数在 [x,x+2k) 中,且有 c 种方案。

考虑 merge (x0,k0,c0),(x1,k1,c1),不妨设 k0k1,发现形成的新类为:(2k1(x0x1)/2k1,k1,2k0c0c1)

枚举 i,j,k 分别在哪个类里,并自然数幂和计算一个类的答案即可。

复杂度 O(log3n+d×polylog(n))。一个朴素的上界是四只 log,实际感觉是两只或三只 log

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;
}

P10855. Gcd with Xor#

link 。提示:xorlog 段区间。

solution

ans 表示最终答案。

ans=i=1nj=in(i,i xor j)k=i=1ndidkj=in[(i,i xor j)=d]=i=1ndidkj=indDi,dDi xor j,ijnμ(D)=T=1n(dTdkμ(T/d))Tij=in[T(i xor j)]

此时注意到 j[i,n],i xor j 显然能被分成 log 段区间,再加上调和级数的 ln,后半部分的复杂度是 O(nlnnlogn)

至于 f(T)=dTdkμ(T/d) 部分,为了避免 logk 的出现,可以线性筛 O(n) 预处理 1k,2k,,nk

此时前半部分计算的复杂度就是调和级数的 O(nlnn)

于是复杂度 O(nlog2n),常数别写太劣就能过。

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;
}
posted @   HaHeHyt  阅读(530)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示
主题色彩
主题色彩