JOI 2016/2017本選 T3 JOIOI 王国

有点长,放个目录。

题意

这题被强行魔改了不知道几次。

给一个填了数字的矩阵,把一个矩阵分成两块,每一块联通并且只能是“阶梯状”的,按原文说是同一块一个点走到另一个点最多只拐一个弯。

求最小化两块内部的极差的最大值。

思路

做这道题虽然花了很长时间,但是很有收获啊。学了标程算法,自己也想出一种。

贪心

第一眼看这一题明显是一个贪心啊qwq。先假装所有数都在同一个块中,也钦定矩阵的最小值必须放在此块内,那么为了减小这块的极差,我们就必须取出最大的放到另一块中(不然就一点用都没有啊)。在取出一个数时,为了维持两块的形状,把他右下方矩形整个都取出,放入另一个块。

然后为了防止最小值并到其他角更优,把矩阵旋转90,180,270度再重复上述操作。

然后卡卡常就好了。 还学了一些清空queue的奇技淫巧。。。

二分答案

代码比贪心不知道短到哪里去了,常数也比贪心不知道小到哪里去了。一个可以写成 O ( n l o g n ) O(nlogn) O(nlogn)的check写成 O ( n 2 ) O(n^2) O(n2)也很稳。

于是思路还是钦定最小值在左上,然后很容易发现极差的单调性,也可以根据题目的说法“最大的最小”来猜测。于是二分极差,小于等于 m i n v a l + m i d minval+mid minval+mid且满足形状要求的数可以放在左上,其他数再求极差,如果小于等于 m i d mid mid,那么 m i d mid mid可行。

旋转一下答案get。

样例

能拍出我的错的样例:


/*
4 4  
1 1 1 1 
1 4 1 5
1 1 2 5
1 5 5 5
-----
ans = 3
*/

/*
3 4
10 12  1  2
14 12 12 11
 6 12  1 13
---------
ans = 12
*/

/*
8 6
23 23 10 11 16 21
15 26 19 28 19 20
25 26 28 16 15 11
11 8 19 11 15 24
14 19 15 14 24 11
10 8 11 7 6 14
23 5 19 23 17 17
18 11 21 14 20 16
----------
ans = 18
*/

/*
10 9
625 978 347 845 971 929 924 953 589 
741 289 975 767 384 795 633 159 842 
261 1002 980 169 655 342 139 209 658 
269 670 110 697 545 593 555 400 709 
901 122 794 0 477 86 833 570 789 
47 708 129 967 513 915 954 351 675 
969 437 212 466 76 346 717 76 112 
696 941 746 916 318 540 20 886 868 
255 228 897 470 69 855 940 338 923 
672 211 662 703 752 850 477 383 128
------------
ans = 967
*/

/*
6 5
43 25 2 93 102 
44 67 71 59 62 
93 47 39 6 59 
11 71 96 28 65 
56 84 84 46 106 
2 103 58 101 24 
--------
ans = 91

solution:
43 25 2 93 | 102 
44 67 71 59| 62 
93 47 39 6 | 59 
      ------
11 71 | 96 28 65 
   ----
56 | 84 84 46 106 
2  |103 58 101 24
*/

/*
20 15
4 39 27 22 10 43 0 27 7 49 7 39 40 26 40 
25 25 2 7 24 26 10 14 6 46 35 30 32 0 37 
37 2 48 46 13 45 4 38 48 31 6 28 27 14 6 
38 51 10 25 33 43 28 37 43 21 0 38 49 48 34 
28 32 42 16 32 22 51 25 33 33 0 0 35 46 11 
8 40 28 39 43 48 27 38 18 23 4 0 50 21 20 
5 38 29 11 34 22 33 36 43 12 8 22 12 51 2 
30 23 28 40 0 6 43 26 51 39 29 13 27 34 19 
23 51 13 26 20 51 25 35 17 30 51 1 42 36 36 
49 14 8 45 41 12 38 21 1 16 12 32 8 42 50 
6 37 5 15 44 5 1 33 48 23 31 8 50 29 3 
10 32 44 48 12 13 40 0 14 44 5 4 52 14 24 
21 25 34 16 7 8 20 3 29 38 3 31 47 17 22 
0 28 33 8 30 18 6 42 44 30 47 0 22 29 49 
10 38 21 8 22 26 49 2 29 45 23 27 20 8 45 
20 27 49 21 46 34 28 37 41 37 44 33 20 44 48 
2 7 21 39 5 8 32 25 46 49 0 32 1 50 10 
12 25 42 30 16 45 8 22 25 49 0 38 25 40 47 
17 15 40 42 37 5 35 38 0 20 5 33 52 48 27 
39 16 40 45 35 9 44 34 7 33 16 29 24 2 24
---------------
ans = 51
*/

