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 << "]" << "\n";
 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 可写成:

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

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

$$
\begin{align*}
&2^0 | 2^{p-1}\quad and\quad q_0 | Sum[b1, b2……bp] \\
or\quad &2^1 | 2^{p-1}\quad and\quad q_1 | Sum[b1, b2……bp] \\
or\quad &……………………………… \\
or\quad &……………………………… \\
or\quad &2^x | 2^{p-1}\quad and\quad 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 << "]" << "\n";
 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 << "]" << "\n";
 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

 

转载于:https://www.cnblogs.com/zaq19970105/p/10861711.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值