SOJ 1005

1005. Roll Playing Games

Constraints

Time Limit: 1 secs, Memory Limit: 32 MB

Description

Phil Kropotnik is a game maker, and one common problem he runs into is determining the set of dice to use in a game. In many current games, non-traditional dice are often required, that is, dice with more or fewer sides than the traditional 6-sided cube. Typically, Phil will pick random values for all but the last die, then try to determine specific values to put on the last die so that certain sums can be rolled with certain probabilities (actually, instead of dealing with probabilities, Phil just deals with the total number of different ways a given sum can be obtained by rolling all the dice). Currently he makes this determination by hand, but needless to say he would love to see this process automated. That is your task. 

For example, suppose Phil starts with a 4-sided die with face values 1, 10, 15, and 20 and he wishes to determine how to label a 5-sided die so that there are a) 3 ways to obtain a sum of 2, b) 1 way to obtain a sum of 3, c) 3 ways to obtain 11, d) 4 ways to obtain 16, and e)1 way to obtain 26. To get these results he should label the faces of his 5-sided die with the values 1, 1, 1, 2, and 6. (For instance, the sum 16 may be obtained as 10 +6 or as 15 +1, with three different "1" faces to choose from on the second die, for a total of 4 different ways.) 

Input

Input will consist of multiple input sets. Each input set will start with a single line containing an integer n indicating the number of dice that are already specified. Each of the next n lines describes one of these dice. Each of these lines will start with an integer f (indicating the number of faces on the die) followed by f integers indicating the value of each face. The last line of each problem instance will have the form 

r m v1 c1 v2 c2 v3 c3 ... vm cm 

where r is the number of faces required on the unspecified die, m is the number of sums of interest, v1, ... ,vm are these sums, and c1, ... ,cm are the counts of the desired number of different ways in which to achieve each of the respective sums. 

Input values will satisfy the following constraints: 1 <= n <= 20, 3 <= f <= 20, 1 <= m <= 10, and 4 <= r <= 6. Values on the faces of all dice, both the specified ones and the unknown die, will be integers in the range 1 ... 50, and values for the vi's and ci’s are all non-negative and are strictly less than the maximum value of a 32-bit signed integer. 

The last input set is followed by a line containing a single 0; it should not be processed.

Output

For each input set, output a single line containing either the phrase "Final die face values are" followed by the r face values in non-descending order, or the phrase "Impossible" if no die can be found meeting the specifications of the problem. If there are multiple dice which will solve the problem, choose the one whose lowest face value is the smallest; if there is still a tie, choose the one whose second-lowest face value is smallest, etc.


  这题思路不难想,先求出已给出n个骰子的组合情况,再用深搜回溯来确定第n+1个骰子的组合,由于第n+1个骰子最多6面,每面数字是1-50,所以直接暴力深搜最多就有50^6种排列组合,如果最后结果是impossible的话就要对50^6种组合进行遍历,那样肯定是超时,所以必须要想办法对解空间树进行剪枝,不得不说要想出能大幅度提高效率的剪枝方法就有点难度了。注意input中注明c是非负数,则c可能为0,所以在进行各种判定时,切记先验c是否为0。具体步骤思路写在了代码注释里。(题目比较复杂,要有耐心)

 
// Problem#: 1005
// Submission#: 4924352
// The source code is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
// URI: http://creativecommons.org/licenses/by-nc-sa/3.0/
// All Copyright reserved by Informatic Lab of Sun Yat-sen University
#include<iostream>
using namespace std;

int surfaceSum[1100];//用来存放前n个确定骰子的组合情况 ,surfaceSum[i]的值是前n个骰子和为i的组合种数 
int surfaceSum1[1100];// 假设前n个骰子共有x种组合方式,若第n+1个有r个面,则n+1个骰子的总组合种数为x*r,即当第n+1个骰子第a个面取一个特定值时,surfaceSum1存储了第n+1个骰子取这个值时的x种组合方式 
int surfaceSum2[1100];//当上一步完成后,将surfaceSum1加到surfaceSum2上,再将surfaceSum1清空,接着再确定第n+1个骰子第a+1个面的值,被清空的surfaceSum1再保存此时的x种情况,然后再加到surfaceSum2上,以后类推,直到第n+1个骰子被完全确定,此时surfaceSum2保存的就是n+1个骰子的全部组合情况,共x*f种。 
int n,r,m,v,c;//n为已知骰子个数,r为第n+1个的面数,m为要达到的条件的个数,v为条件要达到的和,c为所要求的和的个数 
int desire[10][2];//这个数组保存给出的要满足的特定条件,desire【0】【0】保存第一个条件要求的和,desire【0】【1】保存要求满足和的组合的个数。 
int ans[10];//保存第n+1个骰子的各个面的值 
int flag=0;//用来标记是否找到符合要求的第n+1个骰子 
int f;//用来保存每个已知骰子的面数 
int temp;//用来保存已知骰子各个面的值 


void ini(int A[])//清空数组,即将其重置为全0数组 
{
    for(int i=0;i<1100;++i)
    A[i]=0;
}

bool ismatch()//第n+1个骰子每确定一个面的值则可能离目标要求更进一步,所以实际上desire数组保存的是每确定一个值后当前情况距离要求的差距,当desire【i】【1】为0则表明此要求已达到。 
{
    bool result=true;
    for(int i=0;i<m;++i)
    {
        if(desire[i][1]!=0)
        {
            result=false;
            break;
        }
    }
    return result;
}

