Codeforces 958C3 - Encryption (hard) 区间dp+抽屉原理
转自: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 ,那么显然区间[li + 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; }