codeforces 771D Bear ans Company 动态规划

E. Bear and Company
time limit per test
1 second
memory limit per test
256 megabytes
input
standard input
output
standard output

Bear Limak prepares problems for a programming competition. Of course, it would be unprofessional to mention the sponsor name in the statement. Limak takes it seriously and he is going to change some words. To make it still possible to read, he will try to modify each word as little as possible.

Limak has a string s that consists of uppercase English letters. In one move he can swap two adjacent letters of the string. For example, he can transform a string "ABBC" into "BABC" or "ABCB" in one move.

Limak wants to obtain a string without a substring "VK" (i.e. there should be no letter 'V' immediately followed by letter 'K'). It can be easily proved that it's possible for any initial string s.

What is the minimum possible number of moves Limak can do?

Input

The first line of the input contains an integer n (1 ≤ n ≤ 75) — the length of the string.

The second line contains a string s, consisting of uppercase English letters. The length of the string is equal to n.

Output

Print one integer, denoting the minimum possible number of moves Limak can do, in order to obtain a string without a substring "VK".

Examples
Input
4
VKVK
Output
3
Input
5
BVVKV
Output
2
Input
7
VVKEVKK
Output
3
Input
20
VKVKVVVKVOVKVQKKKVVK
Output
8
Input
5
LIMAK
Output
0
Note

In the first sample, the initial string is "VKVK". The minimum possible number of moves is 3. One optimal sequence of moves is:

  1. Swap two last letters. The string becomes "VKKV".
  2. Swap first two letters. The string becomes "KVKV".
  3. Swap the second and the third letter. The string becomes "KKVV". Indeed, this string doesn't have a substring "VK".

In the second sample, there are two optimal sequences of moves. One is "BVVKV"  →  "VBVKV"  →  "VVBKV". The other is "BVVKV"  →  "BVKVV"  →  "BKVVV".

In the fifth sample, no swaps are necessary.


题目描述:给定一个长度为n(n < 76)的字符串,要求通过相邻字符之间的两两交换,使得交换之后的字符串内不会出现“VK”的片段,问最少交换的次数是多少。


思路:要解决这个问题,首先要能够观察出两个性质和一个模型。

           性质1:字符其实只有3种,即V、K、X,X指示除了V和K之外的一切字符,也就是说,我们最后只需要我们将字符串调整成诸如VVXKKKVXK这样的形式最少要多少次交换,至于这两个X各是什么字符,我们并不关心,稍后我们会看到,我们dp出的答案就是这种形式下的最优解。

          性质2:假如我们已经知道最优解时的字符串,且这个字符串开头的v+k+x个字符是由v个‘V’,k个'K',x个‘X’组成的,那么这v个'V'一定是原字符串中的前v个'V',K和X同理。这个的严格数学证明我也不清楚,但是通过把各种情况分类之后可以发现确实满足这个规律。

         模型:已知原字符串和新字符串,将原字符串通过交换相邻字符的方式变为新字符串,一共最少需要多少次交换?这是树状数组求逆序对的经典问题,这个问题的过程可以启发每个字母的贡献是多少。

         分析到这里,我们可以用dp来解决这个问题了:设dp[v][k][x][t]表示用前v个‘V’,k个'K',x个‘X’组成新字符串的前v+k+x个字符,最少需要多少次交换,t表示这个长度为v+k+x的字符串结尾是'V','K'还是'X',这是为了防止产生连续"VK",下面我们来考虑转移,假设我们现在已知dp[v][k][x][t],如果我们在这个字符串后面加上一个'V',那么新状态就是

