题目传送门
我们不妨可以重新理解题意:
有 3 ∗ n 3*n 3∗n张牌,一开始你拿走了 1 到 5 1到5 1到5号的牌,然后你舍弃三张,如果这三张点数相同,则分数加1。然后再拿三张牌,再舍弃三张如果这三张点数相同,则分数加1……最终的最大得分为多少?
首先可以很容易想到一个
d
p
:
dp:
dp:
d
p
i
,
j
,
k
dp_{i,j,k}
dpi,j,k表示考虑到第i轮,结束后手里拿的牌是
i
i
i和
j
j
j的最大得分为多少?
显然的转移方程:
d
p
i
,
j
,
k
=
d
p
i
−
1
,
j
′
,
k
′
+
丢
弃
的
牌
是
否
一
样
dp_{i,j,k}=dp_{i-1,j',k'}+丢弃的牌是否一样
dpi,j,k=dpi−1,j′,k′+丢弃的牌是否一样,这样直接做的话是
O
(
n
5
)
O(n^5)
O(n5)肯定爆炸。那么我们想优化。
如果第
i
i
i轮结束后两张牌都是从第
i
i
i轮抽中的牌值取出来的那么
i
′
,
j
′
i',j'
i′,j′就可以随意,我们可以设
b
e
s
t
=
max
{
d
p
i
−
1
,
j
′
,
k
′
}
,
第
i
轮
取
走
那
两
张
牌
剩
下
的
是
p
best=\max \{dp_{i-1,j',k'}\},第i轮取走那两张牌剩下的是p
best=max{dpi−1,j′,k′},第i轮取走那两张牌剩下的是p则
d
p
i
,
j
,
k
=
max
{
b
e
s
t
,
d
p
i
−
1
,
p
,
p
}
dp_{i,j,k}=\max\{best,dp_{i-1,p,p}\}
dpi,j,k=max{best,dpi−1,p,p}。
同理我们也可以对去出一张牌的情况进行优化,优化完的复杂度为
O
(
n
3
)
O(n^3)
O(n3)因为转移变成
O
(
1
)
O(1)
O(1)的了,可是这样还是过不了,比赛的时候我也就卡在这里了。
其实我们发现每次你只会拿到3张牌,结束后你如果在这一轮中拿出了牌的话,那两张牌一定会有这三张牌中之一。所以我们的有用状态就可以变成
O
(
n
)
O(n)
O(n)级别的了。滚动掉第一维,时间复杂度就可以变成
O
(
n
2
)
O(n^2)
O(n2)的了。
Code:
/*
{By GWj
*/
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rb(a,b,c) for(int a=b;a<=c;++a)
#define rl(a,b,c) for(int a=b;a>=c;--a)
#define LL long long
#define IT iterator
#define PB push_back
#define II(a,b) make_pair(a,b)
#define FIR first
#define SEC second
#define FREO freopen("check.out","w",stdout)
#define rep(a,b) for(int a=0;a<b;++a)
#define SRAND mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())
#define random(a) rng()%a
#define ALL(a) a.begin(),a.end()
#define POB pop_back
#define ff fflush(stdout)
#define fastio ios::sync_with_stdio(false)
#define R(a) cin>>a
#define R2(a,b) cin>>a>>b
#define check_min(a,b) a=min((a),(b))
#define check_max(a,b) a=max((a),(b))
using namespace std;
const int INF=0x3f3f3f3f;
typedef pair<int,int> mp;
/*}
*/
const int MAXN=2002;
int dp[MAXN][MAXN],save[MAXN],save2[MAXN][MAXN],delta/*全局增量*/,best,best2[MAXN];//滚动掉第一维
int best_,best2_[MAXN];
int n,a[MAXN*3];
vector<vector<int> > cards;
int main(){
fastio;
R(n);
n=n+n+n;
rb(i,1,n)
R(a[i]);
if(n==3){
int rest=1;
rb(i,1,n){
if(a[i]==a[1]);
else rest=0;
}
cout<<rest<<endl;
return 0;
}
rb(j,1,n/3){
vector<int> tmp;
if(j==1){
rb(i,1,5){
tmp.PB(a[i]);
}
cards.PB(tmp);
}
else{
if(j==n/3){
tmp.PB(a[n]);
cards.PB(tmp);
}
else{
rb(i,(j-2)*3+5+1,(j-1)*3+5){
tmp.PB(a[i]);
}
cards.PB(tmp);
}
}
}
memset(dp,-63,sizeof(dp));
rep(i,5)
rep(j,5)
{
if(i!=j){
bool ok=1;
int las=-1;
rep(k,5){
if(k==i||k==j) continue;
if(las!=-1&&las!=cards[0][k]){
ok=0;
}
las=cards[0][k];
}
dp[cards[0][i]][cards[0][j]]=dp[cards[0][j]][cards[0][i]]=ok;
}
}
best=-INF;
memset(best2,-63,sizeof(best2));
n/=3;
rb(i,1,n)
{
rb(j,1,n){
check_max(best2[i],dp[i][j]);
}
check_max(best,best2[i]);
}
rb(i,1,n-2){
rb(j,1,n)
save[j]=dp[j][j];
rep(j,3)
rb(k,1,n)
save2[cards[i][j]][k]=dp[cards[i][j]][k];
best_=best;
rb(j,1,n)
best2_[j]=best2[j];
bool ok=1;
rep(j,3)
{
if(cards[i][j]==cards[i][0]);
else{
ok=0;
}
}
if(ok){
delta++;
continue;
}
/*枚举只有拿了1张牌的情况*/
rep(j_,3){//拿走哪一张
//枚举另一张
int j=cards[i][j_];
int need=-1;
rep(k_,3){
int k=cards[i][k_];
if(k_!=j_){
if(need==-1){
need=k;
}
else{
if(need!=k) need=-1;
}
}
}
rb(k,1,n){
check_max(dp[j][k],best2[k]);
check_max(dp[k][j],best2[k]);
if(need>0)
check_max(dp[k][j],save2[need][k]+1);
check_max(dp[j][k],dp[k][j]);
check_max(best2_[k],dp[j][k]);
check_max(best2_[j],dp[j][k]);
check_max(best_,dp[j][k]);
}
}
/*枚举拿走两张的情况*/
rep(j_,3)
rep(k_,3){
if(j_==k_) continue;
int need=0;
rep(l_,3)
if(l_!=j_&&l_!=k_){
need=cards[i][l_];
}
int j=cards[i][j_],k=cards[i][k_];
check_max(dp[j][k],best);
check_max(dp[j][k],save[need]+1);
check_max(dp[k][j],dp[j][k]);
check_max(best2_[k],dp[j][k]);
check_max(best2_[j],dp[j][k]);
check_max(best_,dp[j][k]);
}
best=best_;
rb(j,1,n)
best2[j]=best2_[j],assert(best>=best2[j]);
}
rb(i,1,n)
rb(j,1,n)
{
assert(dp[i][j]==dp[j][i]);
}
check_max(best,dp[cards[n-1][0]][cards[n-1][0]]+1);
cout<<best+delta<<endl;
return 0;
}/*
5
1 2 5 3 3 5 3 1 3 4 3 2 5 1 4
*/
代码感觉挺好写的,就是初始化的时候一定要设置成 − ∞ -\infty −∞,不然会挂。