ZZUOJ 10508: 数列游戏IV

题目链接http://acm.zzu.edu.cn:8000/problem.php?id=10508

题目大意:给定一个序列,长度为N,每次询问为一组区间[Li,Ri],输出Li到Ri中出现恰好两次的不同数的个数. N,M<=2*10^5,序列中元素<=10^9

解题思路:考虑用树状数组解决(大概是一种类型的题目)。树状数组一般用来快速计算更新(logN)前缀和,而对于本题来说,出现次数显然不能单纯随意相加相减,另外,对于右区间靠前的查询来说,对其查询之后后面的数据更新是不会再影响到它的,因此可以离线处理,并且需要在更新的时候针对重复元素进行一些处理。

首先考虑一个元素在序列中不同位置重复出现的情况,如下:

_ x _ x _ x _ x _ x _ (下划线表示出现了若干与x不相同的数字)

从前到后给每个x编号1,2,3,4,5,下面看一下从前到后扫面到这五个位置时如何更新(其中a,b等字母表示这个位置应当具有的值):

  _ x _ x _ x _ x _ x _

1  0

2  a   b       那么应当有 b + a = 1, (b + a) - a = 0, 则 b = 0, a = 1

3  a   b   c       那么应当有 c + b + a = 0, (c + b + a) - (b + a) = 0, (c + b + a) - a = 1, 则 c = 0, b = 1,  a = -1.

4  a   b   c   d    那么应当有 d + c + b + a = 0, d + c + b + a - (c + b + a) = 0, (d + c + b + a) - (b + a) = 1

             (d + c + b + a) - a = 0, 则 d = 0, c = 1, b = -1, a = 0

...

即是:

  _ x _ x _ x _ x _ x _

1  0

2  1   0        

3   -1   1   0      

4  0   -1   1   0     

5  0    0   -1   1   0

然后关系就非常明显了,我们只需要记录下每个位置的数字上次出现的位置,然后 lastpos + 1,la_lastpos - 2, la_la_lastpos + 1, 即可。那么对于任意一个区间来说,由于其中每个数字都满足互相加减的条件,因此直接树状数组相加减即可。

大致过程:记录每个位置对应数字上次出现位置;将查询的区间按照有端点排序;从1~N枚举每个位置,按上述方法更新树状数组,然后计算以这个位置为右端点结束的区间的值。

代码:

 1 const int maxn = 2e5 + 10;
 2 struct node{
 3     int l, r, id;
 4     bool operator < (const node& t) const{
 5         return r < t.r;
 6     }
 7 };
 8 node range[maxn];
 9 int n, m;
10 int a[maxn], ans[maxn], bit[maxn];
11 int last[maxn];
12 map<int, int> mmp;
13
14 int lowbit(int x){
15     return x & (-x);
16 }
17 void add(int x, int v){
18     while(x <= n){
19         bit[x] += v;
20         x += lowbit(x);
21     }
22 }
23 int sum(int x){
24     int ans = 0;
25     while(x > 0){
26         ans += bit[x];
27         x -= lowbit(x);
28     }
29     return ans;
30 }
31 void solve(){
32     memset(last, 0, sizeof(last));
33     memset(bit, 0, sizeof(bit));
34     for(int i = 1; i <= n; i++){
35         last[i] = mmp[a[i]];
36         mmp[a[i]] = i;
37     }
38     sort(range + 1, range + 1 + m);
39     int ind = 1;
40     for(int i = 1; i <= n; i++){
41         if(last[i] != 0){
42             int la = last[i];
43             add(la, 1);
44             if(last[la] != 0){
45                 int lla = last[la];
46                 add(lla, -2);
47                 if(last[lla] != 0)
48                     add(last[lla], 1);
49             }
50         }
51         while(ind <= m && range[ind].r == i){
52             int tml = range[ind].l, tmr = range[ind].r;
53             ans[range[ind].id] = sum(tmr) - sum(tml - 1);
54             ind++;
55         }
56     }
57     for(int i = 1; i <= m; i++){
58         printf("%d\n", ans[i]);
59     }
60 }
61 int main(){
62     scanf("%d %d", &n, &m);
63     for(int i = 1; i <= n; i++)
64         scanf("%d", a + i);
65     for(int i = 1; i <= m; i++){
66         scanf("%d %d", &range[i].l, &range[i].r);
67         range[i].id = i;
68     }
69     solve();
70 }

题目:

10508: 数列游戏IV

Time Limit: 1 Sec  Memory Limit: 128 MB
Submit: 32  Solved: 6
[Submit][Status][Web Board]

Description

给定一个序列,长度为N,每次询问为一组区间[Li,Ri],输出Li到Ri中出现恰好两次的不同数的个数.

Input

第一行两个整数N和M,N表示序列长度,M表示询问次数.(N,M<=2*10^5)

第二行N个整数,表示序列.(序列中元素<=10^9)

以后M行,每行为Li和Ri,表示询问区间.(1<=Li<=Ri<=N)

Output

对于每组询问,输出一行一个整数,表示不相同数的个数.

