题目
问题描述
给定
n
(
1
≤
n
≤
1
0
9
)
n(1\leq n\leq 10^9)
n(1≤n≤109)张法术攻击牌和
m
(
1
≤
m
≤
1
0
9
)
m(1\leq m\leq 10^9)
m(1≤m≤109)张法术回复牌,人物初始拥有
1
1
1点法力值,法力值无上限,再给定询问次数
k
k
k,每次询问会给定初始血量
x
x
x,问通过出牌是否可以恰好消耗完血量(血量恰好扣为
0
0
0)。
出牌阶段,可以出攻击牌和回复牌,攻击牌的初始伤害值为
1
1
1,但每出完一张牌,剩余的攻击牌的伤害值会加一。
分析
需要注意的是,一开始虽然给定了 n ( 1 ≤ n ≤ 1 0 9 ) n(1\leq n\leq 10^9) n(1≤n≤109)张法术攻击牌和 m ( 1 ≤ m ≤ 1 0 9 ) m(1\leq m\leq 10^9) m(1≤m≤109)张法术回复牌,但可能存在攻击牌多于回复牌的情况,造成没有法力攻击,所以要先对攻击牌数进行修正: n = m i n ( n , m + 1 ) n=min(n,m+1) n=min(n,m+1)(当然了,还有可能回复牌多于攻击牌,结果不变)
此时有一个结论:
当使用
n
n
n张攻击牌和
m
m
m张回复牌时
(
n
≤
m
+
1
)
(n \leq m+1)
(n≤m+1),所可以造成的伤害区间为
[
n
2
,
n
m
+
n
(
n
+
1
)
2
]
[n^2,nm+\frac{n(n+1)}{2}]
[n2,nm+2n(n+1)],且区间内每一点均对应有出牌情况。
证明
最小值:采用贪心策略,优先出攻击牌,尽量不让攻击牌被过多增幅。
具体方案也就是先出一张攻击牌,此时没有法力了再出回复牌,交替进行,直至攻击牌全部出完。
由于最终伤害只与攻击牌数目有关,所以此时的伤害值为
∑
i
=
1
n
(
2
∗
i
−
1
)
=
=
n
2
\sum_{i=1}^n{(2*i-1)}=={n^2}
∑i=1n(2∗i−1)==n2
最大值:优先出回复牌,追求最大的增幅效果。
具体方案就是把
m
m
m张回复牌出完后,再出
n
n
n张攻击牌,此时的伤害值为
∑
i
=
1
n
(
m
+
i
)
=
=
n
m
+
n
(
n
+
1
)
2
\sum_{i=1}^n{(m+i)}=={nm+\frac{n(n+1)}{2}}
∑i=1n(m+i)==nm+2n(n+1)
区间内各点对应:在最小伤害值的出牌策略的基础上,把攻击牌和它后面邻近的回复牌的位置进行交换,每次交换可以使得总伤害值加一,最终可以到达最大伤害值的出牌策略,这就是对区间内每个点的遍历。
接下来的任务就是确认是否存在一个区间可以覆盖 x x x。
二分(从区间右端点出发)
伤害区间其实主要还是与 n n n相关,我们通过二分选出一个合适的 n n n,二分的目的是找到一个攻击牌数可以使得最大伤害值大于等于 x x x,同时尽可能使这个数小,以方便区间左端点尽可能的小,使得 x x x可以被区间覆盖。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,q,x;
bool check(ll c) {
return c*(2*m+1+c)/2>=x;
}
int main(){
cin>>n>>m>>q;
n=min(n,m+1);
ll mx=n*(2*m+1+n)/2;
while(q--){
cin>>x;
if(x<=mx) {
ll l=1, r=n;
while(l<r){
int mid=(l+r)>>1;
if(check(mid))r=mid;
else l=mid+1;
}
if(x>=l*l){
puts("YES");
}
else puts("NO");
}
else puts("NO");
}
}
结论(从区间左端点出发)
由
x
x
x直接确定区间的左端点,得到区间右端点,再看这个区间能否覆盖
x
x
x,如果可以就输出
Y
E
S
YES
YES,如果不行,则不存在区间可以覆盖
x
x
x.
区间左端点的确定是假定
x
x
x为最小伤害值时的攻击牌数,如果这个值大于
n
n
n的话,修正为
n
n
n即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,q;
int main(){
cin>>n>>m>>q;
n = min(n,m+1);
while(q--){
ll x;
cin>>x;
ll s=sqrt(x);
if(s>n)s=n;
if(s*(2*m+s+1)/2>=x)puts("YES");
else puts("NO");
}
}