One-Dimensional Battle Ships题解


题目翻译

\(Alice\)\(Bob\) 喜欢在 \(1×n\) 的表格中玩战舰游戏。游戏开始时,\(Alice\)\(k\) 艘战舰,每艘战舰长度为 \(a\) ,她需要把这些战舰不重叠且不相邻地放在格子中(不允许有两艘战舰的格子存在公共边)。但她并不会告诉 \(Bob\) 她放的位置。

接下来,\(Bob\) 会用 \(m\) 颗炮弹尝试打中 \(Alice\) 的战舰,每颗炮弹会选择一个格子打击。但由于 \(Alice\) 喜欢作弊,所以她不会告诉 \(Bob\) 什么时候击中了战舰。请你帮助 \(Bob\) 判断,在第几次发射炮弹后,\(Alice\) 一定会有一艘战舰被击中。

思路详解

这道题其实普遍的方法就两种, 因为 STRADUST 大佬写了二分做法, 所以我就来写一下 STL 做法。(主要原因还是现在在学 STL , 毕竟学了什么就要用什么)(如想查看二分做法, )

回归正题, 上文已经说了我们要用 STL 来解决这道题, 那么, 怎么解决呢?

首先, 我们考虑一个区间 \([l, r]\) 。如果我们想在这个区间里面放战舰, 那么就根据排列组合, 既然两艘舰艇不能挨在一起, 那么就考虑往每艘舰艇的左边都多占一个, 这样就可以两条战舰挨在一起了。 但这样就会出现最左边的战舰贴着左边, 所以我们需要在区间 \([l, r]\) 的左边再加一格, 来保证贴左的问题。 所以, 这个区间的最终的 \(MAX\{可放置舰艇数量\} = (r - l + 1 + 1) / a (a 如题目所示)\) 。接下来的问题就容易的多了。

我们记录一下整个的最大可放置舰艇数为 \(smax\) 。对于每次对于一个独立区间 \([l, r]\) 的轰炸, 假设轰炸位置为 \(mid\) , 因为 \(mid\) 处不能有战舰了, 所以原来的区间 \([l, r]\) 也就变成了两个单独的能放置战舰的区间 \([l, mid - 1]\)\([mid + 1, r]\) 。那么, 这次操作对 \(smax\) 的改动为 \(smax - (r - l + 1 + 1) / a + (mid - 1 - l + 1 + 1) / a + (r - mid - 1 + 1 + 1) / a\) 。这样做似乎有点难用 STL , 那我们再将刚才涉及的那三个区间拿出来变个形: \((l - 1, r + 1), (l - 1, mid), (mid, r + 1)\) 。 变形过后的区间就很容易发现, 其实每次操作过后的区间, 都是可以用一个 set 来存储端点的。 其原因就是每个区间中间一旦发生一次操作, 这个区间就不会再存在, 只会留下两个小的区间了。那么, 就可以想到, 每次进行轰炸的时候, 都找到自己所在的区间, 也就等价为找到自己所在的区间的左端点 (在 set 中在此位置之前第一个编号小于这个位置的位置)和右端点 (同理), 然后更改一下最多可放置舰艇数再把这个位置丢进 set 中。 如果那次操作后最多可放置舰艇数小于了 \(k\) , 那么这次操作 \(Alice\) 就在撒谎。

代码:

#include 
#include 
#include 
using namespace std;
 
set  s1;
 
void read (int &a) {
	a = 0;
	
	char c = getchar ();
	bool bl = 0;
	
	while (c < '0' || c > '9') {
		if (c == '-') {
			bl = 1;
		}
		c = getchar ();
	}
	while (c >= '0' && c <= '9') {
		a = (a << 1) + (a << 3) + (c ^ 48);
		c = getchar ();
	}
	a = bl ? -a : a;
}
void read (long long &a) {
	a = 0;
	
	char c = getchar ();
	bool bl = 0;
	
	while (c < '0' || c > '9') {
		if (c == '-') {
			bl = 1;
		}
		c = getchar ();
	}
	while (c >= '0' && c <= '9') {
		a = (a << 1) + (a << 3) + (c ^ 48);
		c = getchar ();
	}
	a = bl ? -a : a;
}
 
int main () {
	int n, k, a;
	int m;
	int sum = 0;
	
	read (n), read (k), read (a);
        //初始区间
	s1.insert(n + 1);
	s1.insert(0);
	sum = n / a;
	read (m);
	for (int l = 1; l <= m; l ++) {
		int x;
		
		read (x);
		s1.insert(x);
		//找到所在区间
		set  :: iterator i = s1.lower_bound(x), j = s1.upper_bound(x);
		
		i --;
		sum -= (*j - *i - 1) / a;
		sum += (x - *i - 1) / a;
		sum += (*j - x - 1) / a; 
		if (sum < k) {//判断是否撒谎
			printf ("%d", l);
			
			return 0;
		}
	}
	printf ("-1");
}