Gym 100694 F The Berland Championship 网络流+枚举+思维

100 篇文章 0 订阅

F. The Berland Championship

time limit per test

2.0 s

memory limit per test

256 MB

input

standard input

output

standard output

Every year BerSU (Berland State University) holds the programming contest «The Berland Championship». This year Andrey, the student of BSAU (Berland State Alternative University), helped to prepare the problems. There were m problems prepared in total.

Andrey is proud of his university very much so he wants the BSAU team to win. Of course, he isn't allowed to share the problems with BSAU teams, but he can give advice how to form the teams.

n BSAU students want to take part in the competition. For each student, Andrey knows what problems he can solve, and also what is his productivity — the maximum number of problems he can solve during the contest. Every team must consist of three students. Andrey believes that BSAU students will act optimally at the contest, that is, everyone will solve the maximal number of problems he can. However, no student will solve more problems than his productivity, and no team will solve problems that aren't solvable by its students.

Help Andrey to form exactly one team of three students so it could solve the maximal number of problems comparing to all other possible BSAU teams.

Input

The first line contains two integers nm (3 ≤ n ≤ 50, 1 ≤ m ≤ 1000) — the number of BSAU students and the number of problems.

The second line contains n integers pi (0 ≤ pi ≤ m) — the productivities of the students.

Each of the next n lines contains a string of uppercase and lowercase Latin letters namei (|namei| ≤ 10) — the name of the i-th student. All names are pairwise distinct.

Then the binary matrix a of size n × m follows. Its rows correspond to students, and columns — to problems. If ai, j is 1, i-th student can solve j-th problem, otherwise he can't.

Output

In the first line output a single integer s — the maximal number of problems the team formed by Andrey can solve.

In the second line output the names of the students in this team. The names can be written in any order.

If there are several optimal solutions, output any of them.

Examples

input

5 5
3 2 2 3 1
Slava
Andrew
Egor
Denis
Sergey
11011
10100
00001
00100
10000

output

5
Slava Andrew Egor 

input

4 6
1 2 1 2
Denis
Andrew
Egor
Slava
100011
111100
000101
000111

output

5
Denis Andrew Slava 

题意:

       教练想给他的n个学生组一个队去打ACM(所以明显是三个人啦)。他每个人能做出的最多题数,每个人可以做哪些题(被他可以做的题数控制,如样例1中的第一个人,他可以做12 45四道题,但是他最多只能做三道),以及名字。要你选出三个人,让这个队可以做出最多的题数,如果有相同,那么任意输出一种即可。

 

做法:

       先讲一下我原先的想法吧,虽然可能是不好实现的(不想看的人可以跳过这一段直接看下一段我啃了大神的代码之后的理解),我想着把每个问题,每个人都看作一个点,然后先将点和问题,问题和超级汇点进行连接,然后枚举每三个人(因为总数不大只有50),和超级源点进行连边,把边数控制在枚举人之前,同时改变每个人的head(代码中的p),但是这样是不可行的,因为一次dinic之后每条边的流量都在变,所以答案只在第一次枚举的时候会是正确的。

        大神的做法就是不一样,同样是枚举,但是直接忽略了多少个问题这个条件,直接把问题分成了8个集合,分别是

        000(没有人可以做出的问题数量)

        001(第一个人可以单独做出的问题数量)

        010(第二个人可以单独做出的问题数量)

        100(第三个人可以单独做出的问题数量)

        011(第一个人和第二个人都可以做出的问题数量)

        101(第一个人和第三个人都可以做出的问题数量)

        110(第三个人和第二个人都可以做出的问题数量)

        111(第一二三个人都可以做出的问题数量)

        可想而知所有的数量相加一定会等于总题数(这个为什么我就不解释了吧)。

        之后将人与其对应的集合进行连边,流量是该集合的问题数量。为什么呢。假设对第一个人来说,我们之前统计过了now[001](1)有多少,说明这个人可以单独做出这么多题,直接连边,011(3)代表了有多少题是第一个人和第二个人都能做的,说明第一个人还是可以做的,也是要连边(当然第二个人在连接的时候也会连到这个集合来)。

        这样就可以大大减少在完全初始化之后需要连边的数量,而且跑一次dinic也会变得快了不少,还要记得把超级源点向三个人连接这三个人可以做的最大题数流量的边,每次跑完之后维护最大值ans和组队目标a,b,c即可。


