51nod 1406 与查询 dp 考虑每一位 避免重复

Posted tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了51nod 1406 与查询 dp 考虑每一位 避免重复相关的知识,希望对你有一定的参考价值。

题目链接:

http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1406

题意:

有n个整数。输出他之中和x相与之后结果为x的有多少个。x从0到1,000,000

思路:

http://blog.csdn.net/bahuia/article/details/55653495

这题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)
 

还有一种方法。先将每个a和x分为前k位和后20-k位两部分,A0表示某个a的前k位,A1表示后20-k位,同理有X0X1 
设状态: 

dp[x][k]?A0&X0A1=X1a

转移方程: 
dp[x][k]= dp[x][k?1]+dp[x+2^k][k?1]xk0
    dp[x][k?1]xk1

显然dp[x][20]=f(x)
 
 
真难!!! 还是不理解

代码:

代码一:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MS(a) memset(a,0,sizeof(a))
#define MP make_pair
#define PB push_back
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fLL;
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getchar();}
    return x*f;
}
void write(int x)
{
    int a1=0,a[20];
    a[1]=0;
    if (!x) a1++;
    while (x)
    {
        a[++a1]=x%10;x/=10;
    }
    for (int i=a1;i>=1;i--)
        putchar(a[i]+0);
}
//////////////////////////////////////////////////////////////////////////
const int maxn = 1e6+1;

int n,dp[maxn];

int main(){
    n = read();
    int mx = 0;
    for(int i=1; i<=n; i++){
        int x = read();
        dp[x]++;
        mx = max(mx,x);
    }
    int bit = 0;
    while(mx){
        mx >>= 1;
        bit++;
    }

    for(int j=0; j<bit; j++){
        for(int i=1; i<maxn; i++)
            if(i & (1<<j))
                dp[i-(1<<j)] += dp[i];
    }
    dp[0] = n;
    for(int i=0; i<maxn; i++){
        write(dp[i]);
        puts("");
    }


    return 0;
}

代码二:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MS(a) memset(a,0,sizeof(a))
#define MP make_pair
#define PB push_back
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fLL;
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getchar();}
    return x*f;
}
void write(int x)
{
    int a1=0,a[20];
    a[1]=0;
    if (!x) a1++;
    while (x)
    {
        a[++a1]=x%10;x/=10;
    }
    for (int i=a1;i>=1;i--)
        putchar(a[i]+0);
}
//////////////////////////////////////////////////////////////////////////
const int maxn = 1e6+1;

int n,dp[(1<<21)];

int main(){
    n = read();
    for(int i=1; i<=n; i++){
        int x = read();
        dp[x]++;
    }
    
    // for(int j=1; j<21; j++){
    //     for(int i=0; i<maxn; i++){
    //         if(i & (1<<(j-1))){
    //             dp[i] = dp[i][j-1];
    //         }else{
    //             dp[i][j] = dp[i][j-1]+dp[i+(1<<(j-1))][j-1];
    //         }
    //     }
    // }
    // 滚动优化,否则RE    
    for(int j=1; j<21; j++){
        for(int i=0; i<maxn; i++){
            if(i & (1<<(j-1))){
                dp[i] = dp[i];
            }else{
                dp[i] = dp[i]+dp[i+(1<<(j-1))];
            }
        }
    }
    
    for(int i=0; i<maxn; i++){
        write(dp[i]);
        puts("");
    }


    return 0;
}

 

以上是关于51nod 1406 与查询 dp 考虑每一位 避免重复的主要内容,如果未能解决你的问题,请参考以下文章

51nod 1406:与查询

51nod 1406 位运算/dp

[51Nod 1301] 集合异或和 (dp)

51Nod 1042 数字0-9的数量(数位DP)

51nod 1301 集合异或和——异或dp

51nod 1577 异或凑数 线性基的妙用