[春季测试 2023] 密码锁

题目描述

寒假过后,小 I 回到学校,发现自己忘记了自行车锁的密码,于是请你帮忙。

小 I 自行车上的密码锁有 nn 个拨圈,每个拨圈有 kk(k \leq 4k≤4)格。密码锁上的每一格都包含一个正整数,其中第 jj 个拨圈的第 ii 格上的正整数为 a _ {i, j}ai,j​。

(一个锁的例子,其中 k = n = 3k=n=3,每列表示一个拨圈,拨圈的格子从上往下编号。)

你可以对每个拨圈拨若干次(也可以不拨),每拨一次拨圈,它的格子就会进行一次轮换。形式化地,拨第 jj 个拨圈一次,则会让第 jj 个拨圈上第 ii 格的数字移动到第 ((i \bmod k) + 1)((imodk)+1) 格,其他拨圈不动。

(一个拨动拨圈的例子,对左侧的锁拨一次第二个拨圈得到右侧的锁。)

为了方便记忆,小 I 设定密码时要求同一行上的数字尽可能靠近。 形式化地,对于 1 \leq i \leq k1≤i≤k,定义密码锁第 ii 行的松散度为

c(i) = \max \limits _ {j = 1} ^ n a _ {i, j} - \min \limits _ {j = 1} ^ n a _ {i, j}c(i)=j=1maxn​ai,j​−j=1minn​ai,j​

同时定义整个密码锁的松散度为

C = \max \limits _ {1 \leq i \leq k} c(i)C=1≤i≤kmax​c(i)

因为能开锁的状态满足 CC 尽可能小,因此小 I 希望你找出最小的 CC 值。

输入格式

本题有多组测试数据,题目保证一个测试点中所有测试数据的 kk 相同。

第一行包含两个正整数 T, kT,k,分别表示测试数据组数和密码锁拨圈上的格数。

接下来一共 TT 组数据,每组数据格式如下:

第一行包含一个正整数 nn,表示拨圈数。

接下来 kk 行,每行包含 nn 个正整数,其中第 ii 行第 jj 个整数 a _ {i,j}ai,j​ 表示密码锁第 jj 个拨圈上第 ii 格对应的数字。

注意输入的矩阵中每一列对应一个拨圈,而非每一行对应一个拨圈。

输出格式

对于每组数据,输出一行包含一个整数,表示所有方案中 CC 的最小值。

输入输出样例

输入 #1复制

2 3
3
1 2 1
2 3 2
3 1 3
2
1 2
2 1
1 2

输出 #1复制

0
1

输入 #2复制

见选手目录下的 lock/lock2.in。

输出 #2复制

见选手目录下的 lock/lock2.ans。

输入 #3复制

见选手目录下的 lock/lock3.in。

输出 #3复制

见选手目录下的 lock/lock3.ans。

输入 #4复制

见选手目录下的 lock/lock4.in。

输出 #4复制

见选手目录下的 lock/lock4.ans。

输入 #5复制

见选手目录下的 lock/lock5.in。

输出 #5复制

见选手目录下的 lock/lock5.ans。

说明/提示

【样例 1 解释】

第一组样例对应题目描述中的例子。 在拨第二个拨圈一次后,每个拨圈都是 \{1, 2, 3\}{1,2,3},此时松散度为 00。 容易证明无论如何松散度都不可能小于 00,因此输出 00。

以下四个样例分别对应 k = 1, 2, 3, 4k=1,2,3,4 的情况,且样例中 nn 的取值有一定梯度。

【数据范围】

设 \sum n∑n 为一个测试点中所有测试数据的 nn 的和。

对于所有数据,保证 1 \leq T1≤T,1 \leq k \leq 41≤k≤4,1 \leq a _ {i ,j} \leq 3 \times 10 ^ 41≤ai,j​≤3×104。

本题分为两类测试点。

第一类测试点共有十二个,保证 k \leq 3k≤3,n \leq 5 \times 10 ^ 4n≤5×104,\sum n \leq 1.5 \times 10 ^ 5∑n≤1.5×105。

