P3773 [CTSC2017] 吉夫特 题解

Sol

你会发现如果存在 \(2 \le i \le k\),满足 \(C_{a_{b_i}}^{a_{b_{i-1}}}\) 为偶数时,它和前面的、后面的乘起来一定时偶数,所以我们要考虑所有的 \(i\),前面那一堆式子算出来都是奇数。

我们考虑 \(C_m^n\) 为奇数时的条件,我们令 \(f(i)\) 代表 \(i\) 的为 \(2\) 的因子个数,\(C^n_m\) 为奇数转化为 \(f(C^n_m)=0\)

我们都知道 \(C^n_m=\frac{n!}{m!(n-m)!}\),那么应满足 \(f(\frac{n!}{m!(n-m)!})=0\),化简一下:\(f(n!)-f(m!)-f((n-m)!)=0\),即:

\[f(m!)+f((n-m)!)=f(n!) \]

我们考虑如何计算 \(f(x!)\)

你会发现我们可以每次将 \(x\) 除以 \(2\),将目前有多少个 \(2\) 的倍数算出来。

举个例子,我们要算 \(f(9!)\)

手算我们算出 \(9!=362880\)\(f(9!)=7\)

关键来了:

9! -> 1x2x3x4x5x6x7x8x9
4! ->   1 x 2 x 3 x 4   // 有 4 个 2 的倍数
2! ->       1   x   2   // 有 2 个 2 的倍数 -> 原式有 2 个 4 的倍数
1! ->               1   // 有 1 个 2 的倍数 -> 原式有 1 个 8 的倍数
                        // 4+2+1=7,f(9!)=7

稍微解释一下,就是原式中每个数有几个为 \(2\) 的因子个数,它就会被除几次,每次都会算一次贡献。

于是我们就有了递推式 \(f(i!)=f(\left \lfloor \frac{i}{2} \right \rfloor!) + \left \lfloor \frac{i}{2} \right \rfloor\)

简化一下,把阶乘去掉就是 \(f(i)=f(\left \lfloor \frac{i}{2} \right \rfloor) + \left \lfloor \frac{i}{2} \right \rfloor\)

然后你会发现一个很神奇的事情,就是如果你设 \(g(i)=i\),则有 \(g(i)=g(\left \lfloor \frac{i}{2} \right \rfloor) + \left \lfloor \frac{i}{2} \right \rfloor + (i \bmod 2)\)

你会发现 \(g\) 的递推式仅仅比 \(f\) 的递推式后面加了一个 \(i \bmod 2\)

所以如果我们设 \(t(i)\) 表示 \(i\) 的二进制下为 \(1\) 的位数,则有 \(f(i)=i-t(i)\)

于是我们最开始的 \(f(m!)+f((n-m)!)=f(n!)\),就可以转化成 \(m-t(m)+(n-m)-t(n-m)=n-t(n)\)

移项可得 \(t(n)=t(m)+t(n-m)\),至此我们已经不用考虑阶乘的东西,简便多了。

继续找性质!如果存在一个位置,\(n\) 在二进制下为 \(0\)\(m\) 在二进制下为 \(1\),是不可能满足条件的。

有下面 \(2\) 个例子:

第一个:

    n ...10
-   m ...01
-------------
  n-m ...01

第二个:

    n ...100
-   m ...001
--------------
  n-m ...011

你会发现无论如何 \(t(m) + t(n-m)\) 都会大于 \(t(n)\),所以不行。

综上要使 \(t(n)=t(m)+t(n-m)\) 成立,必须有 \(m\) 在二进制下为 \(1\) 的位置的集合是 \(n\) 的子集,即 \((n\&m)=m\)

接下来就是一个简单的 DP 了。

我们不妨先删掉子串的长度 \(\ge 2\) 这个条件,最后给答案减 \(n\)

由于 \(a\) 互不相同,我们考虑记录 \(pos_x\) 代表 \(x\) 的位置。

我们设 \(dp_i\) 表示以 \(i\) 开头的方案数。

因为要保证所以的项都是奇数,所以我们下一个 \(j\) 一定满足 \((i\&j)=j\),所以我们枚举 \(i\) 的子集,判断所处位置是否在 \(i\) 之后即可转移。

初始时 \(dp_i=1\)

最终答案就是 \((\sum dp_i)-n\)

时间复杂度 \(O(3^{ \log V})\)\(V\) 是值域。

Code

太短啦!

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

inline int read()
{
    int x(0);
    char ch(getchar());
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x;
}

const int N=3e5+5,mod=1e9+7;
int n,a[N],dp[N],pos[N],ans;

signed main()
{
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),pos[a[i]]=i;
    for(int i=n;i>=1;i--)
    {
        dp[i]=1;
        for(int j=a[i];j!=0;j=a[i]&(j-1))
            if(pos[j]>i)
                dp[i]=(dp[i]+dp[pos[j]])%mod;
        ans=(ans+dp[i])%mod;
    }
    cout<<(ans-n+mod)%mod;
    return 0;
}

Summarize

这是一道思维难度极高,极好的题,值得细细品味。

posted @ 2025-06-03 20:43  tmp_get_zip_diff  阅读(12)  评论(0)    收藏  举报