ICPC North Central NA Contest 2017 D. Smooth Array —— DP,剪枝

This way

题意:

输入是n,k,s,n表示当前序列长度,s表示任意连续的k个数和为s,你有一个操作,可以将任意一个位置上的数变成0-s中的任意一个数,你至少要操作几次能使得它满足s

题解:

将这个数组分成一段一段长度为k的串之后,每个串都是相同的。
那么就可以将其变成这样一个东西:
在这里插入图片描述
每列的数字都是相同的。它数据范围是5000,暗示我们 n 2 n^2 。那么dp[i][j]表示到第i个位置的时候,前面i列的和为j时最少需要转多少个位置。
那么最简单的想法就是到第i位的时候,枚举当前转换成什么位置,枚举dp[i-1],然后转移,这样是k^3的转移,过不去。
然后发现其实如果k很大,那么列数就很小,k很小,列数就很大。然后可以只枚举当前列有的数进行转移。对于没有的数,不用管它,一定是+第i列的所有位置的数量。所以这种情况就是再枚举一次 d p [ i ] [ j ] ( 0 < = j < = s ) dp[i][j](0<=j<=s) ,它可以从
d p [ i 1 ] [ k ] ( 0 < = k < = j ) dp[i-1][k](0<=k<=j) 转移过来。那么用mi数组维护dp[i-1]的前缀最小值。
状态转移方程就是

dp[i][j]=min(dp[i][j],mi[j]+num[i]);
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+5;
int dp[N][N],mi[N],num[N],siz[N][N];
vector<int>vec[N];
int a[N];
int main()
{
    int n,k,s;
    scanf("%d%d%d",&n,&k,&s);
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]),num[i%k]++;
        if(!siz[i%k][a[i]])
            vec[i%k].push_back(a[i]);
        siz[i%k][a[i]]++;
    }
    for(int i=0;i<=k;i++)
        for(int j=0;j<=s;j++)
            dp[i][j]=1e9;
    //memset(mi,125,sizeof(mi));
    for(int i=0;i<=s;i++){
        dp[0][i]=num[0]-siz[0][i];
        mi[i]=i?min(mi[i-1],dp[0][i]):dp[0][i];
    }
    for(int i=1;i<k;i++){
        for(auto j:vec[i]){
            for(int k=j;k<=s;k++)
                dp[i][k]=min(dp[i][k],dp[i-1][k-j]+num[i]-siz[i][j]);
        }
        for(int j=0;j<=s;j++)
            dp[i][j]=min(dp[i][j],mi[j]+num[i]);

        for(int j=0;j<=s;j++)
            mi[j]=j?min(mi[j-1],dp[i][j]):dp[i][j];
    }
    printf("%d\n",dp[k-1][s]);
    return 0;
}
/*
16 4 10
5 2 3 5 5 2 3 5 5 2 3 5 5 3 1 5
*/
发布了554 篇原创文章 · 获赞 31 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/tianyizhicheng/article/details/104600786