2019.10.22 csp-s模拟测试82 反思总结

重来重来,刚刚就当什么都没发生

 

今天的题属实有些迷惑,各种意义上…总之都很有难度吧。不满归不满,这套题的确不是什么没有意义的题目。

 

为了考验自己的学习能力记忆力,决定不写题解,扔个代码完事了

其实是懒得写一大堆式子的推理以及想表示一下对出题人的敬意

你就不怕你到时候回来看一脸懵逼吗

 

T1:

#include<iostream>
#include<cstdio>
using namespace std;
int t,mod=998244353,mod1=998244351;
long long n;
int main()
{
    scanf("%d",&t);
    long long num=1,x=9;
    while(mod1){
        if(mod1&1)num=num*x%mod;
        x=x*x%mod;
        mod1>>=1;
    }
    while(t--){
        scanf("%lld",&n);
        n%=mod;
        printf("%lld\n",(n*n%mod-1)*num%mod);
    }
    return 0;
}
View Code

T2:

这题考场上出70分思路挺快的,大概20min或者更少,连带读完三道题的时间…这个思路本质上也是推导正解的中间过程

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e5+10;
int n,len,nxt[4*N],lons;
long long ans,num1[4*N],num2[4*N];
char s[2*N],c[2*N],a[4*N],ss[4*N];
void kmp1(int lon){
    strcpy(a+lon+2,s+1);
    a[lon+1]='.';
    int lens=strlen(a+1);
    for(int i=2;i<=lens;i++)nxt[i]=0;
    for(int i=2;i<=lens;i++){
        int j=nxt[i-1];
        while(j&&a[j+1]!=a[i])j=nxt[j];
        if(a[j+1]==a[i])nxt[i]=j+1;
        if(nxt[i]==lon)num1[i-2*lon]++;
    }
}
void kmp2(int lon){
    strcpy(a+lon+2,s+1);
    a[lon+1]='.';
    int lens=strlen(a+1);
    for(int i=2;i<=lens;i++)nxt[i]=0;
    for(int i=2;i<=lens;i++){
        int j=nxt[i-1];
        while(j&&a[j+1]!=a[i])j=nxt[j];
        if(a[j+1]==a[i])nxt[i]=j+1;
        if(nxt[i]==lon)num2[i-lon-1]++;
    }
}
int main()
{
    scanf("%s",s+1);
    lons=strlen(s+1);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",c+1);
        len=strlen(c+1);
        for(int j=1;j<=len;j++){
            strcpy(a+1,c+1);
            strcpy(a+j+1,ss+1);
            kmp1(j);
            strcpy(a+1,c+j);
            strcpy(a+len-j+2,ss+1);
            kmp2(len-j+1);
        }
    }
    for(int i=2;i<=lons;i++){
        ans+=num1[i]*num2[i-1];
    }
    printf("%lld\n",ans);
    return 0;
 } 
考场70pts

这个70分的做法是,读入一个字符串以后,暴力地扫出它的所有前后缀,对每一个前后缀在s串上跑一下kmp。如果是后缀,就在s串匹配成功的位置让计数器cnt2++。如果是前缀,就在s串匹配成功的子串的开始位置让计数器cnt1++。

最后扫一遍计数器数组(到s串的长度),ans+=cnt2i*cnt1i+1

应该是正解里最基础的那部分吧。

