P9361 [ICPC2022 Xi‘an R] Contests 题解

题意

大致题意见题目描述,这里主要解释一下选手 x x x l l l-强于」 选手 y y y

以样例为例,对于第一个询问:1 4。在第一场比赛中选手 1 1 1 的排名已经比选手 4 4 4 的排名靠前,所以 l min ⁡ = 1 l_{\min} = 1 lmin=1

对于第二个询问:5 3。虽然在第一场比赛中选手 5 5 5 比选手 3 3 3 差,但是比选手 4 4 4 好;而第二场比赛中选手 4 4 4 又比选手 3 3 3 好,所以 l min ⁡ = 2 l_{\min}=2 lmin=2

对于第三个询问:6 1。我们用 x → y x \to y xy 表示选手 x x x 比选手 y y y 在某一场比赛中排名更靠前,则有: 6 → 5 → 4 → 3 → 2 → 1 6\to5\to4\to3\to2\to1 654321,所以 l min ⁡ = 5 l_{\min}=5 lmin=5

对于第四个询问:5 2。那么有 5 → 4 → 3 → 2 5\to 4\to 3\to 2 5432,所以 l min ⁡ = 3 l_{\min}=3 lmin=3

思路

暴力的做法是从小到大枚举步数,并维护不超过 k k k 步能到达的所有选手。注意到我们永远可以通过“ m m m 场比赛的前缀并”来描述下一步能到的所有选手,于是模拟过程中我们可以只维护 m m m 个选手编号。

考虑使用倍增加速,预处理每个跳 2 0 , 2 1 , 2 2 , … 2^0,2^1,2^2,\dots 20,21,22, 步能到的 m m m 维最值即可,复杂度 O ( ( n + q ) m 2 log ⁡ n ) O((n+q)m^2\log n) O((n+q)m2logn)

具体地,我们记 f i , j , k f_{i,j,k} fi,j,k 表示 i i i 2 j 2^j 2j 步,能到的第 k k k 场比赛考的最好的排名:

  • j = 0 j=0 j=0 暴力预处理;
  • j > 0 j>0 j>0 我们枚举中转点使用的比赛 c c c,然后先跳 f i , j − 1 , c f_{i,j-1,c} fi,j1,c f j , j , k ← f c , j − 1 , k f_{j,j,k}\leftarrow f_{c,j-1,k} fj,j,kfc,j1,k

Code

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
const int maxm = 10, maxk = 25;
int n,m,T,ans,a[maxm][maxn],b[maxm][maxn];
struct hyf { int c[maxm]; } f[maxk][maxn],g[maxm][maxn]; // 倍增数组
// 两个纯用于压行的函数,展开来写也彳亍
hyf Min(const hyf &x,const hyf &y) {
    hyf res;
    for (int i = 0;i < m;i ++) res.c[i] = min(x.c[i],y.c[i]);
    return res;
}
bool operator >(const hyf &x,const hyf &y) {
    for (int i = 0;i < m;i ++)
        if (x.c[i] <= y.c[i]) return false;
    return true;
}
hyf input(const int &x) {
    hyf res;
    for (int i = 0;i < m;i ++) res.c[i] = b[i][x];
    return res;
}
int main() {
    scanf("%d%d",&n,&m);
    for (int i = 0;i < m;i ++)
        for (int j = 1;j <= n;j ++){
            scanf("%d",&a[i][j]);
            b[i][a[i][j]] = j; // 第i场比赛中选手a[i][j]的名次为j
        }
    // 预处理倍增数组
    // 处理 j = 0
    for (int i = 0;i < m;i ++)
        for (int j = n,x;j >= 1;j --) {
            x = a[i][j], g[i][j] = input(a[i][j]);
            if (j < n) g[i][j] = Min(g[i][j],g[i][j + 1]);
        }
    for (int i = 1;i <= n;i ++) {
        f[0][i] = g[0][b[0][i]];
        for (int j = 1;j < m;j ++)
            f[0][i] = Min(f[0][i], g[j][b[j][i]]);
    }
    // 处理 j > 0
    for (int b = 1;b <= 19;b ++)
        for (int i = 1;i <= n;i ++) {
            f[b][i] = f[b - 1][i];
            for (int j = 0,k;j < m;j ++) 
                k = a[j][f[b - 1][i].c[j]],
                f[b][i] = Min(f[b][i],f[b - 1][k]);
        }
    scanf("%d",&T);
    for (int t = 1,x,y;t <= T;t ++) {
        scanf("%d%d",&x,&y);
        hyf st = input(x), en = input(y);
        // 开始倍增跳
        if (st > en) { // 需要跳几场比赛,也就是一开始st没有en厉害
            int res = 0;
            for (int i = 19;i >= 0;i --) {
                hyf nxt = st;
                for (int j = 0,k;j < m;j ++) 
                    k = a[j][st.c[j]],
                    nxt = Min(nxt,f[i][k]);
                if (nxt > en) res += 1 << i, st = nxt;
            }
            if (res > n) printf("-1\n");
            else printf("%d\n",res + 2); // 记得加上开头 & 结尾
        } else printf("1\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值