求填完图后所填的数值总和的最小值的问题

原题链接
规定n * n(<=2 * 10^3)的图,给出每行每列必须存在的最大值,现在要让你填m(<=8 * 10^5)个点,求填完后所填的数值总和的最小值。
分析:
要使值更小,就要让不需要填最大值的点填0。
枚举行列共出现过的值,对于一个值,若某行与某列的“必须存在的最大值”都是该值,那么若在该行该列的交叉点填下该值,则可以同时满足该行与该列,即“省下一个点不用填”,故可让该行与该列连接一条边,最终最大匹配值就是可以“省下”的点数,而在该值情况下求出来的:
“将该值作为最大值的行数+将该值作为最大值的列数-最大匹配数”就刚好是在该图里最少必须填该值的点数。
答案就累加点数与该值的乘积即可。
PS:这题10^9的复杂度居然没超时,看来题目还是要敢想敢写呀~

int a[maxv],b[maxv],match[maxv],v[2100][2100];
vector<int> g1[maxv],g2[maxv],g[maxv];
map<int,bool> vis;
set<int> all;
bool dfs(int x){
    for(int i=0,y;i<g[x].size();i++){
        if(!vis[y=g[x][i]]){
            vis[y]=1;
            if(!match[y]||dfs(match[y])){
                match[y]=x;return true;
            }
        }
    }
    return false;
}
int main(){
    int n,m,k,x,y;
    LL ans=0,sum=0;
    scanf("%d %d %d",&n,&m,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),g1[a[i]].push_back(i),all.insert(a[i]);
    for(int i=1;i<=n;i++) scanf("%d",&b[i]),g2[b[i]].push_back(i),all.insert(b[i]);
    for(int i=1;i<=m;i++)
        scanf("%d %d",&x,&y),v[x][y]=1;
    for(set<int>::iterator it=all.begin();it!=all.end();it++){
        int i=*it;
        sum=0;
        for(int j=1;j<=n+n;j++) g[j].clear();
        for(int j=0;j<g1[i].size();j++)
            for(int o=0;o<g2[i].size();o++){
                int now1=g1[i][j],now2=g2[i][o];
                if(v[now1][now2]) g[now1].push_back(now2+n),g[now2+n].push_back(now1);
            }
        memset(match,0,sizeof(match));
        for(int j=1;j<=n+n;j++){
            vis.clear();
            if(dfs(j)) sum++;
        }
        sum/=2;
        ans+=(g1[i].size()+g2[i].size()-sum)*i;
    }
    printf("%lld",ans);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值