P2184 贪婪大陆
贪婪大陆
题目背景
面对蚂蚁们的疯狂进攻,小 FF 的 Tower defence 宣告失败……人类被蚂蚁们逼到了 Greed Island 上的一个海湾。现在,小 FF 的后方是一望无际的大海,前方是变异了的超级蚂蚁。小 FF 还有大好前程,他可不想命丧于此, 于是他派遣手下最后一批改造 SCV 布置地雷以阻挡蚂蚁们的进攻。
题目描述
小 FF 最后一道防线是一条长度为 n n n 的战壕,小 FF 拥有无数多种地雷,而 SCV 每次可以在 [ L , R ] [L, R] [L,R] 区间埋放同一种不同于之前已经埋放的地雷。由于情况已经十万火急,小 FF 在某些时候可能会询问你在 [ L ′ , R ′ ] [L',R'] [L′,R′] 区间内有多少种不同的地雷,他希望你能尽快的给予答复。
输入格式
第一行为两个整数 n n n 和 m m m, n n n 表示防线长度, m m m 表示 SCV 布雷次数及小 FF 询问的次数总和。
接下来有 m m m 行,每行三个整数 q , l , r q,l,r q,l,r:
- 若 q = 1 q=1 q=1,则表示 SCV 在 [ l , r ] [l, r] [l,r] 这段区间布上一种地雷;
- 若 q = 2 q=2 q=2,则表示小 FF 询问当前 [ l , r ] [l, r] [l,r] 区间总共有多少种地雷。
输出格式
对于小 FF 的每次询问,输出一个答案(单独一行),表示当前区间地雷总数。
样例 #1
样例输入 #1
5 4 1 1 3 2 2 5 1 2 4 2 3 5
样例输出 #1
1 2
提示
数据规模与约定
- 对于 30 % 30\% 30% 的数据, 0 ≤ n 0 \le n 0≤n, m ≤ 1000 m \le 1000 m≤1000。
- 对于 100 % 100\% 100% 的数据, 0 ≤ n 0 \le n 0≤n, m ≤ 1 0 5 m \le 10^5 m≤105。
考虑每次加入地雷时,维护加入地雷区间开头的个数,和加入地雷结尾区间的个数。
对于每次询问 [l, r]
范围有多少地雷,答案就是总地雷数量 - s1 - s2
其中, s 1 s1 s1 表示结尾在 [ 1 , l − 1 ] [1,l-1] [1,l−1] 的总数, s 2 s2 s2 表示开头在 [ r + 1 , n ] [r+1,n] [r+1,n] 的总数。
显然容斥掉上面两部分,剩余种类的地雷一定在 [l, r]
范围内有分布。
更通俗的来讲,想知道 [l, r] 范围里面的地雷总种类数,那么对于每种种类的地雷所在的范围区间,必须跟 [l, r] 有交集,上面容斥掉的就是两种没有交集的情况。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int const N = 1e5 + 10;
#define lc u << 1
#define rc u << 1 | 1
struct node{
int l, r, s1, s2;
}tr[N << 2];
void pushup(int u){
tr[u].s1 = tr[lc].s1 + tr[rc].s1;
tr[u].s2 = tr[lc].s2 + tr[rc].s2;
}
void build(int u, int l, int r){
tr[u] = {l, r, 0, 0};
if(l == r) return ;
int mid = l + r >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
pushup(u);
}
int askS1(int u, int l, int r){
if(l <= tr[u].l && r >= tr[u].r){
return tr[u].s1;
}
int mid = tr[u].l + tr[u].r >> 1;
int res = 0;
if(l <= mid) res += askS1(lc, l, r);
if(r > mid) res += askS1(rc, l, r);
return res;
}
int askS2(int u, int l, int r){
if(l <= tr[u].l && r >= tr[u].r){
return tr[u].s2;
}
int mid = tr[u].l + tr[u].r >> 1;
int res = 0;
if(l <= mid) res += askS2(lc, l, r);
if(r > mid) res += askS2(rc, l, r);
return res;
}
void modify(int u, int p, int tp){
if(tr[u].l == tr[u].r){
if(tp == 1) tr[u].s1 ++;
else tr[u].s2 ++;
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
if(p <= mid) modify(lc, p, tp);
else modify(rc, p, tp);
pushup(u);
}
void solve(){
int n, m;
cin >> n >> m;
build(1, 1, n);
int cnt = 0;
for(int i = 1; i <= m; i ++){
int op, l, r;
cin >> op >> l >> r;
if(op == 1){
cnt ++;
modify(1, l, 1);
modify(1, r, 2);
}
else{
int res = cnt;
if(l != 1) res -= askS2(1, 1, l - 1);
if(r != n) res -= askS1(1, r + 1, n);
cout << res << '\n';
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
while (T --){
solve();
}
return 0;
}
P1471 方差
方差
题目背景
滚粗了的 HansBug 在收拾旧数学书,然而他发现了什么奇妙的东西。
题目描述
蒟蒻 HansBug 在一本数学书里面发现了一个神奇的数列,包含 N N N 个实数。他想算算这个数列的平均数和方差。
输入格式
第一行包含两个正整数 N , M N,M N,M,分别表示数列中实数的个数和操作的个数。
第二行包含 N N N 个实数,其中第 i i i 个实数表示数列的第 i i i 项。
接下来 M M M 行,每行为一条操作,格式为以下三种之一:
操作 1 1 1:
1 x y k
,表示将第 x x x 到第 y y y 项每项加上 k k k, k k k 为一实数。
操作 2 2 2:2 x y
,表示求出第 x x x 到第 y y y 项这一子数列的平均数。
操作 3 3 3:3 x y
,表示求出第 x x x 到第 y y y 项这一子数列的方差。输出格式
输出包含若干行,每行为一个实数,即依次为每一次操作 2 2 2 或操作 3 3 3 所得的结果(所有结果四舍五入保留 4 4 4 位小数)。
样例 #1
样例输入 #1
5 5 1 5 4 2 3 2 1 4 3 1 5 1 1 1 1 1 2 2 -1 3 1 5
样例输出 #1
3.0000 2.0000 0.8000
提示
关于方差:对于一个有 n n n 项的数列 A A A,其方差 s 2 s^2 s2 定义如下:
s 2 = 1 n ∑ i = 1 n ( A i − A ‾ ) 2 s^2=\frac{1}{n}\sum\limits_{i=1}^n\left(A_i-\overline A\right)^2 s2=n1i=1∑n(Ai−A)2
其中 A ‾ \overline A A 表示数列 A A A 的平均数, A i A_i Ai 表示数列 A A A 的第 i i i 项。样例说明:
操作步骤 输入内容 操作要求 数列 输出结果 说明 0 0 0 - - 1 5 4 2 3
- - 1 1 1 2 1 4
求 [ 1 , 4 ] \left[1,4\right] [1,4] 内所有数字的平均数 1 5 4 2 3
3.0000
平均数 = ( 1 + 5 + 4 + 2 ) ÷ 4 = 3.0000 =\left(1+5+4+2\right)\div 4=3.0000 =(1+5+4+2)÷4=3.0000 2 2 2 3 1 5
求 [ 1 , 5 ] \left[1,5\right] [1,5] 内所有数字的方差 1 5 4 2 3
2.0000
平均数 = ( 1 + 5 + 4 + 2 + 3 ) ÷ 5 = 3 =\left(1+5+4+2+3\right)\div 5=3 =(1+5+4+2+3)÷5=3,方差 = ( ( 1 − 3 ) 2 + ( 5 − 3 ) 2 + ( 4 − 3 ) 2 + ( 2 − 3 ) 2 + ( 3 − 3 ) 2 ) ÷ 5 = 2.0000 =\left(\left(1-3\right)^2+\left(5-3\right)^2+\left(4-3\right)^2+\left(2-3\right)^2+\left(3-3\right)^2\right)\div 5=2.0000 =((1−3)2+(5−3)2+(4−3)2+(2−3)2+(3−3)2)÷5=2.0000 3 3 3 1 1 1 1
将 [ 1 , 1 ] \left[1,1\right] [1,1] 内所有数字加 1 1 1 2 5 4 2 3
- - 4 4 4 1 2 2 -1
将 [ 2 , 2 ] \left[2,2\right] [2,2] 内所有数字加 − 1 -1 −1 2 4 4 2 3
- - 5 5 5 3 1 5
求 [ 1 , 5 ] \left[1,5\right] [1,5] 内所有数字的方差 2 4 4 2 3
0.8000
平均数 = ( 2 + 4 + 4 + 2 + 3 ) ÷ 5 = 3 =\left(2+4+4+2+3\right)\div 5=3 =(2+4+4+2+3)÷5=3,方差 = ( ( 2 − 3 ) 2 + ( 4 − 3 ) 2 + ( 4 − 3 ) 2 + ( 2 − 3 ) 2 + ( 3 − 3 ) 2 ) ÷ 5 = 0.8000 =\left(\left(2-3\right)^2+\left(4-3\right)^2+\left(4-3\right)^2+\left(2-3\right)^2+\left(3-3\right)^2\right)\div 5=0.8000 =((2−3)2+(4−3)2+(4−3)2+(2−3)2+(3−3)2)÷5=0.8000 数据规模:
数据点 N N N M M M 备注 1 ∼ 3 1\sim3 1∼3 N ≤ 8 N\le 8 N≤8 M ≤ 15 M\le 15 M≤15 - 4 ∼ 7 4\sim7 4∼7 N ≤ 1 0 5 N\le 10^5 N≤105 M ≤ 1 0 5 M\le 10^5 M≤105 不包含操作 3 3 3 8 ∼ 10 8\sim10 8∼10 N ≤ 1 0 5 N\le 10^5 N≤105 M ≤ 1 0 5 M\le 10^5 M≤105 -
方差 D = ∑ i = 1 n ( x i − x ‾ ) 2 n D = \frac {\sum_{i=1}^n(x_i-\overline x)^2}{n} D=n∑i=1n(xi−x)2 。
根据这个公式维护线段树即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int const N = 5e4 + 10;
int const inf = 2e18;
#define lc u << 1
#define rc u << 1 | 1
struct node{
int l, r, ln;
int pre, suf, len; // 0 的连续最长前后缀以及最长 0 的连续串
int tag; // 是否区间修改为 0
}tr[N << 2];
void pushup(node &u, node &lson, node &rson){
u.l = lson.l, u.r = rson.r;
u.ln = lson.ln + rson.ln;
u.pre = lson.pre == lson.ln ? lson.pre + rson.pre : lson.pre;
u.suf = rson.suf == rson.ln ? rson.suf + lson.suf : rson.suf;
u.len = max(max(lson.len, rson.len), lson.suf + rson.pre);
}
void pushup(int u){
pushup(tr[u], tr[lc], tr[rc]);
}
void build(int u, int l, int r){
tr[u] = {l, r, 1, 1, 1, 1, inf}; // 默认一开始全是 0
if(l == r) return ;
int mid = l + r >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
pushup(u);
}
void pushdown(int u, int tag){
if(tag != inf){
tr[u].pre = tr[u].suf = tr[u].len = tr[u].ln * (tag == 0);
tr[u].tag = tag;
}
}
void pushdown(int u){
pushdown(lc, tr[u].tag);
pushdown(rc, tr[u].tag);
tr[u].tag = inf;
}
// a[l ~ r] = v
void segModify(int u, int l, int r, int v){
if(l <= tr[u].l && r >= tr[u].r){
pushdown(u, v);
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if(l <= mid) segModify(lc, l, r, v);
if(r > mid) segModify(rc, l, r, v);
pushup(u);
}
// 在线段 u 上找到出现 x 个连续 0 的最左边的编号
int segAsk(int u, int x){
if(tr[u].l == tr[u].r){ // 搜索到叶子节点
return tr[u].l;
}
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if(tr[lc].len >= x) return segAsk(lc, x);
else if(tr[lc].suf + tr[rc].pre >= x) return mid - tr[lc].suf + 1;
else return segAsk(rc, x);
pushup(u);
}
int n, m, op, x, y;
void solve(){
cin >> n >> m;
build(1, 1, n);
while(m --){
cin >> op >> x;
if(op == 1){
if(tr[1].len < x){
cout << "0\n";
continue ;
}
int l = segAsk(1, x);
int r = l + x - 1;
cout << l << '\n';
segModify(1, l, r, 1);
}
else{
cin >> y;
segModify(1, x, x + y - 1, 0);
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
while (T --){
solve();
}
return 0;
}
浮点数精度问题
不管是 C 风格的 printf
还是 C++ 的 setprecision()
都会浮点数进行四舍五入后输出。
#include<bits/stdc++.h>
using namespace std;
int main(){
// setprecison 和 printf 都会对浮点数进行四舍五入后输出
double x = 1.8888;
cout << fixed << setprecision(3) << x << '\n';
printf("%.3lf\n", x);
return 0;
}