LOJ2392 JOISC2017 烟花棒 二分、贪心


传送门


先二分一个最大速度\(v\)

分析移动的性质。很显然的事情是在火焰两边的所有人都会往火焰的方向以最快的速度运动,这样可以使当前位置更早获得火焰,同时当前拥有火焰的若干个人为了传递火焰自然也会以最快的速度移动。

接下来考虑某个没有火的人碰上了有火的人之后决策如何。假设有火的人\(A\)碰上了无火的人\(B\),如果\(A,B\)接下来要去的方向是一致的,那么肯定一起走直到\(A\)灭了再点燃\(B\);否则可以发现在碰上的瞬间点火和先\(AB\)一起走直到\(A\)无火时给\(B\)点火,这两种方案\(B\)能够点到火对应的相对距离范围是一致的。

所以我们可以认为\(AB\)相遇则一定会在一起跑,相当于给火焰增加了\(Tsec\)的燃烧时间,也就是说在任何时刻只会有最多一个位置有火。

那么我们实际需要做的决策就是在两个方向中选择一个方向让火往这边跑,将第一个相遇的位置加入火焰,再去做决策。不难发现在上述论述下,火焰向一边跑了之后到另一边所有人的相对距离不变,所以对于每一个人,如果火焰下一次选择它,那么消耗的时间是固定的,能够获得的时间也是固定的。我们把这两个值称为其权值,记为\((a_i,b_i)\)

接下来的决策过程:从火焰的左边和右边找到第一个位置满足当火焰和这个位置相遇时时间相比开始增加。如果火焰可以往其中一个方向走到达这样的位置那么就走然后继续这个过程,否则肯定无解。

接下来到了走两边都一定让时间减少的部分,这里不能直接选择减的最少的位置走,因为这样的走法可能会影响决策集合从而导致无解。考虑一个常见的贪心Trick:我们已知过程结束时火焰的时间,那么我们倒着考虑,相当于把火焰左右的两个部分分别\(reverse\)\((a_i,b_i)\)交换然后做上述过程。这样不难证明从火焰左边的任何位置到最左端的位置一定能够获得正时间,右边同理。这样我们就可以通过这个问题是否有解得到整个问题是否有解了。

关于最后的Trick可以参考

#include
using namespace std;

int read(){
	int a = 0; char c = getchar(); while(!isdigit(c)) c = getchar();
	while(isdigit(c)){a = a * 10 + c - 48; c = getchar();} return a;
}

#define int long long
#define eps 1e-10
#define PII pair < int , int >
const int _ = 1e5 + 7;
int X[_] , N , K , T;

bool check(int sum , vector < PII > &A , vector < PII > &B){
	int pos1 = 0 , pos2 = 0; if(sum < 0) return 0;
	while(pos1 < A.size() || pos2 < B.size())
		if(pos1 < A.size() && sum + A[pos1].first >= 0) sum += A[pos1++].second;
		else if(pos2 < B.size() && sum + B[pos2].first >= 0) sum += B[pos2++].second;
		else return 0;
	return 1;
}

#define dis(a , b) ((X[a] - X[b]) / 2)

bool check1(int l1 , int r1 , int mid){
	vector < PII > P , Q; int l = 0 , r = N + 1;
	while(l < l1){
		int pl = l , mn = 1e18;
		while(pl != l1){
			++pl; mn = min(mn , dis(pl , l + 1) - (pl - l) * T * mid);
			if(dis(pl + 1 , l + 1) - (pl - l) * T * mid >= 0) break;
		}
		P.push_back(PII(mn , dis(pl + 1 , l + 1) - (pl - l) * T * mid)); l = pl;
	}
	while(r > r1){
		int pr = r , mn = 1e18;
		while(pr != r1){
			--pr; mn = min(mn , dis(r - 1 , pr) - (r - pr) * T * mid);
			if(dis(r - 1 , pr - 1) - (r - pr) * T * mid >= 0) break;
		}
		Q.push_back(PII(mn , dis(r - 1 , pr - 1) - (r - pr) * T * mid)); r = pr;
	}
	return check(T * mid * N - dis(N , 1) , P , Q);
}

bool check(int mid){
	int l = K , r = K; vector < PII > P , Q;
	while(l != 1){
		int pl = l , mn = 1e18;
		while(pl != 1){
			mn = min(mn , (l - pl) * T * mid - dis(l , pl - 1));
			--pl; if(dis(l , pl) <= (l - pl) * T * mid) break;
		}
		if(dis(l , pl) <= (l - pl) * T * mid) P.push_back(PII(mn , (l - pl) * T * mid - dis(l , pl)));
		else break;
		l = pl;
	}
	while(r != N){
		int pr = r , mn = 1e18;
		while(pr != N){
			mn = min(mn , (pr - r) * T * mid - dis(pr + 1 , r));
			++pr; if(dis(pr , r) <= (pr - r) * T * mid) break;
		}
		if(dis(pr , r) <= (pr - r) * T * mid) Q.push_back(PII(mn , (pr - r) * T * mid - dis(pr , r)));
		else break;
		r = pr;
	}

	return check(T * mid , P , Q) && check1(l - 1 , r + 1 , mid);
}

signed main(){
	N = read(); K = read(); T = read() * 2; for(int i = 1 ; i <= N ; ++i) X[i] = read() * 2;
	int L = 0 , R = 2e9 / T + 1;
	while(L < R){
		int mid = (L + R) >> 1;
		check(mid) ? R = mid : L = mid + 1;
	} cout << L; return 0;
}

相关