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的时候要考虑周全。
要有多角度的思维,做题的时候要多利用题目性质解题。

浙公网安备 33010602011771号