测试点编号 n \leqn≤ \sum n \leq∑n≤ k =k=
11 2020 100100 11
22 5 \times 10 ^ 45×104 1.5 \times 10 ^ 51.5×105 11
33 2020 100100 22
44 100100 10001000 22
55 20002000 10 ^ 4104 22
66 5 \times 10 ^ 45×104 1.5 \times 10 ^ 51.5×105 22
77 1010 5050 33
88 5050 500500 33
99 300300 30003000 33
1010 30003000 2 \times 10 ^ 42×104 33
1111 3 \times 10 ^ 43×104 1.2 \times 10 ^ 51.2×105 33
1212 5 \times 10 ^ 45×104 1.5 \times 10 ^ 51.5×105 33

第二类测试点共有八个,保证 k = 4k=4,n \leq 10 ^ 4n≤104, \sum n \leq 3 \times 10 ^ 4∑n≤3×104。

测试点编号 n \leqn≤ \sum n \leq∑n≤ k =k=
1313 1010 5050 44
1414 5050 500500 44
1515 200200 20002000 44
1616 500500 40004000 44
1717 25002500 10 ^ 4104 44
1818 50005000 2 \times 10 ^ 42×104 44
1919 10 ^ 4104 3 \times 10 ^ 43×104 44
2020 10 ^ 4104 3 \times 10 ^ 43×104 44

【后记】

你花了九牛二虎之力算出 CC 的值之后,小 I 却告诉你他已经找开锁师傅用锤子暴力破解了。在你的百般劝说下,小 I 承诺以后锁车不用有大于等于一万个拨圈的密码锁。

 

upd: 被指出 k=3k=3 的写法有问题,已经换上了新写法。

k=1,2k=1,2 的思路非常简单。

我们考虑 k=3k=3,首先显然可以二分答案,然后在 k=2k=2 的时候,我们提到了 \max,\minmax,min,我们考虑扩展一下思路。首先显然全局最大值和最小值在同一行中的情况一定是不优的。所以我们来枚举它们分别在哪两行。注意,这里有可能会出现一些奇怪的情况,比如极值不止一个,或者因为全局最大值和最小值在同一列导致无法达成目标。不过因为我们这样放,只是假设某两行的极值是给定值,而这个假设值比事实上的值带来的限制一定不会更松。所以只要能在这样的限制下找到解,一定是合法的,并且由于最终解中一定可以存在在不同行的 \max,\minmax,min,所以我们也不会遗漏情况。

然后说完这些东西剩下的就很简单了。考虑二分的值确定了这两行数的范围,从而每一列能在不确定的第三行放哪个数也是可以确定的。然后问题就转化为了,有 nn 个数,每个数可以有不超过 33 种的选择,问极差能不能不超过某个数。这当然可以用卡牌游戏做,不过有一个更具有扩展性的做法。

考虑对于每个数 xx,它对于 \minmin 的限制是 [x-k,x][x−k,x],其中 kk 是我们二分的极差。然后我们对于每一列可能的数,我们把它对应的限制求并后加入统计。看看是否存在一个点被 nn 个限制同时覆盖即可。线段求并只需按照 ll 排序后扫一遍,覆盖次数可以用线段树求解。时间复杂度 \mathcal{O}(kn\log^2 a)O(knlog2a)。

然后我们来看看 k=4k=4 的情况。钦定完 \max,\minmax,min 后思路是类似的,只不过这次我们对于 \minmin 的限制变成了 22 维的。模仿上面的思路,我们需要矩阵并和在矩阵并后的图形上整体 +1+1。因为矩阵并之后不像线段依然能保持一个好维护的形状,所以我们考虑把并后的图形拆成若干矩形,然后再用扫描线维护矩形加。这个拆我们可以考虑容斥。考虑矩阵集合的一个子集 SS,我们求出这个集合的交,如果 |S|∣S∣ 为奇数,就矩阵 +1+1,否则就 -1−1,原因显然。如果你还不太理解可以看看这幅图:

蓝色,绿色,黄色矩形直接加,然后红色矩形减,即可。然后就做完了。时间复杂度 \mathcal{O}(k2^kn\log^2 a)O(k2knlog2a)。

我们来考虑其他常数更小的做法。发现在扫描线矩阵求并的过程中,我们要做的事情是对于若干高度一样的矩阵求并。本质上就是线段求并,这启发我们抛弃容斥,用类似扫描线的思路来做这个东西。

