Codeforces 958C3 - Encryption (hard) 区间dp+抽屉原理

时间:2024-12-18 09:04:50

转自:http://www.cnblogs.com/widsom/p/8863005.html

题目大意:

比起Encryption 中级版,把n的范围扩大到 500000,k,p范围都在100以内,然后让你求最小值

基本思路:

记sum[i]表示0 - i 的和对 p 取模的值。

1.如果k * p > n,那么与C2的做法一致,O(k*p*n)复杂度低于1e8。

2.如果k * p <= n

那么根据抽屉原理,必有至少k个sum[i]相同,

那么任意取k - 1个相同的 sum[i],记它们的下标为 l1,l2,......,lk-1 ,那么显然区间[l+ 1, li+1](1<=i<k-1)的贡献为0

有贡献的区间只有[1,l1]和[lk-1 + 1,n]由于两个区间的贡献加起来小于2 * (p - 1) ,所以最后的答案要么为 sum[n],要么为 sum[n] + p

那么怎么判断是前者还是后者呢?

只要判断在sum中能不能找到一个以sum[n]结尾的长度为k的非严格上升子序列就可以了

如果能找到就是sum[n],否则就是 sum[n] + p

LIS的复杂度O(nlogn)

注意:

1)关于第二层循环j的循环方向,反方向就不对了,可以仔细思考一下

关于dp中for循环的方向问题,摘自知乎艾庆兴的回答:

动态规划随便怎么实现都可以,只要把握一个原则,当你计算dp i的时候,一定要保证你用到的那些全部都已经被算出来了,

比如区间dp,一般大区间的dp值由小区间算出来,所以你只要保证循环的时候,算每一个大区间之前,小区间都被算出来,就可以

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<set> using namespace std; typedef long long ll;
typedef long long LL;
typedef pair<int,int> pii;
const int inf = 0x3f3f3f3f;
const int maxn = 500000+10;
const ll mod = 1e9+9; int dp[110][110];
int _dp[maxn];
int a[maxn];
int main(){
int n,k,p;
scanf("%d%d%d",&n,&k,&p);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
a[i]+=a[i-1];
a[i]%=p;
}
if(k*p>n){
memset(dp,inf,sizeof(dp));
dp[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=k;j>=1;j--){
for(int l=0;l<p;l++){
dp[a[i]][j]=min(dp[a[i]][j],dp[l][j-1]+(a[i]-l+p)%p);
}
}
}
printf("%d\n",dp[a[n]][k]);
}else{
memset(_dp,inf,sizeof(_dp));
for(int i=1;i<=n-1;i++){
*upper_bound(_dp+1,_dp+n,a[i])=a[i];
}
if(_dp[k-1]<=a[n]){
printf("%d\n",a[n]);
}else{
printf("%d\n",a[n]+p);
}
}
return 0;
}