题解:CF1967D Long Way to be Non-decreasing

节选自:图论做题记录(三)(2025.5.24 - 2025.31)

首先,如果 \(x\) 次操作可以将 \(a\) 序列变成单调不降的,那么第 \(x + 1\) 次时,我们可以把序列中一个 \(a\) 赋值成它本身,此时序列仍是单调不降的(有点废话,但是是有用的废话),这说明这道题目具有单调性,我们可以先二分答案,那么原问题就变成了判断进行 \(x\) 次操作,能否将 \(a\) 序列变成单调不降的。

此时我们就有了一个贪心的想法,对于序列的第 \(i\) 个位置,它最终变成的这个数一定大于第 \(i - 1\) 个位置,且是最小的,这样才能让后面能变的数字变多。当无论如何进行这 \(x\) 次操作,\(a_i\) 都比 \(a_{i - 1}\) 小的时候,那么就无解了。

不过这样直接判断 \(a_i\) 进行 \(x\) 次操作能变成哪些数肯定会超时,我们考虑优化它。由于有连续将一个数变成另一个数的操作,那么我们考虑将复值的过程建成图,从每个 \(i\)\(b_i\) 连边,此时就构成了一棵基环树森林,一个数变成另一个数需要的操作数,也就变成了基环树上两点距离。

于是我们正着扫一遍整个序列,枚举 \(now\) 为当前可以填的最小的数,如果 \(a_i\)\(now\) 的距离小于 \(x\),那么 \(now\) 就不变,否则 \(now\) 就不断增加直到 \(a_i\)\(now\) 的距离小于 \(x\),如果 \(now\) 已经大于 \(m\),那就无解了,否则就有解。由于 \(now\) 是单调不降的,因此二分答案 check 一次是 \(O(n)\) 的,于是我们用 \(O(T n \log n)\) 的时间复杂度解决了这个问题。

关于如何在有向基环树上求两点间的距离,见图论学习笔记(五):特殊图 / 基环树 / 基环树上两点距离 / 有向图。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define MAXSIZE (1 << 20)
char buf[MAXSIZE], *p1, *p2;
char pbuf[MAXSIZE], *pp;
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin), p1 == p2) ? EOF : *p1++)
#define pc putchar
inline int read(){
	int x = 0;
	char ch = gc();
	while(!isdigit(ch))
		ch = gc();
	while(isdigit(ch)){
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = gc();
	}
	return x;
}
inline void write(int x){
    if(x < 0){
		pc('-');
		x = -x;
	}
	if(x > 9)
		write(x / 10);
	pc(x % 10 + '0');
}
const int N = 1e6 + 9, INF = 0x3f3f3f3f;
struct Edge{
    int v, nex;
} e[N << 1];
int head[N], ecnt;
void addEdge(int u, int v){
    e[++ecnt] = Edge{v, head[u]};
    head[u] = ecnt;
}
int a[N], b[N], fa[N], del[N], L[N], R[N], dep[N], dfncnt, n, m, T;
int find(int x){
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void dfs(int u){
    L[u] = ++dfncnt;
    for(int i = head[u]; i; i = e[i].nex){
        int v = e[i].v;
        dep[v] = dep[u] + 1;
        dfs(v);
    }
    R[u] = dfncnt;
}
bool pd(int v, int u){
    return L[u] <= L[v] && R[u] >= L[v];
}
int ask(int u, int v){
    if(find(u) != find(v))
        return INF;
    if(pd(u, v))
        return dep[u] - dep[v];
    if(pd(b[del[find(u)]], v))
        return dep[u] + 1 + dep[b[del[find(u)]]] - dep[v];
    return INF;
}
int check(int mid) {
	int now = 1;
	for(int i = 1; i <= n; ++ i) {
		while(now <= m && ask(a[i], now) > mid)
            now++;
		if(now > m)
            return 0;
	}
	return 1;
}
void init(){
    for(int i = 1; i <= m; i++)
        head[i] = del[i] = dep[i] = 0;
    dfncnt = ecnt = 0;
}
int main(){
    T = read();
    while(T--){
        init();
        n = read();
        m = read();
        for(int i = 1; i <= n; i++)
            a[i] = read();
        for(int i = 1; i <= m; i++){
            fa[i] = i;
            b[i] = read();
        }
        for(int i = 1; i <= m; i++){
            int x = find(i), y = find(b[i]);
            if(x != y){
                fa[x] = y;
                addEdge(b[i], i);
            } else
                del[x] = i;
        }
        for(int i = 1; i <= m; i++){
            if(fa[i] == i){
                dfncnt = 0;
                dfs(del[i]);
            }
        }
        int l = 0, r = m, ans = - 1;
        while(l <= r) {
            int mid = ((l + r) >> 1);
            if(check(mid)){
                ans = mid;
                r = mid - 1;
            }
            else
                l = mid + 1;
        }
        write(ans);
        pc('\n');
    }
    return 0;
}
posted @ 2025-05-30 12:23  Orange_new  阅读(17)  评论(0)    收藏  举报