CF 1288 E. Messenger Simulator
CF 1288 E. Messenger Simulator
题意想必大家都明白了这里就不赘述了,这里只想重点记录一下几种实现方法
分析
设向前移动的序列为\(a\)序列
- 对于没有向前移动的数字,出现的最小位置就是它最开始的位置,而求解最大位置时,只需要求出a序列中,有多少种比它大即可(注意不要计算重复)
- 对于向前移动了的数字 \(i\),出现的最小位置是1。求解最大位置:将它在a序列中出现的位置存放在vector中,对于 i 出现的第一个位置\(pos\), 计算\([1,pos-1]\)中有多少种比 i 大的数字出现。对于 i 出现的第 j 个位置\(pos_j\), 计算\([pos_{j-1}+1,pos_{j}-1]\) 中有多少种数字出现。
所以问题简化为:
- 求前缀序列中大于某个数字的数字种类数 - 树状数组维护显然可做
- 权值线段树
 
- 求数字种类数 - 将查询排序,莫队(离线)复杂度\(O(n\sqrt{n})\) 
- 主席树(在线)时间复杂度\(O(nlogn)\) 
- 将查询排序,从左到右枚举右端点 \(r\)(即查询的右端点),线段树中维护区间[L,R]中,有多少数字是[1,r]中最后一次出现。 - [1,2,1,2,3,5],当 \(r=6\)时,线段树中每个位置的值为[0,0,1,1,1,1]
- [1,2,3,2,4], 当\(r=4\)时,线段树中每个位置的值为[1,0,1,1,0]
 - 然后直接在线段树上查询即可,复杂度\(O(nlogn)\) 
 
