赞
踩
【题目描述】
在完成了分配任务之后,西部314来到了楼兰古城的西部。
相传很久以前这片土地上(比楼兰古城还早)生活着两个部落,一个部落崇拜尖刀(V
),一个部落崇拜铁锹(∧
),他们分别用V
和∧
的形状来代表各自部落的图腾。
西部314在楼兰古城的下面发现了一幅巨大的壁画,壁画上被标记出了
n
n
n个点,经测量发现这
n
n
n个点的水平位置和竖直位置是两两不同的。
西部314认为这幅壁画所包含的信息与这
n
n
n个点的相对位置有关,因此不妨设坐标分别为
(
1
,
y
1
)
,
(
2
,
y
2
)
,
…
,
(
n
,
y
n
)
(1,y_1),(2,y_2),\dots ,(n,y_n)
(1,y1),(2,y2),…,(n,yn),其中
y
1
∼
y
n
y_1∼y_n
y1∼yn是
1
1
1到
n
n
n的一个排列。
西部314打算研究这幅壁画中包含着多少个图腾。
如果三个点
(
i
,
y
i
)
,
(
j
,
y
j
)
,
(
k
,
y
k
)
(i,y_i),(j,y_j),(k,y_k)
(i,yi),(j,yj),(k,yk)满足
1
≤
i
<
j
<
k
≤
n
1≤i<j<k≤n
1≤i<j<k≤n且
y
i
>
y
j
,
y
j
<
y
k
y_i>y_j,y_j<y_k
yi>yj,yj<yk,则称这三个点构成V
图腾;
如果三个点
(
i
,
y
i
)
,
(
j
,
y
j
)
,
(
k
,
y
k
)
(i,y_i),(j,y_j),(k,y_k)
(i,yi),(j,yj),(k,yk)满足
1
≤
i
<
j
<
k
≤
n
1≤i<j<k≤n
1≤i<j<k≤n且
y
i
<
y
j
,
y
j
>
y
k
y_i<y_j,y_j>y_k
yi<yj,yj>yk,则称这三个点构成∧
图腾;
西部314想知道,这
n
n
n个点中两个部落图腾的数目。
因此,你需要编写一个程序来求出V
的个数和∧
的个数。
【输入格式】
第一行一个数
n
n
n。
第二行是
n
n
n个数,分别代表
y
1
,
y
2
,
…
,
y
n
y_1,y_2,\dots ,y_n
y1,y2,…,yn。
【输出格式】
两个数,中间用空格隔开,依次为V
的个数和∧
的个数。
【数据范围】
对于所有数据,
n
≤
200000
n≤200000
n≤200000,且输出答案不会超过
i
n
t
64
int64
int64。
y
1
∼
y
n
y_1∼y_n
y1∼yn是
1
∼
n
1\sim n
1∼n的一个排列。
【输入样例】
5
1 5 3 2 4
【输出样例】
3 4
【分析】
以V
为例,我们需要遍历每个中间点,即对于每一个三元组
(
i
,
j
,
k
)
(i,j,k)
(i,j,k),我们遍历
j
j
j的位置,以
j
j
j为最低点的V
的数量为
j
左
边
高
于
j
的
点
数
量
×
j
右
边
高
于
j
的
点
数
量
j左边高于j的点数量\times j右边高于j的点数量
j左边高于j的点数量×j右边高于j的点数量,因此我们可以正序扫描一遍数组,对于每个元素
a
[
i
]
a[i]
a[i],统计之前比
a
[
i
]
a[i]
a[i]大的元素数量即为
i
i
i左边高于
i
i
i的元素数量,统计完后将
a
[
i
]
a[i]
a[i]加入到树状数组中,即
a
d
d
(
a
[
i
]
,
1
)
add(a[i],1)
add(a[i],1),表示值为
a
[
i
]
a[i]
a[i]的元素出现次数加一;接着逆序扫描一遍数组,对于每个元素
a
[
i
]
a[i]
a[i],统计之前比
a
[
i
]
a[i]
a[i]大的元素数量即为
i
i
i右边高于
i
i
i的元素数量,统计完后也将
a
[
i
]
a[i]
a[i]加入到树状数组中。
因此本题的完整做法如下:
V
的总数,其之前与之后比
a
[
i
]
a[i]
a[i]小的数的个数的乘积之和即为∧
的总数。【代码】
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 200010;
int a[N], c[N];
int ge[N], le[N];//ge[i]表示第i个数左边比它大的元素数量,le[i]比它小的元素数量
int n;
LL res1, res2;//分别表示两种图腾的数量
int lowbit(int x)
{
return x & -x;
}
int ask(int x)
{
int res = 0;
for (; x; x -= lowbit(x)) res += c[x];
return res;
}
void add(int x, int y)
{
for (; x <= n; x += lowbit(x)) c[x] += y;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)//顺序求一遍每个元素左边比它大或小的元素数量
{
cin >> a[i];
ge[i] = ask(n) - ask(a[i]);
le[i] = ask(a[i] - 1);
add(a[i], 1);//将a[i]加入树状数组,即数字a[i]出现1次
}
memset(c, 0, sizeof c);//清空树状数组
for (int i = n; i; i--)//逆序求一遍每个元素右边比它大或小的元素数量
{
res1 += (LL)ge[i] * (ask(n) - ask(a[i]));
res2 += (LL)le[i] * ask(a[i] - 1);
add(a[i], 1);//将a[i]加入树状数组,即数字a[i]出现1次
}
cout << res1 << ' ' << res2 << endl;
return 0;
}
【题目描述】
给定长度为
N
N
N的数列
A
A
A,然后输入
M
M
M行操作指令。
第一类指令形如C l r d
,表示把数列中第
l
∼
r
l∼r
l∼r个数都加
d
d
d。
第二类指令形如Q x
,表示询问数列中第
x
x
x个数的值。
对于每个询问,输出一个整数表示答案。
【输入格式】
第一行包含两个整数
N
N
N和
M
M
M。
第二行包含
N
N
N个整数
A
[
i
]
A[i]
A[i]。
接下来
M
M
M行表示
M
M
M条指令,每条指令的格式如题目描述所示。
【输出格式】
对于每个询问,输出一个整数表示答案。
每个答案占一行。
【数据范围】
1
≤
N
,
M
≤
1
0
5
1≤N,M≤10^5
1≤N,M≤105
∣
d
∣
≤
10000
|d|≤10000
∣d∣≤10000
∣
A
[
i
]
∣
≤
1
0
9
|A[i]|≤10^9
∣A[i]∣≤109
【输入样例】
10 5
1 2 3 4 5 6 7 8 9 10
Q 4
Q 1
Q 2
C 1 6 3
Q 2
【输出样例】
4
1
2
5
【分析】
树状数组能够完成单点修改、求区间和,而本题需要完成的是区间修改、求单点值。区间修改可以使用差分数组完成,若在 a [ l , r ] a[l,r] a[l,r]加上 d d d,则只需要在差分数组的 l l l和 r + 1 r+1 r+1处进行修改即可,即 c [ l ] + = d , c [ r + 1 ] − = d c[l]+=d,c[r+1]-=d c[l]+=d,c[r+1]−=d,求单点的值即为求差分数组在该点的前缀和。因此可以构造一个差分树状数组,使得修改及求前缀和的操作时间复杂度都为 O ( l o g n ) O(logn) O(logn)。
【代码】
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
typedef long long LL;
const int N = 100010;
int a[N];
int n, m;
LL c[N];
int lowbit(int x)
{
return x & -x;
}
void add(int x, int y)
{
for (; x <= n; x += lowbit(x)) c[x] += y;
}
LL ask(int x)
{
LL res = 0;
for (; x; x -= lowbit(x)) res += c[x];
return res;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
add(i, a[i] - a[i - 1]);//构造差分树状数组
}
while (m--)
{
string op;
int l, r, d;
cin >> op >> l;
if (op == "C")
{
cin >> r >> d;
add(l, d), add(r + 1, -d);//在a[l,r]加上d即为在差分数组c[l]+d,c[r+1]-d
}
else cout << ask(l) << endl;//求a[l]即为求差分数组c[l]的前缀和
}
return 0;
}
【题目描述】
给定一个长度为
N
N
N的数列
A
A
A,以及
M
M
M条指令,每条指令可能是以下两种之一:
C l r d
,表示把
A
[
l
]
,
A
[
l
+
1
]
,
…
,
A
[
r
]
A[l],A[l+1],\dots ,A[r]
A[l],A[l+1],…,A[r]都加上
d
d
d。Q l r
,表示询问数列中第
l
∼
r
l∼r
l∼r个数的和。对于每个询问,输出一个整数表示答案。
【输入格式】
第一行包含两个整数
N
N
N和
M
M
M。
第二行包含
N
N
N个整数
A
[
i
]
A[i]
A[i]。
接下来
M
M
M行表示
M
M
M条指令,每条指令的格式如题目描述所示。
【输出格式】
对于每个询问,输出一个整数表示答案。
每个答案占一行。
【数据范围】
1
≤
N
,
M
≤
1
0
5
1≤N,M≤10^5
1≤N,M≤105
∣
d
∣
≤
10000
|d|≤10000
∣d∣≤10000
∣
A
[
i
]
∣
≤
1
0
9
|A[i]|≤10^9
∣A[i]∣≤109
【输入样例】
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
【输出样例】
4
55
9
15
【分析】
假设原数组为 a a a,差分数组为 b b b,我们已经知道 a [ l , r ] + d a[l,r]+d a[l,r]+d等价于 b [ l ] + = d , b [ r + 1 ] − = d b[l]+=d,b[r+1]-=d b[l]+=d,b[r+1]−=d, a [ i ] a[i] a[i]等价于 b [ i ] b[i] b[i]的前缀和,那么如何算出 a [ i ] a[i] a[i]的前缀和呢?
假设我们需要计算 a [ 1 ] + a [ 2 ] + ⋯ + a [ x ] a[1]+a[2]+\dots +a[x] a[1]+a[2]+⋯+a[x],那么就相当于计算 ( b [ 1 ] ) + ( b [ 1 ] + b [ 2 ] ) + ⋯ + ( b [ 1 ] + b [ 2 ] + ⋯ + b [ x ] ) (b[1])+(b[1]+b[2])+\dots +(b[1]+b[2]+\dots +b[x]) (b[1])+(b[1]+b[2])+⋯+(b[1]+b[2]+⋯+b[x]),如下图所示:
其中蓝色标注的是我们需要计算的结果,将其补全(红色标注)后蓝色部分即为整体减去红色部分,即 ( b [ 1 ] + b [ 2 ] + ⋯ + b [ x ] ) ∗ ( x + 1 ) − ( 1 ∗ b [ 1 ] + 2 ∗ b [ 2 ] + ⋯ + x ∗ b [ x ] ) (b[1]+b[2]+\dots +b[x])*(x+1)-(1*b[1]+2*b[2]+\dots +x*b[x]) (b[1]+b[2]+⋯+b[x])∗(x+1)−(1∗b[1]+2∗b[2]+⋯+x∗b[x]),可以看到式子中有两部分前缀和,一部分是 b [ x ] b[x] b[x]的前缀和,另一部分是 x ∗ b [ x ] x*b[x] x∗b[x]的前缀和,因此同时维护两个差分树状数组即可。
【代码】
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
typedef long long LL;
const int N = 100010;
int a[N];
int n, m;
LL c1[N], c2[N];//c1[i]维护差分数组b[i]的前缀和,c2[i]维护i*b[i]的前缀和
int lowbit(int x)
{
return x & -x;
}
void add(LL c[], int x, LL y)
{
for (; x <= n; x += lowbit(x)) c[x] += y;
}
LL ask(LL c[], int x)//求差分树状数组c[x]的前缀和
{
LL res = 0;
for (; x; x -= lowbit(x)) res += c[x];
return res;
}
LL prefix_ask(int x)//求原数组a[x]的前缀和
{
return ask(c1, x) * (x + 1) - ask(c2, x);
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
add(c1, i, a[i] - a[i - 1]);
add(c2, i, (LL)i * (a[i] - a[i - 1]));
}
while (m--)
{
string op;
int l, r, d;
cin >> op >> l >> r;
if (op == "C")
{
cin >> d;
add(c1, l, d), add(c1, r + 1, -d);
add(c2, l, l * d), add(c2, r + 1, (r + 1) * -d);
}
else cout << prefix_ask(r) - prefix_ask(l - 1) << endl;
}
return 0;
}
【题目描述】
有
n
n
n头奶牛,已知它们的身高为
1
∼
n
1\sim n
1∼n且各不相同,但不知道每头奶牛的具体身高。
现在这
n
n
n头奶牛站成一列,已知第
i
i
i头牛前面有
A
i
A_i
Ai头牛比它低,求每头奶牛的身高。
【输入格式】
第
1
1
1行:输入整数
n
n
n。
第
2
∼
n
2\sim n
2∼n行:每行输入一个整数
A
i
A_i
Ai,第
i
i
i行表示第
i
i
i头牛前面有
A
i
A_i
Ai头牛比它低。
(注意:因为第
1
1
1头牛前面没有牛,所以并没有将它列出)
【输出格式】
输出包含
n
n
n行,每行输出一个整数表示牛的身高。
第
i
i
i行输出第
i
i
i头牛的身高。
【数据范围】
1
≤
n
≤
1
0
5
1≤n≤10^5
1≤n≤105
【输入样例】
5
1
2
1
0
【输出样例】
2
4
5
3
1
【分析】
我们发现,如果说第 K K K头牛的前面有 A k A_k Ak头牛比它矮,那么它的身高 H k H_k Hk就是数值 1 ∼ n 1\sim n 1∼n中第 A k + 1 A_k+1 Ak+1小的没有在 H k + 1 , H k + 2 , ∼ , H n H_{k+1},H_{k+2},\sim ,H_n Hk+1,Hk+2,∼,Hn中出现过的数。
所以说,我们需要建立一个长度为 n n n的 1 1 1序列 b b b,刚开始都是 1 1 1,表示 n n n种身高都有 1 1 1次使用机会,然后从 n n n到 1 1 1倒序扫描每一个 A i A_i Ai,对于每个 A i A_i Ai执行查询和修改操作。
也就是说这道题目的题意就是让我们动态维护一个 01 01 01序列,支持查询第 k k k个 1 1 1所在的位置,以及修改序列中的一个数值。第 k k k个 1 1 1所在的位置是第一个前缀和大于等于 k k k的位置,因此我们可以二分查找出这个位置,时间复杂度为 O ( l o g n ) O(logn) O(logn),每次判断 m i d mid mid的前缀和 a s k ( m i d ) ask(mid) ask(mid)是否大于等于 k k k,时间复杂度也为 O ( l o g n ) O(logn) O(logn),因此总的时间复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)。
【代码】
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100010;
int h[N], c[N], res[N];
int n;
int lowbit(int x)
{
return x & -x;
}
void add(int x, int y)
{
for (; x <= n; x += lowbit(x)) c[x] += y;
}
int ask(int x)
{
int res = 0;
for (; x; x -= lowbit(x)) res += c[x];
return res;
}
int main()
{
cin >> n;
for (int i = 2; i <= n; i++) cin >> h[i];
for (int i = 1; i <= n; i++) add(i, 1);//初始每种身高都有1次使用机会
for (int i = n; i; i--)
{
int k = h[i] + 1;//第i头牛前面有h[i]头牛比它低,因此第i头为剩下的身高中第h[i]+1高的
int l = 1, r = n;
while (l < r)
{
int mid = l + r >> 1;
if (ask(mid) >= k) r = mid;//剩余身高中前缀和第一个大于等于k的即为第k高的身高
else l = mid + 1;
}
res[i] = r;
add(r, -1);//身高用过后使用机会-1
}
for (int i = 1; i <= n; i++) cout << res[i] << endl;
return 0;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。