51Nod - 1406 dp

题意:

有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;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值