跟前面的两道题不同的就是数据变大了,并且变成了求最小值,首先考虑dp
设f[i][j]表示前i个位置里分了j份的最小值。sum[i]表示到i为止val[i]的和 mod p的值。
但是这样会超时,所以考虑优化掉一维,对于式子分析,可以发现后面的 mod p有两种情况,拆开式子分别为
这个可以用两个树状数组维护,每次dp的时候,
当前的j可以由≤s[i]的第一个树状数组和>s[i]的第二个树状数组的最小值加上自己的s[i]。
设树状数组c[0/1][pos][vv],pos表示j的位置,vv表示小于或者大于s[i],因为第二个树状数组需要大于s[i]的和,就是后缀和
所以将[i]变为p-s[i]改为求前缀和。
还有解释在代码里
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,k,p,dp[105],val[500005],s[500005],c[2][105][105];
int lowbit(int x)
{
return x&(-x);
}
void add(int ops,int pos,int xb,int v)
{
xb++;//因为有0。
while(xb<=p+1)//范围为0~p,因为有两种,s[i],p-s[i]。
{
c[ops][pos][xb]=min(c[ops][pos][xb],v);
xb+=lowbit(xb);
}
}
int query(int ops,int pos,int xb)
{
int ret=0x3f3f3f3f;xb++;
while(xb)
{
ret=min(ret,c[ops][pos][xb]);
xb-=lowbit(xb);
}
return ret;
}
int main()
{
scanf("%d%d%d",&n,&k,&p);
for(int i=1;i<=n;i++)scanf("%d",&val[i]),s[i]=(s[i-1]+val[i])%p;
for(int i=1;i<=k;i++)
{
for(int j=0;j<=p+1;j++)
{
c[0][i][j]=c[1][i][j]=0x3f3f3f3f;
}
}
add(0,0,0,0);//这行没啥用
add(1,0,0,0);//这两行相当于初值。
for(int i=1;i<=n;i++)
{
for(int j=1;j<=min(j,k);j++)
{
dp[j]=min(query(0,j-1,s[i]),query(1,j-1,p-s[i]))+s[i];//当前j更新可以从前面所有i。所以下标是这样的,因为要求前缀和,而另一种需要后缀,所以这样
}
for(int j=1;j<=min(i,k);j++)
{
if(dp[j]==0x3f3f3f3f)continue;
add(0,j,s[i],dp[j]-s[i]);
add(1,j,p-s[i],dp[j]-s[i]+p);
}
}
printf("%d\n",dp[k]);
return 0;
}