加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

状压:物品价值-关灯问题

(2020-09-27 21:40:45)
分类: 动态规划
三)Hihocoder 杂货铺P1846 ABC (vjudge)
    杂货铺老板一共有N件物品,每件物品具有m种属性中的一种或多种。从杂货铺老板处购得一件物品需要支付相应的代价。
    现在你需要计算出如何购买物品,可以使得m种属性都在购买的物品中出现,并且支付的总代价最小。
输入
    第一行包含一个整数N。
    以下N行,每行包含一个整数C和一个只包含"A-Z"的字符串,代表购得该物品的代价和其具有的属性。
    对于50%的数据,1 ≤ N ≤ 20
    对于100%的数据,1 ≤ N ≤ 1000 1 ≤ C ≤ 100000
输出
    一个整数,代表最小的代价。如果无论如何凑不齐ABC三种属性,输出-1。
样例输入
    5 3
    10 A
    9 BC
    11 CA
    4 A
    5 B
样例输出
    13

#include "bits/stdc++.h"
using namespace std;
int f[101];
struct bag{ int v,w;} a[1100];
int main(){
    int n,m; cin>>n>>m;
    for (int i=1;i < (1<<m);i++) f[i]=1e9;//f[0]表示未选时,初始化为0
    for (int i=1;i<=n;i++)
    {     cin>>a[i].v;
            string s;cin>>s;
            for (int j=0;j < s.size();j++) a[i].w |=(1<<(s[j]-'A'));
            for (int j=0;j < (1<<n);j++)
            f[j|a[i].w]=min(f[j|a[i].w],f[j]+a[i].v);
    }
    if (f[(1<<m)-1]!=1e9) cout<<f[(1<<m)-1]<<endl;else cout<<-1<<endl;
    return 0;
}

完善程序,单选题:
(1)(B)  
A) i <= (1<<m)  B) i < (1<<m)  C) i < (1<<n)  D) i <= (1<<n)
(2)(D)  
A)j < (1<<m)  B)j < (1<<n)   C)j < strlen(s)   D) j < s.size()
(3)(A)   
   A) a[i].w |=(1<<(s[j]-'A'))      B) a[i].v |=(1<<(s[j]-'A'))
   C) a[i].w |=(1>>(s[j]-'A'))      B) a[i].v |=(1>>(s[j]-'A'))
(4)(C)
A) j < (1<<m)   B) j <= (1<<m)   C) j < (1<<n) D) j <= (1<<n)
(5)(A) 
A) f[j|a[i].w]=min(f[j|a[i].w],f[j]+a[i].v);
B) f[j-a[i].w]=min(f[j-a[i].w],f[j]+a[i].v);
C) f[j+a[i].w]=min(f[j+a[i].w],f[j]+a[i].v);
C) f[j^a[i].w]=min(f[j^a[i].w],f[j]+a[i].v);
f[j|a[i].w]=min(f[j|a[i].w],f[j]+a[i].v);

二)Hihocoder P1486 物品价值 (vjudge)
    小Hi现在有n个物品,每个物品都有一个价值。并且这n个物品总共有m个不同的属性,每个物品都具有其中若干属性。
    小Ho要从中选出若干物品,满足每个属性都正好有奇数个物品拥有,且被选出的物品价值总和最大。你能帮助小Ho完成任务么?
输入
    第一行一个数T(<=10),表示数据组数。对于每一组数据:
    第一行两个数n,m(1<=n<=1000,m<=10)
    接下来每两行描述一件物品。对于每一件物品:
    第一行两个数v和s,表示其价值和所含属性数量(v<=100000,s<=m)
    第二行s个数,表示该物品拥有的属性编号(1<=编号<=m)
输出
    物品价值总和的最大值。
样例输入
    1
    3 2
    2 1
    1
    2 1
    2
    5 2
    1 2
样例输出
    5
