zoukankan      html  css  js  c++  java
  • CometOJ Contest #3 C

    题目链接:https://cometoj.com/contest/38/problem/C?problem_id=1542&myself=0&result=0&page=1&contestID=38&problemID=C

    题目大意

      略

    准备工作

      已知原序列为 a1, a2……an

      设 b1, b2……bp 为原序列的一个子序列。

      定义 Ans(b1, b2……bp) 为对应序列的完美子序列种数。

      定义 Sum(b1, b2……bp) 为对应序列的所有非空子序列的整数之和。

      定义 Sum[b1, b2……bp] = b1 + b2……+ bp

      找一下规律:Sum(b1, b2……bp) = 2p-1 * Sum[b1, b2……bp]。

    分析1

      首先说一下,分析1是为分析2服务的,分析1给出的代码很暴力,不仅超时,还超内存,但是最容易理解。

      一开始我的DP思路是搞一个二维数组,即 dp[i][j] 表示前 i 个元素中,满足序列长度为 j 的完美序列个数。如果我这样定义,那么答案就为$sum_{i = 1}^n dp[n][i]$。然而转移方程根本不晓得怎么推,还是得把模 m 余数为 0 的子序列都找出来,并且你在算完 dp[i][j] 之后算 dp[i + 1][j + 1] 的时候,之前某些模 m 余数不为 0 的子序列在加入 a[i + 1] 之后就有可能为 0,因此子序列模 m 的余数也应该作为一个维度。

      重新定义一下dp数组:dp[i][j][k],表示前 i 个元素中,满足序列长度为 j ,模 m 余数为 k 的子序列个数。这样的话答案就为$sum_{j = 1}^n dp[n][j][0]$。

      在这个定义下,如果我们已经算出来了 dp[i][j][k],那么对于 a[i + 1],有拿和不拿两种选择,对应递推公式如下:

    1. 拿:dp[i + 1][j + 1][(2 * k + a[i+1] * 2j) % m] += dp[i][j][k]。
    2. 不拿:dp[i + 1][j][k] = dp[i][j][k]。

      初始 dp[0][0][0] = 1,因为 m | 0 。

      为了减少空间占用,可以使用滚动数组。

    代码如下

      1 #include <bits/stdc++.h>
      2 using namespace std;
      3  
      4 #define INIT() std::ios::sync_with_stdio(false);std::cin.tie(0);
      5 #define Rep(i,n) for (int i = 0; i < (n); ++i)
      6 #define For(i,s,t) for (int i = (s); i <= (t); ++i)
      7 #define rFor(i,t,s) for (int i = (t); i >= (s); --i)
      8 #define ForLL(i, s, t) for (LL i = LL(s); i <= LL(t); ++i)
      9 #define rForLL(i, t, s) for (LL i = LL(t); i >= LL(s); --i)
     10 #define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)
     11 #define rforeach(i,c) for (__typeof(c.rbegin()) i = c.rbegin(); i != c.rend(); ++i)
     12  
     13 #define pr(x) cout << #x << " = " << x << "  "
     14 #define prln(x) cout << #x << " = " << x << endl
     15  
     16 #define LOWBIT(x) ((x)&(-x))
     17  
     18 #define ALL(x) x.begin(),x.end()
     19 #define INS(x) inserter(x,x.begin())
     20  
     21 #define ms0(a) memset(a,0,sizeof(a))
     22 #define msI(a) memset(a,inf,sizeof(a))
     23 #define msM(a) memset(a,-1,sizeof(a))
     24 #define mcy(d,s) memcpy(d, s, sizeof(s))
     25 
     26 #define MP make_pair
     27 #define PB push_back
     28 #define ft first
     29 #define sd second
     30  
     31 template<typename T1, typename T2>
     32 istream &operator>>(istream &in, pair<T1, T2> &p) {
     33     in >> p.first >> p.second;
     34     return in;
     35 }
     36  
     37 template<typename T>
     38 istream &operator>>(istream &in, vector<T> &v) {
     39     for (auto &x: v)
     40         in >> x;
     41     return in;
     42 }
     43  
     44 template<typename T1, typename T2>
     45 ostream &operator<<(ostream &out, const std::pair<T1, T2> &p) {
     46     out << "[" << p.first << ", " << p.second << "]" << "
    ";
     47     return out;
     48 }
     49 
     50 inline int gc(){
     51     static const int BUF = 1e7;
     52     static char buf[BUF], *bg = buf + BUF, *ed = bg;
     53     
     54     if(bg == ed) fread(bg = buf, 1, BUF, stdin);
     55     return *bg++;
     56 } 
     57 
     58 inline int ri(){
     59     int x = 0, f = 1, c = gc();
     60     for(; c<48||c>57; f = c=='-'?-1:f, c=gc());
     61     for(; c>47&&c<58; x = x*10 + c - 48, c=gc());
     62     return x*f;
     63 }
     64  
     65 typedef long long LL;
     66 typedef unsigned long long uLL;
     67 typedef pair< double, double > PDD;
     68 typedef pair< int, int > PII;
     69 typedef pair< string, int > PSI;
     70 typedef set< int > SI;
     71 typedef vector< int > VI;
     72 typedef map< int, int > MII;
     73 typedef pair< LL, LL > PLL;
     74 typedef vector< LL > VL;
     75 typedef vector< VL > VVL;
     76 const double EPS = 1e-10;
     77 const LL inf = 0x7fffffff;
     78 const LL infLL = 0x7fffffffffffffffLL;
     79 const LL mod = 1e9 + 7;
     80 const int maxN = 5e3 + 7;
     81 const LL ONE = 1;
     82 const LL evenBits = 0xaaaaaaaaaaaaaaaa;
     83 const LL oddBits = 0x5555555555555555;
     84 
     85 // Calculate x^y % p
     86 inline LL pow_mod(LL x, LL y, LL p = mod){
     87     LL ret = 1;
     88     while(y){
     89         if(y & 1) ret = (ret * x) % p;
     90         x = (x * x) % p;
     91         y >>= 1;
     92     }
     93     return ret;
     94 } 
     95 
     96 int n, m, a[maxN]; 
     97 LL ans;
     98 
     99 // dp[i][j][k] 表示在前i个元素组成的序列中,选择了j个数,结果为k(mod m)的序列种数 
    100 LL dp[2][maxN][maxN];
    101 
    102 int main(){
    103     INIT();
    104     cin >> n >> m;
    105     For(i, 1, n) cin >> a[i];
    106     
    107     int now; //< 滚动数组标识 
    108     dp[0][0][0] = 1; //< 算上空序列,方便计算 
    109     For(i, 1, n) {
    110         now = i % 2;
    111         mcy(dp[now], dp[!now]); 
    112         For(j, 0, n) { // 枚举子数列长度 
    113             Rep(k, m) { // 枚举余数 
    114                 // 选a[i] 
    115                 int tmp = (2 * k + a[i] * pow_mod(2, j, m)) % m; //< 产生新的余数 
    116                 dp[now][j + 1][tmp] += dp[!now][j][k];
    117                 // 不选a[i]不必讨论,因为 mcy 函数复制的同时搞定了这个分支 
    118                 // dp[now][j][k] = dp[!now][j][k];
    119             }
    120         }
    121     }
    122     
    123     For(j, 1, n) ans = (ans + dp[now][j][0]) % mod;
    124     cout << ans << endl;
    125     return 0;
    126 }
    View Code

    分析2

      分析2提供了两点优化,但程序仍然通过不了(爆内存),分析3将讨论如何降低空间复杂度。

      设 x 为 m 中质因子 2 的个数。

      由于 m <= 5000,所以 0 <= x <= 12。

      则 m 可写成:

    $$
    egin{align*}
    m &= 2^0 * q_0 \
    &= 2^1 * q_1 \
    &……… \
    &……… \
    &= 2^x * q_x
    end{align*}
    $$

      如果序列 (b1, b2……bp) 是完美的,一定有 m | Sum(b1, b2……bp),也就是说一定有:

    $$
    egin{align*}
    &2^0 | 2^{p-1}quad andquad q_0 | Sum[b1, b2……bp] \
    orquad &2^1 | 2^{p-1}quad andquad q_1 | Sum[b1, b2……bp] \
    orquad &……………………………… \
    orquad &……………………………… \
    orquad &2^x | 2^{p-1}quad andquad q_x | Sum[b1, b2……bp]
    end{align*}
    $$

      上面至少有一个式子是成立的。

      可以发现,当 p > x 时,要证明序列 (b1, b2……bp) 是完美的,只要$q_x | Sum[b1, b2……bp]$成立即可。

      又,在 0 <= p <= x 时,要证明序列 (b1, b2……bp) 是完美的,只要$q_p | Sum[b1, b2……bp]$成立即可。

      因此,当 p > x 时,只要讨论 $m = 2^x * q_x$的拆分情况即可;当 0 <= p <= x 时,只要讨论 $m = 2^p * q_p$ 的拆分情况即可。

      也就是说,对于每一个子序列,都有唯一一个判定式$q_j | Sum[b1, b2……bp]$(0 <= j <= x) 能判断这个序列是否是完美的。

      于是 dp 数组的第三维就不用算出整个和然后模 m 了,而只要算出 Sum[b1, b2……bp] 然后去模对应的 qj 即可。

      以上是第一个优化。

      第二个优化是关于枚举余数部分,其实并没有必要从 0 枚举到 m - 1,只需要枚举到 min(m, qj * 2) 即可,由于第一个优化的关系,随着 j 的增加, qj 两倍两倍地减小,上一级产生的余数刚好为这一级的两倍,所以在不超过 m 的情况下,只需枚举到 qj * 2。

    代码如下

      1 #include <bits/stdc++.h>
      2 using namespace std;
      3  
      4 #define INIT() std::ios::sync_with_stdio(false);std::cin.tie(0);
      5 #define Rep(i,n) for (int i = 0; i < (n); ++i)
      6 #define For(i,s,t) for (int i = (s); i <= (t); ++i)
      7 #define rFor(i,t,s) for (int i = (t); i >= (s); --i)
      8 #define ForLL(i, s, t) for (LL i = LL(s); i <= LL(t); ++i)
      9 #define rForLL(i, t, s) for (LL i = LL(t); i >= LL(s); --i)
     10 #define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)
     11 #define rforeach(i,c) for (__typeof(c.rbegin()) i = c.rbegin(); i != c.rend(); ++i)
     12  
     13 #define pr(x) cout << #x << " = " << x << "  "
     14 #define prln(x) cout << #x << " = " << x << endl
     15  
     16 #define LOWBIT(x) ((x)&(-x))
     17  
     18 #define ALL(x) x.begin(),x.end()
     19 #define INS(x) inserter(x,x.begin())
     20  
     21 #define ms0(a) memset(a,0,sizeof(a))
     22 #define msI(a) memset(a,inf,sizeof(a))
     23 #define msM(a) memset(a,-1,sizeof(a))
     24 #define mcy(d,s) memcpy(d, s, sizeof(s))
     25 
     26 #define MP make_pair
     27 #define PB push_back
     28 #define ft first
     29 #define sd second
     30  
     31 template<typename T1, typename T2>
     32 istream &operator>>(istream &in, pair<T1, T2> &p) {
     33     in >> p.first >> p.second;
     34     return in;
     35 }
     36  
     37 template<typename T>
     38 istream &operator>>(istream &in, vector<T> &v) {
     39     for (auto &x: v)
     40         in >> x;
     41     return in;
     42 }
     43  
     44 template<typename T1, typename T2>
     45 ostream &operator<<(ostream &out, const std::pair<T1, T2> &p) {
     46     out << "[" << p.first << ", " << p.second << "]" << "
    ";
     47     return out;
     48 }
     49 
     50 inline int gc(){
     51     static const int BUF = 1e7;
     52     static char buf[BUF], *bg = buf + BUF, *ed = bg;
     53     
     54     if(bg == ed) fread(bg = buf, 1, BUF, stdin);
     55     return *bg++;
     56 } 
     57 
     58 inline int ri(){
     59     int x = 0, f = 1, c = gc();
     60     for(; c<48||c>57; f = c=='-'?-1:f, c=gc());
     61     for(; c>47&&c<58; x = x*10 + c - 48, c=gc());
     62     return x*f;
     63 }
     64  
     65 typedef long long LL;
     66 typedef unsigned long long uLL;
     67 typedef pair< double, double > PDD;
     68 typedef pair< int, int > PII;
     69 typedef pair< string, int > PSI;
     70 typedef set< int > SI;
     71 typedef vector< int > VI;
     72 typedef map< int, int > MII;
     73 typedef pair< LL, LL > PLL;
     74 typedef vector< LL > VL;
     75 typedef vector< VL > VVL;
     76 const double EPS = 1e-10;
     77 const LL inf = 0x7fffffff;
     78 const LL infLL = 0x7fffffffffffffffLL;
     79 const LL mod = 1e9 + 7;
     80 const int maxN = 5e3 + 7;
     81 const LL ONE = 1;
     82 const LL evenBits = 0xaaaaaaaaaaaaaaaa;
     83 const LL oddBits = 0x5555555555555555;
     84 
     85 // 计算x的二进制位数 
     86 inline int getBits(LL x) {
     87     int cnt = 1;
     88     while(x >>= 1) ++cnt;
     89     return cnt;
     90 } 
     91 
     92 int n, m, a[maxN]; 
     93 LL ans;
     94 
     95 // dp[i][j][k] 表示在前i个元素组成的序列中,选择了j个数,结果为k(mod m)的序列种数 
     96 LL dp[2][maxN][maxN];
     97 // pow2[i] = 2^i 
     98 int pow2[] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192};
     99 
    100 int main(){
    101     INIT();
    102     cin >> n >> m;
    103     For(i, 1, n) cin >> a[i];
    104     
    105     int x = getBits(LOWBIT(m)) - 1; //< x = m中质因子2的个数 
    106     int now; //< 滚动数组标识 
    107     
    108     dp[0][0][0] = 1; //< 算上空序列,方便计算 
    109     For(i, 1, n) {
    110         now = i % 2;
    111         mcy(dp[now], dp[!now]); 
    112         For(j, 0, n) { // 枚举子数列长度 
    113             int q = m / pow2[x];
    114             if(j < x) q = m / pow2[j];
    115             Rep(k, min(m, q << 1)) { // 枚举余数 
    116                 // 选a[i] 
    117                 int tmp = (k + a[i]) % q;
    118                 if(j < x) dp[now][j + 1][tmp] += dp[!now][j][k];
    119                 else dp[now][j + 1][tmp] += dp[!now][j][k];
    120                 // 不选a[i]不必讨论,因为 mcy 函数复制的同时搞定了这个分支 
    121                 // dp[now][j][k] = dp[!now][j][k];
    122             }
    123         }
    124     }
    125     
    126     For(j, 1, n) ans = (ans + dp[now][j][0]) % mod;
    127     cout << ans << endl;
    128     return 0;
    129 }
    View Code

     

    分析3

       分析2中,当 p >= x 时,序列的完美只与$q_x | Sum[b1, b2……bp]$有关,即不同 dp[i][j][k] (x <= j <= n)的 k 都是序列对 qx 取模得到的,余数相同是可以合并的,并不需要专门有个 j 来标识不同,因此可以用 dp[i][x][k] 来代表 $sum_{j = x}^n dp[i][j][k]$。于是,dp数组就有如下分段定义:

    1. 当 0 <= j < x,时,dp[i][j][k] 表示前 i 个元素中,满足序列长度为 j ,模 qj 余数为 k 的子序列个数。
    2. 当 j == x,时,dp[i][j][k] 表示前 i 个元素中,满足所有序列长度大于等于 j ,模 qx 余数为 k 的子序列个数。

      这里只叙述一下 j == x 时的状态转移方程:

    1. 拿:dp[i + 1][j][(k + a[i+1]) % qx] += dp[i][j][k]。
    2. 不拿:dp[i + 1][j][k] = dp[i][j][k]。

      PS:x 是能等于 0 的,所以在计算答案时要变化一下。

    代码如下

      1 #include <bits/stdc++.h>
      2 using namespace std;
      3  
      4 #define INIT() std::ios::sync_with_stdio(false);std::cin.tie(0);
      5 #define Rep(i,n) for (int i = 0; i < (n); ++i)
      6 #define For(i,s,t) for (int i = (s); i <= (t); ++i)
      7 #define rFor(i,t,s) for (int i = (t); i >= (s); --i)
      8 #define ForLL(i, s, t) for (LL i = LL(s); i <= LL(t); ++i)
      9 #define rForLL(i, t, s) for (LL i = LL(t); i >= LL(s); --i)
     10 #define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)
     11 #define rforeach(i,c) for (__typeof(c.rbegin()) i = c.rbegin(); i != c.rend(); ++i)
     12  
     13 #define pr(x) cout << #x << " = " << x << "  "
     14 #define prln(x) cout << #x << " = " << x << endl
     15  
     16 #define LOWBIT(x) ((x)&(-x))
     17  
     18 #define ALL(x) x.begin(),x.end()
     19 #define INS(x) inserter(x,x.begin())
     20  
     21 #define ms0(a) memset(a,0,sizeof(a))
     22 #define msI(a) memset(a,inf,sizeof(a))
     23 #define msM(a) memset(a,-1,sizeof(a))
     24 #define mcy(d,s) memcpy(d, s, sizeof(s))
     25 
     26 #define MP make_pair
     27 #define PB push_back
     28 #define ft first
     29 #define sd second
     30  
     31 template<typename T1, typename T2>
     32 istream &operator>>(istream &in, pair<T1, T2> &p) {
     33     in >> p.first >> p.second;
     34     return in;
     35 }
     36  
     37 template<typename T>
     38 istream &operator>>(istream &in, vector<T> &v) {
     39     for (auto &x: v)
     40         in >> x;
     41     return in;
     42 }
     43  
     44 template<typename T1, typename T2>
     45 ostream &operator<<(ostream &out, const std::pair<T1, T2> &p) {
     46     out << "[" << p.first << ", " << p.second << "]" << "
    ";
     47     return out;
     48 }
     49 
     50 inline int gc(){
     51     static const int BUF = 1e7;
     52     static char buf[BUF], *bg = buf + BUF, *ed = bg;
     53     
     54     if(bg == ed) fread(bg = buf, 1, BUF, stdin);
     55     return *bg++;
     56 } 
     57 
     58 inline int ri(){
     59     int x = 0, f = 1, c = gc();
     60     for(; c<48||c>57; f = c=='-'?-1:f, c=gc());
     61     for(; c>47&&c<58; x = x*10 + c - 48, c=gc());
     62     return x*f;
     63 }
     64  
     65 typedef long long LL;
     66 typedef unsigned long long uLL;
     67 typedef pair< double, double > PDD;
     68 typedef pair< int, int > PII;
     69 typedef pair< string, int > PSI;
     70 typedef set< int > SI;
     71 typedef vector< int > VI;
     72 typedef map< int, int > MII;
     73 typedef pair< LL, LL > PLL;
     74 typedef vector< LL > VL;
     75 typedef vector< VL > VVL;
     76 const double EPS = 1e-10;
     77 const LL inf = 0x7fffffff;
     78 const LL infLL = 0x7fffffffffffffffLL;
     79 const LL mod = 1e9 + 7;
     80 const int maxN = 5e3 + 7;
     81 const LL ONE = 1;
     82 const LL evenBits = 0xaaaaaaaaaaaaaaaa;
     83 const LL oddBits = 0x5555555555555555;
     84 
     85 // 计算x的二进制位数 
     86 inline int getBits(LL x) {
     87     int cnt = 1;
     88     while(x >>= 1) ++cnt;
     89     return cnt;
     90 } 
     91 
     92 int n, m, a[maxN]; 
     93 LL ans;
     94 LL dp[2][13][maxN];
     95 // pow2[i] = 2^i 
     96 int pow2[] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192};
     97 
     98 int main(){
     99     INIT();
    100     cin >> n >> m;
    101     For(i, 1, n) cin >> a[i];
    102     
    103     int x = getBits(LOWBIT(m)) - 1; //< x = m中质因子2的个数 
    104     int now; //< 滚动数组标识 
    105     
    106     dp[0][0][0] = 1; //< 算上空序列,方便计算 
    107     For(i, 1, n) {
    108         now = i % 2; 
    109         mcy(dp[now], dp[!now]);
    110         For(j, 0, min(x, i)) {
    111             int q = m / pow2[j];
    112             Rep(k, min(m, q << 1)) { //< k表示可能用到的余数 
    113                 if(dp[!now][j][k]) {
    114                     int tmp = (k + a[i]) % q; //< 产生新余数 
    115                     // j < x 代表取了 j 个的情况
    116                     // j == x 代表取了 j 个以上的情况 
    117                     if(j < x) dp[now][j + 1][tmp] += dp[!now][j][k] % mod;
    118                     else dp[now][j][tmp] += dp[!now][j][k] % mod;
    119                 }
    120             }
    121         }
    122     }
    123     // 这里做了一下改变,因为x可能等于0,如果从1开始加的话会漏掉很多情况 
    124     For(j, 0, x) ans = (ans + dp[now][j][0]) % mod;
    125     cout << ans - 1 << endl; //< 最后要减去空序列的一种 
    126     return 0;
    127 }
    128 
    129 /*
    130 15 11
    131 1 2 5 7 8 4 3 7 8 3 3 9 9 7 5
    132 2979
    133 
    134 16 8
    135 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    136 64839
    137 
    138 15 6
    139 1 2 5 7 8 4 3 7 8 3 3 9 9 7 5
    140 6547
    141 
    142 15 6
    143 1 2 5 7 8 4 3 7 8 3 3 9 9 7 5
    144 10938
    145 */
    View Code
  • 相关阅读:
    angular 按下回车键触发事件
    vue 父组件与子组件的通信
    最近在开发一个文章聚合的工具
    Martinjingyu的开发环境
    个推push数据统计(爬虫)
    基于redis的订单号生成方案
    电商平台--Mysql主从搭建(2)
    Mysql主从搭建(1)
    mysql物理级别热备脚本
    外键查询及删除
  • 原文地址:https://www.cnblogs.com/zaq19970105/p/10861711.html
Copyright © 2011-2022 走看看