I- Mine Craft(长沙学院2022蓝桥杯选拔赛第一场 )

链接:https://ac.nowcoder.com/acm/contest/17933/I
来源:牛客网
 

题目描

龙龙同学最近迷上了MC,一天,他进入了一个服务器,腐竹非常友好的送了他一个神器,这个神器可以选择一行或一列的区域,将该区域每个顶层方块上增加一个方块或者消去每个顶层方块,他想在一些矩形区域内建一些漂亮的火柴盒,但这些区域内凹凸不平,他是个强迫症,他希望通过该神器对这些区域进行修理,并且使这些区域y轴为0(即海拔为0),由于他希望成为一个时间管理大师,他想知道修理每个区域的最短时间时多少,这可是个伤脑筋的问题,于是他来请求你的帮助,你能写个程序帮他快速解决这个问题吗?

某区域如下图:

令下界合金块层y轴为0;

初始图:

修剪后:

该区域可化简为如下矩阵

2 1 1 2

0 -1 -1 0

1 0 0 1

0 -1 -1 0

你可以对该矩阵的每行每列进行+1或-1操作,每次操作需花费1单位时间,你需求出将该矩阵所有元素都变成0 的最少时间。

输入描述:

第一行一个整数n(1<=n<=1000)

接下来一个n*n的矩阵,矩阵中每个元素aij有-1e9<=aij<=1e9。

输出描述:

输出一个整数,代表最少时间。

解题思路:

 本题是个差分约束的题。对该知识不了解的-->https://www.cnblogs.com/zzz-hhh/p/11200893.html

对于一个矩阵,我们将行列分别编号为1~n,设对i行的操作次数为xi,对j列的操作次数为yj,这样对于矩阵中每个元素aij,我们有xi+yj=aij,可化为xi>=(-yj)+aij&&(-yj)>=xi-aij,这样转换后我们得到了一个不等式组,对于这样一系列不等式求最值我们可以用差分约束来写,对于矩阵中每个元素aij,我们可以建立两条边,即xi到(-yj)有条权值为-aij的边,(-yj)到xi有条权值为aij的边(-yi只是一个编号,我们将它编号为j+n),这样我们就得到了一个有向图,对这个图我们再建立一个超级源点,超级源点到图中每个点的有条权值为0的边,现在这个问题就转变成了求超级源点到图中其他点的最长路问题,但求出最长路并不是最终答案,因为我们要求的是最少操作次数,最少操作次数即为每个点最长路减去所有点的中位数绝对值之和。

参考代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6+10;
const int M = 2e6+10;

ll n,m;
ll e[M],w[M],ne[M],h[N],idx;
ll dis[N];
bool vis[N];

void add(ll a,ll b,ll c){
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}

void spfa(){
    memset(dis,-0x3f3f3f3f,sizeof(dis));
    memset(vis,false,sizeof(vis));
    queue<int>Q;
    for(int i=1;i<=2*n;i++){
        Q.push(i);
        dis[i]=0;
        vis[i]=true;
    }
    while(!Q.empty()){
        int t=Q.front();
        Q.pop();
        vis[t]=false;
        for(int i=h[t];~i;i=ne[i]){
            int y=e[i];
            if(dis[y]<dis[t]+w[i]){
                dis[y]=dis[t]+w[i];
                if(!vis[y]){
                    Q.push(y);
                    vis[y]=true;
                }
            }
        }
    }
}

int main(){
    memset(h,-1,sizeof(h));
    idx=0;
    scanf("%lld",&n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            ll a;
            scanf("%lld",&a);
            add(j+n,i,a);
            add(i,j+n,-a);
        }
    }
    spfa();
    sort(dis+1,dis+1+2*n);
    ll ans=0;
    for(int i=1;i<=2*n;i++){
        ans+=abs(dis[i]-dis[n]);
    }
    cout<<ans<<endl;
}

由于矩阵一定有解,我们也可一用迪杰斯特拉来写,虽然有负边,当我们求最长路时,加一个1e9即可,即dis[j] < dis[v] + w[i] + maxn。

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define Inf 0x3f3f3f3f
const int maxn = 1e9+10;
const int N = 2e5 + 10;
const int M = 2e7 + 10;
int n, m;

inline int read()
{
    int x = 0, y = 1;char c = getchar();
    while (c < '0' || c>'9') { if (c == '-') y = -1;c = getchar(); }
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * y;
}

int e[M], w[M], ne[M], h[N], idx;
ll dis[N], cnt[N];
bool vis[N];

void add(int a, int b, int c) {
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}

void dijk() {
    memset(dis, -Inf, sizeof(dis));
    memset(vis, false, sizeof(vis));
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>>Q;
    for (int i = 0;i < 2 * n;i++) {
        Q.push({ 0,i });
        dis[i] = 0;
    }
    while (Q.size()) {
        auto t = Q.top();
        Q.pop();
        int v = t.second;
        if (vis[v]) {
            continue;
        }
        vis[v] = true;
        for (int i = h[v];~i;i = ne[i]) {
            int j = e[i];
            if (dis[j] < dis[v] + w[i] + maxn) {
                dis[j] = dis[v] + w[i];
                Q.push({ dis[j],j });
            }
        }
    }
}

int main() {
    n=read();
    memset(h, -1, sizeof(h));
    idx = 0;
    int ma = -1;
    for (int i = 0;i < n;i++) {
        for (int j = 0;j < n;j++) {
            int a;
            a=read();
            ma = max(ma, abs(a));
            add(j + n, i, a);
            add(i, j + n, -a);
        }
    }
    dijk();
    sort(dis, dis + 2 * n);
    ll ans = 0;
    for (int i = 0;i < 2 * n;i++) {
        ans += abs(dis[i] - dis[n - 1]);
    }
    cout << ans << endl;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值