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(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
这是一道思维难度极高,极好的题,值得细细品味。

浙公网安备 33010602011771号