除了数据结构的做法, 还有模拟做法,虽然无法很方便的用数组来模拟每个数字向前移动的情况,但是我们可以很方便的用数组来模拟每个数字添加到尾部的操作,由于我们在计算中只需要利用的是这些数字排列顺序,所以可以用树状数组来维护每个位置的情况。初始时按照n到1的顺序放在树状数组上,对于第 i 个移动的数字 a[i], pos[a[i]]表示它现在在树状数组的位置,进行如下操作
- 查询树状数组[1, pos[a[i]]-1], 记结果为 x ,则用 n - x 更新a[i]的答案
- 树状数组中 pos[a[i]] 上的值减1
- pos[a[i]] = n + i ,该等效于移动到队尾
- 树状数组中 pos[a[i]] 上的值加1
下面给出四种代码
- 树状数组 + 主席树
const int N = 300000 + 5;
int a[N], n, m, c[N];
vector<int> v[N];
struct SegTree{
    int lc, rc;
    int dat;
} t[N * 60];
int tot, root[N], mi[N], mx[N], past[N];
int build(int l,int r){
    int p = ++tot;
    if(l == r){
        t[p].dat = 0;
        return p;
    }
    int mid = l + r >> 1;
    t[p].lc = build(l, mid);
    t[p].rc = build(mid + 1, r);
    t[p].dat = 0;
    return p;
}
int insert(int now,int l,int r,int x,int val){ // l,r为当前线段树节点的左右区间
    int p = ++tot;
    t[p] = t[now];
    if(l == r ){
        t[p].dat += val;
        return p;
    }
    int mid = l + r >> 1;
    if(x <= mid)
        t[p].lc = insert(t[now].lc, l, mid, x, val);
    else
        t[p].rc = insert(t[now].rc, mid + 1, r, x, val);
    t[p].dat = t[t[p].lc].dat + t[t[p].rc].dat;
    return p;
}
int query(int p,int L,int R,int l,int r){
    if(L >= l && R <= r){
        return t[p].dat;
    }
    int mid = L + R >> 1;
    int res = 0;
    if(mid >= l)
        res += query(t[p].lc, L, mid, l, r);
    if(mid < r)
        res += query(t[p].rc, mid + 1, R, l, r);
    return res;
}
void upd(int x){
    for (; x <= n;x+=x&-x)
        c[x] += 1;
}
int ask(int x){
    int res = 0;
    for (; x;x-=x&-x)
        res += c[x];
    return res;
}
int main() {
    scanf("%d%d", &n, &m);
    int maxn = n;
    root[0] = build(1, maxn);
    for (int i = 1; i <= m;i++){
        scanf("%d", &a[i]);
        v[a[i]].push_back(i);
        if(past[a[i]]){ //past[a[i]] 为a[i] 上次出现的位置
            root[i] = insert(root[i - 1], 1, m, past[a[i]], -1); //减1操作在旧位置减1
            root[i] = insert(root[i], 1, m, i, 1);//在新位置上+1
        }else{
            root[i] = insert(root[i - 1], 1, m, i, 1);
        }
        past[a[i]] = i;
    }
    root[m + 1] = root[m];
    for (int i = 1; i <= n;i++){
        mi[i] = mx[i] = i;
    }
    for (int i = 1; i <= m;i++){
        mi[a[i]] = 1;
        if(i == v[a[i]][0]){//i是a[i]第一次出现的位置
            mx[a[i]] += ask(n) - ask(a[i]);//树状数组查询[1,i-1]中有多少种数字大于a[i]
            upd(a[i]);
        }
    }
    for (int i = 1; i <= n;i++){
        if(v[i].size()==0)//求解[1,m]中有多少数字大于 i
            mx[i] += ask(n) - ask(i);
    }
    for (int i = 1; i <= maxn; i++) {
        if (v[i].size()) {
            v[i].push_back(m + 1);
            for (int j = 1; j < v[i].size(); j++) {
                mx[i] = max(mx[i], query(root[v[i][j] - 1], 1, m, v[i][j - 1] + 1, v[i][j] - 1) + 1);//主席树在线查询[v[i][j-1]+1, v[i][j]-1]有多少种数字
            }
        }
    }
    for (int i = 1; i <= n;i++){
        printf("%d %d\n", mi[i], mx[i]);
    }
    return 0;
}
- 树状数组+莫队
const int N = 300000 + 5;
#define x first
#define y second
typedef pair<int, int> pii;
int n, m, a[N], c[N];
vector<int> v[N];
vector<pii> qr;
pii res[N];
int cnt[N], tot;
const int T = 550;
void upd (int x){
    for (; x <= n;x+=x&-x)
        c[x] += 1;
}
int ask(int x){
    int res = 0;
    for (; x;x-=x&-x){
        res += c[x];
    }
    return res;
}
void add(int x){
    if(++cnt[x] == 1)
        tot++;
}
void rem(int x){
    if(--cnt[x] == 0)
        tot--;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n;i++){
        res[i].x = res[i].y = i;
    }
    for (int i = 1; i <= m; i++) {
        scanf("%d", &a[i]);
        res[a[i]].x = 1;
        v[a[i]].push_back(i);
    }
    for (int i = 1; i <= m;i++){
        if(i == v[a[i]][0]){
            res[a[i]].y += ask(n) - ask(a[i]); // [a[i]+1,n]区间出现的数字个数
            upd(a[i]);
        }
    }
    for (int i = 1; i <= n;i++){
        if(v[i].size() == 0)
            res[i].y += ask(n) - ask(i);//[i+1,n] 区间出现的个数
        else{
            v[i].push_back(m + 1);
            for (int j = 1; j < v[i].size();j++){
                qr.push_back(make_pair(v[i][j - 1] + 1, v[i][j] - 1));
            }
        }
    }
    //区间排序,左端点分块
    sort(qr.begin(), qr.end(), [](const pii a, const pii b) {
        if(a.x / T != b.x / T){
            return a.x / T < b.x / T;
        }
        if((a.x / T) & 1) return a.y < b.y;//左端点属于奇数块,则右端点按照升序排列,否则按照降序排列,是莫队算法的一种优化
        return a.y > b.y;
    });
    int L = 1, R = 0;
    for (int i = 0;i<qr.size();i++){
        int l = qr[i].x, r = qr[i].y;
        if(r < l)
            continue;
        while(L < l)
            rem(a[L++]);
        while(L > l)
            add(a[--L]);
        while(R > r)
            rem(a[R--]);
        while(R < r)
            add(a[++R]);
        int x = a[qr[i].x - 1];
        res[x].y = max(res[x].y, tot + 1);
    }
    for (int i = 1; i <= n;i++){
        printf("%d %d\n", res[i].x, res[i].y);
    }
    return 0;
}
- 树状数组+线段树
const int N = 300000 + 5;
#define x first
#define y second
typedef pair<int, int> pii;
int n, m, a[N], c[N];
int past[N];
vector<int> v[N];
vector<int> q[N];
pii res[N];
struct SegTree{
    int l, r, dat;
} t[N * 4];
void build(int p,int l,int r){
    t[p].l = l, t[p].r = r;
    if(l == r){
        t[p].dat = 0;
        return;
    }
    int mid = l + r >> 1;
    build(p * 2, l, mid);
    build(p * 2 + 1, mid + 1, r);
    t[p].dat = 0;
}
void upd(int p,int x,int val){
    if(t[p].l == t[p].r && t[p].l == x){
        t[p].dat += val;
        return;
    }
    int mid = t[p].l + t[p].r >> 1;
    if(x <= mid)
        upd(p * 2, x, val);
    if(x > mid)
        upd(p * 2 + 1, x, val);
    t[p].dat = t[p * 2].dat + t[p * 2 + 1].dat;
}
int ask(int p,int l,int r){
    if(t[p].l >= l && t[p].r <= r)
        return t[p].dat;
    int res = 0;
    int mid = t[p].l + t[p].r >> 1;
    if(mid >= l)
        res += ask(p * 2, l, r);
    if(mid < r)
        res += ask(p * 2 + 1, l, r);
    return res;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n;i++){
        res[i].x = res[i].y = i;
    }
    build(1, 1, n);//值域为[1,n]的权值线段树
    for (int i = 1; i <= m; i++) {
        scanf("%d", &a[i]);
        res[a[i]].x = 1;
        v[a[i]].push_back(i);
    }
    for (int i = 1; i <= m;i++){
        if(i == v[a[i]][0]){
            res[a[i]].y += ask(1, a[i] + 1, n);//权值线段树查询[a[i]+1,n]的种类数
            upd(1, a[i], 1);
        }
    }
    for (int i = 1; i <= n; i++) {
        if (v[i].size()) {
            v[i].push_back(m + 1);
            for (int j = 1; j < v[i].size(); j++) {
                q[v[i][j] - 1].push_back(v[i][j - 1] + 1);//离线保存查询
            }
        }else{
            res[i].y += ask(1, i + 1, n);
        }
    }
    build(1, 1, m);//重新构建普通线段树
    for (int i = 1; i <= m;i++){
        if(past[a[i]]){//past[a[i]]为a[i]上一次出现的位置
            upd(1, past[a[i]], -1);
            upd(1, i, 1);
        }
        else
            upd(1, i, 1);
        for (int j = 0; j < q[i].size();j++){
            int l = q[i][j], r = i;
            int x = a[l - 1];
            res[x].y = max(res[x].y, ask(1, l, r) + 1);
        }
        past[a[i]] = i;
    }
    for (int i = 1; i <= n; i++) {
        printf("%d %d\n", res[i].x, res[i].y);
    }
    return 0;
}
- 树状数组+倒序模拟
const int N = 300000 + 5;
typedef pair<int, int> pii;
int n, m, a[N], pos[N], c[N + N];
pii res[N];
void upd(int x,int val){
    for (; x <= n + m;x+=x&-x)
        c[x] += val;
}
int ask(int x){
    int res = 0;
    for (; x; x-=x&-x)
        res += c[x];
    return res;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m;i++)
        scanf("%d", &a[i]);
    for (int i = 1; i <= n;i++){
        res[i].first = res[i].second = i;
        upd(n - i + 1, 1);
        pos[i] = n - i + 1;
    }
    for (int i = 1; i <= m;i++) {
        res[a[i]].first = 1;
        res[a[i]].second = max(res[a[i]].second, n - ask(pos[a[i]] - 1));
        upd(pos[a[i]], -1);
        pos[a[i]] = i + n;
        upd(pos[a[i]], 1);
    }
    for (int i = 1; i <= n;i++){
        res[i].second = max(res[i].second, n - ask(pos[i] - 1));
        printf("%d %d\n", res[i].first, res[i].second);
    }
    return 0;
}
CF 1288 E. Messenger Simulator的更多相关文章
- codeforces 1288E. Messenger Simulator(树状数组)
		链接:https://codeforces.com/contest/1288/problem/E 题意:序列p的长度为n,初始序列为1 2 3 4 ...n,然后有m次操作,每次指定序列中一个数移动到 ... 
