noip模拟60[困]

noip模拟60 solutions

这个考试真的困,困死我了,导致我啥式子也推不出来

这次的题难到家啦,改题的时候一点思路也没有,只能一个字一个字的研究题解

不过战神是真的强,搞出来一个题解都搞不到的算法

T1 整除

题目挺善良的,直接把质因数分解给你了

但是我不会用啊

首先我们去看只有一个质因数的,这个需要保证\(x^m\equiv x(mod\ p)\)

这个的话你直接枚举就好了枚举每一个小于当前模数的数,直接快速幂判断

然后你发现这样之后,多个模数的时候仍然是要保证这个条件的

这个时候,你发现我可以对于每一个模数选出来一种同余方程,然后就组成了一个中国剩余定理

设每一个质因数的解有\(c_i\)个,这样的同余方程有\(\prod^{}_{}c_i\)

而每一个同余方程在\([0,n]\)范围内只有一个解,所以解的个数就是这么多种

直接枚举就好了,判断的时候用快速幂。。。

你发现好像过不了,但是你又发现好像只是求一个数的m次方,这个直接快速幂质数的,剩下的线性筛

AC_code
#include<bits/stdc++.h>
using namespace std;
#define oj
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
const int mod=998244353;
int T,c,m,ans,p;
int ksm(int x,int y){
    int ret=1;
    while(y){
        if(y&1)ret=ret*x%p;
        x=x*x%p;y>>=1;
    }return ret;
}
int pw[10004],pr[10004],cnt;
bool vis[10004];
signed main(){
    #ifdef oj
        freopen("division.in","r",stdin);
        freopen("division.out","w",stdout);
    #endif
    int id;scanf("%lld",&id);
    scanf("%lld",&T);
    while(T--){
        ans=1;scanf("%lld%lld",&c,&m);
        fo(i,1,c){
            int res=0;scanf("%lld",&p);pw[1]=1;cnt=0;
            fo(i,2,p-1){
                if(!vis[i])pr[++cnt]=i,pw[i]=ksm(i,m);
                for(int j=1;j<=cnt&&pr[j]*i<=p-1;j++){
                    pw[i*pr[j]]=pw[i]*pw[pr[j]]%p;
                    vis[i*pr[j]]=true;
                    if(i%pr[j]==0)break;
                }
            }
            fo(i,1,p-1)vis[i]=false;
            fo(i,1,p-1)if(pw[i]==i)res++;res++;
            ans=ans*res%mod;
        }
        printf("%lld\n",ans);
    }
}

上面那个太慢了,要跑三千多,下面推个式子。

你要解的方程是\(x^m\equiv x(mod\ p)\),我们对他化简,设\(g\)是\(p\)的原根

\[g^{im}\equiv g^i(mod\ p) \]

\[im\equiv i(mod \ p-1) \]

上面那个东西在\([0,p-2]\)的范围内有\(\gcd(m-1,p-1)\)个解,因为

先移项

\[i(m-1)\equiv 0(mod\ p-1) \]

\[p-1|i(m-1) \]

\[\frac{p-1}{\gcd(m-1,p-1)}\mid i \]

这个就很明显了吧,\(i\)在\(p-1\)范围内的解就是上面那个答案(战神的代码)

AC_code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
int T,ans;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
signed main()
{
	freopen("division.in","r",stdin);
	freopen("division.out","w",stdout);
	int id;scanf("%lld",&id);
	scanf("%lld",&T);
	while(T--)
	{
		int c,m,p;scanf("%lld%lld",&c,&m);ans=1;
		for(int i=1;i<=c;i++)scanf("%lld",&p),ans=ans*(gcd(p-1,m-1)+1)%mod;
		printf("%lld\n",ans);
	}
	return 0;
}

T2 糖果

就是数量很大嘛

但是你发现好像这个\(a\)是有循环节的啊

所以你理所当然的想到了矩阵快速幂,但是它复杂度炸了,虽然能过

所以正解是??倍增\(DP\)

对于每一种\(a\)分别做一次,最后合并就行了