考虑把所有端点都离散化,然后离散化后相邻的两个位置,它们之间的矩形结构是相同的,可以一起处理。(还有一种理解思路就是 k=3k=3 的情况长高了)找到应该加的矩形后,后面就一模一样了,具体实现可以见代码,或者可以看看这幅图:

我们分别把绿色红色蓝色黄色一条条加进去即可。

代码中是把 x,yx,y 坐标一起离散化,这样单次 work4 的线段树修改次数是 4nk4nk 的,如果 x,yx,y 分别离散化可以做到 2nk2nk,但我懒得写了。时间复杂度 \mathcal{O}(nk\log^2a)O(nklog2a)。

#include 
#include 
#include 
#include 
const int N = 1e5 + 10; int a[5][N]; 
std::vector v3[N]; int d[N], mx, mn;
struct IO
{
    static const int N = 1 << 22;
    char buf[N], pbuf[N], *p1 = buf, *p2 = buf, *pp = pbuf;
    #define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, N, stdin), p1 == p2) ? EOF : *p1++)  
    template 
    void read(T& x)
    {
        x = 0; char ch; int f = 0;
        while ((ch = gc()) < '0' || ch > '9') f |= (ch == '-');
        while (x = (x << 1) + (x << 3) + (ch ^ 48), (ch = gc()) >= '0' && ch <= '9') ;
        if (f) x = ~x + 1;
    }
    void putc(char c)
    {
        if (pp - pbuf == N) fwrite(pbuf, 1, N, stdout), pp = pbuf;
        *pp++ = c;
    }
    void puts(const char* s) { while (*s) putc(*s), ++s; putc('\n'); }
    template 
    void print(T x)
    {
        static int st[20]; int tp = 0;
        if (x < 0) putc('-'), x = ~x + 1;
        do st[++tp] = x % 10, x /= 10; while (x);
        while (tp) putc(st[tp--] + '0');
    }
    ~IO() { fwrite(pbuf, pp - pbuf, 1, stdout); }
}io;
struct SegTree
{
    #define ls(k) (k << 1)
    #define rs(k) (k << 1 | 1)
    struct node{ int mx, tag, clr; }h[N << 2];
    void pushup(int k) { h[k].mx = std::max(h[ls(k)].mx, h[rs(k)].mx); }
    void add(int k, int v) { h[k].mx += v; h[k].tag += v; }
    void clr(int k) { h[k].clr = 1; h[k].mx = h[k].tag = 0; }
    void pushdown(int k)
    {
        if (h[k].clr) clr(ls(k)), clr(rs(k)), h[k].clr = 0;
        if (h[k].tag) add(ls(k), h[k].tag), add(rs(k), h[k].tag), h[k].tag = 0;
    }
    void change(int k, int l, int r, int x, int y, int v)
    {
        if (x <= l && r <= y) return add(k, v);
        int mid = (l + r) >> 1; pushdown(k);
        if (x <= mid) change(ls(k), l, mid, x, y, v);
        if (mid < y) change(rs(k), mid + 1, r, x, y, v);
        pushup(k);
    }
    #undef ls
    #undef rs
}sgt;
bool work3(int n, int k)
{
   	sgt.clr(1);
    for (int i = 1; i <= n; ++i)
    {
        std::pair seg[3];
        for (int j = 0; j < (int)v3[i].size(); ++j)
            seg[j].first = std::max(mn, v3[i][j] - k), seg[j].second = v3[i][j];
        std::sort(seg, seg + v3[i].size());
		sgt.change(1, mn, mx, seg[0].first, seg[0].second, 1);
        ++d[seg[0].first]; --d[seg[0].second + 1];
        for (int j = 1; j < (int)v3[i].size(); ++j) 
        {
            if (seg[j].first > seg[j - 1].second)
				sgt.change(1, mn, mx, seg[j].first, seg[j].second, 1);
            else if (seg[j - 1].second + 1 <= seg[j].second)
				sgt.change(1, mn, mx, seg[j - 1].second + 1, seg[j].second, 1);
        }
    }
	return (sgt.h[1].mx == n);
}
bool check3(int n, int l, int r, int k)
{
    bool flg = 1; for (int i = 1; i <= n; ++i) v3[i].clear();
    for (int i = 1; i <= n; ++i)
    {
        if (a[1][i] >= l && a[2][i] <= r) v3[i].push_back(a[3][i]);
        if (a[2][i] >= l && a[3][i] <= r) v3[i].push_back(a[1][i]);
        if (a[3][i] >= l && a[1][i] <= r) v3[i].push_back(a[2][i]);
        if (v3[i].empty()) { flg = 0; break; }
    }
    if (flg && work3(n, k)) return true;
    flg = 1; for (int i = 1; i <= n; ++i) v3[i].clear();
    for (int i = 1; i <= n; ++i)
    {
        if (a[1][i] >= l && a[3][i] <= r) v3[i].push_back(a[2][i]);
        if (a[2][i] >= l && a[1][i] <= r) v3[i].push_back(a[3][i]);
        if (a[3][i] >= l && a[2][i] <= r) v3[i].push_back(a[1][i]);
        if (v3[i].empty()) { flg = 0; break; }
    }
    if (flg && work3(n, k)) return true;
    return false;
}
std::vector> vec[N]; std::pair v4[N][4]; int siz[N];
int bkt[N * 16], tp, t[20], cnt; 
std::vector> ad[20], dl[20];
bool work4(int n, int k)
{
    tp = 0; int flg = 0;
    for (int i = 1; i <= n; ++i)
    {
        cnt = 0;
        for (int j = 0; j < siz[i]; ++j)
        {
            t[++cnt] = std::max(v4[i][j].first - k, mn);
            t[++cnt] = std::max(v4[i][j].second - k, mn);
            t[++cnt] = v4[i][j].first + 1; 
            t[++cnt] = v4[i][j].second + 1;
        }
        std::sort(t + 1, t + cnt + 1); cnt = std::unique(t + 1, t + cnt + 1) - t - 1;
        for (int j = 1; j <= cnt; ++j) ad[j].clear(), dl[j].clear();
        for (int j = 0; j < siz[i]; ++j)
        {
            int lef = std::lower_bound(t + 1, t + cnt + 1, std::max(v4[i][j].first - k, mn)) - t,
                dw = std::lower_bound(t + 1, t + cnt + 1, std::max(v4[i][j].second - k, mn)) - t,
                rig = std::lower_bound(t + 1, t + cnt + 1, v4[i][j].first + 1) - t,
                up = std::lower_bound(t + 1, t + cnt + 1, v4[i][j].second + 1) - t;
            ad[dw].emplace_back(lef, rig);
            dl[up].emplace_back(lef, rig);
        }
        for (int j = 1; j <= cnt; ++j) d[j] = 0, bkt[++tp] = t[j];
        for (int j = 1; j <= cnt; ++j)
        {
            int sum = 0, las = -1;
            for (auto p : ad[j]) ++d[p.first], --d[p.second];
            for (auto p : dl[j]) --d[p.first], ++d[p.second];
            for (int k = 1; k <= cnt; ++k)
            {
                sum += d[k];
                if (!sum)
                {
                    if (las == -1) continue;
                    vec[t[j]].emplace_back(t[las], 1);
                    vec[t[j]].emplace_back(t[k], -1);
                    vec[t[j + 1]].emplace_back(t[las], -1);
                    vec[t[j + 1]].emplace_back(t[k], 1);
                    las = -1;
                } else if (las == -1) las = k;
            }
        }
    }
    std::sort(bkt + 1, bkt + tp + 1);
    tp = std::unique(bkt + 1, bkt + tp + 1) - bkt - 1;
    for (int i = 1; i <= tp; ++i)
    {
        vec[bkt[i]].emplace_back(mn, 0);
        vec[bkt[i]].emplace_back(mx + 1, 0);
    }
    sgt.clr(1);
    for(int i = 1; i <= tp; ++i)
    {
        auto& v = vec[bkt[i]];
        std::sort(v.begin(), v.end()); int sum = v[0].second;
        for (int j = 1; j < (int)v.size(); ++j)
        {
            if (v[j - 1].first < v[j].first && sum)
                sgt.change(1, mn, mx, v[j - 1].first, v[j].first - 1, sum);
            sum += v[j].second;
        }
        if (sgt.h[1].mx == n) { flg = 1; break; }
    }
    for (int i = 1; i <= tp; ++i) vec[bkt[i]].clear();
    return flg;
}
bool check4(int n, int l, int r, int k)
{
    bool flg = 1; 
    for (int i = 1; i <= n; ++i)
    {
        siz[i] = 0;
        if (a[1][i] >= l && a[2][i] <= r) v4[i][siz[i]++] = std::make_pair(a[3][i], a[4][i]);
        if (a[2][i] >= l && a[3][i] <= r) v4[i][siz[i]++] = std::make_pair(a[4][i], a[1][i]);
        if (a[3][i] >= l && a[4][i] <= r) v4[i][siz[i]++] = std::make_pair(a[1][i], a[2][i]);
        if (a[4][i] >= l && a[1][i] <= r) v4[i][siz[i]++] = std::make_pair(a[2][i], a[3][i]);
        if (!siz[i]) { flg = 0; break; }
    }
    if (flg && work4(n, k)) return true;
    flg = 1;
    for (int i = 1; i <= n; ++i)
    {
        siz[i] = 0;
        if (a[1][i] >= l && a[3][i] <= r) v4[i][siz[i]++] = std::make_pair(a[4][i], a[2][i]);
        if (a[2][i] >= l && a[4][i] <= r) v4[i][siz[i]++] = std::make_pair(a[1][i], a[3][i]);
        if (a[3][i] >= l && a[1][i] <= r) v4[i][siz[i]++] = std::make_pair(a[2][i], a[4][i]);
        if (a[4][i] >= l && a[2][i] <= r) v4[i][siz[i]++] = std::make_pair(a[3][i], a[1][i]);
        if (!siz[i]) { flg = 0; break; }
    }
    if (flg && work4(n, k)) return true;
    flg = 1;
    for (int i = 1; i <= n; ++i)
    {
        siz[i] = 0;
        if (a[1][i] >= l && a[4][i] <= r) v4[i][siz[i]++] = std::make_pair(a[2][i], a[3][i]);
        if (a[2][i] >= l && a[1][i] <= r) v4[i][siz[i]++] = std::make_pair(a[3][i], a[4][i]);
        if (a[3][i] >= l && a[2][i] <= r) v4[i][siz[i]++] = std::make_pair(a[4][i], a[1][i]);
        if (a[4][i] >= l && a[3][i] <= r) v4[i][siz[i]++] = std::make_pair(a[1][i], a[2][i]);
        if (!siz[i]) { flg = 0; break; }
    }
    if (flg && work4(n, k)) return true;
    return false;
}
int main()
{ 
    int qwq, k; io.read(qwq); io.read(k);
    while (qwq--)
    {
        int n; mx = 0, mn = 2e9; io.read(n);
        for (int i = 1; i <= k; ++i)
            for (int j = 1; j <= n; ++j) 
                io.read(a[i][j]), mx = std::max(mx, a[i][j]), mn = std::min(mn, a[i][j]);
        if (k == 1) io.print(mx - mn);
        else if (k == 2)
        {
            for (int i = 1; i <= n; ++i)
                if (a[1][i] < a[2][i]) std::swap(a[1][i], a[2][i]);
            int ans = 0;
            for (int i = 1; i <= 2; ++i)
            {
                int mx = 0, mn = 2e9;
                for (int j = 1; j <= n; ++j)
                    mx = std::max(mx, a[i][j]), mn = std::min(mn, a[i][j]);
                ans = std::max(ans, mx - mn);
            }
            io.print(ans);
        }
        else if (k == 3)
        {
            int l = 0, r = mx - mn, mid, ans = -1;
            while (l <= r)
            {
                mid = (l + r) >> 1;
                if (check3(n, mx - mid, mn + mid, mid)) ans = mid, r = mid - 1;
                else l = mid + 1;
            }
            io.print(ans);
        }
        else if (k == 4)
        {
            int l = 0, r = mx - mn, mid, ans = -1;
            while (l <= r)
            {
                mid = (l + r) >> 1;
                if (check4(n, mx - mid, mn + mid, mid)) ans = mid, r = mid - 1;
                else l = mid + 1;
            }
            io.print(ans);
        }
        io.puts("");
    }
    return 0;
}

 

你可能感兴趣的:(c++)