int min()//求得前n个骰子组合形成的和中的最小值 
{
    for(int i=0;i<1100;++i)
    {
        if(surfaceSum[i])
        return i;
    }
}
int max()//同上,所有和中的最大值 
{
    for(int i=1099;i>-1;--i)
    {
        if(surfaceSum[i])
        return i;
    }
}

bool possible(int index)//用来判断当第n+1个骰子的第a面取了某个值后这个值是否可行 ,index为当前第n+1个骰子第a面的取值 
{
    for(int i=0;i<m;++i)//若desire【i】【1】小于0则说明当前所形成的某个和的个数已超过要求的个数,故此时第a面的取值不合适,返回false 
    {
        if(desire[i][1]<0)
        return false;
        else if(desire[i][1]!=0&&min()+index>desire[i][0])//这个条件就是用来剪枝的,十分重要!当desire【i】【1】大于0时,表明这项条件尚未完全满足,若此时 min+index大于这个和的话, 则说明当第a面取index或任何比index大的值时,都无法将desire【i】这项条件推得离目标更近一步,所以返回false 
        return false;//注意上方的判断条件 desire[i][1]!=0不可缺少 
    }
    return true;
}
bool possi()
{
    for(int i=0;i<m;++i)
    {
        if(desire[i][1]!=0&&(min()+1>desire[i][0]||max()+50<desire[i][0]))//这个函数用来在进行深搜第n+1个骰子的值前就进行判断是否可行,同样注意desire[i][1]!=0不可缺少。 
        return false;
    }
    return true;
}
void DFS(int depth,int index)//深搜确定第n+1个骰子的第depth面的值,其可能值从index起,止于50 
{
    if(depth==r)
    {
        if(ismatch())
        flag=1;
    }
    else
    {
        for(int i=index;i<=50;++i)
        {
            ans[depth]=i;
            for(int j=0;j<1100;++j)//确定第depth面的值后将特定的x种组合存入surfaceSum1 
            {
                if(surfaceSum[j])
                {
                    surfaceSum1[j+i]=surfaceSum[j];
                }
            }
            for(int k=0;k<m;++k)
            {
                desire[k][1]-=surfaceSum1[desire[k][0]];//相应的,离目标可能会有所靠近 
            }
            ini(surfaceSum1);
            if(possible(i))//若为真,说明第depth面取值暂时可行 
            {
                DFS(depth+1,i);//深搜第depth+1面的取值,由于对可能值进行搜索时都是从小到大搜索,所以第depth+1面的值必然不小于i,所以从i开始搜索 
                if(flag)
                return;
                else
                {
                    for(int j=0;j<1100;++j)//第depth面为i时深搜失败,进行回溯 
                    {
                        if(surfaceSum[j])
                        {
                            surfaceSum1[j+i]=surfaceSum[j];
                        }
                    }
                    for(int k=0;k<m;++k)
                    {
                        desire[k][1]+=surfaceSum1[desire[k][0]];
                    }
                    ini(surfaceSum1);
                }
            }
            else//若possible不为真,直接进行回溯 
            {
                    for(int j=0;j<1100;++j)
                    {
                        if(surfaceSum[j])
                        {
                            surfaceSum1[j+i]=surfaceSum[j];
                        }
                    }
                    for(int k=0;k<m;++k)
                    {
                        desire[k][1]+=surfaceSum1[desire[k][0]];
                    }
                    ini(surfaceSum1);
            }
        }
    }
}

int main()
{
    while(cin>>n&&n)
    {
        flag=0;
        ini(surfaceSum);
        cin>>f;
        for(int i=0;i<f;++i)//获得只有第一个骰子时的组合情况 
        {
            cin>>temp;
            surfaceSum[temp]++;
         } 
         if(n>1)//若骰子不止一个,进行迭代,获取两个时的情况,由两个时的组合情况再得出三个时的情况,直到获得前n个骰子的总组合情况 
         {
         for(int i=1;i<n;++i)
         {
            cin>>f;
            for(int j=0;j<f;++j)
            {
                cin>>temp;
                for(int k=0;k<1100;++k)
                {
                    if(surfaceSum[k])
                    {
                       surfaceSum1[k+temp]=surfaceSum[k];
                       surfaceSum2[k+temp]+=surfaceSum1[k+temp];
                    }
                }
                 ini(surfaceSum1);
            }//这个for循环结束后,surfaceSum2保存了前i+1个骰子的组合情况,将其赋值给surfaceSum,使得surfaceSum始终保存着前i+1个骰子的组合情况,以便进行迭代。 
            ini(surfaceSum);
            for(int k=0;k<1100;++k)
            surfaceSum[k]=surfaceSum2[k];
            ini(surfaceSum2);
         }
         }
        cin>>r>>m;
        for(int i=0;i<m;++i)
        {
            cin>>v>>c;
            desire[i][0]=v;
            desire[i][1]=c;
        }
        if(possi())//用possi()这个函数先粗略的进行判断是否有可行解,也算是一种作用有限的剪枝 
        DFS(0,1);
        if(flag)
        {
           cout<<"Final die face values are ";
           for(int i=0;i<r-1;++i)
           cout<<ans[i]<<" ";
           cout<<ans[r-1]<<endl;
       }
        else
            cout<<"Impossible"<<endl;
    }
    return 0;
}                         




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值