zoukankan      html  css  js  c++  java
  • bzoj 1010,1011

    上次应某位同学的要求先把代码给贴上了,今天还是细细讲讲比较好

    bzoj 1010: dp+斜率优化

      首先dp的思路并不是太难想出来,直接给方程:f[i] = min{f[j-1] + (sum[i]-sum[j-1]+i-j-L)

      上面的方程稍微做了一点优化,就是将求和改成了前缀和的方式,快一点。

      但是一看n^2能过? 一看数据眼泪掉下来! so怎么办? 优化呗,然后就是今天的斜率咯。

      什么叫斜率优化?其实个人觉得个人的理解的斜率优化还是跟斜率一点关系都没有,反正张老师给我讲的时候没有提及斜率的概念。那么这到底是一个怎样的优化呢?在dp中比较容易想到的是某一些状态可以用单调队列给直接排除,斜率优化也有这种思想在里面:

      我们将这个方程中的min中的i的那部分提取出来,就会得到一个式子f[i] = min(f(j) + f(i)*g(j)) + g(i)  什么意思呢?  由于对于特定的i值,min中与i相关的量和常数都是无意义的,所以将它们可以合并,然后就会的得到一个如此的式子。然后对于j,k满足j < k的话,如果j的情况比k的情况更差,那么就会有f(j) + f(i)*g(j) < f(k) + f(i)*g(k) 就可以解一个方程得到:(f(j)-f(i))/g(k)-g(j) < f(i) 也就是到了某一i过后,j的状态可以不用了,舍掉就好。因为是满足j<k的那么,那么我们就用一个单调队列来维护之,将它近队,然后在每一个i对于下面的每一个状态和它的下一个的比较与i比关系(就是上面那个方程),如果结果小于i,那么就把队首踢掉。那么算出了一个i的最优后,我们就要把它给入队,然后我们考虑一个问题,对于队尾的两个元素j,k,和新加的i,如果上面那个运算出的最小值小于了i,k的运算最小值,那么队尾元素就被踢掉,然后继续比较。

      吐槽一下,唯一有斜率的地方救只有方程那里了。。。

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    
    typedef long long ll;
    
    const ll maxn = 50001;
    ll N,L;
    ll s[maxn];
    
    ll q[maxn],f[maxn];
    ll l,r;
    
    ll fi(ll i){
    	return s[i]+i-L;
    }
    
    ll fj(ll j){
    	return s[j-1]+j;
    }
    
    double S(ll j,ll k){
    	return (double)(f[j-1]-f[k-1]+fj(j)*fj(j)-fj(k)*fj(k))/(double)(fj(j)-fj(k));
    }
    
    int main(){
    	//freopen("cs.in","r",stdin);
    	scanf("%lld%lld",&N,&L);
    	memset(s,0,sizeof(s));
    	for(ll i = 1; i <= N; i++){
    		ll tmp;
    		scanf("%lld",&tmp);
    		s[i] = s[i-1] + tmp;
    	}
    	memset(q,0,sizeof(q));
    	l = 0, r = 0;
    	q[0] = 1;
    	memset(f,0,sizeof(f));
    	for(ll i = 1; i <= N; i++){
    		while(l < r && S(q[l],q[l+1]) < 2*fi(i)) l++;
    		ll now = q[l]; 
    		f[i] = f[now-1]+(fi(i)-fj(now))*(fi(i)-fj(now));
    		while(l < r && S(q[r],q[r-1]) > S(i+1,q[r])) r--;
    		q[++r] = i+1;
    	}
    	printf("%lld",f[N]);
    	return 0;
    }
    

    1011:

    水题。。一看n^2要爆,然后误差小于5%。。也就是说允许误差,那么我们可以把几项认为位于同一位置,然后维护前缀和。。下面的代码中包含一个非常不严谨的误差保证,记住一点就是合并的时候一定要去中间的点作为合并的位置,不要取两端的!

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <cmath>
    using namespace std;
    const int maxN = 100001;
    const double eps = 1e-7;
    
    int n;
    double m[maxN],sum[maxN];
    double a;
    
    int main(){
    	//freopen("cs.in","r",stdin);
    	scanf("%d%lf",&n,&a);
    	memset(sum,0,sizeof(sum));
    	for(register int i = 1; i <= n; i++){
    		scanf("%lf",&m[i]);
    		sum[i] = sum[i-1] + m[i];
    		int maxn = (int)floor(a*i+eps),len;
    		double ans = 0;
    		while(maxn){
    			len = (int)floor((i-maxn)*0.05)+1;
    			if(maxn < len) len = maxn;
    			ans += (double)(sum[maxn]-sum[maxn-len])/(i-maxn+i-maxn+len-1)*2; 
    			maxn -= len;
    		}
    		printf("%.6lf
    ",ans*m[i]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    C# 在 8.0 对比 string 和 string? 的类型
    C# 在 8.0 对比 string 和 string? 的类型
    C# 使用反射获取私有属性的方法
    C# 使用反射获取私有属性的方法
    win10 uwp 发布旁加载自动更新
    win10 uwp 发布旁加载自动更新
    安装 Sureface Hub 系统 Windows 10 team PPIPro 系统
    安装 Sureface Hub 系统 Windows 10 team PPIPro 系统
    PHP FILTER_SANITIZE_EMAIL 过滤器
    PHP FILTER_SANITIZE_SPECIAL_CHARS 过滤器
  • 原文地址:https://www.cnblogs.com/ianaesthetic/p/3717677.html
Copyright © 2011-2022 走看看