zoukankan      html  css  js  c++  java
  • 【BZOJ】1049: [HAOI2006]数字序列(lis+特殊的技巧)

    http://www.lydsy.com/JudgeOnline/problem.php?id=1049

    题意:给一个长度为n的整数序列。把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。1. 询问最少需要改变多少个数。 2. 在1的条件下每个数改变的绝对值之和的最小值。(n<=35000, 数据随机)

    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <string>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <set>
    #include <map>
    using namespace std;
    typedef long long ll;
    #define rep(i, n) for(int i=0; i<(n); ++i)
    #define for1(i,a,n) for(int i=(a);i<=(n);++i)
    #define for2(i,a,n) for(int i=(a);i<(n);++i)
    #define for3(i,a,n) for(int i=(a);i>=(n);--i)
    #define for4(i,a,n) for(int i=(a);i>(n);--i)
    #define CC(i,a) memset(i,a,sizeof(i))
    #define read(a) a=getint()
    #define print(a) printf("%d", a)
    #define dbg(x) cout << (#x) << " = " << (x) << endl
    #define error(x) (!(x)?puts("error"):0)
    #define rdm(x, i) for(int i=ihead[x]; i; i=e[i].next)
    inline const int getint() { int r=0, k=1; char c=getchar(); for(; c<'0'||c>'9'; c=getchar()) if(c=='-') k=-1; for(; c>='0'&&c<='9'; c=getchar()) r=r*10+c-'0'; return k*r; }
    
    const int N=50005, oo=~0u>>2;
    int a[N], n, b[N], g[N], pos[N], nxt[N], inext[N], f[N];
    
    void init() {
    	for1(i, 1, n) g[i]=oo;
    	for1(i, 1, n) {
    		int k=upper_bound(g+1, g+1+i, b[i])-g;
    		f[i]=k;
    		g[k]=b[i];
    		nxt[i]=pos[k];
    		inext[i]=pos[k-1];
    		pos[k]=i;
    	}
    }
    ll ans[N], c[N];
    void work() {
    	for1(i, 2, n) {
    		int p=inext[i], pos=1;
    		ans[i]=oo;
    		while(p) { if(b[i]>=b[p]) pos=p; p=nxt[p]; }
    		p=pos;
    		ll sum=0, mx=-oo; c[p]=0;
    		for1(j, p+1, i-1) c[j]=c[j-1]+(b[j]<b[i]?1:-1);
    		for3(j, i-1, p) {
    			if(b[j]<=b[i] && f[j]+1==f[i]) {
    				ans[i]=min(ans[i], ans[j]+sum);
    				ans[i]=min(ans[i], ans[j]+sum-(ll)(b[i]-b[j])*(mx-c[j]));
    			}
    			if(mx<c[j]) mx=c[j];
    			sum+=abs(b[i]-b[j]);
    		}
    	}
    }
    
    int main() {
    	read(n); b[1]=-oo; ++n;
    	for1(i, 2, n) read(a[i]), b[i]=a[i]-i;
    	++n; b[n]=oo-n;
    	init();
    	work();
    	printf("%d
    %lld
    ", n-f[n], ans[n]);
    	return 0;
    }
    

      

    又是一题神题啊。orz

    首先第一个问很容易看出

    f[i]=min{f[j]+1, a[i]-a[j]>=i-j}

    设b[i]=a[i]-i

    f[i]=min{f[j]+1, b[i]>=b[j]}

    然后就是lis的log算法。。。。

    第二个问,好神!!!

    首先发现,如果有b[i]>=b[j]且f[i]==f[j]+1时,区间[j, i]中的点一定都是大于b[i]或者小于b[j],很显然吧。。

    而我们要将[j, i]的点变成合法序列一定是存在一个点t,使得[j, t]变成b[j],[t+1, i]变成b[i]。(在原序列中就变成了a[j], a[j+1]=a[j]+1, a[j+2]=a[j]+2...这样)

    如何证明?不会QAQ

    试着证明一下:考虑最优点t,假设b[t]不变成b[j],而是变成b'[t]>b[j],且b'[t]<b[i]。那么因为原b[t]<b[j]或者b[t]>b[i],显然费用为b'[t]-b[t]>b[j]-b[t](当b[t]<b[j]时)b[t]-b'[t]>b[t]-b[i](当b[t]>b[i]时),得出b[j]<b'[t]<b[i]没有b'[t]=b[j]或=b[i]优,即证。

    那么这样搞是n^3的,,,,,,,,,,,,,,

    先试着搞成n^2。考虑当前转移点为i

    我们首先找出离i最远的j,b[i]>=b[j]且f[i]==f[j]+1,那么所有的转移点都包含在区间[j, i]中。

    考虑从i向左枚举至j,当前在k,此时假设现在将所有[k+1, i-1]的点全部变为b[i],那么当k是转移点时,我们需要得到最小值。

    因为现在[k+1, i-1]全都是变成了b[i],那么假设要将其中的点变成b[j],显然:如果原b[x]>b[i],那么费用还需要+(b[i]-b[j]),如果原b[x]<b[i],那么费用就需要-(b[i]-b[j])。假设[k, i]中最优点t,[k+1, t]有y个比b[i]大的点,z个比b[i]小的点,那么需要变化的费用为:

    sum-(b[i]-b[k])*z+(b[i]-b[k])*y=sum-(b[i]-b[k])*(z-y),而区间[j, i]中所有转移点k显然是b[k]单调不降的,所以b[i]-b[k]在单调不增的,所以目标变成最大化(z-y)。

    所以考虑前缀和找出最大的差就行了。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    那么问题变成n^2...

    然后题目说。。。随机数据。。。。。。。。。。。。水过。

    (还有注意一点是,如何快速找到j点,那么我们在lis时向所有转移点连边,然后快速找到即可,否则复杂度会更大)

  • 相关阅读:
    栈和队列的存储结构、线性结构和非线性结构
    java 将一个有大量数据的list集合分成指定大小的list集合
    Java和jdbc实现数据库操作的基础例子
    解决连接Oracle 11g报ORA-01034和ORA-27101的错误和报ORA-00119和ORA-00132这个问题
    Java语言类的特性
    Java类与对象
    Java中的字符串(String)
    Java数组
    Java中的流程控制
    Java中的运算符与表达式
  • 原文地址:https://www.cnblogs.com/iwtwiioi/p/4160945.html
Copyright © 2011-2022 走看看