Codeforces Round 923 (Div. 3)
G.Paint Charges
给定一个包含 n n n 个单元格的水平网格条。在第 i i i 个单元格中,有一个大小为 a i a_i ai 的油漆充电。这个充电可以:
- 要么向左使用 —— 然后所有左边距离小于 a i a_i ai 的单元格(从 max ( i − a i + 1 , 1 ) \max(i - a_i + 1, 1) max(i−ai+1,1) 到 i i i 包括在内)将被涂上油漆,
- 要么向右使用 —— 然后所有右边距离小于 a i a_i ai 的单元格(从 i i i 到 min ( i + a i − 1 , n ) \min(i + a_i - 1, n) min(i+ai−1,n) 包括在内)将被涂上油漆,
- 或者根本不使用。
注意,一个充电最多只能使用一次(即,不能同时向左和向右使用)。允许一个单元格被涂多次。
需要使用充电的最小次数来涂上条带中的所有单元格是多少?
输入
输入的第一行包含一个整数 t t t ( 1 ≤ t ≤ 100 1 \le t \le 100 1≤t≤100) — 测试中的测试案例数量。接下来是 t t t 个测试案例的描述。
每个测试案例由两行指定。第一行包含一个整数 n n n ( 1 ≤ n ≤ 100 1 \le n \le 100 1≤n≤100) — 条带中的单元格数量。第二行包含 n n n 个正整数 a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,…,an ( 1 ≤ a i ≤ n 1 \le a_i \le n 1≤ai≤n),其中 a i a_i ai 是条带从左到右第 i i i 个单元格的油漆充电大小。
保证测试中 n n n 值的总和不超过 1000 1000 1000。
输出
对于每个测试案例,输出需要使用充电的最小次数来涂上条带中的所有单元格。
解题思路
这个题目的题意比较清楚,所以我们直接开始思考。
观察数据范围,考虑是否可以使用区间 dp
。
先尝试区间 dp
最经典的状态,设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示将i到j涂色使用的最少油漆数量。
然后考虑所有涂漆的情况。
定义:假如一个区间的涂色方案没有用到区间外的油漆,那么叫这个涂色方案为独立涂色方案
重点就在于独立涂色方案的转移,然后在将独立涂色方案拼成其它方案。
简单来说,独立涂色方案是由以下三种涂色情况构成的。
然后分别考虑以下三种情况的转移。
设 i
为左端点,j
为右端点。
-
情况1:
if(k+a[k]-1>=j)dp[i][j]=min(dp[i][j],dp[i][k-1]+1);
区间
i
到k-1
的最优涂色方案我们已经求到了,然后考虑扩展到i
到j
。
-
情况2:
if(k-a[k]+1<=i)dp[i][j]=min(dp[i][j],dp[k+1][j]+1);
区间
k+1
到j
的最优涂色方案我们已经求到了,然后考虑扩展到i
到j
。
-
情况3:
if(k!=l&&k-a[k]+1<=i&&l+a[l]-1>=j)dp[i][j]=min(dp[i][j],2ll);
这种情况只会使用
2
个油漆,在区间内枚举两个油漆即可。 -
然后将独立涂色方案拼起来:
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
![图片描述](https://img-blog.csdnimg.cn/img_convert/3973695056b637f22e36f4271f8a74ba.png)
AC_Code
- C++
//
// Created by liuhao.
//
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define int long long
#define endl '\n'
#define vii vector<vector<int>>
signed main() {
ios;
int T;
cin>>T;
while(T--){
int n;
cin>>n;
vector<int>a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
}
vector<vector<int>>dp(n+2,vector<int>(n+2,100));
for(int i=0;i<=n;i++){
dp[i+1][i]=0;
dp[i+1][i]=0;
}
for(int len=1;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
for(int k=i;k<=j;k++){
if(k-a[k]+1<=i)dp[i][j]=min(dp[i][j],dp[k+1][j]+1);
if(k+a[k]-1>=j)dp[i][j]=min(dp[i][j],dp[i][k-1]+1);
}
for(int k=i;k<j;k++){
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
}
for(int k=i;k<j;k++){
for(int l=i;l<=k+1;l++){
if(l==k)continue;
if(k-a[k]+1<=i&&l+a[l]-1>=j)dp[i][j]=min(dp[i][j],2ll);
}
}
}
}
cout<<dp[1][n]<<endl;
}
return 0;
}