Codeforces 417D Cunning Gena【排序+状压dp】

D. Cunning Gena
time limit per test
1 second
memory limit per test
256 megabytes
input
standard input
output
standard output

A boy named Gena really wants to get to the "Russian Code Cup" finals, or at least get a t-shirt. But the offered problems are too complex, so he made an arrangement with his n friends that they will solve the problems for him.

The participants are offered m problems on the contest. For each friend, Gena knows what problems he can solve. But Gena's friends won't agree to help Gena for nothing: the i-th friend asks Gena xi rubles for his help in solving all the problems he can. Also, the friend agreed to write a code for Gena only if Gena's computer is connected to at least ki monitors, each monitor costs b rubles.

Gena is careful with money, so he wants to spend as little money as possible to solve all the problems. Help Gena, tell him how to spend the smallest possible amount of money. Initially, there's no monitors connected to Gena's computer.

Input

The first line contains three integers nm and b (1 ≤ n ≤ 1001 ≤ m ≤ 201 ≤ b ≤ 109) — the number of Gena's friends, the number of problems and the cost of a single monitor.

The following 2n lines describe the friends. Lines number 2i and (2i + 1) contain the information about the i-th friend. The 2i-th line contains three integers xiki and mi (1 ≤ xi ≤ 1091 ≤ ki ≤ 1091 ≤ mi ≤ m) — the desired amount of money, monitors and the number of problems the friend can solve. The (2i + 1)-th line contains mi distinct positive integers — the numbers of problems that the i-th friend can solve. The problems are numbered from 1 to m.

Output

Print the minimum amount of money Gena needs to spend to solve all the problems. Or print -1, if this cannot be achieved.

Examples
input
2 2 1
100 1 1
2
100 2 1
1
output
202
input
3 2 5
100 1 1
1
100 1 1
2
200 1 2
1 2
output
205
input
1 2 1
1 1 1
1
output
-1

题目大意:

一共有M个问题,有N个小伙伴可以帮你解决问题,其中一个监视器的金额为b。

对应接下来N组输入,对应每组输入两行,对应表示第i个小伙伴的信息,其中第一行包含三个元素,第一个元素表示雇佣这个小伙伴帮忙解决问题的花费,第二个表示这个小伙伴帮忙解决问题所需要的监视器的个数,第三个元素表示这个小伙伴能够解决的问题数。

(假如第一个小伙伴需要1个监视器,第二个小伙伴需要两个监视器,那么一共建立起来两个监视器就能同时满足两个条件)

接下来一行,表示能够解决问题的编号。

对应解决M个问题,求一个最小花费方案。


思路:


1、首先不考虑这个监视器的问题:

①对应观察到M只有20,那么我们可以对应设定dp【i】表示雇佣小伙伴解决了状态i时候的这些问题的最小花费。其中i=5.对应二进制表示:101,那么我们就能够表示5这个状态能够解决第一个和第三个问题,但是现在没有解决第二个问题。

②那么不难想到状态转移方程:dp【q】=min(dp【q】,dp【j】+a【i】.val)(其中对应第i个人能够解决状态q比状态j多出来的问题)

③对应解决上式状态转移方程,我们设定tmp【i】表示第i个人能够解决问题的状态,(例如tmp【1】=5,表示第1个人能够解决第一个和第三个问题,但是解决不了第二个问题。)那么我们此时第一层for枚举人,从0到n,然后第二层for枚举状态j,然后判断一下状态j下,有没有第i个人能够解决的问题并且状态j没有解决完的问题,对应判断:if(tmp【i】&j==tmp【i】)那么说明状态j已经解决了所有第i个人能够解决的问题,否则相反,并且我们同时设定q=j|a【i】;那么,dp【q】=min(dp【q】,dp【j】+a【i】.val);

此时状态转移过程最终结果dp【(1<<m)-1】就是解决了所有问题m所需要的最小雇佣花费。


2、那么此时考虑这个监视器问题;

①我们可以二分监视器个数,但是时间复杂度略高,可能会T

②我们其实直接将N个人按照需要监视器的个数从小到大排序即可,那么对应在将每个人都状态转移完毕之后,我们维护一次最小值:

ans=min(ans,dp【(1<<m)-1】+a【i】.yi*b),其中a【i】.yi表示当前这个人需要的监控器个数、这样保证前i个人进行了状态转移之后,当前这第i个人就是需要监控器个数最多的人,那么对应在雇佣的最小花费的基础上,维护一个最小总花费即可。


3、注意数组大小,注意数据范围,注意dp数组初始化大小,需要long long int的数据的地方不要忘记即可。


Ac代码:


#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define ll __int64
#define inf 2000000000000000000
struct node
{
    int xi;
    ll yi,pos;
}a[1515];
int n,m;
ll b;
int tmp[151515];
ll dp[(1<<20)+15];
int cmp(node a,node b)
{
    return a.yi<b.yi;
}
int main()
{
    while(~scanf("%d%d%I64d",&n,&m,&b))
    {
        for(int i=0;i<(1<<m);i++)dp[i]=inf;
        memset(tmp,0,sizeof(tmp));
        for(int i=0;i<n;i++)
        {
            int k;
            scanf("%d%I64d%d",&a[i].xi,&a[i].yi,&k);
            while(k--)
            {
                int x;
                scanf("%d",&x);
                x--;
                tmp[i]+=(1<<x);
            }
            a[i].pos=i;
        }
        sort(a,a+n,cmp);
        dp[0]=0;
        ll ans=inf;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<(1<<m);j++)
            {
                int pos=a[i].pos;
                if((j&tmp[pos])!=tmp[pos])
                {
                    int q=j|tmp[pos];
                    dp[q]=min(dp[q],dp[j]+a[i].xi);
                }
            }
            if(dp[(1<<m)-1]!=inf)
            {
                ans=min(dp[(1<<m)-1]+a[i].yi*b,ans);
            }
        }
        if(ans==inf)printf("-1\n");
        else
        printf("%I64d\n",ans);
    }
}











区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值