2.25 日常(位运算)

来源:互联网 发布:桌面网络图标不见了 编辑:程序博客网 时间:2024/05/19 06:39

今天开始练习位运算,之前都是看课件,今天开始实战。

位操作(POJ - 3748)

其实这是道位运算的基本题,但是由于我之前并没有写过位运算,所以把思路想的特别复杂。又是循环又是加减,事实这些都可以运用左移和取反来操作。除此之外还有,在读入输入十六进制数时可以用%x。
第一个操作是将第x位变成0,我们就可以并一个数,这个数的第x位是0,其他全是1.即~(1<< x)。第二个操作分成两步,一步是将第y-2位变成0,这一步同第一个操作。另一步是将y和y-1位变成1,即或两个数,这两个数的第y位和第y-1位分别是1,其他数是0.即1<< y,1<< (y-1)。

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int x,y,n;int main(){    scanf("%x,%d,%d",&n,&x,&y);    n=n&(~(1<<x));    n=n&(~(1<<(y-2)));    n=n|(1<<y)|(1<<(y-1));    printf("%x",n);}

Binary Number (HDU - 3711)

题目大意:定义一种运算f,f(a,b)表示a和b的二进制数有几位是不同的。同时给定A,B两个集合,对于每一个B集合中的元素,需在A中找到一个元素和这个元素的f最小,如有多个则输出最小的一个。
因为这里的数据不大,可以直接暴力做,对于B中的每一元素进行枚举,用异或运算来判断两个元素用多少位相等,然后去最小的就可以。
同时要注意一个细节,输入中给定的A集合是无序的,我们要进行一次排序,我就是因为这个地方错了三次。而且异或之后求1的个数是有简便方法的,不需要对每一位进行操作。至于简便方法的实现就看我的代码。

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int T,n,m;int a[105],b[105];int main(){    scanf("%d",&T);    while(T--)    {        scanf("%d%d",&n,&m);        for(int i=1;i<=n;i++)            scanf("%d",&a[i]);        sort(a+1,a+n+1);        for(int i=1;i<=m;i++)            scanf("%d",&b[i]);        for(int i=1;i<=m;i++)        {            int mine=0x3fffffff,t;            for(int j=1;j<=n;j++)            {                int cnt=0;                int now=a[j]^b[i];                while(now)//这个就是求有多少个1的简便方法                {                    now=now&(now-1);                    cnt++;                }                if(cnt<mine)                    mine=cnt,t=a[j];            }            printf("%d\n",t);        }    }}

An Easy Problem (POJ - 2453)

题目大意:给定一个数n,求一个比n大而且二进制数中的1的个数和n相等。
有上一题的基础这道题就很简单,只需每次加1,用上道题的方法判断就可以。

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int n,cnt;int main(){    while(1)    {        scanf("%d",&n);        if(n==0)            break;        cnt=0;        int t=n;        while(t)        {            t&=t-1;            cnt++;        }        for(int i=1;;i++)        {            int now=0;            t=n+i;            while(t)            {                t&=t-1;                now++;            }            if(now==cnt)            {                printf("%d\n",n+i);                break;            }        }    }}

N皇后问题 (HDU - 2553)

这道题的位运算实现真的非常巧妙,用三个变量的二进制表示当前行可放的位置,然后进行枚举,推算出下一行可以放的位置。
在我的代码中h表示当前这一行可以放的位置,l和r分别表示主对角线和副对角线,三个量进行或运算取反在与总的方案数((1<< n)-1)并,就得到了当前行可以放的位置。

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int n,ans;void dfs(int h,int l,int r){    int t=(1<<n)-1;    if(h==t)    {        ans++;        return;    }    int p=t&(~(h|l|r));    while(p)    {        int now=p&-p;        p-=now;        dfs(h+now,(l+now)>>1,(r+now)<<1);    }}int main(){    while(1)    {        scanf("%d",&n);        if(n==0)            break;        ans=0;        dfs(0,0,0);        printf("%d\n",ans);    }}

The Number of set (HDU - 3006 )

题目大意:给定n个集合,求这些集合能组成多少个不同的集合。
因为每一个元素都不超过m,而且m最大为14,所以可以直接用二进制将一个集合表示出来。然后与之前所有的情况做或运算,得到的新集合打上标记,最后再来统计一共有多少个标记就可以了。

#include<cstdio>#include<cstring>#include<algorithm>#define MAXN (1<<14)+10using namespace std;int n,m,ans;bool vis[MAXN];int main(){    while(scanf("%d%d",&n,&m)!=EOF)    {        ans=0;        memset(vis,0,sizeof vis);        for(int i=1;i<=n;i++)        {            int t;            scanf("%d",&t);            int now=0;            for(int j=1;j<=t;j++)            {                int x;                scanf("%d",&x);                now|=(1<<(x-1));            }            vis[now]=1;            for(int j=1;j<=MAXN;j++)                if(vis[j])                    vis[j|now]=1;        }        for(int i=1;i<=MAXN;i++)            if(vis[i])                ans++;        printf("%d\n",ans);    }}
0 0