BZOJ 1049 数字序列
Description
现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。
Input
第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。
Output
第一行一个整数表示最少需要改变多少个数。 第二行一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。
Sample Input
4
5 2 3 5
5 2 3 5
Sample Output
1
4
4
HINT
【数据范围】
90%的数据n<=6000。
100%的数据n<=35000。
保证所有数列是随机的。
Source
这道题感觉太神了。
令原数组为a。
对于第一问我们可以反过来思考——要求最少的改动的,不就是求最多的不改动的吗?
f[i]表示前i位最多的不改动的数字个数,f[i]=max(f[j]+1),j需要满足a[i]-a[j]≥i-j。对于条件,我们移一下项,化为a[i]-i≥a[j]-j,令b[i]=a[i]-i,不就是b[i]≥b[j]。细心的朋友一定看出来了,这不就是最长不下降子序列吗!!!O(nlogn)的求法上起。
第二问稍微麻烦一点,我们要知道一个结论:另g[i]表示前i个数,在改动数最少的前提下,最少改动的值。
那么对于所有合法的转移i,j(i>j,f[i]=f[j]+1,b[i]≥b[j]),最优解一定是在i与j之间某个k,k到j的值全为b[j],k+1到i的值全为b[i]。因此就可以dp了。证明自己脑补一下就可以了。。。(自己画画图,根据条件想想应该是可以明白的)
1 #include<cstring> 2 #include<vector> 3 #include<algorithm> 4 #include<iostream> 5 #include<cstdio> 6 #include<cstdlib> 7 using namespace std; 8 9 #define inf (1<<30) 10 #define maxn 35010 11 int tree[maxn],tot,n,f[maxn],a[maxn],bac[maxn]; 12 long long g[maxn],s1[maxn],s2[maxn]; 13 vector <int> vec[maxn]; 14 15 inline int lowbit(int x) { return x & -x; } 16 17 inline void change(int a,int b) { for (;a <= tot;a += lowbit(a)) tree[a] = max(tree[a],b); } 18 19 inline int calc(int a) { int ret = 0; for (;a;a -= lowbit(a)) ret = max(ret,tree[a]); return ret; } 20 21 int main() 22 { 23 freopen("1049.in","r",stdin); 24 freopen("1049.out","w",stdout); 25 scanf("%d",&n); 26 for (int i = 1;i <= n;++i) scanf("%d",a+i),a[i] -= i,bac[++tot] = a[i]; 27 a[++n] = inf; bac[++tot] = a[n]; 28 sort(bac+1,bac+tot+1); tot = unique(bac+1,bac+tot+1) - bac - 1; 29 for (int i = 1;i <= n;++i) 30 { 31 int pos = lower_bound(bac+1,bac+tot+1,a[i]) - bac; 32 f[i] = calc(pos) + 1; 33 change(pos,f[i]); 34 } 35 printf("%d\n",n-f[n]); 36 for (int i = 0;i <= n;++i) vec[f[i]].push_back(i); 37 a[0] = -inf; 38 memset(g,0x7,sizeof(g)); g[0] = 0; 39 for (int i = 1;i <= n;++i) 40 { 41 int nn = vec[f[i] - 1].size(); 42 for (int p = 0;p < nn;++p) 43 { 44 int j = vec[f[i]-1][p]; 45 if (j >= i) break; if (a[i] < a[j]) continue; 46 for (int k = j;k <= i;++k) s1[k]=abs(a[k]-a[j]),s2[k]=abs(a[k]-a[i]); 47 for (int k = j+1;k <= i;++k) s1[k] += s1[k-1],s2[k] += s2[k-1]; 48 for (int k = j;k < i;++k) g[i] = min(g[i],g[j]+s1[k]-s1[j]+s2[i]-s2[k]); 49 } 50 } 51 printf("%lld",g[n]); 52 fclose(stdin); fclose(stdout); 53 return 0; 54 }
高考结束,重新回归。