【考试总结】2022-03-11

卷王

考虑递推得到所有 [0,216) 的所有答案,将序列差分之后设答案为 t 在第 i 秒的操作形式就是将差分序列里面两个相隔 i 的位置反转

那么将 t 秒的所有操作反转过来进行 DP 即可,复杂度 Θ(n22n)

Code Display
const int N=20;
char s[N];
int n,ans[1<<16],a[N];
bool dp[17][1<<16];
signed main(){
    freopen("roll.in","r",stdin); freopen("roll.out","w",stdout);
    dp[0][0]=1;
    int S=1<<16; --S;
    for(int t=0;t<16;++t){
        for(int i=0;i<=S;++i) if(dp[t][i]){
            dp[t+1][i]=1;
            for(int j=1;j<=16;++j){
                int nS=i^(1<<(j-1)); 
                if(j+t+1<=16) nS^=1<<(t+j);
                dp[t+1][nS]=1;
            }
        }
    }
    for(int i=0;i<=S;++i){
        for(int t=0;t<16;++t) if(dp[t][i]){ans[i]=t; break;}
    }
    int T=read(); while(T--){
        scanf("%s",s+1); n=strlen(s+1);
        reverse(s+1,s+n+1);
        if(n<16) rep(i,n+1,16) s[i]='0';
        n=16;
        reverse(s+1,s+n+1);
        rep(i,1,n) a[i]=s[i]-'0';        
        Down(i,n,1) a[i]^=a[i-1];
        int init=0;
        Down(i,n,1) init=init<<1|a[i];
        print(ans[init]);
    }
    return 0;
}

赢王

设前缀和数组为 si

考虑在将 b1 操作为 0 之后 b2 在模 k 意义下值是一样的,那么操作前 i 个数字之后剩下给 bi 的是 j=1i(sjmodk)

所以如果长度为 i 的序列 b1bi 合法那么操作次数最小就是 j=iimin(sjmodk,ksjmodk)

对这个东西求和是经典问题,使用 Fenwick Tree 来维护 modk 每种余数的前缀和的个数和 si 的和

这里个数定义为能造成贡献的区间个数,对于一个前缀模 k 的余数 x,一个位置 i 带来的 min(k(six)modk,(six)modk) 的贡献数量是 i 前面 sjxmodkj 的数量 乘 i 后面 sjxmodkj 的数量

那么扫过一个 x 时带来的增量是可以计算的,这样子就可以树状数组维护了

在查询的时候每个 si 其它的 sj 造成 sisj[k,k2],(k2,0],(0,k2],(k2,k] 的对答案的贡献形式是不一样的,需要分开讨论计算,平凡但是很难不言繁琐

Code Display
const int N=1e6+10;
int n,ans,a[N],k,sum[N];
int lsh[N],m=1,ton[N];
struct Fenwick_Tree{
    int sum[N],cnt[N];
    inline void insert(int pos,int num){
        int val=mul(num,lsh[pos]);
        for(int x=pos;x<=m;x+=x&(-x)) cnt[x]+=num,ckadd(sum[x],val);
        return ;
    }
    inline int query_cnt(int x){
        int res=0;
        for(;x;x-=x&(-x)) ckadd(res,cnt[x]);
        return res;
    }
    inline int query_sum(int x){
        int res=0;
        for(;x;x-=x&(-x)) ckadd(res,sum[x]);
        return res;
    }
}T;
int nowcnt[N];
inline int solve(int l,int r,int coef,int fl){
    int cnt=T.query_cnt(r)-T.query_cnt(l-1);
    int sum=T.query_sum(r)-T.query_sum(l-1);
    return coef*cnt+sum*fl;
}
signed main(){
    freopen("win.in","r",stdin); freopen("win.out","w",stdout);
    n=read(); k=read();
    rep(i,1,n) a[i]=read(),sum[i]=(sum[i-1]+a[i])%k,lsh[++m]=sum[i];
    sort(lsh+1,lsh+m+1); m=unique(lsh+1,lsh+m+1)-lsh-1;
    ans=-n*(n+1)/2;
    rep(i,0,n) sum[i]=lower_bound(lsh+1,lsh+m+1,sum[i])-lsh,ans+=ton[sum[i]],ton[sum[i]]++;
    T.insert(sum[0],--ton[sum[0]]); nowcnt[1]=1;
    for(int i=1;i<=n;++i){
        ans+=solve(lower_bound(lsh+1,lsh+m+1,lsh[sum[i]]-k/2)-lsh,sum[i],lsh[sum[i]],-1);
        ans+=solve(lower_bound(lsh+1,lsh+m+1,lsh[sum[i]]+(k+1)/2)-lsh,m,lsh[sum[i]]+k,-1);
        ans+=solve(1,upper_bound(lsh+1,lsh+m+1,lsh[sum[i]]-k/2-1)-lsh-1,k-lsh[sum[i]],1);
        ans+=solve(sum[i]+1,upper_bound(lsh+1,lsh+m+1,lsh[sum[i]]+(k+1)/2-1)-lsh-1,-lsh[sum[i]],1);
        ton[sum[i]]--; nowcnt[sum[i]]++;
        int deltnum=ton[sum[i]]-nowcnt[sum[i]]+1;
        T.insert(sum[i],deltnum);
    }
    ans=(ans%mod+mod)%mod;
    print(ans);
    return 0;
}

