输入一个数列A1,A2….An(1<=N<=100000),在数列上进行M(1<=M<=100000)次操作,操作有以下两种:
(1) 格式为C I X,其中C为字符“C”,I和X(1<=I<=N,|X|<=10000)都是整数,表示把把a[I]改为X
(2) 格式为Q L R,其中Q为字符“Q”,L和R表示询问区间为[ L ,R] (1<=L<=R<=N),表示询问A[L]+…+A[R]的值。
【solution】
很显然这是一道树状数组的模板题,对一个序列进行区间查询和单点修改操作。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 100010; int n, c[N], m; ll ask(int x) { ll sum = 0; for (; x; x -= x & -x) sum += c[x]; return sum; } void add(int x, int y) { for (; x <= n; x += x & -x) c[x] += y; } int main() { freopen("sum.in", "r", stdin); freopen("sum.out", "w", stdout); scanf("%d", &n); int x, y; for (int i = 1; i <= n; ++ i) { scanf("%d", &x); add(i, x); } scanf("%d", &m); for (int i = 1; i <= m; ++ i) { char c = getchar(); for (; c != 'C' && c != 'Q'; c = getchar()) ; if (c == 'C') scanf("%d%d", &x, &y), add(x, y - ask(x) + ask(x - 1)); else scanf("%d%d", &x, &y), printf("%lld ", ask(y) - ask(x - 1)); } return 0; }
天文学家经常要检查星星的地图,每个星星用平面上的一个点来表示,每个星星都有坐标。我们定义一个星星的“级别”为给定的星星中不高于它并且不在它右边的星星的数目。天文学家想知道每个星星的“级别”。
5 * 4 * 1 2 3 * * *
例如上图,5号星的“级别”是3(1,2,4这三个星星),2号星和4号星的“级别”为1。
给你一个地图,你的任务是算出每个星星的“级别”。
输入的第一行是星星的数目N(1<=N<=60000),接下来的N行描述星星的坐标(每一行是用一个空格隔开的两个整数X,Y,0<=X,Y<=32000)。
星星的位置互不相同。星星的描述按照Y值递增的顺序列出,Y值相同的星星按照X值递增的顺序列出。
【solution】
本题数据已经经过排序,那么从先到后的顺序,每颗星星一定在下一个星星的左边或者下面。
如上图,第三颗星星在第四颗星星的下方,第2颗星星和第一颗星星分别在第三颗星星和第二颗星星的左边。
那么对于每一颗星星,我们只需知道在它之前的星星里有多少颗星星在它的左下方,即有多少颗星星的高度小于等于这个星星的高度,那么就是这个星星的“级别”。
我们可以把每一颗星星的高度用树状数组来维护,每枚举到一颗星星,就进行查询有多少颗星星的高度小于等于它,在将它存入树状数组中。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 60010; int n, c[N]; ll ask(int x) { ll sum = 0; for (; x; x -= x & -x) sum += c[x]; return sum; } void add(int x, int y) { for (; x <= 32001; x += x & -x) c[x] += y; } int main() { freopen("star.in", "r", stdin); freopen("star.out", "w", stdout); scanf("%d", &n); int x, y; for (int i = 1; i <= n; ++ i) { scanf("%d%d", &x, &y); printf("%lld ", ask(x + 1)); add(x + 1, 1); } return 0; }
题目描述
已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数数加上x
2.求出某一个数的值
数据规模与约定
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
【solution】
树状数组的模板题,进行单点查询和区间修改,使用差分和树状数组即可。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 500010; int n, m, a[N], b[N]; ll ask(int x) { ll sum = 0; for (; x; x -= x & -x) sum += b[x]; return sum; } void add(int x, int y) { for (; x <= n; x += x & -x) b[x] += y; } int main() { freopen("tree.in", "r", stdin); freopen("tree.out", "w", stdout); scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]); int num, x, y, z; for (int i = 1; i <= m; ++ i) { scanf("%d", &num); if (num == 1) { scanf("%d%d%d", &x, &y, &z); add(x, z); add(y + 1, -z); } else { scanf("%d", &x); printf("%lld ", (ll)ask(x) + a[x]); } } return 0; }
题目描述
SuperBrother在机房里闲着没事干(再对比一下他的NOIP,真是讽刺啊......),于是便无聊地开始玩“打鼹鼠”......
在这个“打鼹鼠”的游戏中,鼹鼠会不时地从洞中钻出来,不过不会从洞口钻进去(鼹鼠真胆大……)。洞口都在一个大小为n(n<=1024)的正方形中。这个正方形在一个平面直角坐标系中,左下角为(0,0),右上角为(n-1,n-1)。洞口所在的位置都是整点,就是横纵坐标都为整数的点。而SuperBrother也不时地会想知道某一个范围的鼹鼠总数。这就是你的任务。
输入格式
每个输入文件有多行。
第一行,一个数n,表示鼹鼠的范围。
以后每一行开头都有一个数m,表示不同的操作:
m=1,那么后面跟着3个数x,y,k(0<=x,y<n),表示在点(x,y)处新出现了k只鼹鼠;
m=2,那么后面跟着4个数x1,y1,x2,y2(0<=x1<=x2<n,0<=y1<=y2<n),表示询问矩形(x1,y1)-(x2,y2)内的鼹鼠数量;
m=3,表示老师来了,不能玩了。保证这个数会在输入的最后一行。
询问数不会超过10000,鼹鼠数不会超过int。
输出格式
对于每个m=2,输出一行数,这行数只有一个数,即所询问的区域内鼹鼠的个数。
【solution】
这很显然就是将例题的树状数组二维化了,另外没有什么改动。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1050; int n, c[N][N]; ll ask(int x, int y) { ll sum = 0; for (; x; x -= x & -x) for (int yy = y; yy; yy -= yy & -yy) sum += c[x][yy]; return sum; } void add(int x, int y, int z) { for (; x <= n + 1; x += x & -x) for (int yy = y; yy <= n + 1; yy += yy & -yy) c[x][yy] += z; } int main() { freopen("input.in", "r", stdin); freopen("output.out", "w", stdout); scanf("%d", &n); int num, x, y, z, x1, y1, x2, y2; for (; ; ) { scanf("%d", &num); if (num == 1) { scanf("%d%d%d", &x, &y, &z); add(x + 1, y + 1, z); } if (num == 2) { scanf("%d%d%d%d", &x1, &y1, &x2, &y2); printf("%lld ", ask(x2 + 1, y2 + 1) + ask(x1, y1) - ask(x1, y2 + 1) - ask(x2 + 1, y1)); } if (num == 3) return 0; } }
但是本题需要注意的有几个点:
每只鼹鼠的坐标从(0,0)开始,那么由于树状数组是不能有0的,所以我们需要对读进来的每一个坐标进行偏移。本题中将树状数组的横纵坐标各增加1,也有些题偏移多位或者离散化的。
在二维的ask函数和add函数中,第二重循环中的变量不能直接用参数y进行操作,因为第一重循环每一次都会改变x,那么第二重循环的y都要从刚开始的参数y进行循环。
题目描述
一台密码机按照以下的方式产生密码:首先往机器中输入一系列数,然后取出其中一部分数,将它们异或以后得到一个新数作为密码。现在请你模拟这样一台密码机的运行情况,用户通过输入控制命令来产生密码。 密码机中存放了一个数列,初始时为空。密码机的控制命令共有3种:
ADD x 把x加入到数列的最后。
REMOVE x 在数列中找出第一个等于x的数,把它从数列中删除。
XOR BETWEEN x AND y对于数列中所有大于等于x并且小于等于y的数依次进行异或,输出最后结果作为密码。如
果只有一个数满足条件,输出这个数。如果没有任何数满足条件,输出0。
你可以假设用户不会REMOVE一个不存在于数列中的数,并且所有输入的数都不超过20000。
输入格式
输入文件password.in包括了一系列的控制命令。每个控制命令占据单独一行。输入文件中没有多余的空行。文件不超过60000行。
输出格式
对于每个XOR命令,依次在password.out中输出一行包括你的密码机所产生的密码。输出文件中不应该包含任何的多余字符。
【solution】
这题实际上就是考察对xor运算的熟悉性。xor运算是支持前缀和的,即
sum[1]=a[1],sum[2]=a[1]^a[2],sum[3]=a[1]^a[2]^a[3]... a[x]^a[x+1]^a[x+2]^...^a[y-2]^a-1]^a[y]=sum[y]^sum[x-1]
所以我们只需用树状数组维护前缀和即可。
#include<bits/stdc++.h> using namespace std; const int N = 60020; int n, c[N]; int read() { int num = 0; bool flag = 1; char c = getchar(); for (; c < '0' || c > '9'; c = getchar()) if (c == '-') flag = 0; for (; c >= '0' && c <= '9'; c = getchar()) num = (num << 3) + (num << 1) + c - 48; return flag ? num : -num; } int ask(int x) { int sum = 0; for (; x; x -= x & -x) sum ^= c[x]; return sum; } void add(int x, int y) { for (; x <= 60010; x += x & -x) c[x] ^= y; } int main() { freopen("password.in", "r", stdin); freopen("password.out", "w", stdout); int x, y, z; char c = getchar(); for (;c >= 'A' && c <= 'Z' ; c = getchar()) { if (c == 'A' || c == 'R') { x = read(); add(x, x); } else if (c == 'X') { x = read(), y = read(); if (x > y) puts("0"); else printf("%d ", ask(x - 1) ^ ask(y)); } } return 0; }
本题的查询步骤中,如果x>y,则需输出0.
Balanced Photo(USACO2017Jan HLOJ536)
题目描述
FJ正在安排他的N头奶牛站成一排来拍照。(1<=N<=100,000)序列中的第i头奶牛的高度是h[i],且序列中所有的奶牛的身高都不同。
就像他的所有牛的照片一样,FJ希望这张照片看上去尽可能好。他认为,如果L[i]和R[i]的数目相差2倍以上的话,第i头奶牛就是不平衡的。(L[i]和R[i]分别代表第i头奶牛左右两边比她高的数量)。如果L[i]和R[i]中较大者比较小者的数量严格大于两倍的话,这头奶牛也是不平衡的。FJ不希望他有太多的奶牛不平衡。
请帮助FJ计算不平衡的奶牛数量。
输入格式
第一行一个整数N。
接下N行包括H[1]到H[n],每行一个非负整数(不大于1,000,000,000)。
输出格式
一行,一个整数,表示不平衡的奶牛数量。
【solution】
这道题就是让我们求每一个数左边比它高和右边比它高的奶牛数,如果较大者严格大于较小者的两倍,那么这头奶牛就是不平衡的,求不平衡奶牛的数量。
很快想到的就是通过树状数组在每次枚举到一头奶牛时进行查询和插入。但是奶牛的身高相当的高,我们不能直接用树状数组进行维护。
因为这道题只要奶牛的数量,而不需要具体的值,我们就可以用上文提到过的离散化操作。
记录每头奶牛的编号,按身高进行排序。这样我们就将按编号顺序插入奶牛的身高变为按身高插入奶牛的编号了。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 200010; int n, c[N], l[N], r[N], ans = 0, t[N], cnt = 1; struct node{ int val, id; }a[N]; int read() { int num = 0; bool flag = 1; char c = getchar(); for (; c < '0' || c > '9'; c = getchar()) if (c == '-') flag = 0; for (; c >= '0' && c <= '9'; c = getchar()) num = (num << 3) + (num << 1) + c - 48; return flag ? num : -num; } int ask(int x) { int sum = 0; for (; x; x -= x & -x) sum += c[x]; return sum; } void add(int x, int y) { for (; x <= n; x += x & -x) c[x] += y; } bool cmp(node x, node y) { if (x.val == y.val) return x.id < y.id; return x.val < y.val; } int main() { freopen("bphoto.in", "r", stdin); freopen("bphoto.out", "w", stdout); int x, y, z; scanf("%d", &n); for (int i = 1; i <= n; ++ i) scanf("%d", &a[i].val), a[i].id = i; sort(a + 1, a + n + 1, cmp); for (int i = 1; i <= n; ++ i) { t[a[i].id] = cnt; if (a[i].val != a[i + 1].val) ++ cnt; } for (int i = 1; i <= n; ++ i) { l[i] = i - 1 - ask(t[i]); add(t[i], 1); } memset(c, 0, sizeof(c)); for (int i = n; i; -- i) { r[i] = n - i - ask(t[i]); add(t[i], 1); } for (int i = 1; i <= n; ++ i) { if (l[i] > r[i]) swap(l[i], r[i]); if (l[i] * 2 < r[i]) ++ ans; } printf("%d ", ans); return 0; }
题目描述
人品是必不可少的,人品还是守恒的。每个人的人品都是不同的,并且有正的(选择题可以用骰子全过),也有负的。
海亮高级中学有n (1 <= n <= 100,000)个学生,第i个人的人品值是A_i(-10,000 <= A_i <= 10,000).
海亮高级中学决定重新规划寝室,要让每个寝室的人品和不小于0.寝室的人品和是这个寝室里所有人的人品和。
因为名单是按照学籍号给出的,所以,现在你不能调整学生的顺序,只能按照输入的顺序把学生依次分进各个寝室,当然,为了增加难度,每个寝室的人数可以不同,我们甚至可以把n个学生放在同一个寝室。我们的口号,人品比宿舍重要。
比如现在有4个人,人品分别是2, 3, -3, 1。我们可以按照下面分寝室:
(2 3 -3 1) (2 3 -3) (1) (2) (3 -3 1) (2) (3 -3) (1)
这样保证每个寝室的人品和不小于0.
现在的问题是,如果按照这种原则,有多少种分寝室的方法。当然,方法可能很多,你只需要输出方案数对 1,000,000,009 取余数即可。
输入格式
第一行一个整数n
接下来n行,表示每个学生的人品A_i.
输出格式
一个整数,如题所求的那个余数。
【solution】
由于学生要按照顺序,所以一般的贪心应该是过不掉的,所以考虑dp。
设dp[i]表示分第i位dalao时分寝室的方案数。
因为按顺序,所以很显然dp[i]=Σdp[j](j<i,sum[i]-sum[j]>=0, dp[0]=1).
sum为序列a的前缀和。
因为转移的条件为i>j,sum[i]-sum[j]>=0.
即j<i,sum[j]<=sum[i].
那么很容易地我们可以想到离散化,记录编号,通过前缀和排序,将编号插入树状数组中。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 100010, Mod = 1000000009; int n, t[N], cnt = 1; ll ans = 0; struct node{ int sum, id; }a[N]; int read() { int num = 0; bool flag = 1; char c = getchar(); for (; c < '0' || c > '9'; c = getchar()) if (c == '-') flag = 0; for (; c >= '0' && c <= '9'; c = getchar()) num = (num << 3) + (num << 1) + c - 48; return flag ? num : -num; } bool cmp(node x, node y) { return x.sum == y.sum ? x.id < y.id : x.sum < y.sum; } int c[N]; void add(int x, int y) { for (; x <= 100000; x += x & -x) c[x] += y, c[x] %= Mod; } ll ask(int x) { ll sum = 0; for (; x; x -= x & -x) sum += c[x], sum %= Mod; return sum; } int main() { freopen("protest..in", "r", stdin); freopen("protest..out", "w", stdout); n = read(); for (int i = 1; i <= n; ++ i) a[i].sum = read() + a[i - 1].sum, a[i].id = i; sort(a, a + n + 1, cmp); for (int i = 0; i <= n; ++ i) { t[a[i].id] = cnt; if (a[i].sum != a[i + 1].sum) ++ cnt; } add(t[0], 1); for (int i = 1; i <= n; ++ i) { ans = ask(t[i]); add(t[i], ans); } printf("%lld ", ans % Mod); return 0; }
楼兰图腾(HLOJ1438 《算法竞赛进阶值南》0x42树状数组中的题)
Description
在完成了分配任务之后,西部314来到了楼兰古城的西部。相传很久以前这片土地上(比楼兰古城还早)生活着两个部落,一个部落崇拜尖刀(‘V’),一个部落崇拜铁锹(‘∧’),他们分别用V和∧的形状来代表各自部落的图腾。
西部314在楼兰古城的下面发现了一幅巨大的壁画,壁画上被标记出了N个点,经测量发现这N个点的水平位置和竖直位置是两两不同的。西部314认为这幅壁画所包含的信息与这N个点的相对位置有关,因此不妨设坐标分别为(1,y1),(2,y2),…,(n,yn),其中y1~yn是1到n的一个排列。
西部314打算研究这幅壁画中包含着多少个图腾,其中V图腾的定义如下(注意:图腾的形式只和这三个纵坐标的相对大小排列顺序有关)1 <= i < j < k <= n且yi > yj,yj < yk;
而崇拜∧的部落的图腾被定义为1 <= i < j < k <= n且yi < yj , yj > yk;
西部314想知道,这n个点中两个部落图腾的数目。因此,你需要编写一个程序来求出V的个数和∧的个数。
= = = 精简版说明:
平面上有 N(N≤10^5 ) 个点,每个点的横、纵坐标的范围都是 1~N,任意两个点的横、纵坐标都不相同。
若三个点 (x1,y1),(x2,y2),(x3,y3)(x1,y1),(x2,y2),(x3,y3) 满足 x1<x2<x3,y1>y2x1<x2<x3,y1>y2 并且 y3>y2y3>y2,则称这三个点构成"v"字图腾。
若三个点 (x1,y1),(x2,y2),(x3,y3)(x1,y1),(x2,y2),(x3,y3) 满足 x1<x2<x3,y1<y2x1<x2<x3,y1<y2 并且 y3<y2y3<y2,则称这三个点构成"^"字图腾。
求平面上"v"和"^"字图腾的个数。
Input Format
第一行一个数n
第二行是n个数,分别代表y1,y2……yn
Output Format
两个数
中间用空格隔开
依次为V的个数和∧的个数
【solution】
对于每一个点,用树状数组求出横坐标和纵坐标小于该点的点的数量,再相减求出横坐标小于该点,纵坐标大于该点的点的数量;再反向进行循环,求出横坐标大于该点,纵坐标小于等于和大于该点的点的数量。即可求出题目所求。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 200010; int n, a[N], c[N], l[N], r[N]; ll ans1 = 0, ans2 = 0; int read() { int num = 0; bool flag = 1; char c = getchar(); for (; c < '0' || c > '9'; c = getchar()) if (c == '-') flag = 0; for (; c >= '0' && c <= '9'; c = getchar()) num = (num << 3) + (num << 1) + c - 48; return flag ? num : -num; } int ask(int x) { int sum = 0; for (; x; x -= x & -x) sum += c[x]; return sum; } void add(int x, int y) { for (; x <= n; x += x & -x) c[x] += y; } int main() { freopen("totem.in", "r", stdin); freopen("totem.out", "w", stdout); int x, y, z; scanf("%d", &n); for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]); for (int i = 1; i <= n; ++ i) { l[i] = ask(a[i]); add(a[i], 1); } memset(c, 0, sizeof(c)); for (int i = n; i; -- i) { r[i] = ask(a[i]); add(a[i], 1); } for (int i = 2; i < n; ++ i) ans2 += 1LL * l[i] * r[i], ans1 += 1LL * (i - 1 - l[i]) * (n - i - r[i]); printf("%lld %lld ", ans1, ans2); return 0; }
A Simple Problem with Integer(同上题 HLOJ1436)
题目描述
对于数列 A1, A2, ... , AN. 你要进行2个操作:将一个区间的数同加上某个数,输出一段区间的和。
输入格式
第一行2个整数表示数列长度和操作次数. 1 ≤ N,Q ≤ 100000.
第二行为数列 A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000.
接下来的Q行操作:
"C a b c" 表示将 Aa, Aa+1, ... , Ab.加上c. -10000 ≤ c ≤ 10000.
"Q a b" 输出区间[a,b]的和。
输出格式
输出所有询问的答案,每行1个。
【solution】
这题就是在树状数组区间修改单点查询模板上进行修改,为区间修改区间查询。
在树状数组区间修改单点查询模板中,由于ask(x)为b数组1-x的前缀和,即那一个点的值。
那么在一段区间l,r中,[l,r]的和即为
Σask(i) (l<=i<=r) = Σask(x) (1<=x<=r) - Σask(y) (1 <= y < l).
在 Σask(x) (1<=x<=r) 中,
原式=Σ(i = 1~x)Σ(j = 1~i) b[j] = Σ(i = 1~x) (x - i + 1) * b[i] (可以根据b[i]被计算的次数得到)=(x+1)*Σb[i]-Σi*b[i] (i=1~x).
所以我们需要在增加一个树状数组来维护i*b[i]的前缀和。
那么
Σask(x) (1 <= x <= r) - Σask(y) (1 <= y < l) = (sum[x] - (x + 1) * ask(x) - ask2(x)) - (sum[y - 1] - y * ask(y) - ask2(y - 1)).
/* Σask(x) (1 <= x <= r) */ /* Σask(y) (1 <= y < l) */
即为所求.
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 500010; int n, m; ll a[N], b[N], c[N]; ll ask(int x) { ll sum = 0; for (; x; x -= x & -x) sum += b[x]; return sum; } void add(int x, int y) { for (; x <= n; x += x & -x) b[x] += y; } ll ask2(int x) { ll sum = 0; for (; x; x -= x & -x) sum += c[x]; return sum; } void add2(int x, int y) { for (; x <= n; x += x & -x) c[x] += y; } int main() { freopen("b.in", "r", stdin); freopen("b.out", "w", stdout); scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++ i) scanf("%lld", &a[i]), a[i] += a[i - 1]; char c[1]; int x, y, z; for (int i = 1; i <= m; ++ i) { scanf("%s", c); if (c[0] == 'C') { scanf("%d%d%d", &x, &y, &z); add(x, z); add(y + 1, -z); add2(x, x * z); add2(y + 1, -(y + 1) * z); } else { scanf("%d%d", &x, &y); printf("%lld ", (ll)(a[y] - a[x - 1] + ask(y) * (y + 1) - ask(x - 1) * x - ask2(y) + ask2(x - 1))); } } return 0; }
题目描述
FJ有n头奶牛n≤100000。已知它们的身高为1到n各不相同,但是不知道每头奶牛的具体身高。
FJ带领着n头奶牛去犬国进行友好的会晤。
现在到了晚餐时间,n头奶牛排成一排出发去犬国第一大酒店用餐。FJ跟奶牛们强调了很多遍,要从矮到高排以彰显奶牛的风采;但是奶牛们并不听话。
FJ决定自己动手来给它们按身高排序。
但是FJ又没有犬国那么高的智商,所以它并没有记录下每一个位置的奶牛的身高。他只记录下了对于每一个位置的奶牛,排在它前面并且身高比它矮的奶牛的数量。
他不想在强大的犬国面前丢脸,所以找到了你。你需要告诉他排在每一个位置的牛的身高。
输入格式
第1行:单个整数,n
第2行到第n行:每行一个整数,描述了第1个位置到第i−1个位置的奶牛,有多少头奶牛是比第i头奶牛矮的。
输出格式
第1..n行:每行一个整数,第i行描述排在第i个位置的奶牛的身高。
【solution】