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位,同理有X0与X1 
设状态:

dp[x][k]?满足A0&X0且A1=X1的a的个数

转移方程:

dp[x][k]= dp[x][k?1]+dp[x+2^k][k?1](x的第k位为0)

    dp[x][k?1](x的第k位为1)

显然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;
}
时间: 2024-07-30 07:56:36

51nod 1406 与查询 dp 考虑每一位 避免重复的相关文章

51nod 1406 与查询

垃圾选手练dp 考虑对于一个数,能够把它表示出来也一定可以把它某些1的位变成0变成的数表示出来 那么用大的数更新小的,容易想到每次都把这个大的数的1个1的位变成0 但是这样还是会有重复的情况 比如10010被10110和11010更新,但是这两个数都会被11110更新到 那么DP再加一维,f[i][zt]表示zt这个数当前只受到前i位是1的数的更新,对于前i-1位的更新可以直接加上,第i位的更新在当前的for循环里面处理 这样就不会重复了,同时可以发现这个是一个类似背包的东西,所以i的那维也可以

51nod 1406 位运算/dp

http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1406 1406 与查询 题目来源: CodeForces 基准时间限制:2 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 收藏 关注 有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

51nod 1406:与查询

51nod 1406:与查询 题目链接:http://www.51nod.com/onlineJudge/submitDetail.html#!judgeId=222358 题目大意:给出$n$个数,问这$n$个数与$x$做位与($\&$)后值为$x$的有多少个. DP 显然暴力是不行的. 由题目可得,若$a \& x=x$,则$x$的二进制表示中为$1$的位,$a$也必为$1$. 故若$x$和$y$仅有一位不同,且$x\&y=x$,则 与$x$做位与后值为$x$的数中 必包含 与

sql语句查询同一表内多字段同时重复的记录 sql数据库重复记录删除

分享下用sql语句删除数据库中重复记录的方法.比如现在有一人员表 (表名:peosons) 若想将姓名.身份证号.住址这三个字段完全相同的记录查询出来select p1.* from persons p1,persons p2 where p1.id<>p2.id and p1.cardid = p2.cardid and p1.pname = p2.pname and p1.address = p2.address可以实现上述效果.几个删除重复记录的SQL语句 1.用rowid方法2.用gr

1.sql 查询和删除多条字段的重复语句

查询 select a.* from Base_UserDeptRole a inner join( select DeptRoleId,UserId from Base_UserDeptRole(表) group by DeptRoleId,UserId having count(*)>1) tem on tem.UserId=a.UserId and tem.DeptRoleId=a.DeptRoleId 删除 select distinct DeptRoleId,UserId into #

状压dp中常用的位运算式子

原址:状压dp入门 为了更好的理解状压dp,首先介绍位运算相关的知识. 1.’&’符号,x&y,会将两个十进制数在二进制下进行与运算,然后返回其十进制下的值.例如3(11)&2(10)=2(10). 2.’|’符号,x|y,会将两个十进制数在二进制下进行或运算,然后返回其十进制下的值.例如3(11)|2(10)=3(11). 3.’^’符号,x^y,会将两个十进制数在二进制下进行异或运算,然后返回其十进制下的值.例如3(11)^2(10)=1(01). 4.’<<’符号

51nod 1201 整数划分(dp)

题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1201 题解:显然是一道dp,不妨设dp[i][j]表示数字i分成j个一共有几种分法. 那么转移方程式为: dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1] 表示将i - 1划分为j个数,然后j个数都+1 还是不重复,将i - 1划分为j - 1个数,然后j - 1个数都+1,再加上1这个数. 然后就是j的范围要知道1+2+

51NOD 1202 子序列个数 DP

http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1202&judgeId=225600 这题看起来挺复杂,但是真正的dp还是挺好理解的.唯独是想不到的,应该把样例模拟一遍. 比如1.2.4.2 考虑第一个,只有"1"这一个子序列 考虑前两个,有:"1", "12", "2" 前三个,有:"1", "12"

51nod 1021 石子归并(dp)

51nod 1021 石子归并 题解:从i到j合并的最小值:dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]); 最后dp[1][n]即为所求结果. 1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #define CLR(a,b) memset((a),(b),sizeof((a))) 5 using name