- CF #344 D. Messenger KMP/Z
		题目链接:http://codeforces.com/problemset/problem/631/D 给定两个压缩形式的字符串,如a3b5a4k7这样的形式 问A在B中出现次数. 分类讨论,如果A是 ... 
- Educational Codeforces Round 80 (Rated for Div. 2) E. Messenger Simulator
		可以推出 min[i]要么是i要么是1,当a序列中存在这个数是1 max[i]的话就比较麻烦了 首先对于i来说,如果还没有被提到第一位的话,他的max可由他后面的这部分序列中 j>=i 的不同数 ... 
- Educational Codeforces Round 80 D E
		A了三题,rk1000左右应该可以上分啦,开 
- Educational Codeforces Round 80 (Rated for Div. 2)
		A. Deadline 题目链接:https://codeforces.com/contest/1288/problem/A 题意: 给你一个 N 和 D,问是否存在一个 X , 使得 $x+\lce ... 
- Educational Codeforces Round 80 A-E简要题解
		contest链接:https://codeforces.com/contest/1288 A. Deadline 题意:略 思路:根据题意 x + [d/(x+1)] 需要找到一个x使得上式小于等于 ... 
- 2021record
		2021-10-14 P2577 [ZJOI2004]午餐 2021-10-13 CF815C Karen and Supermarket(小小紫题,可笑可笑) P6748 『MdOI R3』Fall ... 
