【做题】CF388D. Fox and Perfect Sets——线性基&数位dp

原文链接https://www.cnblogs.com/cly-none/p/9711279.html

题意:求有多少个非空集合(S subset N)满足,(forall a,b in S, a igotimes b in S),且(S)中的最大元素不超过(n)。对(10^9 + 7)取模。

(n leq 10^9)

显然,每个合法的集合(S)都可以由一个线性基来生成。然而,一个集合可以有多个线性基。如果我们能让每个合法集合和每个符合某条件的线性基一一对应,那么我们就能把问题转化为对某类线性基的计数。

考虑对线性基进行高斯消元。结果就是,在线性基中的每一位,要么是一个基的最高位,那么其他的基上的这一位都是0;否则所有基上这一位可以任意填。因为它们可以由任何线性基高斯消元得到,所以每一个合法集合都能有这样的一个线性基。同样,每一个线性基可以生成一个合法的集合。剩下的问题就在于,证明所有不同的高斯消元后的线性基,生成的集合是不同的。

  • 两个线性基的基数不同或基的最高位不同。那么它们生成的集合显然不同。
  • 基数和每个基的最高位都相同。考虑两个线性基中最大的不相同的基。我们取出其中的一个基,尝试在另一个线性基中表达这个数。但当我们遇到它们不相同的最高一位时,因为这一位可以是1或0,所以没有一个基的最高一位是这一位,所以那个基不能被另一个线性基表达。那么,这两个线性基生成的集合就是不同的。

接下来,我们对线性基做dp就可以了。容易得到,高斯消元后的线性基,能表达出的最大元素就是所有基的异或和。那么,我们从高到低在每一位上讨论,是否存在一个基的最高位是这一位以及这一位上所有基的异或和就好了。当然要再套一个数位dp。

时间复杂度(O(log^2 n))

#include <bits/stdc++.h>
using namespace std;
const int N = 40, MOD = (int)(1e9 + 7);
int dp[N][N][2],len,lim[N],n,ans;
int calc(int x,int sgn) {
  if (x == 0) return sgn ^ 1;
  return 1 << x >> 1;
}
int main() {
  cin >> n;
  if (n == 0) return puts("1"), 0;
  while (n) {
    lim[++len] = n&1;
    n >>= 1;
  }
  reverse(lim+1,lim+len+1);
  dp[0][0][1] = 1;
  for (int i = 0 ; i < len ; ++ i)
    for (int j = 0 ; j <= i ; ++ j)
      for (int k = 0 ; k < 2 ; ++ k) {
        (dp[i+1][j][k & (lim[i+1]^1)] += 1ll * dp[i][j][k] * calc(j,0) % MOD) %= MOD;
        if ((!k) || lim[i+1]) {
          (dp[i+1][j][k] += 1ll * dp[i][j][k] * calc(j,1) % MOD) %= MOD;
          (dp[i+1][j+1][k] += dp[i][j][k]) %= MOD;
	}
      }
  for (int i = 0 ; i <= len ; ++ i)
    (ans += (dp[len][i][0] + dp[len][i][1]) % MOD) %= MOD;
  printf("%d
",ans);
  return 0;
}


小结:关键也就在于线性基可以通过高斯消元避免重复。算是涨知识了。

原文地址:https://www.cnblogs.com/cly-none/p/9711279.html