P2036Perket(状态压缩通过)
Posted leader-one
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P2036Perket(状态压缩通过)相关的知识,希望对你有一定的参考价值。
Perket 是一种流行的美食。为了做好 Perket,厨师们必须小心选择配料,以便达到更好的口感。你有N种可支配的配料。对于每一种配料,我们知道它们各自的酸度 SS 和甜度 BB。当我们添加配料时,总的酸度为每一种配料的酸度总乘积;总的甜度为每一种配料的甜度的总和。
众所周知,美食应该口感适中;所以我们希望选取配料,以使得酸度和甜度的绝对差最小。
另外,我们必须添加至少一种配料,因为没有美食是以白水为主要配料的。
输入格式
第一行包括整数 NN,表示可支配的配料数。
接下来 NN 行,每一行为用空格隔开的两个整数,表示每一种配料的酸度和甜度。
输入数据保证,如果我们添加所有配料,总的酸度和甜度都不会超过 10^9109。
输出格式
输出酸度和甜度的最小的绝对差。
输入输出样例
1 3 10
7
4 1 7 2 6 3 8 4 9
1
说明/提示
1le Nle 101≤N≤10。
首先这个题目给我们最多十种调料,数字不大,因此可以使用状态压缩去枚举每一种情况。
但是看了很多网上的博客,网上的视频。都没有很好的,面向小白的状态压缩教程。
因此本人决定~~明早python翘了~~深夜发一篇题解。
本人会竭力细致的讲解。So,让我们开始吧!
------------
什么是状态压缩?
状态压缩,可以把复杂的状态压缩到一个简单的数来保存。
是不是和本题看起来一点关系都没有?
别着急!
在讲解状态压缩之前,我们要知道二进制这个神奇的东西,以及知道如何将十进制转化为二进制。
下面是十进制转化成二进制的代码(短除法)
------------
```cpp
void dectobin(int n)
{
int result = 0, k = 1, i, temp;
temp = n;
while (temp)
{
i = temp % 2;
result = k * i + result;
k = k * 10;
temp = temp / 2;
}
printf("%d
", resu
```
------------
某同学:哦,我的天啊!我知道十进制转化为二进制又有什么用啊?对于本题没有一点帮助啊!
在计算机世界中 0 代表 伪 ,1 代表 真。
那么是不是我们可以用1代表这个调料被选用了,0代表它没有被选用。
注意!注意!
因此假如有四份调料ABCD,某个厨子就让我选ABC三种。
那么用1代表选,0代表不选
是不是这种情况我们可以改写成:
选,选,选,不选
1,1,1, 0
然后啊我们看啊,这个逗号好像完全不影响啊
我们再把逗号删掉,是不是就是1110(这是不是代表这种状态了)
这个1110如果以二进制的角度看,是不是就**唯一对应**一个数了呢
也就是8+4+2=14这个数
~~好了恭喜你已经学会一半的状态压缩了~~
------------
我们现在知道了可以用二进制去表示所有调料是否使用的所有状态
加深印象再举几个例子:还是ABCD
我选了D,ABC不选就是0001
ABCD全选就是1111
ABCD都不选就是0000
有些同志就要说了:啊~,你这个东西啊,我知道用二进制代表状态有什么用呢?难道手撸所有的状态???打表???
NO!
对于这个题目来说一共有2*2*2……*2总共1024种状态
某同志:所以,二进制转化成十进制后对应的最大的数是1024???
其实不然,由上面的例子可以看出
十个调料如果全部要选的话:
是1111111111(十个1)
是2^11 - 1 也就是2047这么大
最多全选,最少选一种(题目要求,所以不是0)
也就是说111111111 ->->->0000000001
当然:0000000010,也满足只选一种
------------
综上所述,我们可以用2047去代表1111111111,然后不停的让自减1,达到枚举全部状态的目的
明白了这些我们再说另外一个很重要的东西 **位运算**
在这里我们简单的说几个下面出现了的,其余的可以去百度
------------
**& 与 运算符** 作用:两位同时为“1”,结果才为“1”,否则为0
运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;
**左移运算符(<<)**
将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
例:a = a << 2 将a的二进制位左移2位,右补0,
左移1位后a = a * 2;
若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。
**右移运算符(>>)**
将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
操作数每右移一位,相当于该数除以2。
例如:a = a >> 2 将a的二进制位右移2位,
左补0 or 补1 得看被移数是正还是负
------------
然后我们就发现了一个是原来判断一个数二进制某位是不是1只要来一个按位 & 运算符就OK了啊
```cpp
if (i & (1 << (j - 1)))
```
也就是这句代码
左移运算符:
1<<1 二进制下 :10
1<<2 二进制下: 100
因为二进制的10可以判断1110(随便举得一个二进制数做例子)
的从右往左数的第二位是不是1
二进制的100可以判断11001(同上)
的从右往左数的第三位是不是1
所以我们只要来一个循环不断产生
1,10,100,1000……
这样就可以知道到底是哪个调料被选用了
### 枚举状态 + 扫描情况 + 努力学习写出的c++代码 ==AC!
最后AC代码如下:
细心的同志会发现我开了一个 f[2048]但是完全没有使用啊哈哈哈哈
------------
```cpp
#include <iostream>
#include <cmath>
using namespace std;
int f[2048];
struct
{
int s; //酸度
int t; //甜度
} shicai[18];
int main()
{
int N;
cin >> N;
for (int i = 1; i <= N; i++)
cin >> shicai[i].s >> shicai[i].t;
int c = 9999999; //甜度差,先设置为无限大
for (int i = (1 << N) - 1; i >= 1; i--)
{
int t_t = 0;
int s_t = 1; //s_t千万不要设置为0
for (int j = 1; j <= N; j++) //开始状态压缩,扫一扫
{
if (i & (1 << (j - 1))) //现在对比的是第j位调料
{
t_t += shicai[j].t;
s_t *= shicai[j].s;
}
}
c = min(c, abs(t_t - s_t));
}
cout << c << endl;
return 0;
}
```
以上是关于P2036Perket(状态压缩通过)的主要内容,如果未能解决你的问题,请参考以下文章