Codeforces Round 923 (Div. 3)

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(iai+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+ai1,n) 包括在内)将被涂上油漆,
  • 或者根本不使用。

注意,一个充电最多只能使用一次(即,不能同时向左和向右使用)。允许一个单元格被涂多次。

需要使用充电的最小次数来涂上条带中的所有单元格是多少?

输入

输入的第一行包含一个整数 t t t ( 1 ≤ t ≤ 100 1 \le t \le 100 1t100) — 测试中的测试案例数量。接下来是 t t t 个测试案例的描述。

每个测试案例由两行指定。第一行包含一个整数 n n n ( 1 ≤ n ≤ 100 1 \le n \le 100 1n100) — 条带中的单元格数量。第二行包含 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 1ain),其中 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);

    区间 ik-1 的最优涂色方案我们已经求到了,然后考虑扩展到 ij

图片描述

  • 情况2:if(k-a[k]+1<=i)dp[i][j]=min(dp[i][j],dp[k+1][j]+1);

    区间 k+1j 的最优涂色方案我们已经求到了,然后考虑扩展到 ij

图片描述

  • 情况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]);

图片描述

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;
}
  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值