Codeforces Round #384 (Div. 2) 734E(二分答案+状态压缩DP)

题目大意

给定一个序列an,序列中只有1~8的8个整数,让你选出一个子序列,满足下列两个要求

1.不同整数出现的次数相差小于等于1

2.子序列中整数分布是连续的,即子序列的整数必须是1,1,1....1,2,2,2.....2,2.......连续分布

做法:

那么我们可以二分不同整数出现的次数,假如说二分出现次数是L,那么可以证明有a个整数出现次数是L,有(8-a)个整数出现次数是L-1(同时不难证明L+1比L更优)

这样每个整数只有两种状态,要么选L个,要么选L-1个。

压缩决策s,s的第i位表示是否对整数i做出了决策

再用容器把每一个不同整数出现的序列记录下来,这样就可以进行dp了

设dp[i][s]表示从原序列扫到前i个数,已经做出了s的决策,出现L次的不同整数个数最大是多少

枚举对第k个整数做决策,dp[i][s]就可以转移到另外两个状态

dp[next(k, L-1)][s|(1<<k)] = max(itself, dp[i][s]);

dp[next(k, L)][s|(1<<k)] = max(itself, dp[i][s]+1);

其中next(k, L)是指从i开始,往后的第L个k的整数位置

然后取dp[1~n][(1<<8)-1]中的最大值,返回答案即可

另外如果最大值小于等于0(注意等于!!),说明不存在解,返回0即可。

需要注意的是,当l = 2都不可行的时候需要特判处理。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int maxn = 1111;
const int inf = 1e9;
int dp[maxn][1<<8], a[maxn], cur[maxn], n;
vector <int> in[10];
int can(int L)
{
    for(int i = 1; i <= 8; i++) cur[i] = 0;
    memset(dp, 128, sizeof(dp));
    dp[1][0] = 0;
    for(int i = 1; i <= n; i++)
    {
        for(int s = 0; s < (1<<8); s++)
        {
            if(dp[i][s] < 0) continue;
            for(int k = 1; k <= 8; k++)
            {
                if(s & (1<<(k-1))) continue;
                if(in[k].size() - cur[k] < L-1) continue;
                dp[in[k][cur[k] + L - 2]][s|(1<<(k-1))] = max(dp[in[k][cur[k] + L - 2]][s|(1<<(k-1))], dp[i][s]);
                if(in[k].size() - cur[k] < L)  continue;
                dp[in[k][cur[k] + L - 1]][s|(1<<(k-1))] = max(dp[in[k][cur[k] + L - 1]][s|(1<<(k-1))], dp[i][s]+1);
            }
        }
        cur[a[i]]++;
    }
    int ans = -inf;
    for(int i = 1; i <= n ; i++) ans = max(ans, dp[i][(1<<8) - 1]);
    if(ans <= 0) return 0;
    return ans * L + (8 - ans)*(L-1);
}

int main()
{
    //freopen("a.txt", "r", stdin);
    cin>>n;
    for(int i = 1; i <= n; i++) cin>>a[i];
    for(int i = 1; i <= n; i++) in[a[i]].push_back(i);
    int l = 2, r = n, ans = -inf;
    while(l < r)
    {
        int mid = (l+r)>>1;
        if(can(mid)) l = mid+1;
        else r = mid;
    }
    if(can(2) == 0)
    {
        ans = 0;
        for(int i = 1; i <= 8; i++) if(in[i].size() > 0) ans++;
        cout<<ans<<endl;
    }
    else
    {
        ans = max(can(l), can(l-1));
        cout<<ans<<endl;
    }
}
原文地址:https://www.cnblogs.com/Saurus/p/6183721.html