Problem - 1000F - Codeforces

线段树 离线处理

Problem - 1000F - Codeforces

问题描述:一个序列,q次询问。求区间[l, r]中只出现一次的数(任意一个即可)。

思路:离线处理,用线段树。将询问按右端点进行排序,预处理pre数组。pre数组表示这个数上一次出现的下标。

​ 如何用线段树进行操作?线段树可以用于单点修改,区间查询,只需要将这一题转换为此即可。区间查询查询一个pair的最小值pairfirst是上一次出现该数字的下标,second是本次遍历的下标,这样对于每一次的查找来说,最小值的first如果都大于或等于查询的ask.l,就表示一定不存在解,就是0。否则,它下标对应得数字就是离ask.l最近的只出现一次的数字。 如何进行单点修改呢。对于当前下标而言,对当前下标进行修改,将原来的PII值改为<上一次的下标位置,本次下标位置>,同时也要对上一次的下标位置的点进行单点修改,修改值即为

代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

// #define Multiple_groups_of_examples
#define IOS std::cout.tie(0);std::cin.tie(0)->sync_with_stdio(false);
#define dbgnb(a) std::cout << #a << " = " << a << '\n';
#define dbgtt cout<<" !!!test!!! "<<endl;
#define rep(i,x,n) for(int i = x; i <= n; i++)

#define all(x) (x).begin(),(x).end()
#define pb push_back
#define vf first
#define vs second

typedef long long LL;
typedef pair<int,int> PII;

const int INF = 0x3f3f3f3f;
const int N = 2e6 + 21;
int w[N];
int a[N];
struct SegTree {
    int l,r;
    PII val; // vf 上一次出现的下标位置  vs 本次出现的下标位置
}tr[N << 2];
int ans[N], pre[N], color[N];
inline int ls(int u) {return u << 1; }
inline int rs(int u) {return u << 1 | 1; }
void pushup(int u) { // 找最小值
    tr[u].val = min(tr[ls(u)].val, tr[rs(u)].val);
}
void build(int u, int l, int r) { // 建树
    if(l == r) tr[u] = {l,r,{INF, INF}};
    else {
        tr[u] = {l,r,{INF,INF}};
        int mid = l + r >> 1;
        build(ls(u),l,mid); build(rs(u), mid + 1, r);
    }
}
void modify(int u, int l, int r, PII reval) { // 将 [l,r] 区间进行修改,虽然就一个点 [l,l] [r,r] (
    if(tr[u].l >= l && tr[u].r <= r) {
        tr[u].val = reval;
        return ;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid) modify(ls(u),l,r,reval);
    if(r > mid) modify(rs(u), l, r,reval);
    pushup(u);
}
PII query(int u, int l, int r) { // 查找最小值
    if(tr[u].l >= l && tr[u].r <= r) {
        return tr[u].val;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    PII tmp = {INF, INF};
    if(l <= mid) tmp = query(ls(u),l,r);
    if(r > mid) tmp = min(tmp, query(rs(u),l,r));
    return tmp;
}
void inpfile();
void solve() {
    int n; cin>>n;
    // dbgtt
    rep(i,1,n) {
        cin>>a[i];
        pre[i] = color[a[i]]; // 找上一次出现的下标
        color[a[i]] = i; // 将该数字出现下标进行更新
    }
    build(1,1,n); // 建树
    int q; cin>>q;
    vector<array<int,3>> lit(q + 1); // l,r, i
    rep(i,1,q) { // 离线处理, 需要知道答案下标
        cin>>lit[i][0]>>lit[i][1];
        lit[i][2] = i;
    }
    sort(lit.begin() + 1, lit.end(), [](array<int,3> pre, array<int,3> suf) {
        return pre[1] < suf[1]; // 对 r 进行排序
    });
    for(int i = 1, j = 1; i <= q; ++i) {
        while(j <= n && j <= lit[i][1]) { 
            modify(1,j,j,{pre[j], j}); // 对 [j,j] 区间进行修改
            if(pre[j]) modify(1,pre[j],pre[j],{INF,pre[a[j]]}); // 如果该数字至少出现2次,将上一次的置INF
            ++j;
        }
        auto tmp = query(1,lit[i][0], lit[i][1]); // 得到第i次查询答案
        if(tmp.vf < lit[i][0]) ans[ lit[i][2] ] = a[ tmp.vs]; // 如果上一次出现的下标位置小于查询l,则可以,反之不存在,为0
    }
    // 按查询顺序输出
    rep(i,1,q) cout<<ans[i]<<'\n';
}
int main()
{
    #ifdef Multiple_groups_of_examples
    int T; cin>>T;
    while(T--)
    #endif
    solve();
    return 0;
}
void inpfile() {
    #define mytest
    #ifdef mytest
    freopen("ANSWER.txt", "w",stdout);
    #endif
}

你可能感兴趣的:(cf,算法题,算法)