洛谷-P11105 [ROI 2023] 解密 题解

QOJ 可以评测

我们先考虑两个简单的 Subtask。

Subtask 1

\(k=1\)。一个自然的想法是直接传这个数 \(+1\),如果数是 \(n\) 就传 \(1\)。也就是传 \((x \bmod n)+1\)。

Subtask 2

对两个数分别执行上面的 \(+1\) 操作即可。但是两数如果在环上相邻就炸了,此时我们把每个数 \(+2\) 即可。

正解

上面的做法启发我们把 \([1,n]\) 的值域视作首尾相连的环(即 \(n\) 的后继为 \(1\))。

前两个 Subtask 的做法启发我们想到如下策略:

  1. 初始时 \(R=\varnothing\)
  2. 给 \(T\) 中所有数打标记
  3. 从小到大遍历 \(T\),从 \(T\) 中每个数 \(x\) 开始,找到它往后第一个未标记的数 \(y\)
  4. 标记 \(y\) 并将 \(y\) 加入 \(R\)

对于解密,依然从小到大遍历,只需要把以上过程中的"往后"换成"往前"即可。手玩样例,发现这个策略非常对。

但是直接暴力做是 \(O(n^2)\) 的。不难发现所有未标记的数最多形成 \(k+1\) 个连续段。开一个 set 维护这些连续段即可。时间复杂度 \(O(\sum k\log k)\)。

cpp 复制代码
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i(a);i<b;++i)
#define eb emplace_back
#define vi vector<int>
#define pii pair<int,int>
using namespace std;
constexpr int INF=2e9;
set<pii> s;
const vi Encode(int n,int k,vi v){
    vi res;
    s.clear();
    sort(v.begin(),v.end());
    if(v[0]>1) s.emplace(1,v[0]-1);
    if(v[k-1]<n) s.emplace(v[k-1]+1,n);
    rep(i,0,k-1) if(v[i]+1<v[i+1]) s.emplace(v[i]+1,v[i+1]-1);
    rep(i,0,k){
        auto it=s.upper_bound({v[i],INF});
        if(it==s.end()) it=s.begin();
        int l=it->first,r=it->second;
        res.eb(l);
        s.erase(it);
        if(l+1<=r) s.emplace(l+1,r);
    }
    sort(res.begin(),res.end());
    return res;
}
const vi Decode(int n,int k,vi v){
    vi res;
    s.clear();
    sort(v.begin(),v.end());
    if(v[0]>1) s.emplace(1,v[0]-1);
    if(v[k-1]<n) s.emplace(v[k-1]+1,n);
    rep(i,0,k-1) if(v[i]+1<v[i+1]) s.emplace(v[i]+1,v[i+1]-1);
    rep(i,0,k){
        auto it=s.upper_bound({v[i],INF});
        if(it==s.begin()) it=s.end();
        it=prev(it);
        int l=it->first,r=it->second;
        res.eb(r);
        s.erase(it);
        if(l<=r-1) s.emplace(l,r-1);
    }
    sort(res.begin(),res.end());
    return res;
}