[LuoguP1203][USACO1.1]P1203 Broken Necklace

博客详细介绍了如何使用动态规划解决LuoguP1203和USACO1.1题目Broken Necklace的问题。通过分析每个点左右方向上连续珠子的最大数量,构建转移方程,最终求解最优解。代码实现简洁明了。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Solution

这道题数据规模奇小,因此大部分人都使用了暴力搜索的方法,这也是我一开始的想法。

对于 100100%100 的数据,3≤n≤3503≤n≤3503n350

的确可以如此,但暴力搜索的方法也需要进行一些奇怪的判断,因此我又决定直接打dp的解法,其实dp也是很自然的一种想法……

Dynamic Programming

我们可以发现,每个点向左向右,取蓝色取红色能连续取的个数一定是确定的。
于是我们定义dp数组:

int lr[maxn];
//lr[i]代表从i点向左不取i点
//即在 [1,i-1] 范围内从i-1开始能连续取多少个红色珠子
int lb[maxn];
//lb[i]代表从i点向左不取i点
//即在 [1,i-1] 范围内从i-1开始能连续取多少个蓝色珠子
int rr[maxn];
//rr[i]代表从i点向右取i点
//即在[i,n] 范围内从i开始能连续取多少个红色珠子
int rb[maxn];
//rb[i]代表从i点向右取i点
//即在[i,n] 范围内从i开始能连续取多少个蓝色珠子

那么在一个点断开,能取得的珠子个数就是:
ans[i]=max(lr[i],lb[i])+max(rr[i],rb[i])ans[i] = max(lr[i],lb[i]) + max(rr[i],rb[i])ans[i]=max(lr[i],lb[i])+max(rr[i],rb[i])

相信转移方程非常自然吧,我们先考虑向左取的情况:

  1. 前一个点为白色,那么有:
    lr[i]=lr[i−1]+1lr[i] = lr[i-1] + 1lr[i]=lr[i1]+1
    lb[i]=lb[i−1]+1lb[i] = lb[i-1] + 1lb[i]=lb[i1]+1
  2. 前一个点为红色,那么有:
    lr[i]=lr[i−1]+1lr[i] = lr[i-1] + 1lr[i]=lr[i1]+1
    lb[i]=0lb[i] = 0lb[i]=0
  3. 前一个点为蓝色,那么有:
    lr[i]=0lr[i] = 0lr[i]=0
    lb[i]=lb[i−1]+1lb[i] = lb[i-1] + 1lb[i]=lb[i1]+1

为什么考虑前一个点呢?
因为lr[i]lr[i]lr[i]lb[i]lb[i]lb[i]代表的是区间[1,i−1][1,i-1][1,i1]内从点i−1i-1i1开始取能连续取多少,因此实际考虑的是点i−1i-1i1的颜色。
向右的情况也是同理,有:

  1. 当前点为白色,那么有:
    rr[i]=rr[i+1]+1rr[i] = rr[i+1] + 1rr[i]=rr[i+1]+1
    rb[i]=rb[i+1]+1rb[i] = rb[i+1] + 1rb[i]=rb[i+1]+1
  2. 当前点为红色,那么有:
    rr[i]=rr[i+1]+1rr[i] = rr[i+1] + 1rr[i]=rr[i+1]+1
    rb[i]=0rb[i] = 0rb[i]=0
  3. 当前点为蓝色,那么有:
    rr[i]=0rr[i] = 0rr[i]=0
    rb[i]=rb[i+1]+1rb[i] = rb[i+1] + 1rb[i]=rb[i+1]+1
    实现也很简单:
for (int i = 2; i <= n; ++i)
{
   if (s[i - 1] == 'w')
        lb[i] = lb[i - 1] + 1, lr[i] = lr[i - 1] + 1;
    else if (s[i - 1] == 'b')
        lb[i] = lb[i - 1] + 1, lr[i] = 0;
    else
        lb[i] = 0, lr[i] = lr[i - 1] + 1;
}
for (int i = n - 1; i; --i)
{
    if (s[i] == 'w')
        rb[i] = rb[i + 1] + 1, rr[i] = rr[i + 1] + 1;
    else if (s[i] == 'b')
        rb[i] = rb[i + 1] + 1, rr[i] = 0;
    else
        rb[i] = 0, rr[i] = rr[i + 1] + 1;
}

Finally

于是我们处理出了每个点向左向右取红取蓝最多能连续取多少个珠子。
那么从i−1i-1i1点,向左最多能取多少呢?
left[i−1]=max(lr[i],lb[i])left[i-1] = max(lr[i],lb[i])left[i1]=max(lr[i],lb[i])
iii点,向右最多能取
right[i]=max(rr[i],rb[i])right[i] = max(rr[i],rb[i])right[i]=max(rr[i],rb[i])
假定我们断开i−1i-1i1iii,那么答案就是:
ans=left[i−1]+right[i]ans = left[i-1] + right[i]ans=left[i1]+right[i]

ans=max(lr[i],lb[i])+max(rr[i],rb[i])ans = max(lr[i],lb[i]) + max(rr[i],rb[i])ans=max(lr[i],lb[i])+max(rr[i],rb[i])
最后扫一遍统计答案即可。

int ans = 0;
for (int i = 1; i <= n; ++i)
    ans = max(ans, max(lb[i], lr[i]) + max(rb[i], rr[i]));

当然,还要注意答案不能超过原始的长度。

Code

拆环为链等细节就不赘述了。

#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 710;
char s[maxn];
int n,lb[maxn], lr[maxn], rb[maxn], rr[maxn];
inline int max(const int &a, const int &b) { return a > b ? a : b; }
int main()
{
    scanf("%d%s", &n, s + 1);
    memcpy(s + n + 1, s + 1, n);
    n <<= 1;
    for (int i = 2; i <= n; ++i)
    {
        if (s[i - 1] == 'w')
            lb[i] = lb[i - 1] + 1, lr[i] = lr[i - 1] + 1;
        else if (s[i - 1] == 'b')
            lb[i] = lb[i - 1] + 1, lr[i] = 0;
        else
            lb[i] = 0, lr[i] = lr[i - 1] + 1;
    }
    for (int i = n - 1; i; --i)
    {
        if (s[i] == 'w')
            rb[i] = rb[i + 1] + 1, rr[i] = rr[i + 1] + 1;
        else if (s[i] == 'b')
            rb[i] = rb[i + 1] + 1, rr[i] = 0;
        else
            rb[i] = 0, rr[i] = rr[i + 1] + 1;
    }
    int ans = 0;
    for (int i = 1; i <= n; ++i)
        ans = max(ans, max(lb[i], lr[i]) + max(rb[i], rr[i]));
    if (ans > n >> 1)
        ans = n >> 1;
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值