[CF936D]World of Tank

题目

传送门 to CF

思路

重点在于,找到坦克的操作究竟应该是怎样的。

比如我们很容易发现,如果你摧毁了一个障碍物,那么你就应该从它的废墟上开过。但是,更重要也更难发现的是:在走到那个废墟前,不应该变道

为什么呢?假设你变道出去,然后又回来(为了碾过废墟),由于炮弹在这一段区间内不会被阻挡(能打到后面的废墟),所以你没有绕开障碍物;唯一可能就是你过去开炮了。根据 “必须碾过废墟” 理论,你需要碾过所有新废墟。

假如这个新废墟在原废墟前面,岂不自讨苦吃——何不绕开它,直接走原来这条路?毕竟你终将到达原废墟。走原来的路,可以少开一次炮,一定更优。

所以新废墟在原废墟之后(或者同一列)。但这也是荒谬的。炮弹可达之处,坦克必可至。于是坦克可以直接走另一条路,绕开原废墟,然后到达新废墟处。毕竟你终将到达新废墟,二者等效。那么,最初坦克就根本不需要轰击原废墟了啊!

证毕。此时我们会发现:当坦克刚走到一条路上时,它面前的障碍物都应该没被轰击。所以不需要存储障碍物的状态了!只需要考虑从某个地方开始一直往后走的过程!

利用提前开炮的能力,走 x x x 步可以开 ⌊ x t ⌋ \lfloor{x\over t}\rfloor tx 炮。其实相当于每开一炮消耗 t t t 的能量,然后移动可以一直积蓄能量。设计 d p \tt dp dp f ( x , y ) f(x,y) f(x,y) 表示走到 ( x , y ) (x,y) (x,y) 处最多可以拥有多少能量。转移显然是从 f ( x , y ⊕ 1 ) f(x,y\oplus 1) f(x,y1) f ( x − 1 , y ) f(x{\rm-}1,y) f(x1,y) 转移而来。

在最近的障碍物之间任意一处换道,效果都相同,不妨就让其在经过某个障碍物后立刻换道。此时 x x x 的取值只有 O ( m 1 + m 2 ) \mathcal O(m_1+m_2) O(m1+m2) 个,可以通过本题。

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
    
    
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MAXN = 1000005;
int x[2][MAXN], m[2], all[MAXN<<1], t;
int dp[MAXN<<1][2], pre[MAXN<<1][2];

int lc[MAXN], cnt_lc; // lane changing
int shot[MAXN][2], cnt_shot;
/// @return last position to shot
int print(int r,int y){
    
    
	if(!r && !y) return 0; // just began cooling time
	if(pre[r][y] == 1){
    
    
		print(r,y^1), lc[++ cnt_lc] = all[r];
		return all[r]-dp[r][y]; // what's saved
	}
	if(pre[r][y] == 0) return print(r-1,y);
	int lst = print(r-1,y)+t; // one more shot
	shot[++ cnt_shot][0] = lst;
	return shot[cnt_shot][1] = y+1, lst;
}
int main(){
    
    
	scanf("%*d"); // n is useless
	rep(i,0,1) m[i] = readint();
	t = readint();
	rep(d,0,1) rep(i,1,m[d]) x[d][i] = readint();
	for(int a=1,b=1; a<=m[0]||b<=m[1]; )
		if(b > m[1] || (a <= m[0] && x[0][a] <= x[1][b]))
			all[a+b-1] = x[0][a], ++ a;
		else all[a+b-1] = x[1][b], ++ b; // merge sort
	int tot = unique(all+1,all+m[0]+m[1]+1)-all-1;
	drep(i,tot,1) all[i<<1] = all[i]+1, all[(i<<1)-1] = all[i];
	tot = unique(all+1,all+(tot<<1)+1)-all-1;
	all[0] = 0, dp[0][0] = dp[0][1] = 0;
	pre[0][1] = 1; // from lane changing
	for(int i=1,now[2]={
    
    1,1}; i<=tot; ++i){
    
    
		for(int d=0; d!=2; ++d){
    
    
			while(now[d] <= m[d] && x[d][now[d]] < all[i]) ++ now[d];
			dp[i][d] = dp[i-1][d]+(all[i]-all[i-1]); // charged
			if(dp[i-1][d] == -1) dp[i][d] = -1; // cannot transfer
			if(x[d][now[d]] == all[i]){
    
    
				dp[i][d] -= t; // make shot lose energy
				if(dp[i][d] > 0) pre[i][d] = 2; // shot
				else dp[i][d] = -1; // fail to do that
			}
			else pre[i][d] = 0; // just move ahead
		}
		rep(d,0,1) if(dp[i][d^1] >= 0 && x[d][now[d]] != all[i])
			if(dp[i][d] < min(dp[i][d^1],t)) // at most one shot
				dp[i][d] = min(dp[i][d^1],t), pre[i][d] = 1; // change lane
	}
	if(dp[tot][0] >= 0 || dp[tot][1] >= 0){
    
    
		puts("Yes"), print(tot,bool(dp[tot][1]>=0));
		printf("%d\n",cnt_lc);
		rep(i,1,cnt_lc) printf("%d ",lc[i]);
		printf("\n%d\n",cnt_shot);
		rep(i,1,cnt_shot) printf("%d %d\n",shot[i][0],shot[i][1]);
		putchar('\n');
	}
	else puts("No");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42101694/article/details/122380200