1 条题解

  • 0
    @ 2025-3-8 20:44:32

    知识点:枚举,贪心

    对于每个挑战者每一个回合肯定是用贪心的思路选择硬币数总和最大的一行或一列,但是按照这个思路我们会发现一个问题,每次选择之后,那一行或一列的同学就出列没在队伍里,相应的位置也就没有硬币了,然后选择后每一行或者每一列的总数就会发生改变,然后最大值的位置就会改变,所以说我们可能需要重新找最大值,可是这样会不会太麻烦了?我们观察一下数据范围,1n,m121 \leq n,m \leq 12 所以说我们可以先枚举列数(只选1,31,3列,只选2,42,4列... 只选 ...... 列)先对列进行求和,然后对列贪心,然后选完列之后然后再对行求和,然后对行贪心,然后每次记录最大值,与下一次的最大值比较,最终求出答案(当然反过来先选行再选列也是可以的)

    然后对于每次选择,我们怎么知道之前选过哪几列,哪几种情况呢,这个我们直接进行搜索就行了,这个就不多说,然后平常像这种的枚举我们可以用 0101 串枚举

    所谓0101串枚举,就是我们在每个个体都面对两种选择的时候,可以用一个0101串表示,比如说对于本题,每一行有选和不选两种可能,假设有55行的话,我们就可以用一个长度为550101串来表示,00表示不选11表示选,就像一个boolbool数组一样,如:0100101001 表明第1,3,41,3,4行不选,第2,52,5行要选。 只是用一个数字来表示比用数字表示方便,为什么方便呢?因为所有长度小于nn0101串,转成十进制之后就是所有小于2n2^n的数字,这样从002n12^n−1枚举数字就是从000000…00枚举到111111…11。这样比写个搜索枚举所有选和不选的情况快乐吧!

    #include <bits/stdc++.h>
    #define ll long long
    #define endl '\n'
    using namespace std;
    
    const int N = 20;
    int n, m, k;
    int a[N][N];
    ll sh[N], sl[N];//sh[i]是第i行的和,sl[i]是第i列的和(在行选完之后用)
    bool b[N];//b[i]是01串转换过来的bool数组
    ll ans = 0; //存答案,注意总和会超过int的最大值要用long long
    
    int deal(int x){  //把x这个数字转换成bool数组
        memset(b, 0, sizeof(b)); //不清数组会爆炸!!!
        int cnt = 0, i = 1; //cnt存01串里面1的个数
        while (x){
            if (x&1) {  //如果x的最后一位是1
                cnt++; 
                b[i] =1;
            }
            x >>= 1;  //x右移一位,等价于除以二取整
            i++;
        }
        return cnt;
    }
    
    void Solve(){
    //————————————读入——————————————
        scanf("%d%d%d", &n, &m, &k);
        for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++){
            scanf("%d", &a[i][j]);
            sh[i] += a[i][j];
        }
    
    //——如果k>n或者k>M,那么肯定就把矩阵选完了————
    //这里我这么处理主要是因为我后面的有个判断把这种情况给直接continue掉了,
    //k=n或者m就已经能把矩阵选完,所以结果肯定是一样的
        if (k > n) k = n;
        if (k > m) k = m;
    //————————————————————————————————————————
    
    //—————枚举选行的所有可能性,贪心处理列——————
        int tmp = (1<<n) -1;  //1<<n就等于2^n
        for (int T =0; T <= tmp; T++){ //T用来枚举行的状态
            int numh = deal(T);//将状态T转为bool数组,并返回有多少个1,既要选多少行
            int numl = k - numh; //numl是要选多少列
            if (numl > m || numl < 0) continue; 
            //numl>m或者numl<0,说明行的方案不对(其实也有可能是k太大,这里就是上文说的k比较大的时候直接continue没了的地方)。
            
            ll  sum=0;  //sum是本次枚举的方案的和
            for (int i = 1; i <= n; i++) //把选中的行都加上
                if (b[i]) sum += sh[i];
    
            memset(sl, 0, sizeof(sl));//行选得不同矩阵就不同,所以sl数组每次都要清干净,不然又得爆炸
            for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                if (!b[i]) sl[j] += a[i][j]; //第i行没有选,j列的和需要加上a[i][j](第i行选了a[i][j]就清零了不能加)
            sort(sl+1, sl+1+m);
            for (int i=1,j=m; i<=numl; i++,j--) sum += sl[j]; //把最大的numl列的和加到方案里
            ans = max(ans, sum);//维护答案
        }
    //————————————枚举+贪心结束——————————————
        printf("%lld\n", ans);
    
    	return ;
    }
    
    int main(){
    
    	int T = 1;
    
    	while(T--){
    		Solve();
    	}
    
    	return 0;
    }
    
    • 1

    信息

    ID
    189
    时间
    1000ms
    内存
    256MiB
    难度
    9
    标签
    递交数
    15
    已通过
    4
    上传者