代码如下:

    

#include<bits/stdc++.h>
using namespace std;
const int Ni = 20005;
const int MAX = 1<<26;
struct Edge{
    int u,v,c;
    int next;
}edge[3*Ni];
int n,m;
int edn;//边数
int p[Ni];//父亲
int d[Ni];
int sp,tp;//原点,汇点

void addedge(int u,int v,int c){
    edge[edn].u=u; edge[edn].v=v; edge[edn].c=c;
    edge[edn].next=p[u]; p[u]=edn++;

    edge[edn].u=v; edge[edn].v=u; edge[edn].c=0;
    edge[edn].next=p[v]; p[v]=edn++;
}
int bfs(){
    queue <int> q;
    memset(d,-1,sizeof(d));
    d[sp]=0;
    q.push(sp);
    while(!q.empty()){
        int cur=q.front();
        q.pop();
        for(int i=p[cur];i!=-1;i=edge[i].next){
            int u=edge[i].v;
            if(d[u]==-1 && edge[i].c>0){
                d[u]=d[cur]+1;
                q.push(u);
            }
        }
    }
    return d[tp] != -1;
}
int dfs(int a,int b){
    int r=0;
    if(a==tp)return b;
    for(int i=p[a];i!=-1 && r<b;i=edge[i].next)
    {
        int u=edge[i].v;
        if(edge[i].c>0 && d[u]==d[a]+1)
        {
            int x=min(edge[i].c,b-r);
            x=dfs(u,x);
            r+=x;
            edge[i].c-=x;
            edge[i^1].c+=x;
        }
    }
    if(!r)d[a]=-2;
    return r;
}

int dinic(int sp,int tp){
    int total=0,t;
    while(bfs()){
        while(t=dfs(sp,MAX))
        total+=t;
    }
    return total;
}
string name[55];
int cando[55],line[55][1005],now[20];
void init(){
    edn=0;//初始化
    memset(p,-1,sizeof(p));
    memset(now,0,sizeof(now));
}
int main(){
    scanf("%d%d",&n,&m);
    sp=0,tp=15;
    for(int i=1;i<=n;i++)
        scanf("%d",&cando[i]);
    for(int i=1;i<=n;i++)
        cin>>name[i];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%1d",&line[i][j]);
    int ans=-1,a,b,c;
    for(int i=1;i<=n-2;i++){
        for(int j=i+1;j<=n;j++){
            for(int k=j+1;k<=n;k++){
                init();
                addedge(sp,1,cando[i]);
                addedge(sp,2,cando[j]);
                addedge(sp,3,cando[k]);
                for(int pro=1;pro<=m;pro++){
                    int temp=0;
                    if(line[i][pro]) temp|=1;
                    if(line[j][pro]) temp|=2;
                    if(line[k][pro]) temp|=4;
                    now[temp]++;  //枚举每个问题有多少人能做,用上面的方法进行存储
                }
                for(int man=0;man<3;man++){
                    for(int t=1;t<8;t++){ //将人和集合连边
                        if(t&(1<<man)) addedge(man+1,t+3,now[t]);
                    }
                }
                for(int i=4;i<=10;i++)
                    addedge(i,tp,now[i-3]);  //集合和超级汇点连边
                int nowans=dinic(sp,tp);
                if(nowans>ans){
                    ans=nowans;
                    a=i,b=j,c=k;
                }
            }
        }
    }
    printf("%d\n",ans);
    cout<<name[a]<<" "<<name[b]<<" "<<name[c]<<endl;
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值