Sample Input

5 1
1 2 1 1 1
1 3

Sample Output

1

HINT

Source

Raywzy

时间: 2024-11-05 16:29:19

ZZUOJ 10508: 数列游戏IV的相关文章

ZZUOJ 10508 树状数组

链接: http://acm.zzu.edu.cn:8000/problem.php?id=10508 题意: 给定一个序列,长度为N,每次询问为一组区间[Li,Ri],输出Li到Ri中出现恰好两次的不同数的个数. 题解: 先对a离散化一下,当然也可以不离散化,用map也行.离线做,按右端点排序,从1遍历到n,更新树状数组和ans 对于a数组,我们记录上一个和a[i]相等的位置last[i],更新树状数组不太好想,具体看代码 代码: 31 int n, m; 32 int a[MAXN]; 33

【算法学习笔记】89. 序列型动态规划 SJTU OJ 4020 数列游戏

http://acm.sjtu.edu.cn/OnlineJudge/problem/4020 一上手就来了一个删点 排序+DFS.... 虽然正确性没问题 但是超时 只有60分. 主要在于不知道怎么减少搜索量 思路就是删除一些肯定不能在的点, 然后经过条件判断 DFS地去搜索最长的路径 #include <iostream> #include <vector> #include <algorithm> #include <cstring> #include

zzuli - 第七届校赛

题目传送:"玲珑杯"第七届郑州轻工业学院ACM程序设计大赛--正式赛 先给出我自己看了的题的题解吧 A - 彩票 水题 AC代码: #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int main() { int T, N; scanf("%d", &T); while(T--) { scanf("%d&

浅谈差分数组的原理及简单应用

一.差分数组的定义及用途 1.定义: 对于已知有n个元素的离线数列d,我们可以建立记录它每项与前一项差值的差分数组f:显然,f[1]=d[1]-0=d[1];对于整数i∈[2,n],我们让f[i]=d[i]-d[i-1]. 2.简单性质: (1)计算数列各项的值:观察d[2]=f[1]+f[2]=d[1]+d[2]-d[1]=d[2]可知,数列第i项的值是可以用差分数组的前i项的和计算的,即d[i]=f[i]的前缀和. (2)计算数列每一项的前缀和:第i项的前缀和即为数列前i项的和,那么推导可知

第19场双周赛总结

2020-02-10 5311. 将数字变成 0 的操作次数 给你一个非负整数 num ,请你返回将它变成 0 所需要的步数. 如果当前数字是偶数,你需要把它除以 2 :否则,减去 1 . class Solution { public: int numberOfSteps (int num) { int count = 0; while(num){ if(num &1) num--; else num /= 2; count++; } return count; } }; 1343. 大小为

斐波那契数列取石子游戏

#include <stdio.h>int main(){ int n,i; int a[45]={2,3}; scanf("%d",&n); for (i=2;i<45;i++) a[i]=a[i-1]+a[i-2]; for(i=0;i<45;i++) { if(n==a[i]) printf("Bob!"); break; } if(i==45) printf("Bob!");return 0;} 或c++版

bzoj 4636: 蒟蒻的数列

4636: 蒟蒻的数列 Description 蒟蒻DCrusher不仅喜欢玩扑克,还喜欢研究数列 题目描述 DCrusher有一个数列,初始值均为0,他进行N次操作,每次将数列[a,b)这个区间中所有比k小的数改为k,他想知 道N次操作后数列中所有元素的和.他还要玩其他游戏,所以这个问题留给你解决. Input 第一行一个整数N,然后有N行,每行三个正整数a.b.k. N<=40000 , a.b.k<=10^9 Output 一个数,数列中所有元素的和 Sample Input 4 2 5

51nod Bash游戏(V1,V2,V3,V4(斐波那契博弈))

Bash游戏V1 有一堆石子共有N个.A B两个人轮流拿,A先拿.每次最少拿1颗,最多拿K颗,拿到最后1颗石子的人获胜.假设A B都非常聪明,拿石子的过程中不会出现失误.给出N和K,问最后谁能赢得比赛. 例如N = 3,K = 2.无论A如何拿,B都可以拿到最后1颗石子. Input 第1行:一个数T,表示后面用作输入测试的数的数量.(1 <= T <= 10000) 第2 - T + 1行:每行2个数N,K.中间用空格分隔.(1 <= N,K <= 10^9) Output 共T

【矩阵快速幂】HDU 4549 : M斐波那契数列(矩阵嵌套)

[题目链接]click here~~ [题目大意] M斐波那契数列F[n]是一种整数数列,它的定义如下: F[0] = a F[1] = b F[n] = F[n-1] * F[n-2] ( n > 1 ) 现在给出a, b, n,你能求出F[n]的值吗?对每组测试数据请输出一个整数F[n],由于F[n]可能很大,你只需输出F[n]对1000000007取模后的值即可,每组数据输出一行. [Source] :2013金山西山居创意游戏程序挑战赛――初赛(2) [解题思路] 这个题稍微有点难度,就