[LuoguP1203][USACO1.1]P1203 Broken Necklace

Solution

這道題數據規模奇小,所以大部分人都使用了暴力搜索的方法,這也是我一開始的想法。html

對於 100 100% 的數據, 3 n 350 3≤n≤350 web

的確能夠如此,但暴力搜索的方法也須要進行一些奇怪的判斷,所以我又決定直接打dp的解法,其實dp也是很天然的一種想法……數組

Dynamic Programming

咱們能夠發現,每一個點向左向右,取藍色取紅色能連續取的個數必定是肯定的。
因而咱們定義dp數組:app

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開始能連續取多少個藍色珠子

那麼在一個點斷開,能取得的珠子個數就是:
a n s [ i ] = m a x ( l r [ i ] , l b [ i ] ) + m a x ( r r [ i ] , r b [ i ] ) ans[i] = max(lr[i],lb[i]) + max(rr[i],rb[i]) svg

相信轉移方程很是天然吧,咱們先考慮向左取的狀況:spa

  1. 前一個點爲白色,那麼有:
    l r [ i ] = l r [ i 1 ] + 1 lr[i] = lr[i-1] + 1
    l b [ i ] = l b [ i 1 ] + 1 lb[i] = lb[i-1] + 1
  2. 前一個點爲紅色,那麼有:
    l r [ i ] = l r [ i 1 ] + 1 lr[i] = lr[i-1] + 1
    l b [ i ] = 0 lb[i] = 0
  3. 前一個點爲藍色,那麼有:
    l r [ i ] = 0 lr[i] = 0
    l b [ i ] = l b [ i 1 ] + 1 lb[i] = lb[i-1] + 1

爲何考慮前一個點呢?
由於 l r [ i ] lr[i] l b [ i ] lb[i] 表明的是區間 [ 1 , i 1 ] [1,i-1] 內從點 i 1 i-1 開始取能連續取多少,所以實際考慮的是點 i 1 i-1 的顏色。
向右的狀況也是同理,有:code

  1. 當前點爲白色,那麼有:
    r r [ i ] = r r [ i + 1 ] + 1 rr[i] = rr[i+1] + 1
    r b [ i ] = r b [ i + 1 ] + 1 rb[i] = rb[i+1] + 1
  2. 當前點爲紅色,那麼有:
    r r [ i ] = r r [ i + 1 ] + 1 rr[i] = rr[i+1] + 1
    r b [ i ] = 0 rb[i] = 0
  3. 當前點爲藍色,那麼有:
    r r [ i ] = 0 rr[i] = 0
    r b [ i ] = r b [ i + 1 ] + 1 rb[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 1 i-1 點,向左最多能取多少呢?
l e f t [ i 1 ] = m a x ( l r [ i ] , l b [ i ] ) left[i-1] = max(lr[i],lb[i])
i i 點,向右最多能取
r i g h t [ i ] = m a x ( r r [ i ] , r b [ i ] ) right[i] = max(rr[i],rb[i])
假定咱們斷開 i 1 i-1 i i ,那麼答案就是:
a n s = l e f t [ i 1 ] + r i g h t [ i ] ans = left[i-1] + right[i]

a n s = m a x ( l r [ i ] , l b [ i ] ) + m a x ( r r [ i ] , r b [ i ] ) ans = max(lr[i],lb[i]) + max(rr[i],rb[i])
最後掃一遍統計答案便可。xml

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

固然,還要注意答案不能超過原始的長度。htm

Code

拆環爲鏈等細節就不贅述了。token

#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;
}
相關文章
相關標籤/搜索