OIFC NOI2022 day27

高维游走 travel

做法一(DP 套 DP)

比较自然,但笔者没想到。

假设第 \(i\) 维最远走到 \(s_i\),则往返的方案数为

\[\prod_{i=1}^m\binom{t_0-\sum\limits_{j=1}^{i-1}s_j}{s_i}\prod_{i=1}^m\binom{t_i}{s_i} \]

有贡献的必要条件是上式 \(\mod2=1\)。根据 Lucas 定理,\(\binom{n}{m}\mod2=1\) 当且仅当 \(m\subseteq n\)。所以上式 \(\mod2=1\) 当且仅当 \(\forall i\neq j,s_i\cap s_j=\varnothing\)\(\forall i,s_i\subseteq t_i\cap t_0\),故考虑拆位。

考虑用数位 DP 判断一个固定疲劳度 \(x\) 的方案数奇偶性。设 \(dp_{i,j}\in\{0,1\}\) 表示从低位往高位扫,现在在第 \(i\) 位,进 \(j\) 位,方案数 \(\mod 2\) 的值,使用归纳不难证明 \(j\in[0,m)\)

考虑 DP 套 DP。(如果对这个技巧熟练的话可以直接设出,但笔者不熟练,所以绕一下)考虑抽象为 DFA,设 \((i,S)\) 表示在第 \(i\) 位,方案数为奇数的进位集合为 \(S\),终止状态为 \((\log_2V,S),0\in S\),转移就是上面的 DP。

现在可以设计外层 DP,设 \(f_{i,S}\) 表示到可以到达状态 \((i,S)\) 的疲劳度数量(只保留 \(0\sim i\) 位),转移就是沿着上述自动机转移,如果上述自动机有一个 \((i,S)\rightarrow (i',S')\) 的转移,则这个 DP 有转移 \(f_{i,S}\rightarrow f_{i',S'}\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef complex<double> comp;
typedef unsigned int uint;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int M=15,Lg=40;
int m;
ll t[M],f[Lg][1<<10];
void __INIT__(){}
void __SOLVE__(){
    scanf("%d",&m);
    for(int i=0;i<=m;i++) scanf("%lld",&t[i]);
    int lg=63^__builtin_clzll(m*t[0]);
    for(int i=0;i<=lg+1;i++) for(int s=0;s<(1<<m);s++) f[i][s]=0;
    f[0][1]=1;
    for(int i=0;i<=lg;i++){
        for(int s=0;s<(1<<m);s++){
            int s0=0,s1=0;
            for(int i=0;i<m;i++) s0|=((s>>(i<<1)&1)<<i),s1|=((s>>((i<<1)+1)&1)<<i);
            for(int k=0;k<2;k++){
                int ss=0;
                for(int j=0;j<=m;j++){
                    if(j&&(!(t[0]>>i&1)||!(t[j]>>i&1))) continue;
                    int tmp;
                    if(k==(j&1)) tmp=(s0<<(j>>1));
                    else tmp=(s1<<((j+(j&1))>>1));
                    ss^=tmp;
                }
                f[i+1][ss]+=f[i][s];
            }
        }
    }
    // for(int i=0;i<=lg+1;i++){
    //     for(int s=0;s<(1<<m);s++) printf("%lld ",f[i][s]);
    //     printf("\n");
    // }
    ll ans=0;
    for(int s=1;s<(1<<m);s+=2) ans+=f[lg+1][s];
    printf("%lld\n",ans);
}
int main(){
    #ifndef JZQ
    freopen("travel.in","r",stdin);
    freopen("travel.out","w",stdout);
    #endif
    int T=1;
    scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

做法二(多项式)

不是很自然,笔者差点想到了。

与上面类似地拆位,考虑用生成函数固定疲劳度。第 \(k\) 位的生成函数

\[F_k(z)=1+\sum_{i=1}^m[t_{0,k}=1\land t_{i,k}=1]z^i \]

对于固定的疲劳度 \(x\)

\[f(x)=[z^x]\prod_kF_k(z^{2^k}) \]

直接做显然不太好做,类似 Bostan-Mori 的思想,考虑分奇偶函数。设 \(F_0(z)=F_{0,0}(z^2)+zF_{0,1}(z^2)\),则 \(n\) 的规模减半。

笔者当时以为奇偶两部分可以合并处理,但不行。 考虑把 \(F\) 状压,设 \(dp_{i,f}\) 表示保留下标 \(\ge i\) 的多项式,再乘上 \(f\)\(popcount\)。随便 DP 一下就行了。

比赛 match

关键词:LCT。

题解

字符串 string

题解

posted @ 2026-01-14 15:45  SmpaelFx  阅读(0)  评论(0)    收藏  举报