状态压缩DP——二进制运用

题目
SJTU 1329 聚餐
洛谷 P2622 关灯问题II
洛谷 P1171 售货员的难题(待解决)
Storm in Lover //2018.7.22测试题

聚餐

题目描述

Description

为了庆祝机考,ACM班的m个同学决定去聚餐。

到了餐厅以后,他们发现一共有n个可供选择的菜(编号为1,2,⋯, n),所以每个同学都向负责点菜的班长大人提出了一些要求。比如,一个同学表示,他一定要吃辣;另一个同学表示,他不能看到维生素C。当然,要满足所有人的所有要求是很难做到的,因此只要有至少一个要求被满足,这个同学就会开心的去吃饭。

现在问题来了:班长是否能选择一些菜,使得所有人都能开心?

Input Format

第一行是一个正整数t,表示数据组数。每一组数据之间有一行空行。

对于一组数据,第一行有两个正整数n, m,用空格隔开。接下来有m行,每行有不超过n个整数,中间用空格隔开。

对于第i行的某个整数,如果是正整数k,表示同学i一定要吃编号为k的菜;如果是负整数-k,表示同学i不能忍受餐桌上有编号为k的菜。

Output Format

如果能够使所有人都开心,则输出”Bingo!”,否则输出”Sigh…”。

每组数据的输出结果占一行。
Sample Input

2

3 5
1 -2 3
-3
-1 3
1 3
-2 -3

3 5
-1 -2
1 -2
1 -2
1
1 2 3

Sample Output
Sigh…
Bingo!

Constraints
1 <= t <= 5;
1 <= n <= 20;
1 <= m <= 60

题目分析:
转载:http://www.cnblogs.com/LonelyRyan/p/8429968.html

关灯问题II

题目描述

现有n盏灯,以及m个按钮。每个按钮可以同时控制这n盏灯——按下了第i个按钮,对于所有的灯都有一个效果。按下i按钮对于第j盏灯,是下面3中效果之一:如果a[i][j]为1,那么当这盏灯开了的时候,把它关上,否则不管;如果为-1的话,如果这盏灯是关的,那么把它打开,否则也不管;如果是0,无论这灯是否开,都不管。

现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。
输入输出格式
输入格式:

前两行两个数,n m

接下来m行,每行n个数,a[i][j]表示第i个开关对第j个灯的效果。

输出格式:

一个整数,表示最少按按钮次数。如果没有任何办法使其全部关闭,输出-1

输入样例#1

3
2
1 0 1
-1 1 0

输出样例#1

2

说明

对于20%数据,输出无解可以得分。

对于20%数据,n<=5

对于20%数据,m<=20

上面的数据点可能会重叠。

对于100%数据 n<=10,m<=100

题目分析:
转载:https://www.luogu.org/blog/niiick/solution-p2622

Storm in Lover

题目描述

海未面前现在有 n 个靶子。按照绘里的指示,海未需要把这 n 个靶子全部射穿才行。
一开始,海未的精力为 m,力度为 a。每射穿一个靶子,海未对弓的熟练度就会上升,此时
力度会增加 b。
每个靶子都有一个单独的耐久度 d[i],每次受到海未的攻击,靶子的耐久度都会减少海未当
前的力度的值。当靶子的耐久度为 0 或 0 以下时,靶子被破坏。但是,如果海未在一次射箭
后并没有射穿这个靶子,海未的精力会减少 c[i]。如果海未通过一次射箭破坏了靶子,她的
精力不会受到影响。
海未可以自由地选择射靶子的顺序。请问,海未是否能够通过合理地安排射靶子的顺序,使
得她能够将这些靶子全部射穿?如果能,她完成任务后最多能省下多少精力?如果不能,她
最多可以射穿多少个靶子?

输入格式

输入的第一行为四个正整数 n,m,a,b。
接下来 n 行,每行两个正整数 c[i],d[i]。

输出格式

输出的第一行仅包含一个字符串。请回答海未酱是否能完成全部射穿靶子的任务。如果可以
完成,输出“Yes”。如果不能完成,输出“No” 。(两者均不包括引号)
第二行仅包含一个正整数。若第一行为“Yes”,则你应输出所剩的最多精力值;若第一行为
“No”,则你应输出最多能射穿的靶子数。

