Diverse Segments 全网最抽风做法

Diverse Segments


1.前言

气运不好,一拿手就想错了方向,然后就写得特别复杂……

神奇 DJ 调码真厉害 (((舔

2.题解

首先想暴力,考虑一个区间 [ l , r ] [l, r] [l,r],记值为 v a l val val 的元素为 p : p 1 , p 2 . . . p k p: p_1, p_2 ... p_k p:p1,p2...pk

那么由于我们可以修改的区间是连续的,所以 p 2 . . . p k − 1 p_2 ... p_{k - 1} p2...pk1 这些元素一定会被框入我们的答案区间,把这些点剔除后就只剩下 p 1 , p k p_1, p_k p1,pk,所以答案区间只需要包含 p 1 p_1 p1 p n p_n pn 即可。

那么记这些被剔除的点的编号值域为 [ l , r ] [l, r] [l,r],那么答案区间 [ L , R ] [L, R] [L,R] 必须满足 L < l , R > r L < l, R > r L<l,R>r

还有一种情况,就是没有被剔除的点或者相同的两个元素都处于 [ 1 , L − 1 ] [1, L - 1] [1,L1] [ R + 1 , n ] [R + 1, n] [R+1,n]

这种情况特判即可,那么 R R R 必须要大于这两个元素下标的最小值。

接下来分别将剩下的两个难点:

  1. 求得 [ l , r ] [l, r] [l,r]
  2. 判断选择 p 1 p_1 p1 还是 p n p_n pn

第一个难点

枚举 i i i,表示给 a [ i ] a[i] a[i] 找值相同的最远的点(当然得都在某个给定区间)。

那么我们只需求出 max ⁡ ( r j ) ( l j ≤ i ) \max (r_j) (l_j \leq i) max(rj)(lji),我们发现可以给区间按左端点为第一关键字从小到大排序,然后双指针,求出最远的区间右端点后在这个区间内寻找到第二靠左的和第二靠右的点即可。


第二个难点

和第一个难点类似的思路,从小到大( [ 1 , l ] [1, l] [1,l])枚举 L L L,然后发现如果错过了某个 p 1 p_1 p1,那么就需要 R ≥ p n R \geq p_n Rpn 了(对头已经一定不在区间里,那么队尾一定要在区间里),与 p n p_n pn 比个 max ⁡ \max max 即可。


3.参考代码

#include <set>
#include <map>
#include <cmath>
#include <queue>
#include <stack>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define fi first
#define se second
#define db double
#define LL long long
//#define int long long
#define PII pair <int, int>
#define ULL unsigned long long
#define MP(x,y) make_pair (x, y)
#define rep(i,j,k) for (int i = (j); i <= (k); i++)
#define per(i,j,k) for (int i = (j); i >= (k); i--)

template <typename T>
void read (T &x) {
    x = 0; T f = 1; 
    char ch = getchar ();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar ();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + ch - '0';
        ch = getchar ();
    }
    x *= f;
}
template <typename T, typename... Args>
void read (T &x, Args&... Arg) {
    read (x), read (Arg...);
}
const int MaxPrint = 1000;
int Poi_For_Print, Tmp_For_Print[MaxPrint + 5];
template <typename T>
void write (T x) {
	if (x == 0) {
		putchar ('0');
		return;
	}
    bool flag = (x < 0 ? 1 : 0);
    x = (x < 0 ? -x : x);
    while (x) Tmp_For_Print[++Poi_For_Print] = x % 10, x /= 10;
    if (flag) putchar ('-');
    while (Poi_For_Print) putchar (Tmp_For_Print[Poi_For_Print--] + '0');
}
template <typename T, typename... Args>
void write (T x, Args... Arg) {
    write (x); putchar (' '); write (Arg...);
}
template <typename T, typename... Args>
void print (T x, char ch) {
    write (x); putchar (ch);
}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }

const int Maxn = 2 * 1e5;
const int Inf = 0x3f3f3f3f;

int t, n, m;
int a[Maxn + 5], cnt;
PII b[Maxn + 5], c[Maxn + 5];
set <int> bak[Maxn + 5];

vector <int> lsh;
int Find (int x) { return lower_bound (lsh.begin (), lsh.end (), x) - lsh.begin () + 1; }

signed main () {
//     freopen ("C:\\Users\\Administrator\\Desktop\\lihan\\1.in", "r", stdin);
//    freopen ("C:\\Users\\Administrator\\Desktop\\lihan\\1.out", "w", stdout);

    read (t);
    while (t--) {
        read (n, m);
        
        lsh.clear ();
        rep (i, 1, n) bak[i].clear ();
        
        rep (i, 1, n)
            read (a[i]), lsh.push_back (a[i]);
        sort (lsh.begin (), lsh.end ()); lsh.erase (unique (lsh.begin (), lsh.end ()), lsh.end ());
        rep (i, 1, n)
            a[i] = Find (a[i]), bak[a[i]].insert (i);
        rep (i, 1, m)
            read (b[i].fi, b[i].se);
        sort (b + 1, b + 1 + m);

        int poi = 0, _max = -Inf;
        int l = n, r = 0;
        rep (i, 1, n) {//求出 [l, r] 
            while (poi + 1 <= m && b[poi + 1].fi <= i) // b[poi + 1] 询问区间的最短点囊括了 i 
                _max = Max (_max, b[++poi].se);
            if (_max < i) {
            	continue;
			}
            auto pl = bak[a[i]].lower_bound (i), pr = bak[a[i]].upper_bound (_max);
            if (pr == bak[a[i]].begin ()) continue;
            pr--;
            if (pl == bak[a[i]].end ()) continue;
            if (pr == bak[a[i]].begin ()) continue;
            pl++, pr--; // 选出第二大和第二小的编号 
            if (pl == bak[a[i]].end ()) continue;
            if (*pl <= *pr) l = Min (l, *pl), r = Max (r, *pr);
        }

        poi = 0, _max = -Inf;
        int Now_Max = r, res = Inf; // Now_Max 即为 R 
        
        rep (i, 1, n) {//特判分居 [l, r] 的同一侧的情况 
            while (poi + 1 <= m && b[poi + 1].fi <= i)
                _max = Max (_max, b[++poi].se);
            if (_max < i) {
            	continue;
			}
            auto pl = bak[a[i]].lower_bound (i), pr = bak[a[i]].upper_bound (_max);
            if (pl == bak[a[i]].end ()) continue;
            if (pr == bak[a[i]].begin ()) continue;
            pr--;
            if (*pl < *pr && (*pr <= l || *pl >= r)) l = Min (l, *pr), Now_Max = Max (Now_Max, *pl);
		}
		
        poi = 0, _max = -Inf;
        rep (i, 1, l) {//枚举 L 
            while (poi + 1 <= m && b[poi + 1].fi < i) 
                _max = Max (_max, b[++poi].se);
            if (_max < i) {
            	res = Min (res, Now_Max - i + 1);
            	continue;
			}
			if (i == 1) {
				res = Min (res, Now_Max - i + 1);
				continue;
			}
            auto pl = bak[a[i - 1]].lower_bound (i), pr = bak[a[i - 1]].upper_bound (_max); //现在与头 a[i - 1] 错过 
            if (pl == bak[a[i - 1]].end ()) {//求出 p[n] 
            	res = Min (res, Now_Max - i + 1);
            	continue;
			}
            if (pr == bak[a[i - 1]].begin ()) {
            	res = Min (res, Now_Max - i + 1);
            	continue;
			}
            pr--;
            if (*pl <= *pr) Now_Max = Max (Now_Max, *pr);
            res = Min (res, Now_Max - i + 1);
        }
        print (Max (res, 0), '\n');
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值