题意:
有n个整数。输出他之中和x相与之后结果为x的有多少个。x从0到1,000,000
Input
第一行输入一个整数n。(1<=n<=1,000,000). 第二行有n个整数a[0],a[1],a[2],...a[n-1],以空格分开.(0<=a[i]<=1,000,000)
Output
对于每一组数据,输出1000001行,第i行对应和i相与结果是i的有多少个数字。
Input示例
3 2 3 3
Output示例
3 2 3 2 0 0 …… 后面还有很多0
思路:
这道题不会写,还是对于dp的理解不够深。
这题dp的思路很巧妙,分析一下题目,很容易想到,若已经求出一个数x的合法结果数,那么对于x位数上的子集来说,x的结果对他们也同样适用,例如,能和x=10110进行与运算能得到x的数,和x的子集y=10100进行与运算也一定能得到y。这样就能有一个大概的思路,设dp[x]为n个数中和x进行与运算能得到x的数的个数,dp[x] = sum(dp[z]),其中x是z的子集,但是这样的思路并不完全正确,因为计算sum(dp[z])会包含大量的重复。
举个例子,x=10100,那么z就有11100,10110,11110等等,注意到在计算dp[11100]的时候就已经算过了dp[11110],这里算x的时候又同时算了dp[11100]和dp[11110],就出现了重复。所以这里不妨每次只用比x多一位为1的数当作z,如dp[10100] = dp[11100] + dp[10110] + dp[10101]。
但是这样的结果还是存在重复,上面dp[11100]和dp[10110]在计算的时候都会算上dp[11110],dp[11110]就重复算了两次。
关键就在这一步,回忆类似背包的思想,按位来考虑,设dp[j][i]表示当前处理到第j位的时候,和i进行与运算能得到i的个数,那么dp[j][i] = sum(dp[j-1][k]),其中i是k的子集。这样处理就不会产生上述的重复。
当然j这一维在处理的时候可以省略,那么方程就是dp[i] = sum(dp[k]),复杂度O(nlogn)
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 1e6 + 10;
void read(int &res) {
res = 0;
char ch;
if (ch >= '0' && ch <= '9') {
res = ch - '0';
}
while ((ch = getchar()) >= '0' && ch <= '9') {
res = res * 10 + (ch - '0');
}
}
void write(int a) {
if (a < 0) {
putchar('-');
a = -a;
}
if (a >= 10) {
write(a / 10);
}
putchar(a % 10 + '0');
}
int dp[MAXN];
int main() {
int n, Max = 0;
read(n);
for (int i = 1; i <= n; i++) {
int x;
read(x);
dp[x]++;
Max = max(Max, x);
}
int Max_bit = 0, tmp = Max;
while (tmp) {
Max_bit++;
tmp >>= 1;
}
for (int j = 0; j < Max_bit; j++) {
for (int i = 1; i <= Max; i++) {
if (i & (1 << j)) {
dp[i - (1 << j)] += dp[i];
}
}
}
dp[0] = n;
for (int i = 0; i <= 1000000; i++) {
write(dp[i]);
puts("");
}
return 0;
}