UPC H: Team 并查集和DP

 

输入 n,m

给 3*n个人的能力值,和m 对 关系,

有关系的人必须组成一队,,(一队只能3人)

2 1
1 2 3 4 5 6
3 4

整理后的结果是    

单人的  1 2 5 6   两人一组的是  (3,4)  要想组成一队,就是 1,2,5   (3,4,6)  这是一种组法

组成队伍后的能力值是最小的那个人的能力值,所有队伍能力值最大是多少?

 

大佬思路: 并查集处理原始数据,变成 单人双人小组分开

组成队伍的情况只有两种,1,三个单人 。2,一单一双

然后DP。(这四个字让我眼前一黑)

 

dp[i][j]  表示 用了 i 个单人 ,j个双人 小组来   组成的 队伍 的 能力最大值

dp[i][j]  需要考虑的是,能否组成队伍,怎么组成队伍(最弱的人分给哪两个倒霉蛋)

状态转移方程:(不知道写的对不对)

dp[i][j]=max(dp[i-3][j] + 单【i】, dp[i-1][j-1] + min(单【i】,双【j】)  )

i,j 状态需要前面的状态推出来,那就从前向后递推,dp[0][0] = 0

如果 dp【i】【j】能组成队伍  dp【i+3】【j】 和 dp【i+1】【j+1】就都能组成队伍

dp【i+3】【j】=max(dp【i+3】【j】,dp【i】【j】+a【i】);

dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+min(a[i],b[j]  )  )


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[3100][3100];
int f[3003];
ll dui[3003];
int sum[3003];
ll v[3003];
vector<int>q1;
vector<int>q2;
vector<int>q3;
bool flag=0;

int findd(int a)
{
    if(f[a]==a)
        return a;
    else
        return f[a]=findd(f[a]);
}

void marge(int a,int b)
{
    int fa=findd(a);
    int fb=findd(b);
    if(fa!=fb){
//        if(fa<fb){
            f[fb]=fa;       //合并,
            sum[fa]+=sum[fb];// 把  第 fb 支队伍的人数 加入到第 fa支队伍中去

            if(sum[fa]>3)
                flag=1;

            sum[fb]=0;
            v[fa]=min(v[fa],v[fb]);//  第fa 支队伍的能力值,,,其实直接用 dui【】 这个数组就行
//
//        }
//        else
//        {
//            f[fa]=fb;
//            sum[fb]+=sum[fa];
//            if(sum[fb]>3)
//                flag=1;
//            sum[fa]=0;
//            v[fb]=min(v[fa],v[fb]);
//        }
    }
}
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<3*n;i++)
    {
        cin>>dui[i];  //第i人的能力 
        f[i]=i;       //第i人属于哪个队伍
        sum[i]=1;     //第i人所在队伍人数
        v[i]=dui[i];  //第i人的能力
    }

    for(int i=0;i<m;i++){
        int a,b;
        cin>>a>>b;
        marge(a-1,b-1);
    }

    if(flag==1){
        cout<<-1<<endl;
        return 0;
    }


    for(int i=0;i<3*n;i++)
    {
        if(sum[i]==1){
            q1.push_back(v[i]);
        }
        if(sum[i]==2){

            q2.push_back(v[i]);
        }
        if(sum[i]==3){
            q3.push_back(v[i]);
        }
    }

    sort(q1.begin(),q1.end());

    sort(q2.begin(),q2.end());
    sort(q3.begin(),q3.end());

    
    int l1=q1.size();
    int l2=q2.size();
    int l3=q3.size();
  
  q2.push_back(0);
  
    memset(dp,-1,sizeof dp);
    dp[0][0]=0;

    for(int i=0;i<l1;i++)
        for(int j=0;j<=l2;j++) // 这里一定要  =  l2   因为  dp[i+3][j] 的j 会到 l2
        {
            if(dp[i][j]!=-1)
            {
            dp[i+3][j]=max(dp[i+3][j],dp[i][j]+q1[i]);
            dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+min(q1[i],q2[j]));
            }
        }
  long long  ans=0;

  if(dp[l1][l2]==-1)
  {
      cout<<-1<<endl;
  }
  else{

    for(int i=0;i<l3;i++)
    ans+=q3[i];

    cout<<(ll)dp[l1][l2]+ans<<endl;
  }
    return 0;
}

问题:不会发生   一个人同时加入了,由三个单人组成的队伍和 一个单人,一个两人小组的队伍

不会的,因为你已经排序了

最优解的计算方式只有一种:

着急开班会,字迹潦草,多多包涵。

这个题,我觉得还不是特别理解,,希望有人和我讨论讨论。

自己太菜了没办法呀,自己想绝对想不到

偷偷放上超哥 的代码  应该不会被发现的,,,哈哈哈

//
//#include<cstdio>
//#include<cstring>
//#include<algorithm>
//using namespace std;
//const int maxn = 3e3+5;
//int n,m;
//int a[maxn],b[maxn],fa[maxn],ta,tb;
//long long ans=0,dp[maxn][maxn];
//
//int Find(int x) {
//    return fa[x]==-1?x:fa[x]=Find(fa[x]);
//}
//void Union(int x,int y) {
//    x=Find(x); y=Find(y);
//    if (x!=y) {
//        fa[y]=x;
//        a[x]=min(a[x],a[y]);
//        b[x]+=b[y];
//        b[y]=0;
//    }
//}
//int main()
//{
//    memset(fa,-1,sizeof fa);
//    scanf("%d%d",&n,&m);
//    for (int i=1;i<=n*3;i++) {
//        scanf("%d",&a[i]); b[i]=1;
//    }
//    for (int i=1;i<=m;i++) {
//        int x,y;
//        scanf("%d%d",&x,&y);
//        Union(x,y);
//    }
//    for (int i=1;i<=n*3;i++) {
//        if (b[i]==1) a[ta++]=a[i];
//        if (b[i]==2) b[tb++]=a[i];
//        if (b[i]==3) ans+=a[i];
//        if (b[i]>=4) return 0*puts("-1");
//    }
//    sort(a,a+ta);
//    sort(b,b+tb);
//
//    memset(dp,-1,sizeof dp);
//    dp[0][0]=0;
//    for (int i=0;i<ta;i++)
//    for (int j=0;j<tb;j++)
//        if (dp[i][j]!=-1){
//        dp[i+3][j]=max(dp[i+3][j],dp[i][j]+a[i]);
//        dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+min(a[i],b[j]));
//    }
//    if (dp[ta][tb]==-1)
//        printf("-1\n");
//    else
//        printf("%lld\n",ans+dp[ta][tb]);
//
//}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值