/*
3 10
1 13 1 65 17 85 61 71 81 61 
97 9 37 91 1 76 36 1 21 41 
31 26 69 41 24 81 13 51 21 85
-------------
ans = 88
*/

代码

在放正解之前当然要看一看虽然已经被hack掉但是A了65分的代码(还有一些样例):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int dx[4] = {-1, 0, 1, 0};
const int dy[4] = {0, -1, 0, 1};
const int inf = 1e9;
const int N = 2010;
int n, m, a[N][N], ans;
struct NODE{
    int x, y, val;
    bool operator < (const NODE &u)const{
        return val < u.val;
    }
    bool operator > (const NODE &u)const{
        return val > u.val;
    }
};
priority_queue<NODE> gg;
priority_queue<NODE, vector<NODE>, greater<NODE> > lg;
int minval, maxval, minl, maxl, minr, maxr;
queue<pair<int, int> > q;
int used[N][N], b[N][N];

inline void Rotate()
{
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j)
            b[j][n-i+1] = a[i][j];
    swap(n, m);
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j)
            a[i][j] = b[i][j];
}

inline void Update(int sx, int sy, int dir, int &mina, int &maxa)
{
    // cout << sx << " " << sy << " " << dir << ":::---_____---:::" << endl;
    while (!q.empty()) q.pop();
    q.push(make_pair(sx, sy)); used[sx][sy] = dir;
    while (!q.empty()){
        int ux = q.front().first;
        int uy = q.front().second;
        // cout << ux << " " << uy << endl;
        q.pop();
        if (mina == -1) mina = a[ux][uy];
        mina = min(mina, a[ux][uy]);
        maxa = max(maxa, a[ux][uy]);
        for (int k = dir; k <= dir+1; ++ k){
            int nx = ux+dx[k];
            int ny = uy+dy[k];
            if (used[nx][ny] != -1 || nx <= 0 || nx > n || ny <= 0 || ny > m) continue;
            used[nx][ny] = dir;
            q.push(make_pair(nx, ny));
        }
    }
}

inline void Solve()
{
    minl = maxl = minr = maxr = -1;
    memset(used, -1, sizeof(used));
    while (!gg.empty()) gg.pop();
    while (!lg.empty()) lg.pop();
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j){
            gg.push((NODE){i, j, a[i][j]});
            lg.push((NODE){i, j, a[i][j]});
        }
    while (1){
        maxval = -1;
        while (!gg.empty()){
            int x = gg.top().x;
            int y = gg.top().y;
            if (used[x][y] != -1) gg.pop();
            else{
                maxval = gg.top().val;
                break;
            }
        }
        if (maxval == -1) break;
        while (!lg.empty()){
            int x = lg.top().x;
            int y = lg.top().y;
            if (used[x][y] != -1) lg.pop();
            else{
                minval = lg.top().val;
                break;
            }
        }
        // cout << maxval << " " << minval << " !" << endl;
        int pminl = minl, pmaxl = maxl, pminr = minr, pmaxr = maxr;
        if (maxl-maxr >= maxr-minr){
            while (gg.top().val == maxval){
                int x = gg.top().x;
                int y = gg.top().y;
                if (used[x][y] == -1) Update(x, y, 2, minr, maxr);
                gg.pop();
            }
        } else {
            while (lg.top().val == minval){
                int x = lg.top().x;
                int y = lg.top().y;
                if (used[x][y] == 2){
                    if (pminl == -1) ans = min(ans, maxval-minval);
                    else ans = min(ans, min(max(max(maxval, pmaxl)-pminl, pmaxr-pminr), max(pmaxl-pminl, pmaxr-min(minval, pminr))));
                    return;
                }
                else if (used[x][y] == -1) Update(x, y, 0, minl, maxl);
                lg.pop();
            }
        }
        // cout << minl << " " << maxl << " " << minr << " " << maxr << endl;
    }
    ans = min(ans, max(maxl-minl, maxr-minr));
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j)
            scanf("%d", &a[i][j]);
    ans = inf;
    for (int i = 0; i < 4; ++ i){
        // cout << endl;
        // for (int j = 1; j <= n; ++ j){
        //     for (int k = 1; k <= m; ++ k)
        //         cout << a[j][k] << " ";
        //     cout << endl;
        // }
        Solve();
        if (i < 3) Rotate();
    }
    printf("%d\n", ans);
    return 0;
}

贪心:

