https://atcoder.jp/contests/abc294
A - Filter
不解释
void solve(){
int n;
cin >> n;
for(int i = 1, k; i <= n; i ++){
cin >> k;
if(!(k & 1))
cout << k << ' ';
}
}
B - ASCII Art
不解释
void solve(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
int k;
cin >> k;
if(k == 0)
cout << '.';
else cout << (char)('A' + k - 1);
}
cout << endl;
}
}
C - Merge Sequences
题太长了,我解释一下题意吧
给你两个数组
A
,
B
A,B
A,B,然后你把这两个数组放到数组
C
C
C里面,排序后,问你原来
A
,
B
A,B
A,B数组中的数字在
C
C
C中的位置,保证
A
,
B
A,B
A,B中的每一个数字都不一样
void solve(){
int n, m;
cin >> n >> m;
std::vector<int> a(n), b(m), c;
map<int, int> mp;
int idx = 0;
for(auto &i : a) cin >> i, c.pb(i);
for(auto &i : b) cin >> i, c.pb(i);
sort(c.begin(), c.end());
for(auto i : c)
mp[i] = ++ idx;
for(auto i : a) cout << mp[i] << ' ';
cout << endl;
for(auto i : b) cout << mp[i] << ' ';
cout << endl;
}
D - Bank
还是说一下题意
给你一个序列
1
∼
N
1\sim N
1∼N
给你三个操作:
1,选一个没选过的第一个数字放到集合里面
2,给你
x
x
x,删除集合里面的
x
x
x
3,问你集合里面最小的数字
所以全是set的操作,所以直接模拟就行
void solve(){
int n, q;
cin >> n >> q;
int id = 1;
set<int> s;
while(q --){
int st;
cin >> st;
if(st == 1 && id <= n){
s.insert(id ++);
}
else if(st == 2){
int x; cin >> x;
s.erase(x);
}
else{
cout << *s.begin() << endl;
}
}
}
E - 2xN Grid
这个题面也不是很好读
给你
L
,
N
1
,
N
2
L,N1,N2
L,N1,N2,然后给你
(
v
1
i
,
l
1
i
)
(
i
=
1
∼
N
1
)
(v_{1i}, l_{1i})\ \ (i = 1\sim N1)
(v1i,l1i) (i=1∼N1)和
(
v
2
i
,
l
2
i
)
(
i
=
1
∼
N
2
)
(v_{2i}, l_{2i})\ \ (i = 1\sim N2)
(v2i,l2i) (i=1∼N2)
(但是很苟的是,这俩
v
,
l
v,l
v,l全给一列了,就很难看)
意味着,从位置1开始,有
l
i
l_i
li长度的数字全是
v
i
v_i
vi
所以上下两行,就可以表示成数组
a
L
a_L
aL和数组
b
L
b_L
bL
他给了两行,所以就问你,对于
1
∼
L
1\sim L
1∼L中有多少个位置
j
j
j中,
a
j
=
b
j
a_j=b_j
aj=bj
所以直接双指针指就可以了,把数组
a
a
a变成两维,
a
i
1
a_{i1}
ai1表示当前段的右边界的位置,所以
a
(
i
−
1
)
1
+
1
a_{(i - 1)1}+1
a(i−1)1+1也意味着
a
i
1
a_{i1}
ai1的左边界,
a
i
0
a_{i0}
ai0就表示当前段的数字是啥
直接双指针就行了
const int N = 1e5 + 10;
ll a[N][2], b[N][2], cnt;
int n1, n2;
ll L;
void solve(){
cin >> L >> n1 >> n2;
for(int i = 1; i <= n1; i ++) cin >> a[i][0] >> a[i][1], a[i][1] += a[i - 1][1];
for(int i = 1; i <= n2; i ++) cin >> b[i][0] >> b[i][1], b[i][1] += b[i - 1][1];
ll ans = 0;
int id1 = 1, id2 = 1;
while(id1 <= n1 && id2 <= n2){
if(a[id1][0] == b[id2][0]){
ans += min(b[id2][1], a[id1][1]) - max(b[id2 - 1][1], a[id1 - 1][1]);
if(b[id2][1] > a[id1][1])
id1 ++;
else if(b[id2][1] < a[id1][1])
id2 ++;
else id1 ++, id2 ++;
}
else{
if(b[id2][1] > a[id1][1])
id1 ++;
else if(b[id2][1] < a[id1][1])
id2 ++;
else id1 ++, id2 ++;
}
}
cout << ans << endl;
}
F - Sugar Water 2
这个题,我只能说,我想写这个篇题解就是因为这个题
关于浮点数二分的讨论
题意:
两个人
分别有
N
,
M
N,M
N,M杯糖水
糖和水量分别是
a
i
,
b
i
a_i,b_i
ai,bi和
c
i
,
d
i
c_i,d_i
ci,di
这样就可以算每杯糖水的糖含量了(
100
x
x
+
y
%
\frac{100x}{x + y}\%
x+y100x%)
这样两个人可以选两杯水混合,混合的可能总共有
N
∗
M
N*M
N∗M种
问第
K
K
K大的浓度是多少
精确到小数点后9位
解题思路:
首先,我们知道了每杯水的含糖量和含水量,那么每杯水的糖浓度就是
100
x
x
+
y
%
\frac{100x}{x + y}\%
x+y100x%
所以,两杯水混合后的浓度就是
100
(
x
1
+
x
2
)
(
x
1
+
x
2
)
+
(
y
1
+
y
2
)
①
\frac{100(x_1+x_2)}{(x_1+x_2)+(y_1+y_2)}①
(x1+x2)+(y1+y2)100(x1+x2)①
直观地讲,当我们确定一个
x
1
x_1
x1的时候,当
y
2
y_2
y2不变,
x
2
x_2
x2增加的时候,浓度一定增加
trick1:
假设我们的答案浓度为
k
k
k(小数),那么
k
=
x
x
+
y
k=\frac{x}{x+y}
k=x+yx②
所以
x
=
k
1
−
k
∗
y
x=\frac{k}{1-k}*y
x=1−kk∗y③
根据①式得,
x
=
x
1
+
x
2
x=x_1+x_2
x=x1+x2,
y
=
y
1
+
y
2
y=y_1+y_2
y=y1+y2④
③④结合可知,
(
x
1
−
k
1
−
k
y
1
)
+
(
x
2
−
k
1
−
k
y
2
)
=
0
(x_1-\frac{k}{1-k}y_1)+(x_2-\frac{k}{1-k}y_2)=0
(x1−1−kky1)+(x2−1−kky2)=0⑤
我们可以设
s
1
=
x
1
−
k
1
−
k
y
1
s_1=x_1-\frac{k}{1-k}y_1
s1=x1−1−kky1,
s
2
=
x
2
−
k
1
−
k
y
2
s_2=x_2-\frac{k}{1-k}y_2
s2=x2−1−kky2
所以这样看就非常直观了
这个问题就可以优化为,已知
s
1
s_1
s1,找
s
2
s_2
s2,相加等于0
我们只需要枚举
x
1
x_1
x1,然后把
s
1
s_1
s1记录下来,然后在另一个数组里面找
s
2
s_2
s2就行
但是,现在我们知道的是:我们已知
k
k
k,找对应的
s
1
s
2
s_1\ s_2
s1 s2,还是没有解决第
K
K
K大问题
我们需要思考,首先我们确定了
s
1
s_1
s1和
k
k
k,要让
k
k
k增大,
x
2
y
2
x_2\ y_2
x2 y2需要满足什么条件
要让
k
k
k增加,首先需要让②等式中
x
x
x和
y
y
y的比例增加,反应在①~⑤中就是,⑤中的等式变成不等式
即
(
x
1
−
k
1
−
k
y
1
)
+
(
x
2
−
k
1
−
k
y
2
)
>
0
(x_1-\frac{k}{1-k}y_1)+(x_2-\frac{k}{1-k}y_2)>0
(x1−1−kky1)+(x2−1−kky2)>0⑥
现在,我们得到了一个结论:当我们确定
s
1
s_1
s1和
k
k
k时,要让
k
k
k增加,就是让
s
1
>
−
s
2
s_1>-s_2
s1>−s2
所以,当我们确定
k
k
k的时候,我们可以把
s
1
s_1
s1求出来然后排序存放在数组
V
V
V中,再遍历另一个数组枚举
s
2
s_2
s2,在
V
V
V中二分出来
s
1
≥
−
s
2
s_1≥-s_2
s1≥−s2的下标,把下标求和
s
u
m
sum
sum,
s
u
m
sum
sum就表示:对于所有的
s
1
s
2
s_1\ s_2
s1 s2,有多少种组合满足其浓度
k
′
<
k
k'<k
k′<k,这样第
K
K
K小问题就求出来了,第
K
K
K大问题就可以在每次求完下标的时候用枚举的数组长度
l
e
n
len
len减下标
然后就是我们经典的二分求
k
k
k的操作了
代码如下
const int N = 5e4 + 10;
long double a[N], b[N], c[N], d[N];
ll n, m, k;
int check(double mid){
double x = mid / (1 - mid);
vector<long double> v;
for(int i = 1; i <= n; i ++)
v.pb(a[i] - b[i] * x);
sort(v.begin(), v.end());
ll ans = 0;
for(int i = 1; i <= m; i ++)
ans += n - (lower_bound(v.begin(), v.end(), - (c[i] - d[i] * x)) - v.begin());
if(ans >= k)
return 0;
return 1;
}
void solve(){
cin >> n >> m >> k;
for(int i = 1; i <= n; i ++) cin >> a[i] >> b[i];
for(int i = 1; i <= m; i ++) cin >> c[i] >> d[i];
long double l = 0, r = 1;
while(fabs(l - r) > eps){
long double mid = (l + r) / 2;
if(check(mid))
r = mid;
else l = mid;
}
cout << l * 100 << endl;
}
trick2:
现在我们看到代码了
非常易懂,但是有个疑问,eps我们应该定义为多少
首先:for循环二分
先说一下正确性:我们求mid的过程是
(
l
+
r
)
/
2
(l+r)/ 2
(l+r)/2所以求30次mid之后,即
2
−
30
<
1
0
−
9
2^{-30}<10^{-9}
2−30<10−9
通过这种方式可以保证误差小于1e-9
(实测,for40次能过35次会wa)
其次:
e
p
s
=
1
0
−
9
eps=10^{-9}
eps=10−9
这个是错的
因为,此时我们精确到1e-9,但是输出的时候我们需要乘100,这样精确度就会达到1e-7了,显然精确度不够
最后:
e
p
s
=
1
0
−
11
eps=10^{-11}
eps=10−11
这样就可以解决上述的问题,但是需要long double才可以。
那么开始讨论正确的二分姿势:
关于for循环二分,我只能证明它的正确性,但是它依然是一个不确定的东西,不知道就在哪里奇奇怪怪地卡掉了
所以还得是eps
当我们需要精确到1e-13以内的话,都可以用long double解决二分精度问题,但是当需要更加精确的时候呢?
此时我们需要对整数部分和小数部分分别二分
①先二分整数部分,确定整数部分的值,这样的话,整数部分的精确度可以达到
2
126
2^{126}
2126(1e37左右)
②再对小数部分二分,我们小数部分变成整数,即乘个
1
0
k
10^{k}
10k,让小数部分变成整数,再进行二分操作,这小数部分的精度就可以达到
2
−
126
2^{-126}
2−126(1e-37左右)
(G和Ex有时间再更,全是课,要似了)