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位,同理有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; }
以上是关于51nod 1406 与查询 dp 考虑每一位 避免重复的主要内容,如果未能解决你的问题,请参考以下文章