杂题记 2
写在前面:分解大致为 题,题目难度高,大部分题个人认为的实际难度不低于洛谷的紫题。
#
下边设 为实际中的 。首先根据样例猜测一定有解。
按照提示,注意到第 次 ,第二次 。
但是第三次似乎没啥规律?这时注意到可以倍增,第 次 。于是 一定是一个解。但是要求最小解。
由于我们可以快速求每 次操作数组会变成啥,直接倍增即可,类似倍增 求出最大的不变为全 的操作数,最后 即可。
#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;
}
#
考虑把题目中的“加点”理解为删点,发现每个点可以被删当且仅当这个点只有一条连边,删点后删去这个点连的所有边。
发现这个性质很严,手模一下,发现对于一个边双,若边双内有 个点,那么整个边双都不可被删,这样性质就很好了。
考虑缩点后的森林(图不联通),称边双内点为 的为白点,否则为黑点。
考虑森林里一颗全为白点的树,dp 计算。发现钦定一个点为根会漏算,于是把这个树中所有点都当做根算一遍答案。记 为这棵树的大小,发现 个点的方案在以不是这 个点为根时都会算一遍,于是要除上 。当 时方案就不重不漏,不用动。
简单说一下 dp 式,大概是 ,枚举完所有子节点后算上加上这个点的贡献:,复杂度同树形背包 。
剩下的树可以拆分成若干以黑点连出的白点为根的树,同上 dp,当发现子节点转移到黑点的时候直接 return,精细实现一下发现是对的。这时候以那个点为根不重不漏。
把每颗树的答案合并起来和上面的 dp 类似,记得 处答案的转移要特别注意,实现精细,还用不懂看代码。
#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;
}
#
首先看到样例就猜测 为奇数答案为 ,否则为啥剩下三样例个全是偶数?
考虑反证,记 表示 。则由题意 。则 。
考虑每条边的贡献,由于每条边会在两个端点贡献一次,于是 ,矛盾。
接下来考虑每种形态的树有怎样的填法,注意到类似 Prüfer 序列从叶节点剥,填法唯一。
考虑直接求 很困难,于是算每条边的贡献。首先边在 上等价于割掉这条边 在不同连通块上。设两个连通块的大小为 。若边权为 ,则两边子树内的 值都不变,于是两边都是合法的子树,。否则两边一定都不合法, 都是奇数。
考虑 的两个连通块有多少种构成树的方案,首先两个子树分别是 和 ,然后两边各选一个点连边是 ,然后除掉 的剩下 个点给 的连通块 个是 .
记 ,则答案为 ,经验证:在 处这样答案仍是对的,复杂度 。
注意到拆开组合数这是一个卷积形式,于是能 做到 算出 时的答案,但是在这题是没必要的。
#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;
}
#
丢一道类似 trick 的题:CF364D。
首先,容易注意到答案上界为 ,因为一定能在不超过 次操作内变成全偶数。
而后有结论:在最终的答案中,一定存在至少一半的元素,它们最多被操作过一次。
反证:如果有至少一半两次,那么总个操作数就 了。
考虑这个一半怎么用,发现每次随机一个数组中的数,它们被操作次数 的概率是 的,于是直接随机若干个,枚举它被 或不动,枚举它质因子 check 即可。不会 check 就别做这题了。
设随机 个,,则复杂度为 ,错误率为 ,于是取 即可通过,具体取值看喜好。
#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;
}
#
为了简洁下面用 代替分数。
首先特判 有 的倍数的情况,发现当且仅当两者均为 倍数时才有贡献,于是这部分答案为 。
考虑令 表示 ,类似定义 ,则有:,且 个数与自然数对 一一对应。
这时 。
考虑算贡献,记 表示 的个数,则答案为 。
设 为原根,,则 。
此时有 ,其中 满足 。
记 表示 的个数,则答案为 。
考虑固定 ,求 的数量。
枚举 ,那么 的取值只有 种,我们需要保证 。
那么有 ,也就是 。
于是 在 下唯一确定,此时 在 在有 种取值。于是此时合法的 的方案数为 。
于是 .
其中第二个等号是枚举 时统计 。记 ,则 ,做狄利克雷前缀和即可。
其中分解质因数要用 。
考虑计算这时狄利克雷前缀和的复杂度,记 的质因数分解为:,则复杂度为 ,由于我实现不精细要对所有质因数进行一次排序,此时复杂度为 。
质因数分解的复杂度为 ,于是总复杂度 ,查表发现足以通过本题。具体不懂的可以看代码。
注意,写代码时我的式子是:,显然这是等价的。
#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;
}
#
锐评:出题人只会 太不牛了!既然出了就得会但 啊。而且官方题解写的一坨屎,太复杂了(
这题的有个小套路是参考P5860,推荐同时练习。
前置知识:多项式理论(要会多项式 ),概率期望, 反演。
分析复杂度时默认 同阶。
考虑设第 个颜色出现的最早时间为 ,则要求
发现 比 好求,进行 反演,令 :
考察 ,发现一个位置的颜色属于 的概率为 ,由于概率期望互为倒数,于是
于是
这时发现分母部分是和的倒数,难刻画,考虑剥离。
考虑 ,求 ,
这东西先考虑朴素 dp:设 表示 ,则有转移:
,初值为 ,欲 求
考虑套路性的设 ,则
于是 ,此时大部分人都会分治 做到 。
考虑求 ,限制条件为 ,记 的有 个,进行 :
里面直接暴力算即可,套多项式 ,复杂度为 ,注意不要漏 前的负号和前面 的系数。
#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;
}
#
前置知识:竞赛图强联通分量性质,只要用到本文的结论 。
竞赛图强连通缩点后的 呈链状, 前面的所有点向后面的所有点连边。
通常性的,我们考虑应考虑前 个强连通分量,于是我们对所有图缩点后对这样的划线计数:
假设某条线前面有 个点(注意不是强联通分量),后面有 个点,则这个竞赛图可以拆成:
前 个点组成的竞赛子图
后 个点组成的竞赛子图
所有前 个点向所有后 个点连的一条有向边
于是我们要把 个点划分成大小为 两个集合,两个集合内的点要以某种方式连成竞赛图,而后按照第三个条件再连边。
记 ,则方案数为 ,于是答案为
复杂度 不等,取决于实现的精细(我代码是带 的)
此题很难低于 的原因:
由于 ,于是 。此时幂次出现了平方而且要求和,是经典不太可做问题。
#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;
}
#
硬生生把一个困难的蓝想了黑的做法。(下面讲的是蓝的思维难度的做法)
前置知识:Kummer 定理,勒让德定理。
显然特判 和 的情况,现在情况是
考虑变形:
若它是整数,根据 Kummer 定理, 等于 在 进制下进位的次数。
于是根据数据范围有 。
于是
于是
于是 ,否则不是整数。
考虑若它不是整数,则存在素数 使得 ,即 。
于是 ,于是 必为 中一个数的素因子。由于 ,直接枚举而后分解素因子即可判断。
瓶颈在于分解素因子,设其复杂度为 ,则总复杂度为
其中 为 ,而最外层的 为 ,显然由分析这时数始终是 级别的。
如果用 pollard-rho 则这题能做 。那个黑的想法就不讲了,太蠢了。
#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;
}
#
前置知识:扩展欧拉定理,幂塔。前置题目:P4139,一定要先做这个!
沿用记号:记
想到幂塔这题就容易多了。感觉第一篇题解是没想清楚欧拉定理的细节写的,那个对较小数的处理太粗糙了!感觉这样直接求比他的递归更容易想到啊。
由前置题目推广:记 ,则 ,使得 , 为常数。
于是取充分大的 ,则 为一组解。(你可以想象成 理解)
但是 太大了,考虑 为一组解,想要:。
不妨设 ,于是由扩展欧拉可以变成构造:。
记 ,则我们容易构造出 为满足条件且 的解。
于是要求 ,然后 exgcd 合并。
而求这个显然就转换为了前置题目,直接套上即可,复杂度 。注意 exgcd 时要用 __int128。
#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;
}
#
前置知识:P4137 的在线单次 求区间 做法,CF1870E 的结论。
赛事做繁了,没冲出来。发现第二篇题解难理解,第一篇题解有个细节繁了,于是我结合了一下。除去主席树板子,代码非常短。
这篇题解是目前第一篇题解和第二篇的结合,由重合请见谅。
如果不想看结论证明直接忽略即可,没啥影响。
称 是极小区间,当且仅当不存在 ,。则有结论:极小区间只有 个。
- 证明:设 是极小区间,则显然 ,不妨设 ,则由于删掉端点 要变化,于是 。若存在 , 是极小区间,则 ,于是 在 中出现过。于是删去 , 不变。于是固定 , 的情况只对应一个 ,所以只有 个了。
考虑如何求所有极小区间。如果直接按证明方法求是非常难写的。于是考虑巧妙方法。
设 ,则称 为 区间。
考虑一个 的极小区间,同样不妨设 ,则由于极小性,于是 在 中没有出现。
考虑删去 之后, 变为 ,不妨设 区间 对应的极小子区间为 ,则 一定在 中出现,于是 。
- 这说明: 区间必定为一个 区间向一端扩展到第一个数 得到。其中 。
考虑从 依次求出所有 极小区间。对于每个 维护 极小区间的 vector。
每次先把所有 区间进行如上扩展,设一个扩展完后 ,则把新区间丢到 的 vector 里。这里求扩展完的 用上面说的单次 的方法。
这时候所有 极小区间都在 的 vector 里了,但注意扩展完的不一定都是极小的,于是排除掉即可。就不多不少的求出了 极小区间,一直做下去即可。记得初始化极小 区间。
由于极小区间总共只有 个,乘上求区间 的 ,于是复杂度为 。
求出所有极小区间后,考虑所有对应 的极小子区间 的大区间的形态。
设 左侧第一个 的位置为 , 右侧第一个 的位置为 。
则所有对应的大区间为:左端点在 ,右端点在 的所有区间。于是 ,存在长为 的区间 。
于是问题就转化成了:维护 个集合,区间插入一个数,最终对所有集合求 。把插入操作差分,用一个 set 动态维护 即可。具体看代码。
#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;
}
#
人类智慧题。需要一些矩阵乘法的知识辅助理解。
首先 次操作时容易的,按顺序把 依次归位即可。每个数归位由于每次距离减半于是是 的。
这时候随机一下会不会很优秀?但是有佬证明了这个算法期望下依然是 次的。不足以通过。
到了人类智慧部分了,看这个结构本来不是很好逆,但是依然要逆。
不妨设初始排列为 , 的排列为 。操作为 ,则 。
考虑左右求逆。操作本质为矩阵乘法,于是类似的做。同时 的排列可以看做单位矩阵。
于是 。由于矩乘中 ,于是 。
- 令逆操作 ,相当于对逆排列 进行编号 的逆操作,最终要变成 的排列。
同样的,我们依然按顺序把 依次归位。然后把操作逆序输出。
观察对 逆操作,则位置 上的数变到位置 了。同时 的位置 ,对 逆操作即可把位置 上的数移动到位置 上。
设 在位置 。如果 ,我们先把 倍增到 范围,而后进行一次逆操作即可。次数为 。
看似没有优化?但是不妨先进行随机打乱,此时假设 在 中随机选取,则 。
其中 是极小的余项,可以用函数图像感受一下。近似部分用了 Stirling 公式估计阶乘。
于是 ,于是总次数估计为 次。足以通过。事实上次数上界设为 也能飞快就出结果。
关于随机打乱:方法是随机选常数个区间进行逆操作。直接对 这样做即可,还原为 后,最后逆序输出时这样显然不影响正确性。
注意最开始是对逆排列进行上述操作!具体细节看代码,代码很短。
#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;
}
#
前置知识:齐次线性递推。
注意到瓶颈在于组合数上标有 。
考虑 ,于是容易构造出答案为:.
直接上齐次递推中的 Bostan-mori 算法即可,复杂度 或 ,取决于多项式乘法的实现。
在 时需要 来加速。否则超时。
一个细节就是最后的分母始终是 ,不需要求逆得出结果。还有不懂可以看代码。
#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;
}
#
第一个线性做法题解。下文规定若 或 ,则 。
先不考虑 ,设长度为 的 序列满足条件的方案数为 ,则答案为 。考虑线性求 。
设总和为 ,分讨:
若 ,则数量为 。
否则,若序列合法一定存在前缀和为 。
首先总和要 ,而又不存在为 的前缀和,且序列中元素在 中,于是存在。
特判 ,此时考虑 位置的前缀和为 ,发现 ,又发现 不满足条件,于是 。
递推下去,于是 均为 ,其他位置任意。
再分讨,若 ,则对 中填数方案计数,否则判断全填 可不可行即可。
后者是容易的,前者是要 个数,和为 ,简单计算后发现为 。
于是这时方案数为 。
设 同阶,复杂度 。代码。
接下来考虑线性求 ,这是容易的。先预处理阶乘逆元。
。就能每次 递推啦。
最后线性求 。第一步思路来源于官方题解,但最后式子比官方题解简单。
于是 。一样预处理阶乘线性递推即可。
最终复杂度 。
#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;
}
#
。提示:拉格朗日定理。
前置知识:拉格朗日定理。
设 ,则 的所有素因子都满足条件。
考虑剩下的,若 为满足条件的素数,则多项式 必有因式 。同余这步可以参考前置知识中文章。
于是得出 。
对于每个素数判一下即可,由拉格朗日定理得 。
复杂度 ,跑不满。
//洛谷 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;
}
#
下文计算复杂度时设 同阶。
枚举每种样式选了多少个,设当前考虑的情况为样式 分别选了 个,考虑此时猜中的概率。
先计算选出这种情况的方案数。为 。
考虑根据桌面上牌表示的信息情况总数。
前 张有 种排列方式,枚举倒置的牌,计算得:
。
于是猜中的概率为 ,然后要与 取 ,直接 计算即可。
考虑这种情况出现的概率,发现是 ,用你喜欢的写法 算即可。
枚举合法的 的情况直接 dfs 做即可,情况数上界为选若干非负整数和为 的方案数。即 。
当 同阶时,情况数 。
总复杂度就是 ,随便过。
#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;
}
#
只有写了题解才能明白每一步的原委。
考虑拆 :
对最后两部分 dp 即可。
首先套路性地记 表示填到 ,逆序对数为 的方案数。
则 ,记下 的前缀和进行前缀和优化即可。
则前半部分的总贡献为 。
记 表示填到 ,逆序对数为 的方案中当前所有 的和。
则 。
类似记 表示填到 ,逆序对数为 的方案中当前所有 的和。
考虑倒着做,令 ,。
此时若正着做,则有:。
考虑 ,则 。
答案为 。
记下 的前缀和和 的前缀和,优化 dp 即可。具体细节看代码。
复杂度 ,空间复杂度经过优化后为 。
#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;
}
#
目前最优复杂度题解。
显然答案具有可二分性。现在考虑判断 是否满足条件。
令 ,则我们需要找到最小的非负整数 使得 。
这显然是可以通过 exgcd 求解的,具体细节看代码。
注意:当 且解出 时,需要把 加到最小正整数。
然后考虑最终数 的条件,只需 ,把 表示为字符串,进行 比较即可。
瓶颈在于二分加比较,复杂度 。
注意到二分不同值时比较的不同位只有 级别,即前几位是解出来的 ,后面大多数为都是 ,于是存在 预处理 比较的做法。留作思考(我懒得写了)。
于是最优复杂度可以看作 ,于是可以加强到 这样。
#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;
}
#
我起了,一枪秒了。ABC g 就这素质了,感觉好熟悉的题,但一下子又找不到原题。
套路性的,设抽袜子次数为 。则 。
考虑 的意义,即抽 次后袜子仍两两不同的概率。
首先算方案数,设 ,显然是 。
考虑两两不同的方案数,颜色 的生成函数为:,即不选有一种方案,选一个有 种方案。
而最后 个袜子可以任意排列,于是方案数为:。
于是 .
于是只需求出 的 次项系数即可。
这是一个经典分治 问题,复杂度 , 不要太慢就足以通过。
求 利用 递推即可。
记 的个数为 ,,则 。
单个 直接二项式定理展开即可,而后对 做分治 ,此时复杂度应为 ,更优秀一点,但我懒得写啦,自己上面那个做法已经跑飞快了。
#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;
}
#
前置知识,线性/线性对数求自然数幂和:CF622F,这里为了实现简洁带了 。
考虑设 表示 且 的个数,则 。
猜测/观察到 形成的值相同的连续段个数不多,一个朴素的上界是 段。
- PS: 实际执行下来感觉是 级别的,不是很会证。
考虑怎么快速求出这些连续段以及其对应的 的值。
套路性的,考虑把 拆成若干 的合并。
此时维护 这样一个类,表示数在 中,且有 种方案。
考虑 merge ,不妨设 ,发现形成的新类为:。
枚举 分别在哪个类里,并自然数幂和计算一个类的答案即可。
复杂度 。一个朴素的上界是四只 ,实际感觉是两只或三只 。
//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;
}
#
记 表示最终答案。
此时注意到 显然能被分成 段区间,再加上调和级数的 ,后半部分的复杂度是 。
至于 部分,为了避免 的出现,可以线性筛 预处理 。
此时前半部分计算的复杂度就是调和级数的 。
于是复杂度 ,常数别写太劣就能过。
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】