- CSU-ACM2020寒假集训比赛2
		A - Messenger Simulator CodeForces - 1288E 两种解法,我选择了第二种 mn很好求,联系过就是1,没联系过就是初始位置 第一种:统计同一个人两次联系之间的出现的 ... 
- Codeforces题解集 1.0
		记录 Codeforces 2019年12月19日到 2020年2月12日 的部分比赛题 Educational Codeforces Round 82 (Rated for Div. 2) D Fi ... 
随机推荐
- VMware 16.1虚拟机安装
			VMware 16.1创建虚拟机 1.1首先打开我们安装好的VMware点击创建新的虚拟机 典型为自动安装,接口位默认 自定义安装更自由,可以把按需求选择 VMware16版本,只能选择Worksta ... 
- 2020DevOps状态报告——变更管理
			如果你的公司还没有走向平台化,现在仍然可以是很大的飞跃.您仍然可以通过解决公司的变更管理流程来加快软件交付.在本章中,我们将研究我们在公司内部所学的变更管理模式.我们将向您展示什么是有效的,什么是无效 ... 
- Docker踩过的坑
			前言 主要是记录Docker遇到的坑,更多的是因为自己的粗心大意,以此警示 正文 Dockerfile里的RUN 某一次把启动服务的命令写在了 Dockerfile 中,后来发现服务一直拉不起来. 原 ... 
- 【MySQL 高级】知识拓展
			MySQL高级 知识拓展 MySQL高级 知识拓展 数据量 和 B+树 的关系 事务隔离级别集底层原理MVCC 唯一索引和普通索引的关键不同点 MRR:multi range read 练习和总结 
- wpf 在不同DPI下如何在DrawingVisual中画出清晰的图形
			环境Win10 VS2017 .Net Framework4.7.1 本文仅讨论在DrawingVisual中进行的画图. WPF单位,系统DPI,显示器DPI三者的定义及关系 WPF单位:一 ... 
- Spark Streaming处理Flume数据练习
			把Flume Source(netcat类型),从终端上不断给Flume Source发送消息,Flume把消息汇集到Sink(avro类型),由Sink把消息推送给Spark Streaming并处 ... 
- ctfhub技能树—web前置技能—http协议—Cookie
			打开靶机环境 查看显示内容 根据提示,需要admin登录才能得到flag 题目介绍为Cookie欺骗.认证.伪造 介绍一下cookie和session 一.cookie: 在网站中,http请求是无状 ... 
- [Usaco2007 Dec]宝石手镯
			题目描述 贝茜在珠宝店闲逛时,买到了一个中意的手镯.很自然地,她想从她收集的 N(1 <= N <= 3,402)块宝石中选出最好的那些镶在手镯上.对于第i块宝石,它的重量为W_i(1 & ... 
- Git 创建新分支检查分支
			创建分支和切换分支,也可以称为检出分支 创建新分支 git branch branchName 切换到新分支 git checkout branchName 上面两个命令也可以合成为一个命令: git ... 
- 如果using语句中出现异常,资源会被释放掉吗?
			<CLR Via C#>第三版 P489 在using内部抛出了异常,被using的对象还是会被释放掉. Using编译时会自动生成Try Finally代码块. 同样Using只能用于实 ... 