首先对于状态表示,题目说必须是奇数个,是不是可以想到,用1表示放了奇数个,0表示放了偶数个,那么通过异或操作就可完成放入过程了(可以自己手动推一下为什么异或可以表示放入状态)。
用f[i][j]表示在放入第i个物品,状态为j的时候的最优解,则可以列出和上题类似的状态转移方程:
f[i][j∧a[i].w]=max(f[i][j∧a[i].w],f[i-1][j]+a[i].v);
#include "bits/stdc++.h"
using namespace std;
struct rec{int v,w;}a[1010];
int f[2050][2050];
int main(){
    int T,n,m;cin>>T;
    while (T--)
    {     cin>>n>>m;
            memset(f,-1,sizeof f);
            memset(a,0,sizeof a);
        f[0][0]=0;//初始化
            for (int i=1;i<=n;i++)
            {     int s;cin>>a[i].v>>s;
                    for (int j=1;j<=s;j++)
                    {     int e;cin>>e;
                            a[i].w|=1<<(e-1);
                    }
            for (int j=0;j<(1<<m);j++)
                if (f[i-1][j]!=-1){
                    f[i][j^a[i].w]=max(f[i][j^a[i].w],f[i-1][j]+a[i].v);
                    f[i][j]=max(f[i][j],f[i-1][j]);
            }
    }
    cout<<max(f[n][(1<<m)-1],0)<<endl;
    }
    return 0;
}
完善程序:
1) (B) A) f[0][0]=1     B) f[0][0]=0    C)f[1][0]=1    D) f[1][0]=0
2) (C) A) a[i].w^=1<<(e-1)          D) a[i].w&=1<<(e-1)                               
       C) a[i].w|=1<<(e-1)          D) a[i].w*=1<<(e-1)
3) (D)
A) f[i][j+a[i].w]=max(f[i][j+a[i].w],f[i-1][j]+a[i].v)
    B) f[i][j|a[i].w]=max(f[i][j|a[i].w],f[i-1][j]+a[i].v)
C) f[i][j&a[i].w]=max(f[i][j&a[i].w],f[i-1][j]+a[i].v)
D) f[i][j^a[i].w]=max(f[i][j^a[i].w],f[i-1][j]+a[i].v)
4) (A)  A)f[i-1][j]   B)f[i][j-1]  C)f[i-1][j-1]  D) f[i][j]

三)P2622关灯问题
现有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
#include "bits/stdc++.h"
using namespace std;
int n , m , state[1200];//n<=10,所以前1023是有用的   表示到达i状态需要走的步数
int trans[1200][105]; //trans[i][j]表示对状态i按下按钮j的状态
int q[1200000],now;//q代表即将处理的队列
int temp,open[110],close[110];
int main(){
    int l=1,r=1;
    scanf("%d%d", &n ,&m);
    for( int i = 1 ; i <= m ; i++)  close[i] = (1<<n)-1;//open[i]=0;
    q[1] = (1<<n)-1;//初始状态:所有灯都开着,每一位都是1
    for(int i = 0 ; i <= (1<<n) ; i++)  state[i]=-1;//所有状态初始化-1,表示之前没有遍历到这个状态
    state[(1<<n)-1]=0;// 当前位置不需要移动就可以到达
    for(int i = 1; i <= m ; i++)//m个按钮
      for(int j =1 ; j <= n ; j++){//每个按钮对灯j的控制情况
    scanf("%d", &temp);
    if( temp == 1 )  close[i] -= (1<<(j-1));//A行按钮i可以让灯j关上
    if( temp == -1 ) open[i] += (1<<(j-1));//B行 打开
    }
    for( int i = 0 ; i < (1<<n); i++ )//枚举从000到111的所有状态
    for( int j =1 ; j<= m ; j++)//枚举每一个按钮
        trans[i][j] =  i & close[j]  | open[j];//经过按钮j,i会变成trans[i][j]
    while( l <= r ){//当队列中还有任务时
        if(state[0]!=-1) break;
            now=q[l++];
            for(int i = 1 ; i <= m ; i++)//对于每一个按钮
                if( state[trans[now][i]] == -1 ){//如果没有到过这个状态
                    state[trans[now][i]] = state[now]+1;//这个状态可以由now状态走一步得到
                    q[++r] = trans[now][i];//准备处理这个状态
                }
        }
    cout<<state[0]<<endl;//到0状态需要走的步数
    return 0;
}

完善程序:
1) (C)   A) 1<<n-1  B) 1<<(n-1)  C) (1<<n)-1     D) (1<<n)
2) A行之中,与下划线语句效果相同的语句是(B)
    A) &=(1<<(j-1))   B)&=~(1<<(j-1))  C) |=(1<<(j-1))   D)|=~(1<<(j-1))
3) B行之中,与下划线语句效果相同的语句是(C)
    A) &=(1<<(j-1))   B)&=~(1<<(j-1))  C) |=(1<<(j-1))   D)|=~(1<<(j-1))
4) (A)
A) i & close[j]  | open[j]      B )  close[j]  & open[j]
C) i & close[j]  & open[j]      D)  i |  close[j]  | open[j]
5)(D)
  A) state[0]!=0   B) state[0]==0  C) state[0]==-1   D) state[0]!=-1
6)(D)
    A) state[now] = state[trans[now][i]]
B) state[trans[now][i]] = state[now]
    C) state[now] = state[trans[now][i]]+1
D) state[trans[now][i]] = state[now]+1

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有