This way
题意:
输入是n,k,s,n表示当前序列长度,s表示任意连续的k个数和为s,你有一个操作,可以将任意一个位置上的数变成0-s中的任意一个数,你至少要操作几次能使得它满足s
题解:
将这个数组分成一段一段长度为k的串之后,每个串都是相同的。
那么就可以将其变成这样一个东西:
每列的数字都是相同的。它数据范围是5000,暗示我们
。那么dp[i][j]表示到第i个位置的时候,前面i列的和为j时最少需要转多少个位置。
那么最简单的想法就是到第i位的时候,枚举当前转换成什么位置,枚举dp[i-1],然后转移,这样是k^3的转移,过不去。
然后发现其实如果k很大,那么列数就很小,k很小,列数就很大。然后可以只枚举当前列有的数进行转移。对于没有的数,不用管它,一定是+第i列的所有位置的数量。所以这种情况就是再枚举一次
,它可以从
转移过来。那么用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
*/