首页 > 代码库 > Zoj 3616 Choir III 【有想法的暴力】【容斥】

Zoj 3616 Choir III 【有想法的暴力】【容斥】

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3616

题目大意:给出n, m, b, g (0 < n <= 100, 0 < m <= 2000, 0 < b, g <= m * n)这些数字,下面n行,每一行有2*m个数字,第一个数字表示一个同学对于合唱的贡献,第二个数字表示同学的性别,b和g表示在一个矩形内至少有的男生和女生的数量。求一个矩形使得这个矩形内的对合唱的贡献值最大并且其不能有贡献值是负数的同学,求最大的贡献值。

最开始看到这个题目,思路是找到特殊点(被贡献值是-1的点所连成的x轴或者y轴所分割的点),然后对这些分割的点进行遍历查找时间复杂度是O(m*m),查找贡献值的过程可以是O(1),而m的个数最大可能是20000,所以是思路错误。

后来看题解是这样的:

题目给出的数据范围100行 2000列,行很少,所以可以考虑暴力枚举子矩阵的行数(起始行,和终止行),然后再从第一列开始枚举,直到某一列出现负数,就计算一下与出现负数的上一列之间的和,判断是否满足男女人数的要求,再用来更新答案即可。

#include<iostream>
#include<stdio.h>
using namespace std;
int n,m,b,g;
int num[105][2005],sex[105][2005];
int girl[105][2005],boy[105][2005];
int c1[105][2005],sum[105][2005];

void init(){
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
        scanf("%d%d",&num[i][j],&sex[i][j]),sex[i][j]--;
    for(int i=1;i<=n;i++) sum[i][0] = 0, girl[i][0] = 0, boy[i][0] = 0, c1[i][0] = 0;
    for(int j=1;j<=m;j++) sum[0][j] = 0, girl[0][j] = 0, boy[0][j] = 0, c1[0][j] = 0;
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){
        sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + num[i][j];
        girl[i][j]= girl[i-1][j]+ girl[i][j-1]- girl[i-1][j-1]+ sex[i][j];
        boy[i][j] = boy[i-1][j] + boy[i][j-1] - boy[i-1][j-1] + (!sex[i][j]);
        c1[i][j]   = c1[i-1][j]   + c1[i][j-1]   - c1[i-1][j-1]   + (num[i][j]<0);
    }
}

bool judge(int s, int t, int col){  //判断在第col列,第s行到第t行有没有-1
    return c1[t][col]-c1[s-1][col]-c1[t][col-1]+c1[s-1][col-1];
}

int calc(int s, int t, int pre, int now){
    return sum[t][now]-sum[s-1][now]-sum[t][pre-1]+sum[s-1][pre-1];
}

bool ok(int s, int t, int pre, int now){
    return ( (boy[t][now]-boy[s-1][now]-boy[t][pre-1]+boy[s-1][pre-1]) >=b
        &&(girl[t][now]-girl[s-1][now]-girl[t][pre-1]+girl[s-1][pre-1]) >=g);
}

int main ()
{
    while(~scanf("%d%d%d%d",&n,&m,&b,&g)){
        init();

        int ans = 0;
        for(int i=1;i<=n;i++){
            for(int j=i;j<=n;j++){
                int pre = 0 ;
                for(int k=1;k<=m;k++)

                    if(judge(i,j,k)){
                        if(k==1 || judge(i,j,k-1)) pre = k;
                        else {
                            int s = calc(i,j,pre+1,k-1);
                            if(s>ans && ok(i,j,pre+1,k-1)) ans = s;
                            pre = k;
                        }
                    }

                    if(!judge(i,j,m)){
                        int s = calc(i,j,pre+1,m);
                        if(s>ans && ok(i,j,pre+1,m)) ans = s;
                    }
                }

        }
        if(ans != 0) printf("%d\n",ans);
        else puts("No solution!");
    }
}
预处理以及枚举的方法是个亮点。

Zoj 3616 Choir III 【有想法的暴力】【容斥】