小沙的炉石(二分+思维)

题目

原题链接


问题描述

给定 n ( 1 ≤ n ≤ 1 0 9 ) n(1\leq n\leq 10^9) n1n109张法术攻击牌和 m ( 1 ≤ m ≤ 1 0 9 ) m(1\leq m\leq 10^9) m1m109张法术回复牌,人物初始拥有 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) n1n109张法术攻击牌和 m ( 1 ≤ m ≤ 1 0 9 ) m(1\leq m\leq 10^9) m1m109张法术回复牌,但可能存在攻击牌多于回复牌的情况,造成没有法力攻击,所以要先对攻击牌数进行修正: 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) (nm+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(2i1)==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");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值