QOJ7789-一道位運算找規律好題

HarlemBlog發表於2024-11-21

題面

原題連結

思路

先從特殊性質入手,考慮 \(s=0\) 的情況。

不難發現,路徑是已經確定的,那麼我們可以考慮手搓幾步。

\[0000\longrightarrow0001\longrightarrow0011\longrightarrow0010\longrightarrow0000\longrightarrow0100\longrightarrow0101\cdots \]

可以看出似乎是兩個二進位制位繫結在一起變化的,為了方便,我們不妨將其變作四進位制。

\[00 \longrightarrow01\longrightarrow03\longrightarrow02\longrightarrow00 \\ \longrightarrow10\longrightarrow11\longrightarrow13\longrightarrow12\longrightarrow10 \\ \longrightarrow30\longrightarrow31\longrightarrow33\longrightarrow32\longrightarrow30 \\ \longrightarrow20\longrightarrow21\longrightarrow23\longrightarrow22\longrightarrow20 \\ \longrightarrow00\longrightarrow100\longrightarrow101\longrightarrow\cdots\\ \]

規律已經很顯然了,每一個四進位制位都在做 \(0\rightarrow1\rightarrow3\rightarrow2\rightarrow0\)的重複運動。

接下來,我們考慮第二個特殊性質:如何得出第一次到達某個點的步數?(仍然假設從 \(0\) 開始)

不難發現,我們可以單獨計算每一個四進位制位對於結果的貢獻。

假如說當前位是第 \(k\) 位(從 \(1\) 開始編號),值為 \(a\),用 \(f(a)\) 表示 \(a\) 值出現的序號(根據重複運動的先後順序),那麼這一位的貢獻 \(x\) 為:

\[x=f(a)\cdot\sum_{i=0}^{k-1}4^i \]

整個數的步數就是每一位的貢獻之和。上述公式容易理解,不做具體解釋。

接下來,我們考慮多次到達的情況。

從本質來說,只有 \(0\) 能夠被到達多次,而其他數字都只能到達一次。所以,重複到達的次數,必然是由這個數中的 \(0\) 決定的。

每一次重複,當且僅當這一位完成了一次迴圈,也就是從 \(0\)\(0\) 的過程,並且此時其他位都沒有發生改變。我們注意到當這一位回到 \(0\) 時,所有比它低的位上都只能是 \(0\),因為當它剛好變回 \(0\),比它低的位就全為 \(0\),而它如果再走一步,就必須變化比它高的一位,這與此時其他位都沒有發生改變的要求不符。

所以,重複到達的次數,與這個數末尾連續的 \(0\) 個數有關。因為每一位 \(0\) 都可以貢獻一次重複,那麼最大經過次數就是 \(z+1\),其中 \(z\) 表示末尾連續 \(0\) 個數。注意到,\(0\) 是沒有次數上限的。

下面考慮總步數,我們可以分成第一次到達的步數以及重複的步數。第一次到達的步數就是 \(k=1\),那麼考慮重複所需要的步數。

事實上,一次重複就是一次迴圈,每一位 \(0\) 都能貢獻一次重複,並且是從低位到高位貢獻。並且我們知道每一位進行一次迴圈的步數,那麼我們就可以知道重複 \(k\) 次一共需要的步數。公式化地表示為:

\[X=\sum_{i=1}^{k-1}4^i \]

至此,我們已經解決了特殊性質 \(s=0\) 的情況。那麼我們考慮 \(s\ne 0\) 的情況。

不難發現,從 \(s\) 走到 \(t\) 等價於從 \(0\) 走到 \(s\oplus t\)。然後就沒有然後了。

\(len_4\) 表示對應數的四進位制位數,\(x_i(S)\)表示 \(S\) 在四進位制下的第 \(i\) 位(從 \(1\) 開始編號),答案即為:

\[Ans=\sum_{i=1}^{len_4(s\oplus t)}(f(x_i(s\oplus t))\cdot \sum_{j=0}^{k-1}4^j)+\sum_{i=1}^{k-1}4^i \]

實現

並沒有什麼需要特別講的細節問題。

程式碼

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N=1e6+5;
const ll mod=1e9+7;
const ll inv3=333333336;

string s,t;int k;
int xo[N],so[N>>1];
ll ans;
int tim[4]={0,1,3,2};

ll qpow(ll a,int b){
    ll res=1;
    while(b){
        if(b&1)res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}

ll sk(ll k){
    return (qpow(4,k+1)-1+mod)%mod*inv3%mod;
}

void solve(){
    ans=0;
    cin>>s>>t>>k;
    reverse(s.begin(),s.end());
    reverse(t.begin(),t.end());
    int n=max(s.size(),t.size());
    for(int i=0;i<n;i++){
        if(i>=s.size())xo[i]=t[i]-'0';
        else if(i>=t.size())xo[i]=s[i]-'0';
        else xo[i]=(s[i]-'0')^(t[i]-'0');
        so[i>>1]=0;
    }
    int m=-1,zr=-1;
    for(int i=0;i<n;i++){
        if(i&1)(so[i>>1]|=(xo[i]<<1));
        else (so[i>>1]|=xo[i]);
        if(so[i>>1]>0){
            m=(i>>1);
            if(zr==-1)zr=(i>>1);
        }
    }
    m++;
    if(m==0){
        cout<<sk(k-1)-1<<"\n";
        return;
    }
    if(k>zr+1){
        cout<<-1<<"\n";
        return;
    }
    for(int i=0;i<m;i++){
        ans=(ans+sk(i)*tim[so[i]]%mod)%mod;
    }
    ans=(ans+sk(k-1)-1+mod)%mod;
    cout<<ans<<"\n";
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;cin>>t;
    while(t--)solve();
    return 0;
}

相關文章