码迷,mamicode.com
首页 > 其他好文 > 详细

[ZROI]分组

时间:2019-09-07 22:12:19      阅读:104      评论:0      收藏:0      [点我收藏+]

标签:维表   string   name   相同   情况下   code   经验   reg   har   

\(Description\)

\(n\)个员工,需要从中选择\(k\)组员工,每组\(2\)人,\(1\)人组长了,\(1\)人组员。
每个员工有三个数值:\(w,s,q\)\(w\)表示经验值,\(s\)表示薪水,\(q\)是意愿\(q=1,2,3\)
\(q=1\)表示该员工想当组长,\(q=2\)表示该员工想当组员,\(q=3\)表示该员工随意
要求每个组内组长的经验值\(>\)组员的经验值
输入\(n,k\)以及\(n\)个人的\(w_i,s_i,q_i\)
输出在满足所有人要求且经验值符合要求的情况下,恰好组成\(k\)组的最小薪水花费

\(Solution\)

仔细想想发现这道“提高组\(T1\)”贪心是不可做的
于是考虑\(DP\)
一维表示当前位置,一维表示已经完成的组合的数目,一维表示只有组员的组合的数目
状态设计好后为保证转移显然需要排序

为保证满足经验值要求,可以按照经验值从小到大排序,当经验值相同时组长排在最后面,组员排在最前面,中间是\(p=3\)的员工

当这样排序时,每次放置组长到有人的组时保证经验值满足要求\((>=的情况也考虑)\)了,且相当于每一个合法的可以匹配的组员,组长都考虑过了,这样就既满足了合法性又不会遗漏情况。显然这样的排序是正确的。

为什么设计只有组员的组合的数目而不考虑只有组长得组合的数目呢,因为需要保证经验的限制,也就是后面的人必须比前面的人经验值小,才能保证转移的合法性,所以这和从小到大排序只考虑只有组员的情况是等价的,换句话说,这样设计状态所有状况组合都已经考虑到了。

这道题难在排序和状态的设计

\(dp\)过程显然,若当前是组长,则可以考虑不选或加入已有的一组(特判是否有)
如果是组员则考虑不选或建新组(若超过\(k\)则不新建)
不选的时候状态继承
注意的几点:
1.\(j,kd,j+kd<=i\)
2.注意判断无解的情况(\(dp[n+1][k][0]=INF\))
3.开\(long long\)\(dp\)数组第一维滚动优化

重要优化(\(40pts-->100pts\))

技术图片

所以\(dp\)数组开\(2*600*600\)即可

\(Code\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define re register
#define N 1000100
#define ll long long
using namespace std;
int n,k;
const int maxn =1020;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct node{
    int w,p;
    ll s;
}pe[100010];
bool cmp(node A,node B)
{
    if(A.w!=B.w) return A.w<B.w;
    if(A.p==1) return 0;
    else if(A.p==2) return 1;
    else if(A.p==3&&B.p==1) return 1;
    else if(A.p==3&&B.p==2) return 0;
}
ll dp[2][maxn][maxn];//dp[i][j][k]表示当前考虑到i个人
//已经组成了j组,还有k组只有组员的最小代价 
void DP()
{
    memset(dp,0x3f3f3f3f,sizeof(dp));
    dp[1][0][0]=0;
    for(re int i=1;i<=n;++i)
    {
        for(re int j=0;j<i;++j)
        {
            for(re int kd=0;(kd+j<=k)&&(kd+j<i);++kd)
            {
                
                dp[(i+1)%2][j][kd]=min(dp[i%2][j][kd],dp[(i+1)%2][j][kd]);//不选择的情况 
                if(pe[i].p==1)
                {
                    if(kd>0) dp[(i+1)%2][j+1][kd-1]=min(dp[(i+1)%2][j+1][kd-1],(ll)dp[i%2][j][kd]+pe[i].s);
                }
                else if(pe[i].p==2)
                {
                    if(kd+1<=k)dp[(i+1)%2][j][kd+1]=min(dp[(i+1)%2][j][kd+1],(ll)dp[i%2][j][kd]+pe[i].s);
                }
                else
                {
                    if(kd>0) dp[(i+1)%2][j+1][kd-1]=min(dp[(i+1)%2][j+1][kd-1],(ll)dp[i%2][j][kd]+pe[i].s);
                    if(kd+1<=k)dp[(i+1)%2][j][kd+1]=min(dp[(i+1)%2][j][kd+1],(ll)dp[i%2][j][kd]+pe[i].s);
                }
    
            }
        }
    }
}
int main()
{
    n=read(),k=read();
    if(n<=2*k){ printf("-1\n");return 0; }
    for(re int i=1;i<=n;++i)
    {
        pe[i].w=read();
        pe[i].s=read();
        pe[i].p=read();
    }
    sort(pe+1,pe+n+1,cmp);
    DP();
    if(dp[(n+1)%2][k][0]==4557430888798830399)
    {
        printf("-1\n");
        return 0;
    }
    printf("%lld\n",dp[(n+1)%2][k][0]);
    //printf("%d %d\n",maxn,N);
    return 0;
}

蒟蒻\(RE80\)分目前不知道怎么回事,有大佬知道请在评论区指点一下

[ZROI]分组

标签:维表   string   name   相同   情况下   code   经验   reg   har   

原文地址:https://www.cnblogs.com/Liuz8848/p/11483180.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!