【CF958C3】Encryption(抽屉原理)


题目链接

  • 给定一个长度为 \(n\) 的整数序列 \(a\)
  • 要求将它划分成 \(k\) 个非空子段,使得每段数之和模 \(p\) 的总和最小。
  • \(1\le n\le5\times10^5\)\(2\le k,p\le100\)

抽屉原理+最长不降子序列

\(s\)\(a\) 的前缀和模 \(p\) 的值。

首先如果 \(n < kp\),直接 \(O(nkp)\) 暴力 DP ,每次枚举上一段结尾 \(s_i\) 的值转移就可以了。

而由抽屉原理,发现当 \(n\ge kp\) 时,\(s\) 必然能找到一种数出现至少 \(k\) 次,也就是说肯定存在分段方案使得除开头和结尾以外所有段都模 \(p\)\(0\)

因此答案小于 \(2p\),只可能是 \(s_n\)\(s_n+p\)

只需检验答案是不是 \(s_n\)。一种划分方案得出的值是 \(s_n\) 充要于划分出的每一段结尾 \(s_i\) 单调不降。

所以求出 \(s\) 的最长不降子序列,检验是否大于等于 \(k\) 即可。

代码:\(O(k^2p^2)/O(np)\)

#include
#define Tp template
#define Ts template
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 500000
#define M 100
using namespace std;
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	char oc,FI[FS],*FA=FI,*FB=FI;
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
int n,k,p,a[N+5],s[N+5],F[M+5];
int f[2][M*M+5],g[M+5];I void BF()//暴力
{
	RI i,j,o,INF;for(memset(f,60,sizeof(f)),INF=f[0][0],f[0][0]=0,o=1;o<=k;++o)
	{
		for(j=0;j^p;++j) g[j]=INF;for(i=0;i<=n;++i)
		{
			for(f[o&1][i]=INF,j=0;j^p;++j) f[o&1][i]=min(f[o&1][i],g[j]+(s[i]-j+p)%p);//枚举上一段结尾的值
			g[s[i]]=min(g[s[i]],f[o&1^1][i]);//更新这个余数的最小DP值
		}
	}printf("%d\n",f[k&1][n]);
}
int main()
{
	RI i,j;for(scanf("%d%d%d",&n,&k,&p),i=1;i<=n;++i) scanf("%d",a+i),s[i]=(s[i-1]+a[i])%p;if(n