能想到正解的一部分对于一个菜鸡来说太不容易了赶紧详细写一下【?】

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e5+10,p=1500007,mod=998244353;
int n,len,cnt1=1,cnt2=1,lens;
char s[2*N],c[2*N];
int tree1[2*N][27],tree2[2*N][27];
unsigned long long hash1[2*N],hash2[2*N],ks[2*N];
long long ans;                                   
struct node{
    int m;
    #define m 1500007
    int head[2][m+10],Next[2][2*N],tot[2];
    long long siz[2][2*N];
    unsigned long long ver[2][2*N];
    void ins(unsigned long long has,int opt,long long sum){
        unsigned long long x=has%m;
        for(int i=head[opt][x];i;i=Next[opt][i]){
            if(has==ver[opt][i]){
                siz[opt][i]+=sum;
                return;
            }
        }
        ver[opt][++tot[opt]]=has,siz[opt][tot[opt]]=sum,Next[opt][tot[opt]]=head[opt][x],head[opt][x]=tot[opt];
        return;
    }
    long long get(unsigned long long has,int opt){
        unsigned long long x=has%m;
        for(int i=head[opt][x];i;i=Next[opt][i]){
            if(has==ver[opt][i]){
                return siz[opt][i];
            }
        }
        return 0;
    }
}h;
void insert(){
    int now=1;
    unsigned long long has=0;
    for(int i=1;i<=len;i++){//前缀 
        has=has*p+c[i];
        if(!tree1[now][c[i]-'a'])tree1[now][c[i]-'a']=++cnt1;
        h.ins(has,0,1);
        now=tree1[now][c[i]-'a'];
    }
    now=1,has=0;
    for(int i=len;i>=1;i--){//后缀 
        has=has*p+c[i];
        if(!tree2[now][c[i]-'a'])tree2[now][c[i]-'a']=++cnt2;
        h.ins(has,1,1);
        now=tree2[now][c[i]-'a'];
    }
}
void dfs1(int now,unsigned long long has){
    for(int i=0;i<26;i++){
        if(tree1[now][i]){
            long long val=has*p+i+'a';
            long long sum=h.get(has,0);
            h.ins(val,0,sum);
            dfs1(tree1[now][i],val);
        }
    }
}
void dfs2(int now,unsigned long long has){
    for(int i=0;i<26;i++){
        if(tree2[now][i]){
            long long val=has*p+i+'a';
            long long sum=h.get(has,1);
            h.ins(val,1,sum);
            dfs2(tree2[now][i],val);
        }
    }
}
long long work(int x){
    int l=1,r=x,ans1=0,ans2=0;
    while(l<=r){//后缀 
        int mid=(l+r)/2;
        unsigned long long val=hash2[mid]-hash2[x+1]*ks[x-mid+1];
        long long sum=h.get(val,1);                             
        if(sum){
            ans2=sum;
            r=mid-1; 
        }
        else l=mid+1;
    }
    l=x+1,r=lens;
    while(l<=r){//后缀 
        int mid=(l+r)/2;
        unsigned long long val=hash1[mid]-hash1[x]*ks[mid-x];
        long long sum=h.get(val,0);                         
        if(sum){
            ans1=sum;
            l=mid+1;
        }
        else r=mid-1;
    }
    return 1ll*ans1*ans2;
}
int main()
{
    scanf("%s",s+1);
    lens=strlen(s+1);
    ks[0]=1;
    for(int i=1;i<=lens;i++){
        hash1[i]=hash1[i-1]*p+s[i];
        ks[i]=ks[i-1]*p;          
    }               
    for(int i=lens;i>=1;i--){
        hash2[i]=hash2[i+1]*p+s[i];
    }                          
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",c+1);
        len=strlen(c+1);
        insert();
    }
    dfs1(1,0);
    dfs2(1,0);
    for(int i=1;i<lens;i++){
        ans+=work(i);
    }
    printf("%lld\n",ans);
    return 0;
}
View Code

很神奇的思路转化:让一个中间点前面衔接的最长后缀代表所有更靠近中间点的后缀,记录一个前缀和。因为不用考虑每个前后缀具体的位置,只要存在包含它的更长前后缀就一定能同时对答案产生贡献,所以可以利用trie树来统计一下前缀和。就trie树的操作过程来说,的确非常适合统计数量的前缀和信息。用hash映射所有存在的前后缀,在每个中间点记录答案的时候二分这个最长前后缀。相当于用hash和trie树替代掉了原本一次次跑kmp匹配的过程,非常优秀。

关于我为什么这么生气的原因,这题在hash上搞幺蛾子?嗯?

大概率是我菜吧

 

记一下要点:巩固一下ull自然溢出,以及哈希表

哈希表本质是利用关键字的分类(散列函数映射)来加速查询

T3:

