我们先考虑两个简单的 Subtask。
Subtask 1
\(k=1\)。一个自然的想法是直接传这个数 \(+1\),如果数是 \(n\) 就传 \(1\)。也就是传 \((x \bmod n)+1\)。
Subtask 2
对两个数分别执行上面的 \(+1\) 操作即可。但是两数如果在环上相邻就炸了,此时我们把每个数 \(+2\) 即可。
正解
上面的做法启发我们把 \([1,n]\) 的值域视作首尾相连的环(即 \(n\) 的后继为 \(1\))。
前两个 Subtask 的做法启发我们想到如下策略:
- 初始时 \(R=\varnothing\)
- 给 \(T\) 中所有数打标记
- 从小到大遍历 \(T\),从 \(T\) 中每个数 \(x\) 开始,找到它往后第一个未标记的数 \(y\)
- 标记 \(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;
}