【2020NOI.AC省选模拟#5】C. 光滑序列
题目链接
原题解:
光滑的序列一定有长度为$K$的循环节。
使用动态规划,设$F(i,j)$为使前$i$个整数的和为$j$的最小修改次数。
记$cost(i,v)$为令$A_i,A_{i+K},A_{i+2K},\cdots$等于$v$需要而修改次数。则$F(i,j)=\min\limits_v \{F(i-1,j-v)+cost(i,v)\}$。
可以发现,$A_i,A_{i+K},A_{i+2K},\cdots$中至多有$O(\dfrac{N}{K})$个不同的值。
我们可以先用$F(i,j)=\min \limits_v \{F(i-1,j-v)+\left\lfloor \dfrac{N-i}{K} \right\rfloor +1\}$一次性转移没有在这些位置中出现过的$v$,然后再对这$O(\dfrac{N}{K})$个$v$单独转移。
复杂度为$O(KS\times \dfrac{N}{K})=O(NS)$。
补充:
$O(N^2)$比$O(N^3)$改进的地方是两点:
- 使用$f(i-1,j)$的前缀最小值来设定$f(i,j)$的初值。
- 枚举增加的数进行转移,于是可以判断这种数存不存在,进而减少无效转移。因为模$K$相同的位置只有$O(\dfrac{N}{K})$个,所以复杂度就对了。
代码(100分):
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define IL inline #define RG register using namespace std; #define RI RG int #define RC RG char const int N=5000; int n,k,s,a[N+3]; int b[N+3][N+3],c[N+3]; int f[N+3][N+3]; int main(){ scanf("%d%d%d",&n,&k,&s); for(RI i=1;i<=n;i++) scanf("%d",&a[i]); for(RI i=1,t=1;i<=n;i++,t+1>k?t=1:t++){ b[t][a[i]]++; c[t]++; } for(RI i=1;i<=s;i++) f[0][i]=n+1; f[0][0]=0; for(RI i=1;i<=k;i++){ for(RI j=0,x=f[i-1][0];j<=s;j++){ x=min(x,f[i-1][j]); f[i][j]=x+c[i]; } for(RI j=0;j<=s;j++) if(b[i][j]) for(RI p=j;p<=s;p++) f[i][p]=min(f[i][p],f[i-1][p-j]+c[i]-b[i][j]); } printf("%d",f[k][s]); return 0; }