codevs 2594 解药还是毒药

2594 解药还是毒药

题目描述 Description

Smart研制出对付各种症状的解药,可是他一个不小心,每种药都小小地配错了一点原料,所以这些药都有可能在治愈某些病症的同时又使人患上某些别的病症(你可能会问那…那是解药还是毒药啊?)……,经过Smart的努力,终于弄清了每种药的具体性能,他会把每种药能治愈的病症和能使人患上的病症列一张清单给你,然后你要根据这张清单找出能治愈所有病症的最少药剂组合……顺便说一声,病症的数目不超过10种,而且他的药是用不完的,就是说每种药剂都可以被重复使用。

输入描述 Input Description

给你们的单子里第一行是病症的总数n(1≤n≤10)。第二行是药剂的种类m(0<m≤100)。

以下有m行,每行有n个数字用空格隔开,文件的第i+2行的n个数字中,如果第j个数为1,就表示第i种药可以治愈病症j(如果患有这种病的话则治愈,没有这种病则无影响),如果为0表示无影响,如果为-1表示反而能使人得上这种病(无病患上,有病无影响)。Smart制的药任何两种性能都不同。

输出描述 Output Description

你只要输出用的最少的药剂数就可以了,其实还有可能用尽了所有的药也不能将所有病治愈,那样的话你们只要输出“The patient will be dead.”就可以了。

样例输入 Sample Input

3

2

1 0 1

-1 1 0

样例输出 Sample Output

2

数据范围及提示 Data Size & Hint

1≤n≤10

0<m≤100

本题可以转化为最短路问题

我们设第i种病没治愈为1,治愈了为0。例:二进制状态100表示第1种症状被治愈了,第2、3种症状没被治愈,转化为十进制为4。

初始状态为二进制状态下的n个1,转化为十进制为2^n-1,目标状态为0。把由初始状态到达目标状态所经历的所有状态看做一个个点,所有边的边权为1。

那么题目转化为:从状态2^n-1到状态0的最短路径。

基本算法:SPFA

解题所需自定义函数:十进制与二进制的转换

十进制转二进制:

以10为例:

10%2=0,10/2=5;

5%2=1,5/2=2;

2%2=0,2/2=1;

1%2=1,1/2=0;结束

所以为1010

二进制转十进制:

以1010为例:

等于0*2^0 + 1*2^1 + 0*2^2 + 1*2^3=10

#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
int n,m;
int head=0,tail=0;//我用的是左闭右开区间,即区间范围为[head,tail-1] 
int l[11];//二进制状态存储数组 
int a[101][11];//a[i][j]表示第i种药剂对症状j的疗效 
bool t[2050];//十进制判重数组 
struct node
{
    int w,sum;//w当前状态,十进制,sum达到状态w所需的药剂数 
};
node q[10001];//队列 
node now;
int change(int s)
{
    if(!s) return 0;
    return l[s]+change(s-1)*10;
}
int work(int d,int g)//d:第d种药剂,g:使用d药剂之前的状态,十进制 
{
    for(int i=n;i;i--)//将十进制g转化为二进制,并一位一位的存在l数组中 
    {
        l[i]=g%2;
        g/=2;
    }
    for(int i=1;i<=n;i++)//第d种药剂对状态的修改 
    {
        if(a[d][i]==1) l[i]=0;
        else if(a[d][i]==-1) l[i]=1; 
    }
    int er=change(n);//因为l数组中的数是一位一位存的,这一句要把l[]数组中的数合起来。例:l[1]=1,l[2]=0;er=10
    int k=0,u=0;//二进制er转为十进制k 
    while(er)
    {
        int y=er%2;
        k+=y*pow(2,u);
        u++;
        er/=10;
    }
    return k;
}
void push(node cur)
{
    q[tail++]=cur;
}
void pop()
{
    head++;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
     for(int j=1;j<=n;j++)
      scanf("%d",&a[i][j]);
    q[tail].w=pow(2,n)-1;//初始状态n个1所代表的二进制数,转化为十进制为2^n-1 
    q[tail].sum=0;//初始没用任何药剂 
    t[q[0].w]=true;
    tail++;//入队,队尾指针后移 
    while(head<tail)//队列不为空 
    {
        for(int i=1;i<=m;i++)//枚举每一种药剂 
        {
            now=q[head];//取出队首 
            int h=work(i,now.w);//在用第i种药剂之前,状态为now.w,用了第i种药剂之后,状态为h 
            if(!h)//目标状态(治愈) 
            {
                printf("%d",now.sum+1);//因为now存的值是使用药剂i之前的值,而h更新是使用了药剂i之后,所以要+1 
                return 0;
            } 
            if(!t[h])//不是目标状态且状态h在之前没有出现过。因为spfa实质是宽搜,如果状态h在之前出现过,它在之前已经入队 
            {
                now.sum++;//药剂数+1 
                now.w=h;//状态转移 
                push(now);//入队 
                t[h]=true; //状态h标记为已出现过 
            }        
        }
        pop();//队首出队 
    }
    printf("The patient will be dead.");//若队列中没有状态了,但还没有结束程序,则无药可治 
}
原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/6220547.html