CSP模拟7

保龄了!!!!保龄了!!!!


A.卷

一眼树形DP板子,一个点选与不选。然后就挂了……
取模会使一个大数变小。所以dp里记录的值无意义。我们可以把相乘变成对数相加的形式。比较对数就好。对数数组要double。


B.简单题

组合数学。

我们向限制连边变成一条链。
$ 1 \rightarrow 2 \rightarrow 4 \rightarrow8 …… $

$ 3 \rightarrow 6 \rightarrow 12 …… $

$ 5 \rightarrow 10 …… $

我们在这一条链上A,B集合是隔一个选一个的。
然后我们可以发现用 $ 2^xp (p\in奇数) $ 可以构造出所有数,不会有漏掉的数。

考虑每条链的贡献。
我们可以发现当链长为奇数和为偶数的情况贡献是不一样的。
当为偶数时就是一半一半两种情况贡献为2。
当为奇数时一个集合是$ len $ 一个是 $ len+1 $。A的可行大小一定是有一定范围 $ [l,r] $ 而 $ l $ 之上的贡献一定有奇链贡献而成的直接组合数 $ C _{奇链总数} ^{m-l} $ 组合数较大可用lucas

Code

#include<cstdio>
#include<cmath>

#define int long long

using namespace std;
const int mod=10000019;
const int N=10000019+5;

inline int read(){
    int x=0,f=1;
    char ch=getchar();

    while(ch<'0'||ch>'9'){
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}

int ksm(int a,int b){
    int ans=1;

    while(b){
        if(b&1)
            ans=ans%mod*a%mod;
        a=a%mod*a%mod;
        b>>=1;
    }
    return ans%mod;
}

int fac[N+5];

int C(int n,int m){
    if(n<m)
        return 0;
    return fac[n]%mod*ksm(fac[m],mod-2)%mod*ksm(fac[n-m],mod-2)%mod;
}

int lucas(int n,int m){
    if(n==m)
        return 1;
    if(n<m)
        return 0;
    return lucas(n/mod,m/mod)%mod*C(n%mod,m%mod)%mod;
}

int n,Q;

signed main(){
    n=read(),Q=read();

    fac[0]=1;

    for(int i=1;i<=mod-1;i++){
        fac[i]=fac[i-1]%mod*i%mod;    
    }

    int l=0,r=0,tot1=0,tot2=0;

    for(int i=1;i<=(int)log2(n)+1;i++){
        int p1=n/pow(2,i)+1,p2=(n/pow(2,i-1));

        if(p1&1)
            p1--;
        if(p2&1)
            p2++;
        
        // printf("%lld %lld\n",p1,p2);

        int num=(p2-p1)/2;

        if(i&1)
            tot1+=num;
        else
            tot2+=num;
        l+=num*(i/2);
    }

    r=l+tot1;

    int ans=ksm(2,tot2)%mod;

    while(Q--){
        int m=read();

        if(m<l||m>r){
            printf("0\n");
            continue;
        }    

        printf("%lld\n",ans%mod*lucas(tot1,m-l)%mod);
    }

    return 0;
}

C.粉丝
同样的DP第二次还是不会……
image


D.字符串
好神奇的题。

首先我们可以把两端回文的都删掉 $ qjdddqqqdjq \rightarrow ddqqq $
然后考虑剩下的可以是 $ A'+B+C+D 或 B+C+D+E' $ 两种情况。两种情况是类似的所以我们正过来反过去比两遍就行。

考虑如何处理剩余的串。发现剩下的串中,有一段连续回文串,有一段与 $ A' $ 构成回文串。我们可以用马拉车算出其中回文半径并进行扩展。把串反过来接在后面跑KMP。不断更新答案。

Code
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=5000005;

char s[N];
int len;
int ans;
char a[N<<1],b[N<<1];
int nxt[N<<1],mxn[N<<1];
int p[N<<1];

inline void work(){
    memset(p,0,sizeof(p));
    memset(nxt,0,sizeof(nxt));
    memset(mxn,0,sizeof(mxn));

    int n=(len<<1)+1;

    // for(int i=1;i<=len;i++){
    //     printf("%c",s[i]);
    // }
    // printf("\n");

    a[0]='$';
    for(int i=1;i<=len;i++){
        a[i*2-1]='#';
        a[i*2]=s[i];
    }
    a[len*2+1]='#';

    // for(int i=1;i<=n;i++){
    //     printf("%c",a[i]);
    // }
    // printf("\n");
    // for(int i=1;i<=n;i++){
    //     printf("%c",a[i]);
    // }
    // printf("\n");

    int r=0,pos=0;

    for(int i=1;i<=n;i++){
        if(i<r)
            p[i]=min(p[pos*2-i],r-i);
        else
            p[i]=1;
        while(a[i+p[i]]==a[i-p[i]])
            ++p[i];
        if(r<i+p[i]){
            r=i+p[i];
            pos=i;
        }
    }

    // for(int i=1;i<=n;i++){
    //     printf("%d ",p[i]);
    // }
    // printf("\n");

    for(int i=1;i<=len;i++)
        b[i]=b[len*2-i+1]=s[i];
    
    n=len*2;

    for(int i=n-1,j=0;i>=1;i--){
        while(j&&b[i]!=b[n-j])
            j=nxt[j];
        if(b[i]==b[n-j])
            j++;
        nxt[i]=j;
    }

    for(int i=len;i>=1;i--)
        mxn[i]=max(mxn[i+1],nxt[i]);
	// 用来判断是否会重叠
    for(int i=1;i<=n;i++){
        int l=(i-p[i])/2,r=(i+p[i])/2;

        if(nxt[r]<=l)
            ans=max(ans,p[i]-1+2*nxt[r]);
        if(mxn[r]>=l) // 重叠
            ans=max(ans,p[i]-1+2*l);
    }
}

int main(){
    // freopen("4.in","r",stdin);
    // freopen("4.out","w",stdout);

    scanf("%s",s+1);

    len=strlen(s+1);

    int pos1=1,pos2=len;

    // for(int i=1;i<=len;i++){
    //     printf("%c",s[i]);
    // }

    while(pos1<=pos2){
        if(s[pos1]!=s[pos2])
            break;
        pos1++,pos2--;
    }
    --pos1;

    len-=pos1*2;

    for(int i=1;i<=len;i++)
        s[i]=s[i+pos1];

    // for(int i=1;i<=len;i++)
    //     printf("%c",s[i]);
    // printf("\n");
    // printf("%d ",len);

    work();
    reverse(s+1,s+1+len);
    work();

    // printf("%d",pos1);

    printf("%d",ans+pos1*2);

    return 0;
}

我们去掉冗余的东西再算的,再想到剩下的可以是 $ A'+B+C+D 或 B+C+D+E' $ 两种情况。就好做多了。就算知道也想不到KMP。
mxn 数组很神秘。

posted @ 2023-07-29 06:53  Ysz_y  阅读(38)  评论(0)    收藏  举报