稳王

不难发现最优策略是把牌攒到能一次杀掉 boss 再一起打出

问题本质上是计算每种不能杀死 boss 的牌型的出现概率之和加 1,那么分为如下若干种情况讨论即可:

  • 全拿到复读牌:i=1+13i=12

  • 全部为毒药牌,不到 n+1 张不能获胜:i=1n13i

  • 全部为火球牌,不拿到 m=n12 不能获胜:i=1m13i

  • 同时有毒药和火球牌,先出一张毒药,那么可以将答案表示为 F(x)=i=1+(13(x+x3))i,Ans=i=1n[xi]F(x)

    但是这里并不能保证毒药和火球都被拿到了,所以要分别减掉 F(x)=i=1+(13x)iF(x)=i=1+(13x3)i 的前 n 项系数和

    这里需要满足多出来一张毒药牌,所以可以将 nn+1 再做

  • 同时有毒药和复读牌,本质相同,这里的项变成了 (x+x2)i,容斥的方式也和上面完全一样

  • 同时有 3 张牌,本质仍然相同,项变成了 (x+x3+x4)i,算 7 次而已

注意单项没必要矩阵快速幂,可以直接等比数列求和

不需要预处理向量也是可以通过的

Code Display
template<const int N>struct Matrix{
    int a[N][N]; Matrix(){memset(a,0,sizeof(a));}
    Matrix operator *(const Matrix &b)const{
        Matrix res;
        rep(i,0,N-1) rep(k,0,N-1) rep(j,0,N-1) ckadd(res.a[i][j],mul(a[i][k],b.a[k][j]));
        return res;
    }
    inline void init(){
        for(int i=0;i<N;++i) a[i][i]=1;
        return ;
    }
    inline Matrix qpow(int n){
        Matrix b,c=*this; b.init();
        while(n){
            if(n&1) b=b*c;
            n>>=1; c=c*c;
        } return b;
    }
};
const int inv2=(mod+1)/2,inv3=ksm(3,mod-2);
inline int calc(int x){
    //sigma i in [1,x] 1/(3^i)
    return mul(inv2,del(1,ksm(inv3,x)));
}
signed main(){
    freopen("stable.in","r",stdin); freopen("stable.out","w",stdout); 
    int T=read(); while(T--){
        int n=read(),m=(n-1)/2,ans=inv2; //first type

        ckadd(ans,calc(m)); //third type
        ckdel(ans,del(1,ksm(inv3,m))); //sixth type
        ckadd(ans,mul(2,del(1,ksm(mul(2,inv3),m))));

        Matrix<3>a; //fourth type
        a.a[0][2]=a.a[1][0]=a.a[2][2]=1;
        a.a[0][1]=a.a[1][1]=inv3;
        a=a.qpow(n);
        ckadd(ans,add(mul(4,mul(mul(inv3,inv3),a.a[1][2])),mul(inv3,a.a[0][2])));
        ckdel(ans,calc(n/2));

        Matrix<4>b; //fifth type
        b.a[1][0]=b.a[2][1]=b.a[3][3]=1;
        b.a[0][3]=b.a[2][3]=b.a[0][2]=b.a[2][2]=inv3;
        ckadd(ans,b.qpow(n).a[2][3]);

        Matrix<5>c; //seventh type
        c.a[1][0]=c.a[2][1]=c.a[3][2]=c.a[4][4]=1;
        c.a[0][3]=c.a[1][3]=c.a[3][3]=inv3;
        c.a[0][4]=c.a[1][4]=c.a[3][4]=inv3;
        ckadd(ans,c.qpow(n).a[3][4]);
        c.a[3][3]=c.a[3][4]=0;
        ckdel(ans,c.qpow(n).a[3][4]);
        c.a[3][3]=c.a[3][4]=inv3;
        
        c.a[1][3]=c.a[1][4]=0;
        ckdel(ans,c.qpow(n).a[3][4]);
        c.a[1][3]=c.a[1][4]=inv3;
        
        c.a[0][3]=c.a[0][4]=0;
        ckdel(ans,c.qpow(n).a[3][4]);
        
        ckadd(ans,calc(n/4));

        print(add(ans,1));
    } 
    return 0;
}
posted @   没学完四大礼包不改名  阅读(114)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示