dp[v + 1][k][x][1]   (t == 1表示以‘V’结尾),那新加的这个V产生了多少贡献呢?实际上,我们本来就已经知道了dp[v][k][x][t]中的v个‘V’,k个'K',x个‘X’的位置,那么我们可以把这些位置标记掉,然后我们找到第一个没被标记掉的'V',也就是这次要用的'V',那么,这个V之前没被标记掉的位置的个数cnt,就是这个V的贡献,这就相当于是产生了cnt个逆序对,那么dp[v + 1][k][x][1]= min(dp[v + 1][k][x][1]  , dp[v][k][x][t] + cnt)。在末尾加'K','X'时的转移类似,但是要注意如果当前末尾是'V',下一个字符不要接'K'。

       实现时,采用刷表明显比填表的实现容易,我的代码写的比较冗长,但是应该还算直观。

#pragma warning(disable:4786)
#pragma comment(linker, "/STACK:102400000,102400000")
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LL long long
#define FOR(i,f_start,f_end) for(int i=f_start;i<=f_end;++i)
#define mem(a,x) memset(a,x,sizeof(a))
#define lson l,m,x<<1
#define rson m+1,r,x<<1|1
using namespace std;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const double PI = acos(-1.0);
const double eps=1e-6;
const int maxn = 80;
LL dp[maxn][maxn][maxn][5];
char s[maxn];
int n , vis[maxn];
void trans(int v , int k , int x , int t)
{
    mem(vis , 0);
    int cnt = 1 , v_blank = 0 , k_blank = 0 , x_blank = 0;
    for(int i = 1 ; i <= n && cnt <= v ; i++){
        if(s[i] == 'V'){
            vis[i] = 1;     ++cnt;
        }
    }
    cnt = 1;
    for(int i = 1 ; i <= n && cnt <= k ; i++){
        if(s[i] == 'K'){
            vis[i] = 1;     ++cnt;
        }
    }
    cnt = 1;
    for(int i = 1 ; i <= n && cnt <= x ; i++){
        if(s[i] != 'V' && s[i] != 'K'){
            vis[i] = 1;     ++cnt;
        }
    }
    for(int i = 1 ; i <= n ; i++){
        if(s[i] == 'V' && !vis[i])      break;
        if(!vis[i])     ++v_blank;
    }
    for(int i = 1 ; i <= n ; i++){
        if(s[i] == 'K' && !vis[i])      break;
        if(!vis[i])     ++k_blank;
    }
    for(int i = 1 ; i <= n ; i++){
        if(s[i] != 'V' && s[i] != 'K'&& !vis[i])      break;
        if(!vis[i])     ++x_blank;
    }
    dp[v + 1][k][x][1] = min(dp[v + 1][k][x][1] , dp[v][k][x][t] + v_blank);
    if(t != 1)
        dp[v][k + 1][x][2] = min(dp[v][k + 1][x][2] , dp[v][k][x][t] + k_blank);
    dp[v][k][x + 1][3] = min(dp[v][k][x + 1][3] , dp[v][k][x][t] + x_blank);
}
int main()
{
    scanf("%d" , &n);
    scanf("%s" , s + 1);
    for(int i = 0 ; i <= n ; i++){
        for(int j = 0 ; j <= n ; j++){
            for(int k = 0 ; k <= n; k++){
                for(int t = 1 ; t <= 3 ; t++){
                    dp[i][j][k][t] = INF;
                }
            }
        }
    }
    dp[0][0][0][0] = 0;
    int vsum = 0 , ksum = 0 , xsum = 0;
    for(int i = 1 ; i <= n ; i++){
        if(s[i] == 'V')     ++vsum;
        else if(s[i] == 'K')    ++ksum;
        else                   ++xsum;
    }
    for(int v = 0 ; v <= vsum ; v++){
        for(int k = 0 ; k <= ksum; k++){
            for(int x = 0 ; x <= xsum ; x++){
                if(!v && !k && !x){
                    trans(0 , 0 , 0 , 0);       continue;
                }
                for(int t = 1 ; t <= 3 ; t++){
                    trans(v , k , x , t);
                }
            }
        }
    }
    LL ans = INF;
    for(int t = 1 ; t <= 3 ; t++){
        ans = min(ans , dp[vsum][ksum][xsum][t]);
    }
    printf("%lld\n",ans);
    return 0;
}


你可能感兴趣的:(dp)