codeforces1468D Firecrackers

题目链接

题目:

有一个 1 × n 1 \times n 1×n的网格图,小混混在第 a a a个格子里,警察在第 b b b个格子里,小混混有 m m m个鞭炮,第 i i i个鞭炮会在扔出去后 s i s_i si秒爆炸。每秒钟会发生按顺序发生以下三件事情直到小混混被警察抓住:
(1)小混混选择移动一格或者扔一个鞭炮
(2)应在这一秒爆炸的鞭炮爆炸
(3)警察向小混混的方向移动一格
问小混混被抓到之前最多可以使多少个鞭炮爆炸。
( 2 ≤ n ≤ 1 0 9 , 1 ≤ m ≤ 2 × 1 0 5 , 1 ≤ a , b ≤ n , a ≠ b ) (2 \le n \le 10^9,1 \le m \le 2 \times 10^5,1 \le a,b \le n,a \ne b) (2n109,1m2×105,1a,bn,a=b)

题解:

下面只讨论 a < b a<b a<b的情况, a > b a>b a>b的情况同理。
最贪的情况是小混混在第1个格子被警察抓住,在这种情况下小混混会在 b − 1 b-1 b1秒被抓住,他需要移动 a − 1 a-1 a1格,所以他最多可以扔 min ⁡ { m , b − a − 1 } \min\{m,b-a-1\} min{m,ba1}个鞭炮。考虑他是怎么扔这些鞭炮的,他肯定会选择 s s s值尽量小的扔,因为这样可以尽快地爆炸。且可以发现他扔的鞭炮一定全部爆炸,不然没有爆炸的鞭炮就没有必要扔,还白白占了1秒的时间。将鞭炮按 s s s值从小到大排序,基于上面的两个结论,那么他扔的鞭炮一定是一个前缀。现在的问题就变成了找到一个最长的前缀,满足这些鞭炮都可以爆炸,那么我们可以二分前缀长度 k k k,然后 c h e c k check check这个前缀的鞭炮能不能都爆炸。 c h e c k check check的时候我们考虑贪心,当第 i i i个扔爆炸所需时间为 s j s_j sj的鞭炮时,这个鞭炮会在 i + s j i+s_j i+sj的时候爆炸,那么我们就要安排一个扔鞭炮的顺序,使 i + s j i+s_j i+sj的最大值最小。最优策略为第一个扔 s s s值最大的,第二个扔 s s s值次大的,依此类推,为什么这样是最优的?考虑在这个顺序下将两个鞭炮的顺序互换,两个鞭炮所需的爆炸时间分别为 s i , s j ( s i < s j ) s_i,s_j(s_i<s_j) si,sj(si<sj),它们原先所排在的位置分别为 p 1 , p 2 p_1,p_2 p1,p2,那么 p 1 > p 2 p_1>p_2 p1>p2,现在的两者贡献的最大值为 max ⁡ { s i + p 1 , s j + p 2 } \max\{s_i+p_1,s_j+p_2\} max{si+p1,sj+p2},互换顺序后,最大值为 max ⁡ { s i + p 2 , s j + p 1 } = s j + p 1 \max\{s_i+p_2,s_j+p_1\}=s_j+p_1 max{si+p2,sj+p1}=sj+p1,而 s j + p 1 > s i + p 1 , s j + p 1 > s j + p 2 s_j+p_1>s_i+p_1,s_j+p_1>s_j+p_2 sj+p1>si+p1,sj+p1>sj+p2,所以 max ⁡ { s i + p 2 , s j + p 1 } > max ⁡ { s i + p 1 , s j + p 2 } \max\{s_i+p_2,s_j+p_1\}>\max\{s_i+p_1,s_j+p_2\} max{si+p2,sj+p1}>max{si+p1,sj+p2},所以互换任意两个鞭炮的顺序后,最大值不会变小。所以这种顺序是最优的, c h e c k check check一下这种顺序下的最大值是否小于等于 b − 1 b-1 b1即可。

复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;

#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const int maxn=2e5+5;
ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int t,n,m,a,b;
int s[maxn];
int ck1(int x){
	int rem=b-2,f=1;
	for(int i=x;i>=1;i--){
		if(s[i]>rem){
			f=0;
			break;
		}
		rem--;
	}
	return f;
}
int ck2(int x){
	int rem=n-b-1,f=1;
	for(int i=x;i>=1;i--){
		if(s[i]>rem){
			f=0;
			break;
		}
		rem--;
	}
	return f;
}
int main(void){
	// freopen("in.txt","r",stdin);
	scanf("%d",&t);
	while(t--){
		scanf("%d%d%d%d",&n,&m,&a,&b);
		for(int i=1;i<=m;i++){
			scanf("%d",&s[i]);
		}
		sort(s+1,s+m+1);
		if(a<b){
			int tim=b-a-1;
			int l=1,r=min(tim,m),ans=0;
			while(l<=r){
				int m=(l+r)>>1;
				if(ck1(m)){
					ans=m;
					l=m+1;
				}
				else{
					r=m-1;
				}
			}
			printf("%d\n",ans);
		}
		else{
			int tim=a-b-1;
			int l=1,r=min(tim,m),ans=0;
			while(l<=r){
				int m=(l+r)>>1;
				if(ck2(m)){
					ans=m;
					l=m+1;
				}
				else{
					r=m-1;
				}
			}
			printf("%d\n",ans);
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值