#pragma GCC optimize("3", "inline", "Ofast")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int dx[2] = {1, 0};
const int dy[2] = {0, 1};
const int inf = 1e9;
const int N = 2010;
int n, m, a[N][N], ans;
struct NODE{
    int x, y, val;
    bool operator < (const NODE &u)const{
        return val < u.val;
    }
    bool operator > (const NODE &u)const{
        return val > u.val;
    }
};
priority_queue<NODE> q;
int minval, mina, maxa;
char used[N][N];
queue<pair<int, int> > p;

inline void chkMin(int &x, int y){if (x > y) x = y;}
inline void chkMax(int &x, int y){if (x < y) x = y;}

int b[N][N];
inline void Rotate()
{
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j)
            b[j][n-i+1] = a[i][j];
    swap(n, m);
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j)
            a[i][j] = b[i][j];
}

int ux, uy, nx, ny;
inline void Update(int sx, int sy)
{
    while (!p.empty()) p.pop();
    p.push(make_pair(sx, sy)); used[sx][sy] = 1;
    while (!p.empty()){
        ux = p.front().first;
        uy = p.front().second;
        p.pop();
        chkMin(mina, a[ux][uy]);
        chkMax(maxa, a[ux][uy]);
        for (int k = 0; k < 2; ++ k){
            nx = ux+dx[k];
            ny = uy+dy[k];
            if (used[nx][ny] || nx <= 0 || nx > n || ny <= 0 || ny > m) continue;
            used[nx][ny] = 1;
            p.push(make_pair(nx, ny));
        }
    }
}

void Solve()
{
    memset(used, 0, sizeof(used));
    q = priority_queue<NODE>();
    // 因为while基本到一半的时候就break掉了,很多元素还留在堆里
    // 如果while (!q.empty()) q.pop()就稳稳地TLE了,而且常数大了10倍,有点可怕
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j){
            q.push((NODE){i, j, a[i][j]});
        }
    chkMin(ans, q.top().val-minval);
    mina = inf; maxa = 0;
    while (!q.empty()){
        while (!q.empty() && used[q.top().x][q.top().y]) q.pop();
        if (q.empty()) break;
        Update(q.top().x, q.top().y);
        while (!q.empty() && used[q.top().x][q.top().y]) q.pop();
        if (q.empty()) break;
        if (maxa-mina > q.top().val-minval){
            chkMin(ans, maxa-mina);
            break;
        }
        chkMin(ans, q.top().val-minval);
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    minval = inf;
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j){
            scanf("%d", &a[i][j]);
            if (a[i][j] < minval) minval = a[i][j];
        }
    ans = inf;
    for (int i = 0; i < 4; ++ i){
        Solve();
        if (i < 3) Rotate();
    } 
    printf("%d\n", ans);
    return 0;
}

二分答案:

#include<bits/stdc++.h>
using namespace std;
const int inf = 1e9;
const int N = 2010;
int n, m, a[N][N], ans, minval, maxval, maxl[N][N], minr[N][N];

inline void chkMin(int &x, int y){if (x > y) x = y;}
inline void chkMax(int &x, int y){if (x < y) x = y;}

int b[N][N];
void Rotate()
{
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j)
            b[j][n-i+1] = a[i][j];
    swap(n, m);
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j)
            a[i][j] = b[i][j];
}

void Initialize()
{
    for (int i = 1; i <= n; ++ i){
        maxl[i][0] = 0;
        for (int j = 1; j <= m; ++ j)
            maxl[i][j] = max(maxl[i][j-1], a[i][j]);
        maxl[i][m+1] = inf;
        minr[i][m+1] = inf;
        for (int j = m; j >= 1; -- j)
            minr[i][j] = min(minr[i][j+1], a[i][j]);
    }
}

bool Check(int l)
{
    int minv = inf, pre = m+1;
    for (int i = 1; i <= n; ++ i){
        bool fl = 1;
        for (int j = 1; j <= pre; ++ j)
            if (maxl[i][j] > l){
                chkMin(minv, minr[i][j]);
                pre = j;
                fl = 0;
                break;
            }
        if (fl) chkMin(minv, minr[i][pre]);
    }
    return (maxval-minv <= l-minval);
}

void Solve()
{
    int l = 0, r = inf, mid, ret;
    while (l <= r){
        mid = (l+r)>>1;
        if (Check(mid+minval))
            ret = mid, r = mid-1;
        else
            l = mid+1;
    }
    chkMin(ans, ret);
}

int main()
{
    scanf("%d%d", &n, &m);
    maxval = 0;
    minval = inf;
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= m; ++ j){
            scanf("%d", &a[i][j]);
            chkMax(maxval, a[i][j]);
            chkMin(minval, a[i][j]);
        }
    ans = inf;
    for (int i = 0; i < 4; ++ i){
        Initialize();
        Solve();
        if (i < 3) Rotate();
    }
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值