样例输入:
2 10 1 1
1 4
1 6

样例输出:
Yes
5

数据范围:
对于 30%的数据,n<=6。
对于 50%的数据,n<=9。
对于 70%的数据,n<=14。
对于 100%的数据,n<=21;1<=a,b,c,d<=32767。

题目分析

本题给出了数据范围,我们发现n最大只有21,这意味着什么?我们可以考虑状态压缩!另外题目中提到的求所剩精力的最大值,并且可以随便按顺序去打,都在提示着我们可以用状压DP去做。
不过考场中很有可能想不到状压怎么打,不过我们可以拿部分分。因为打的顺序随便,于是我们就可以用全排列来枚举顺序,然后再暴力一个一个打就是的了。(注意精力为0不能打)
程序代码(暴力搜索50分)

#include
#include
#include
#include
#include
using namespace std;
int n,m,a,b,r[30],c[30],d[30],ans,sum,anss,summ,vis[30];
inline int read(){
    int x=0,w=1;char ch;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
    return x*w;
}
inline void work(){//暴力打靶
    ans=m;int aa=a;sum=0;
    for(register int i=1;i<=n;++i) {
        if(ans>0){
            if(aa>d[r[i]])  {sum++;aa+=b;continue;}//如果能够一次打
            else {//不能一次打
                if(d[r[i]]%aa==0)ans=ans-((d[r[i]]/aa)+1)*c[r[i]];
                else { ans=ans-(d[r[i]]/aa)*c[r[i]]; }
                /*这里要分情况写,比如一个靶的耐久度为7,你每次打的伤害为2,
                那你必须要花费c[i]*3的精力,而如果靶的耐久度为6,
                则只需c[i]*2的精力*/
                if(ans<=0) break;
                sum++;aa+=b;
            }
        }
        else break;
    }
    anss=max(anss,ans);
    summ=max(summ,sum);
}
void dfs(int k) {//全排列
    if(k==n+1) { work(); }
    for(register int i=1;i<=n;++i) {
        if(!vis[i]) { vis[i]=true; r[k]=i; dfs(k+1);vis[i]=false;}
    }
}
int main(){
    freopen("storm.in","r",stdin);
    freopen("storm.out","w",stdout);
    n=read(); m=read(); a=read(); b=read(); anss=0;
    for(register int i=1;i<=n;++i) {c[i]=read();d[i]=read();}
    dfs(1);
    if(summ==n) cout<<"Yes"<else cout<<"No"<return 0;
}
/*
6 11030 5125 134
10698 11888
30903 2010
2668 23647
1604 27025
5281 21776
17729 10999
*/

程序代码(AC)

#include
#include
#include
#include
#include
using namespace std;
int n,m,a,b,ans2,ans1,d[30],c[30];
int s[2100000],f[2100000];
inline int read(){
    int x=0,w=1;char ch;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
    return x*w;
}
int main(){
    freopen("storm.in","r",stdin);
    freopen("storm1.out","w",stdout);
    n=read();m=read();a=read();b=read();
    for(register int i=0;i//从0开始,方便状压
    for(register int i=1;i<(1<>1]+(i&1);
    //计算i这个数的二进制中有多少个1,一种写法,自己手动模拟一下即可
    f[0]=m;
    for(register int i=0;i<(1<1;++i)
        for(register int j=0;jif((i&(1<0)//假如这一位还是0,那我们把它变为1
                f[i|(1<1<0))*c[j]);
        //后面那个==0就是像上面那个暴力程序中详解的那样去处理
    for(register int i=(1<1;i;--i)
        if(s[i]>ans1&&f[i]>0) ans1=s[i],ans2=f[i];
        else if(s[i]==ans1&&f[i]>ans2) ans2=f[i];
    if(ans1==n) puts("Yes"),cout<else puts("No"),cout<return 0;
}

小结:

本蒟认为这两到题都很好的将二进制运用到了状压里面,且两个都是状压搜索,以后会将状压DP补上.可以说先接触简单的,方便以后学习状压DP吧.

你可能感兴趣的:(搜索,状压,二进制)