Doing Homework

有n个任务,每个任务有一个截止时间,超过截止时间一天,要扣一个分。
求如何安排任务,使得扣的分数最少。

Input
有多组测试数据。第一行一个整数表示测试数据的组数
第一行一个整数n(1<=n<=15)
接下来n行,每行一个字符串(长度不超过100)表示任务的名称和两个整数,分别表示任务的截止时间和完成任务需要的天数。 这n个任务是按照字符串的字典序从小到大给出。 

Output
每组测试数据,输出最少扣的分数的。 并输出一个完成任务的方案,如果有多个方案,输出字典序最小的一个。

Sample Input

2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3

Sample Output

2
Computer
Math
English
3
Computer
English
Math

状态压缩dp,遇到这种状态不能像背包容量一样分割的情况。可以使用二进制来表示状态,来进行状态的转移。
拿此题第一个实例举例,使用 111 表示三个任务都完成,011表示第一,二个任务完成。010 表示第二个任务完成。以此类推。
所以状态转移的情况为:
---------------------------------------------------111
-----------------------------011-----------------101---------------------110
-----------------------001------010-------100-------001---------100-------010
上面的状态可以由下面的状态得到,例如,011 可以由 001 状态加上010来得到,也可以由010加上001来得到。由此我们可以先求出最底一层简单的状态,再求出最上面的最终状态。下面将结合代码详细讲解利用这种思想解题。代码中以第一个例子来讲解

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

int T, N;

struct node
{
    char name[106];
    int limit,use;
};

typedef struct node Node;

Node A[20];

int dp[1<<16];	//到第 i 个状态时所扣的分最小值

int time[1<<16];  //到第 i 个状态所经过的时间

int Path[1<<16];	//到达第 i 个状态所走的前一步路经

void PP(int x)
{
    if(x <= 0)
        return;

    PP(x - (1 << Path[x]));

    cout << A[Path[x]].name << endl;
}

int main()
{
    freopen("G:\\data.txt", "r", stdin);

    cin >> T;

    while(T--)
    {
        cin >> N;

        memset(A, 0, sizeof(A));
        memset(dp, 0, sizeof(dp));
        memset(time, 0, sizeof(time));
        memset(Path, 0, sizeof(Path));

        for(int i = 0; i < N; i++)
        {
            cin >> A[i].name >> A[i].limit >> A[i].use;
        }

        int tol = 1 << N;

        for(int i = 1; i < tol; i++)  //从001 状态到 111  状态枚举 
        {
            dp[i] = INT_MAX;

            for(int j = 0; j < N ; j++)      //试探第 j 个任务
            {
                int tmp = 1 << j;			//转换成对应的二进制表示
												//如果两个状态可以转移,则其中一个有相同的位,例如 011 和 001 最后一位都为1,011 和100就无法转换。
                if(!(tmp & i))              //无法由tmp状态转移到i,舍弃
                    continue;
												// i - tmp 为转换的中间状态,例如,011 可以由 001 + 010 转换而来, 当tmp 为001 时, i - tmp = 011 - 001 = 010 正好为转换的中间状态。
                int cc = time[i-tmp] + A[j].use - A[j].limit;   //可以由tmp状态转移到i状态
																				//
                if(cc < 0)
                    cc = 0;

                if(dp[i] >= cc + dp[i-tmp])  //因为要求字典序最小的,而且任务是按照字典序增大的顺序给出的,所以要加等于号
                {
                    dp[i] = cc + dp[i-tmp];
                    time[i] = time[i-tmp] + A[j].use;
                    Path[i] = j;
                }
            }
        }

        cout << dp[tol-1] << endl;

        PP(tol-1);
    }

    return 0;
}

这题给我最大的启示就是当遇到不能像背包容量那样分解时,可以考虑使用这种二进制进行状态分解和转移。

全部评论

相关推荐

1个小白:可以考虑投一下字节
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务