题解——碰杯
题目描述
今天的酒席有n个人,他们要同时举杯,成对碰杯。碰杯的时候,不能有人不参与碰杯,也不希望有手臂交叉这种别扭的情况出现每个人都有一个喜爱的酒种类,每个人想要与和自己喝一样酒的人碰杯,请你设计一个方法,在保证每个人参与碰杯,且没有手臂交叉的情况下,有最多的人与喝一样酒的人碰杯,输出最多有多少人能与喝一样酒的人碰杯。
输入输出
第一行一个数n,表示酒桌上人的个数。
第二行n个数ci,逆时针方向依次表示坐在第i个位置的人喝哪种酒,第1个和第n个人是相邻的。
样例
【输入样例1】
6
1 2 2 1 3 3
【输出样例1】
3
对于这道题目,显然可以看出是一道区间Dp,但是如何进行合法的区间合并,是本题的关键。
没有手臂交叉,就是在进行区间合并时不能将合法的分割点所分成的左右区间中进行统计匹配。那么,这就需要我们合并区间时做一些修改。
我们来分析下面这组数据:
现定义2个合法区间 dp[i~k-1] 和 dp[k+1~j-1]。如果分割点为 K ,末端点为 j , 我们保证 dp[i~k] 和 dp[k~j] 中没有人交叉碰杯,那么只需要将 k , j 提出在进行合并即可。 该方法正确性显然。
所以我们得到转移方程
( c[ j ] == c[ k ] )dp[ i ][ j ] = max( dp[ i ][ j ] , dp[ i ][ k - 1 ] + dp[ k+1 ][ j - 1 ] + 1 );
( c[ j ] != c[ k ] )dp[ i ][ j ] = max( dp[ i ][ j ] , dp[ i ][ k - 1 ] + dp[ k+1 ][ j - 1 ] );
那么,本题就差一个区间Dp的框架了。
AC代码
**注:本题可以不开 long long **
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll dp[ 1005 ][ 1005 ] , ans , N , c[ 1005 ] ;
inline ll read()
{
ll s = 0,w = 1;
char g = getchar();
while(g<'0'||g>'9'){if(g=='-')w*=-1;g = getchar();}
while(g>='0'&&g<='9'){s = s*10+g-'0';g = getchar();}
return s*w;
}
void init_(){
freopen("toasting.in","r",stdin);
freopen("toasting.out","w",stdout);
}
void prepare_(){
N = read() ; memset( dp , 0 , sizeof(dp) ) ;
for( register int i = 1 ; i <= N ; i ++ )c[ i ] = read() ;
}
void Dp_(){
for( register int j = 2 ; j <= N ; j++ )
for( register int i = j - 1 ; i >= 1 ; i -= 2 )
for( register int k = i ; k <= j - 1 ; k += 2 ){ // compared c[ i ] with c[ k ] , so we need add dp i,k-1 k+1,j-1
if( c[ j ] == c[ k ] )dp[ i ][ j ] = max( dp[ i ][ j ] , dp[ i ][ k - 1 ] + dp[ k+1 ][ j - 1 ] + 1 );
else dp[ i ][ j ] = max( dp[ i ][ j ] , dp[ i ][ k - 1 ] + dp[ k+1 ][ j - 1 ] );
}
}
void Find_ans(){
cout<< dp[ 1 ][ N ] <<endl;
}
int main()
{
init_();
prepare_() ;
Dp_() ;
Find_ans();
return 0 ;
}