转化成逆排列(下标和值互换),设其为 ,那么操作就变成了如果 ,则可以交换 ,也就是对于任意的 , 始终要在 前面。
如果钦定了某些元素之间的相对顺序,那么任何满足这个相对顺序的序列都能通过不断交换两个相邻的元素互相得到。
假设要让 变为 ,现将 中的元素重标号为其在 中的下标,如果其为 的排列那么就得到 了。当其不为 时,一定存在相邻的逆序对,交换这个逆序对即可。如果不存在逆序对,说明交换得到了 .由于我们每次交换的是逆序对,并且 和 的相对顺序限制相同,所以不会存在无法交换的情况。
现在假装得到了这些限制,想要使得原序列的字典序最小,就需要让逆排列的 尽可能靠前,在此基础上 尽可能靠前,然后是 尽可能靠前......
如果只看这些限制,就是菜肴制作这道题了,将 DAG 的反图建出来之后,拓扑排序时每次选择编号最大的放在序列的最后。但这道题如果正着建图并且每次取编号最小的放在最前面也是对的,小粉兔在这里给了证明我就不另画蛇添足什么了(
证明一下为什么建出 DAG 的反图然后每次选择编号最大的放在序列的最后是对的:
考虑如果不将最大的 放在序列的最后,而是放了 ,由于 是没有往后移动的限制的,所以将 移动到最后,这样本在 后且 的编号都往前移动了一个位置(至少有一个 会往前移动一个位置),比原先更优,所以任何一种不是 在最后的方案都能如此调整成一个更优的方案,所以 一定在序列的最后。处理好 之后,变成了一个子问题。得证。
实现上,可以用一个线段树维护原序列的区间还未出队的元素的最大值,来优化找 度点的过程。
时间复杂度 .
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#define pb emplace_back
#define mp std::make_pair
#define fi first
#define se second
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef std::pair<int, int> pii;
typedef std::vector<int> vi;
const ll mod = 998244353;
ll Add(ll x, ll y) { return (x+y>=mod) ? (x+y-mod) : (x+y); }
ll Mul(ll x, ll y) { return x * y % mod; }
ll Mod(ll x) { return x < 0 ? (x + mod) : (x >= mod ? (x-mod) : x); }
ll cadd(ll &x, ll y) { return x = (x+y>=mod) ? (x+y-mod) : (x+y); }
ll cmul(ll &x, ll y) { return x = x * y % mod; }
template <typename T> T Max(T x, T y) { return x > y ? x : y; }
template<typename T, typename... T2> T Max(T x, T2 ...y) { return Max(x, y...); }
template <typename T> T Min(T x, T y) { return x < y ? x : y; }
template<typename T, typename... T2> T Min(T x, T2 ...y) { return Min(x, y...); }
template <typename T> T cmax(T &x, T y) { return x = x > y ? x : y; }
template <typename T> T cmin(T &x, T y) { return x = x < y ? x : y; }
template <typename T>
T &read(T &r) {
r = 0; bool w = 0; char ch = getchar();
while(ch < '0' || ch > '9') w = ch == '-' ? 1 : 0, ch = getchar();
while(ch >= '0' && ch <= '9') r = r * 10 + (ch ^ 48), ch = getchar();
return r = w ? -r : r;
}
template<typename T1, typename... T2>
void read(T1 &x, T2& ...y) { read(x); read(y...); }
const int N = 500100;
const int inf = 0x7fffffff;
int n, k, a[N], b[N], id[N], ct;
#define ls tree[x].lson
#define rs tree[x].rson
#define tl tree[x].l
#define tr tree[x].r
int trnt;
struct Node {
int lson, rson, l, r, mx;
}tree[N << 1];
inline void pushup(int x) { tree[x].mx = Max(tree[ls].mx, tree[rs].mx); }
int build(int l, int r) {
int x = ++trnt; tl = l; tr = r;
if(l == r) {
tree[x].mx = a[l];
return x;
}
int mid = (l + r) >> 1;
ls = build(l, mid); rs = build(mid+1, r);
pushup(x);
return x;
}
int qmax(int x, int l, int r) {
if(tl >= l && tr <= r) return tree[x].mx;
int mid = (tl + tr) >> 1, s = -inf;
if(mid >= l) cmax(s, qmax(ls, l, r));
if(mid < r) cmax(s, qmax(rs, l, r));
pushup(x);
return s;
}
void modify(int x, int p, int v) {
if(tl == tr) {
tree[x].mx = v;
return ;
}
int mid = (tl + tr) >> 1;
if(mid >= p) modify(ls, p, v);
else modify(rs, p, v);
pushup(x);
}
#undef ls
#undef rs
#undef tl
#undef tr
std::priority_queue<int>q;
bool check(int i) { return qmax(1, i-k+1, i+k-1) == a[i]; }
signed main() {
read(n); read(k);
for(int i = 1; i <= n; ++i) read(a[i]), id[a[i]] = i;
build(1, n);
for(int i = 1; i <= n; ++i)
if(check(i))
q.push(i);
ct = n;
while(!q.empty()) {
int x = q.top(); q.pop();
b[ct--] = x;
modify(1, x, -inf);
int t = 0;
t = qmax(1, x-k+1, x);
if(t != -inf && check(id[t]))
q.push(id[t]);
t = qmax(1, x, x+k-1);
if(t != -inf && check(id[t]))
q.push(id[t]);
}
for(int i = 1; i <= n; ++i) a[b[i]] = i;
for(int i = 1; i <= n; ++i) printf("%d\n", a[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?