计蒜客 课程规划( 景驰无人驾驶1024编程邀请赛 D)

在 T 大 X 院,每年都要安排课程。所需要安排的课程分为 mm 个课组,每门课组有不超过 TT 门课,共有 NN 名教授可以教课。聘请教授 ii,X 院需要花费经费 c_ic
​i
​​ ,一名教授可以教某个课组里面的两门课,但当年只能聘请他教其中一门。

在每个课组还可以被分成小课组,如果把每门课程看成一个点,每名教授看成连接两门课程的边,则这样得到的图中的每个连通块被称作一个小课组,显然一个课组由若干小课组组成。学校规定,聘请的教授最后要使得总的小课组数目保持不变(与聘请所有教授的总小课组数一致),且能满足开设的课程尽量多。

对于不同课组,学校有不同的资助政策,具体来说,对于第 ii 个课组,学校最多可以为 X 院出钱聘请 p_ip
​i
​​ 名教授。但总共只能资助不超过 WW 门课。

由于 X 院经费有限,他们希望能够充分利用学校的政策,在开设尽量多课程的前提下,花费最少的经费聘用教授。

输入格式

第一行输入测试组数 T(1 \le T \le 10)T(1≤T≤10)。

对于每一组测试数据,第一行三个数, N(N \le 100000)N(N≤100000),m( m \le 5000)m(m≤5000),T(T \le 100000)T(T≤100000),W(W \le 100)W(W≤100)。意义如题目中所述。

接下来 NN 行,每行 44 个数 d_i,a_i,b_i,c_id
​​ 。表示第 ii 名教授可以教第 d_id
​i
​​ 个课组里的第 a_ia
​i
​​ 门课和第 b_ib
​i
​​ 门课,聘请他的费用是 c_ic
​i
​​ 。

接下来一行 mm 个数,p_1,p_2,\cdots,p_m(0 \le p_i \le 10)p

输入保证最终答案小于 2^{31}2
​31
​​ 。

输出格式

对于每一组数据,输出两行,第一行表示最多开设多少门课,第二行表示在开设最多课的基础上最少花费多少钱。

样例输入

1
4 2 3 1
1 1 2 1
1 2 3 3
1 3 1 4
2 1 2 3
1 1
样例输出

4

7

分析:光是读题就读了一晚上和一上午,最后才明白就是一水题,最小生成树!!!

          仔细独读题就知道 就是对每一个连通块求最小生成树 然后在最小生成树上在加一条可以值尽量小的边使得干好使得每一个点都能匹配一条边既一个课程一个老师(或者没有边可加)。

          找出那些边后  就是 把最大值贪心就行!

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
#include<vector>
const int maxn = 100000;
const int inf = 1e9;
typedef long long ll;
using namespace std;
struct Edge{
    int x,y,c,tid;
}e[maxn+5];
int n,m,T,w,pos[maxn+5],pos_tot,ans_clas,ans_cost;
int p[5015],fa[maxn+5],used[5015],vis[maxn+5];
vector<int>clas[5015];
int find_fa(int x)
{
    if(fa[x] == -1) return x;
    return fa[x] = find_fa(fa[x]);
}
bool cmp(int i,int j)
{
    return e[i].c < e[j].c;
}
bool cmp2(int i,int j)
{
    return e[i].c > e[j].c;
}
int main()
{
    int Cas;
    scanf("%d",&Cas);
    while(Cas--)
    {
        scanf("%d %d %d %d",&n,&m,&T,&w);
        for(int i=0;i<=m;i++) clas[i].clear();

        for(int i=1;i<=n;i++)
        {
            scanf("%d %d %d %d",&e[i].tid,&e[i].x,&e[i].y,&e[i].c);
            clas[e[i].tid].push_back(i);
        }
        for(int i=1;i<=m;i++) scanf("%d",&p[i]);

        pos_tot = ans_cost = ans_clas = 0;
        for(int c=1;c<=m;c++)
        {
            int len = clas[c].size(), cnt = 0,tot=0;
            for(int i=1;i<=T;i++) fa[i] = -1,vis[i]=0;

            for(int i=0;i<len;i++)//求有多少连通块
            {
                Edge E = e[clas[c][i]];
                int fx,fy;
                fx = find_fa(E.x), fy = find_fa(E.y);
                vis[E.x] = vis[E.y] = 1;
                if(fx!=fy) fa[fx] = fy;
            }

            for(int i=1;i<=T;i++) if(vis[i]) tot++;
            for(int i=1;i<=T;i++) if(fa[i]==-1&&vis[i]) cnt++;
            for(int i=0;i<len;i++) used[i] = 0;
            sort(clas[c].begin(),clas[c].end(),cmp);//把边排序
            cnt = tot - cnt;// 要把这些连通块 生成树 需要边的数量
            ans_clas += cnt;

            for(int i=1;i<=T;i++) fa[i] = -1;

            for(int i=0,t=0;i<len&&t<cnt;i++)// 最小生成树
            {
                Edge E = e[clas[c][i]];
                int fx,fy;
                fx = find_fa(E.x), fy = find_fa(E.y);
                if(fx!=fy)
                {
                    t++, used[i] = 1;
                    fa[fx] = fy;
                    ans_cost+=E.c;
                    pos[++pos_tot] = clas[c][i];//记录 要取的边
                }
            }
            for(int i=1;i<=T;i++) vis[i] = 0;
            for(int i=0;i<len;i++)// 在生成树上 加一条边
            {
                if(used[i]) continue;
                Edge E = e[clas[c][i]];
                int fx = find_fa(E.x);
                if(vis[fx]) continue;// 一个连通块只能加一条
                vis[fx] = 1;
                ans_clas++,pos[++pos_tot] = clas[c][i],ans_cost+=E.c;
            }
        }
        sort(pos+1,pos+pos_tot+1,cmp2);//把要取的边排序

        for(int i=1;i<=pos_tot&&w;i++)//贪心!
        {
            int id = e[pos[i]].tid;
            if(p[id])
            {
                p[id]--,w--;
                ans_cost-=e[pos[i]].c;
            }
        }
        printf("%d\n",ans_clas);
        printf("%d\n",ans_cost);
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_32944513/article/details/78340861
今日推荐