hrbust 1663 水果【状压dp】【原创1000篇纪念!!!】

水果
Time Limit: 3000 MSMemory Limit: 32768 K
Total Submit: 20(6 users)Total Accepted: 9(6 users)Rating: Special Judge: No
Description

吃水果可以补充维生素ABCDE….

z在月初买了N种水果,每种水果各买了一个。每种水果都有一个初始的新鲜度Ti,每过一天就会减少Di,即第二天的新鲜度为Ti-Di,第3天为Ti-Di*2,依次类推。一旦新鲜度变为负数,那个水果就不能吃了。

z每天都按照一种水果的搭配来吃水果,这些搭配不是随意的,而是只能从事先计划好的M中搭配中选择一种。每种搭配由Ki个水果组成,一种搭配的营养值是由Ki个水果当天的新鲜度之和再加上一个额外的搭配营养值Ei组成。需要注意的是当某种搭配中的一个水果不能吃的时候,这个搭配就不能用了;而且,有可能存在两个搭配里面的组合是一样的,但是额外的搭配营养值不同。

为了能够获得更多的营养,小z希望找出一种最优的方案,你能帮助他计算出最优方案下一共获得的营养值么?


Input

多组测试数据。

每组数据第一行输入一个整数N表示水果的种数,按1~N编号。(0<N<15)

接下来的N行,每行两个整数Ti,(Ti<100)Di(Di<100),表示第i种水果的初始新鲜度和每天的递减值。

N+2行为一个正整数M,表示搭配的种数。(0<M<20)

接下来的M行,每行先是一个正整数Ki,表示组成这个搭配的水果的数目,然后是一个非负整数Ei(Ei<100),表示这种搭配额外的营养值,最后是Ki个整数,每个整数为水果的编号。

Output

对于每组数据输出一行,包含一个整数,表示最大的营养值。

Sample Input
2
3 1
4 2
2
1 1 1
1 1 2
2
3 1
4 2
3
1 1 1
1 1 2
2 2 1 2

Sample Output
8
9

Author
`Wind @hrbust


思路:


1、观察到数据范围N,M都在20以内,那么考虑状压dp,其中又包含一个第几天到底吃第几种搭配的问题,那么我们设定dp【i】【j】表示第i天,吃了状态为j的水果的时候的最多营养值收获。(例如当前j=5,5=101(二进制),其表示我们现在吃了第一种和第三种食物,第二种食物并没有吃)


2、对应我们首先进行各项预处理:

①设定tmp【i】,表示第i种搭配的食物需要吃的水果的状态。

②设定dead【i】,表示第i天坏掉了的水果的状态。


3、然后我们考虑其状态转移方程:
①dp【i】【j】=max(dp【i】【j】,dp【i-1】【q】+val【k】+cal(i,k));

表示今天的状态我们从昨天转移过来。

②其中k表示第i天吃的搭配编号,val【k】表示第k种搭配获得的营养值,cal(i,k)表示所有食物新鲜值的总和。

③那么我们第一层for枚举i,第二层for枚举状态j,第三层for枚举k;表示我们第i天要吃第k种搭配的食物,那么我们接下来需要判断当前状态j是否可行。

④那么我们对应判断这一天的时候,第k种搭配中的食物有没有坏掉的,如果有坏掉的,那么不进行状态转移,接下来判断一下状态j是否包含第k种搭配中的所有食物。如果满足,那么当前dp【i】【j】就是一个可行状态。那么对应q=j-tmp【k】,表示上一天我没有吃第k种搭配的食物,那么我们接下来直接进行状态转移即可。


4、那么其解就在dp【i】【j】(0<=i<m&&0<=j<(1<<n))中,我们这里暴力维护一波最大值即可。

总时间复杂度估计为:O(N*M*(1<<N));


Ac代码:


#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
struct node
{
    int ti,di;
}a[50];
int n,m;
int dp[24][(1<<16)];
int dead[50];
int val[50];
int tmp[50];
int cal(int day,int k)
{
    int sum=0;
    for(int i=0;i<n;i++)
    {
        if(((1<<i)&tmp[k])!=0)
        {
            sum+=a[i].ti-a[i].di*day;
        }
    }
    return sum;
}
int main()
{
    while(~scanf("%d",&n))
    {
        memset(dead,0,sizeof(dead));
        memset(dp,-1,sizeof(dp));
        memset(val,0,sizeof(val));
        memset(tmp,0,sizeof(tmp));
        for(int i=0;i<n;i++)
        {
            scanf("%d%d",&a[i].ti,&a[i].di);
        }
        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            int kk;
            scanf("%d%d",&kk,&val[i]);
            while(kk--)
            {
                int x;
                scanf("%d",&x);
                x--;
                tmp[i]+=(1<<x);
            }
        }
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(a[j].ti-a[j].di*i<0)
                {
                    dead[i]+=(1<<j);
                }
            }
        }
        for(int i=0;i<m;i++)
        {
            int j=tmp[i];
            dp[0][j]=max(val[i]+cal(0,i),dp[0][j]);
        }
        for(int i=1;i<m;i++)//第i天
        {
            for(int j=0;j<(1<<n);j++)//这一天的状态
            {
                for(int k=0;k<m;k++)//这一天想要吃第k种搭配的食物
                {
                    if((j&tmp[k])==tmp[k])
                    {
                        if((tmp[k]&dead[i])==0)//这一天想要吃的食物中没有坏的
                        {
                            int q=j-tmp[k];
                            if(dp[i-1][q]==-1)continue;
                            dp[i][j]=max(dp[i-1][q]+val[k]+cal(i,k),dp[i][j]);
                        }
                    }
                }
            }
        }
        int output=0;
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<(1<<n);j++)
            {
                output=max(output,dp[i][j]);
            }
        }
        printf("%d\n",output);
    }
}






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值