B. Saving the City

B. Saving the City

题目大意:

给一串01序列,1是炸弹,i位置的炸弹爆炸会引爆i+1位置和i-1位置的炸弹。引爆炸弹的代价是a,在空白位置添加炸弹的代价是b。问引爆所有炸弹的最小代价和是多少。

思路和代码:

首先删除前缀0和后缀0,将连续1压缩。(没什么好说的)

我的方法:

先预处理一个pre数组,若ti为1,prei就是i位置之前的第一个1的位置。当然,i==1时特殊处理。

做dp数组,dpi表示把i前面(包括i)的所有炸弹引爆的最小花费。

具体看dp过程,状态转移方程在其中。

void solve(){
	ll num1 = 0 ;
	ll a , b ; cin >> a >> b ;
	string s ;
	cin >> s ;
	string t = "" ;
	rep(i , 0 , s.size() - 1){
		num1 += s[i] - '0' ;
		if(!t.size()) t += s[i] ;
		else{
			if(s[i] == *(t.end() - 1) && s[i] == '1') continue ;
			t += s[i] ;
		}
	}
	if(num1 <= 1){
		cout << num1 * a << "\n" ;
		return ;
	}
	while(t.size() && t[0] == '0') t.erase(0 , 1) ;
	while(t.size() && *(t.end() - 1) == '0') t.erase(t.size() - 1) ;
	
	ll len = t.size() ;
	t = "*" + t ;
	vct<ll> prei( len + 1 , 0) ;
	prei[1] = 1 ;
	ll l = 1 , r = 1 ;
	while(l <= r && r <= len){
		r = l + 1 ;
		while(r <= len && t[r] == '0') r ++ ;
		if(r <= len)prei[r] = l ;
		l = r ;
	}

	vct<ll> dp(len + 1 , INF) ;
	dp[0] = 0 ;
	dp[1] = a ;
	rep(i , 2 , len){
		if(t[i] == '0') dp[i] = dp[i - 1] ;
		else{
			dp[i] = min(dp[i] , dp[i - 1] + a) ;
			dp[i] = min(dp[i] , dp[prei[i] - 1] + (i - prei[i] - 1) * b + a) ;
			dp[i] = min(dp[i] , dp[prei[i]] + (i - prei[i] - 1) * b ) ;
		}
	}
	cout << dp[len] << "\n" ;

}//code_by_tyrii 

这是dp解法,而tyq的方法更加简单:

举例:1000001(假设两个1之间有num个0)

引爆这两个1有两种方法:单独引爆和连在一起引爆。那什么时候连在一起引爆呢?
$$
a+a>a+num*b
$$

$$
a>num*b
$$

这是什么意思呢,就是说我们本来判断哪种方法更优需要将两个炸弹都炸掉。这样的话10010101这样的就判断不了。有了上面这个式子我们就可以判断什么时候应该将两个炸弹连起来。

小结:

dp的时候要考虑周全。

要有多角度的思维,做题的时候要多利用题目性质解题。

posted @ 2022-02-27 00:40  tyrii  阅读(31)  评论(0)    收藏  举报