AC_code
#include<bits/stdc++.h>
using namespace std;
#define oj
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
const int N=1e7+5;
const int M=105;
const int mod=998244353;
int n,m,a[N],A,B,P;
int pos[N],sum[M],dp[M][M][M],f[M][M][M],ans[M][M],tim[M];
int jc[M],inv[M];
int ksm(int x,int y){
    int ret=1;
    while(y){
        if(y&1)ret=ret*x%mod;
        x=x*x%mod;y>>=1;
    }return ret;
}
signed main(){
    #ifdef oj
        freopen("sugar.in","r",stdin);
        freopen("sugar.out","w",stdout);
    #endif
    scanf("%lld%lld",&n,&m);
    scanf("%lld%lld%lld%lld",&a[1],&A,&B,&P);
    pos[a[1]]=1;
    fo(i,2,n){
        a[i]=(a[i-1]*A+B)%P+1;
        if(pos[a[i]]){
            int bas=(n-pos[a[i]]+1)/(i-pos[a[i]]);
            int ys=(n-pos[a[i]]+1)%(i-pos[a[i]]);
            fo(j,pos[a[i]],i-1){
                if(a[j]>m)a[j]=m;
                sum[a[j]]++;
            }
            fo(j,1,m)sum[j]*=bas;
            fo(j,pos[a[i]],pos[a[i]]+ys-1)sum[a[j]]++;
            fo(j,1,pos[a[i]]-1){
                if(a[j]>m)a[j]=m;
                sum[a[j]]++;
            }
            break;
        }
        pos[a[i]]=i;
    }
    if(a[1]>m||!sum[a[1]])
        fo(i,1,n){
            if(a[i]>m)a[i]=m;
            sum[a[i]]++;
        }
    jc[0]=1;fo(i,1,m)jc[i]=jc[i-1]*i%mod;
    inv[0]=1;inv[m]=ksm(jc[m],mod-2);
    fu(i,m-1,1)inv[i]=inv[i+1]*(i+1)%mod;
    fo(i,1,m){
        if(!sum[i]){f[i][tim[i]][0]=1;continue;}
        fo(j,0,i)dp[i][0][j]=1;
        fo(l,1,60)fo(j,0,m)fo(k,0,j)dp[i][l][j]=(dp[i][l][j]+dp[i][l-1][j-k]*dp[i][l-1][k]%mod*jc[j]%mod*inv[j-k]%mod*inv[k])%mod;
        int nn=sum[i];f[i][0][0]=1;
        fu(l,60,0){
            if((1ll<<l)>nn)continue;
            nn-=(1ll<<l);tim[i]++;
            fo(j,0,m)fo(k,0,j)f[i][tim[i]][j]=(f[i][tim[i]][j]+f[i][tim[i]-1][j-k]*dp[i][l][k]%mod*jc[j]%mod*inv[j-k]%mod*inv[k])%mod;
        }
    }
    ans[0][0]=1;
    fo(i,1,m)fo(j,0,m)fo(k,0,j)ans[i][j]=(ans[i][j]+ans[i-1][j-k]*f[i][tim[i]][k]%mod*jc[j]%mod*inv[j-k]%mod*inv[k])%mod;
    printf("%lld",ans[m][m]);
}

T3 打字机

这个我不会,但是我A了,

不过这个\(DP\)确实玄学,

主要是理解数组是关于后缀定义的,并且你不确定转移某一位会不会造成贡献,所以两种转移是不一样的

AC_code
#include<bits/stdc++.h>
using namespace std;
#define oj
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
const int N=100005;
const int T=25;
char s[N],t[T];
int sn,tn,ad;
int m,l[N],r[N];
int dp[N][T][T*2];
signed main(){
    #ifdef oj
        freopen("print.in","r",stdin);
        freopen("print.out","w",stdout);
    #endif
    scanf("%s",s+1);
    scanf("%s",t+1);
    sn=strlen(s+1);tn=strlen(t+1);
    ad=tn+1;
    memset(dp,0x3f,sizeof(dp));
    fo(i,0,sn)fo(j,0,tn)fo(k,0,-j-1+ad)dp[i][j][k]=-1;
    fo(k,ad,ad+tn)dp[0][0][k]=0;
    fo(i,0,sn){
        fo(j,0,tn){
            fo(k,0,ad+tn){
                if(i<sn)dp[i+1][j][k]=min(dp[i+1][j][k],dp[i][j][k]+1);
                if(j<tn)dp[i][j+1][k-1]=min(dp[i][j+1][k-1],dp[i][j][k]);
                if(i<sn&&j<tn)dp[i+1][j+1][k+(s[i+1]==t[j+1])]=min(dp[i+1][j+1][k+(s[i+1]==t[j+1])],dp[i][j][k]+1);
            }
        }
    }
    scanf("%d",&m);
    fo(i,1,m){
        scanf("%d%d",&l[i],&r[i]);int pos;
        fo(k,0,ad+tn)if(dp[r[i]][tn][k]>=r[i]-l[i]+1){pos=k;break;}
        int ans=r[i]-l[i]+1-(pos-ad);
        printf("%d\n",ans);
    }
}
上一篇:Luogu 3302 森林(树上维护主席树)


下一篇:png转tif