#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const int mod=3e5+7;
int t;
ll n,m,k,ans,rec[mod+10],inv[mod+10],minn;
ll ksm(ll x,int k){
    ll num=1;
    while(k){
        if(k&1)num=num*x%mod;
        x=x*x%mod;
        k>>=1;
    }
    return num;
}
void work(){
    inv[0]=rec[0]=rec[1]=1;
    for(int i=2;i<=mod-1;i++)rec[i]=rec[i-1]*i%mod;
    inv[mod-1]=ksm(rec[mod-1],mod-2);
    for(int i=mod-2;i>=1;i--)inv[i]=inv[i+1]*(i+1)%mod;
}
ll cal(ll x,ll y){
    if(x<y)return 0;
    if(!y||!x)return 1;
    return rec[x]*inv[y]%mod*inv[x-y]%mod;
}
ll C(ll x,ll y){
    if(x<y)return 0;
    if(!y)return 1;
    return C(x/mod,y/mod)*cal(x%mod,y%mod)%mod;
}
int main()
{
    scanf("%d",&t);
    work();
    while(t--){
        scanf("%lld%lld%lld",&n,&m,&k);
        if(k==1){
            n%=mod,m%=mod;
            printf("%lld\n",n*m%mod);
            continue;
        }
        minn=min(n,m);
        ans=0;
        if(n>m)swap(n,m);
        ans=(ans+n%mod*C(m,k)%mod+m%mod*C(n,k))%mod;
        ans=(ans+2*(2*C(n,k+1)%mod+(m-n+1)%mod*C(n,k))%mod)%mod;
        if(k==5){
            minn%=mod;
            long long n1=n,m1=m;
            n%=mod,m%=mod;
            minn=((min(n1,m1)-1)/2)%mod;
            ans=(ans+2*(minn*n%mod*m%mod-2*(1+minn)%mod*minn%mod*inv[2]%mod*(m+n)%mod+4*minn%mod*(minn+1)%mod*(2*minn+1)%mod*ksm(6,mod-2)%mod+mod)%mod)%mod;
        }
        else if(k==4){
            minn%=mod;
            long long n1=n,m1=m;
            n%=mod,m%=mod;
            ans=(ans+(minn*n%mod*m%mod-(1+minn)*minn%mod*inv[2]%mod*(m+n)%mod+minn*(minn+1)%mod*(2*minn+1)%mod*ksm(6,mod-2)%mod+mod)%mod)%mod;
            minn=min(m1/2,n1)%mod;
            ans=(ans+2*(minn*n%mod*m%mod-(1+minn)*minn%mod*inv[2]%mod*(m+2*n)%mod+2*minn%mod*(minn+1)%mod*(2*minn+1)%mod*ksm(6,mod-2)%mod+mod)%mod)%mod;
            swap(n1,m1);
            swap(n,m);
            minn=min(m1/2,n1)%mod;
            ans=(ans+2*(minn*n%mod*m%mod-(1+minn)*minn%mod*inv[2]%mod*(m+2*n)%mod+2*minn%mod*(minn+1)%mod*(2*minn+1)%mod*ksm(6,mod-2)%mod+mod)%mod)%mod;
            minn=((min(n1,m1)-1)/2)%mod;
            ans=(ans+5*(minn*n%mod*m%mod-2*(1+minn)%mod*minn%mod*inv[2]%mod*(m+n)%mod+4*minn%mod*(minn+1)%mod*(2*minn+1)%mod*ksm(6,mod-2)%mod+mod)%mod)%mod;
        }
        else if(k==3){
            minn%=mod;
            long long n1=n,m1=m;
            n%=mod,m%=mod;
            ans=(ans+4*(minn*n%mod*m%mod-(1+minn)*minn%mod*inv[2]%mod*(m+n)%mod+minn*(minn+1)%mod*(2*minn+1)%mod*ksm(6,mod-2)%mod+mod)%mod)%mod;
            minn=min(m1/2,n1)%mod;
            ans=(ans+2*(minn*n%mod*m%mod-(1+minn)*minn%mod*inv[2]%mod*(m+2*n)%mod+2*minn%mod*(minn+1)%mod*(2*minn+1)%mod*ksm(6,mod-2)%mod+mod)%mod)%mod;
            swap(n1,m1);
            swap(n,m);
            minn=min(m1/2,n1)%mod;
            ans=(ans+2*(minn*n%mod*m%mod-(1+minn)*minn%mod*inv[2]%mod*(m+2*n)%mod+2*minn%mod*(minn+1)%mod*(2*minn+1)%mod*ksm(6,mod-2)%mod+mod)%mod)%mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
/*
3
7 7 3
7 6 3
8 9 3

1
5 5 1
*/
View Code

记一下平方和公式:

 

差点忘了这个:

朱世杰恒等式

其实它和平方和公式也有点联系

 

posted @ 2019-10-22 14:21  Chloris_Black  阅读(168)  评论(0编辑  收藏  举报