【考试总结】2022-03-11


卷王

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

那么将 \(t\) 秒的所有操作反转过来进行 \(\rm DP\) 即可,复杂度 \(\Theta(n^22^n)\)

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;
}

赢王

设前缀和数组为 \(s_i\)

考虑在将 \(b_1\) 操作为 \(0\) 之后 \(b_2\) 在模 \(k\) 意义下值是一样的,那么操作前 \(i\) 个数字之后剩下给 \(b_i\) 的是 \(\sum\limits_{j=1}^i \left(s_j\bmod k\right)\)

所以如果长度为 \(i\) 的序列 \(b_1\dots b_i\) 合法那么操作次数最小就是 \(\sum\limits_{j=i}^i \min(s_j\bmod k,k-s_j\bmod k)\)

对这个东西求和是经典问题,使用 \(\text {Fenwick Tree}\) 来维护 \(\mod k\) 每种余数的前缀和的个数和 \(s_i\) 的和

这里个数定义为能造成贡献的区间个数,对于一个前缀模 \(k\) 的余数 \(x\),一个位置 \(i\) 带来的 \(\min(k-(s_i-x)\mod k,(s_i-x)\mod k)\) 的贡献数量是 \(i\) 前面 \(s_j\equiv x\mod k\)\(j\) 的数量 乘 \(i\) 后面 \(s_j\equiv x\mod k\)\(j\) 的数量

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

在查询的时候每个 \(s_i\) 其它的 \(s_j\) 造成 \(s_i-s_j\in[-k,-\frac k2],(-\frac k2,0],(0,\frac k2],(\frac 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;
}

稳王

不难发现最优策略是把牌攒到能一次杀掉 \(\text{boss}\) 再一起打出

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

  • 全拿到复读牌:\(\sum\limits_{i=1}^{+\infty}\frac{1}{3^i}=\frac 12\)

  • 全部为毒药牌,不到 \(n+1\) 张不能获胜:\(\sum\limits_{i=1}^{n} \frac 1{3^i}\)

  • 全部为火球牌,不拿到 \(m=\lfloor\frac{n-1}2\rfloor\) 不能获胜:\(\sum\limits_{i=1}^{m}\frac{1}{3^i}\)

  • 同时有毒药和火球牌,先出一张毒药,那么可以将答案表示为 \(F(x)=\sum\limits_{i=1}^{+\infty} \left(\frac13(x+x^3)\right)^i,Ans=\sum\limits_{i=1}^n[x^i]F(x)\)

    但是这里并不能保证毒药和火球都被拿到了,所以要分别减掉 \(F'(x)=\sum\limits_{i=1}^{+\infty} \left(\frac13x\right)^i\)\(F'(x)=\sum\limits_{i=1}^{+\infty} \left(\frac13x^3\right)^i\) 的前 \(n\) 项系数和

    这里需要满足多出来一张毒药牌,所以可以将 \(n\leftarrow n+1\) 再做

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

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

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

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

Code Display
templatestruct 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>=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;
}

相关