zoukankan      html  css  js  c++  java
  • 「SDOI2016」征途

    征途

    Pine开始了从S地到T地的征途。

    从S地到T地的路可以划分成(n)段,相邻两段路的分界点设有休息站。

    Pine计划用(m)天到达T地。除第(m)天外,每一天晚上Pine都必须在休息站过夜。所以,一段路必须在同一天中走完。

    Pine希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。

    帮助Pine求出最小方差是多少。

    设方差是(v),可以证明,(v imes m^2)是一个整数。为了避免精度误差,输出结果时输出(v imes m^2)

    对于 (100\%) 的数据,(1 le n le 3000)

    题解

    先推导一波方差。

    [s^2=frac 1msum_{i=1}^m(overline{v}-v_i)^2\ =frac 1mleft(moverline{v}^2-2overline{v}sum_{i=1}^mv_i+sum_{i=1}^m v_i^2 ight)\ =-overline{v}^2+frac 1msum_{i=1}^m v_i^2 ]

    答案为

    [s^2m^2=-left(sum_{i=1}^mv_i^2 ight)+msum_{i=1}^mv_i^2 ]

    所以要最小化的就是(sum_{i=1}^mv_i^2)

    斜率优化

    (f(i,j))表示前(i)天走了前(j)段路程的最小代价。

    [f(i,j)=min_{0le k<j}{f(i-1,k)+(s_j-s_k)^2} ]

    化成一次函数式

    [2s_js_k+f(i,j)-s_j^2=f(i-1,k)+s_k^2 ]

    点坐标很明显,单调队列维护下凸包即可。

    时间复杂度(O(mn))。话说我怎么几次都把(j)写成(i),调了好久。

    #include<bits/stdc++.h>
    #define co const
    #define il inline
    template<class T> T read(){
    	T x=0,w=1;char c=getchar();
    	for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    	return x*w;
    }
    template<class T>il T read(T&x){
    	return x=read<T>();
    }
    using namespace std;
    #define int long long
    
    co int N=3003;
    int n,m,s[N];
    int f[N][N],q[N];
    
    int up(int i,int k1,int k2){
    	return f[i][k2]+s[k2]*s[k2]-f[i][k1]-s[k1]*s[k1];
    }
    int down(int k1,int k2){
    	return s[k2]-s[k1];
    }
    signed main(){
    	read(n),read(m);
    	for(int i=1;i<=n;++i) s[i]=s[i-1]+read<int>();
    	for(int i=1;i<=n;++i) f[1][i]=s[i]*s[i];
    	for(int i=2;i<=m;++i){
    		int l=0,r=0;
    		for(int j=1;j<=n;++j){
    			while(l<r&&2LL*s[j]*down(q[l],q[l+1])>=up(i-1,q[l],q[l+1])) ++l;
    			int k=q[l];
    			f[i][j]=f[i-1][k]+(s[j]-s[k])*(s[j]-s[k]);
    			while(l<r&&up(i-1,q[r],j)*down(q[r-1],q[r])<=up(i-1,q[r-1],q[r])*down(q[r],j)) --r;
    			q[++r]=j; // edit 1:i->j
    		}
    	}
    	printf("%lld
    ",m*f[m][n]-s[n]*s[n]);
    	return 0;
    }
    

    导数零点二分

    这道题更像一个wqs二分的模板题,我现在把它叫做导数零点二分。

    为什么呢?推荐FlashHu的博客。

    考虑(最优解-段数)的函数图像,给每一段追加的权值相当于左右移动导函数的零点。我们要做的就是让零点恰好是我们要求的段数。

    时间复杂度(O(n log s_n))

    #include<bits/stdc++.h>
    #define co const
    #define il inline
    template<class T> T read(){
    	T x=0,w=1;char c=getchar();
    	for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    	return x*w;
    }
    template<class T>il T read(T&x){
    	return x=read<T>();
    }
    using namespace std;
    #define int long long
    
    co int N=3003;
    int n,m,s[N];
    int f[N],g[N];
    int q[N],l,r;
    
    int up(int a,int b){
    	return f[b]+s[b]*s[b]-f[a]-s[a]*s[a];
    }
    int down(int a,int b){
    	return s[b]-s[a];
    }
    void solve(int w){
    	q[l=r=0]=0;
    	for(int i=1;i<=n;++i){
    		while(l<r&&2*s[i]*down(q[l],q[l+1])>up(q[l],q[l+1])) ++l; // edit 1:>=
    		f[i]=f[q[l]]+w+(s[i]-s[q[l]])*(s[i]-s[q[l]]),g[i]=g[q[l]]+1;
    		while(l<r&&up(q[r],i)*down(q[r-1],q[r])<up(q[r-1],q[r])*down(q[r],i)) --r;
    		q[++r]=i;
    	}
    }
    signed main(){
    	read(n),read(m);
    	for(int i=1;i<=n;++i) s[i]=s[i-1]+read<int>();
    	int l=0,r=s[n]*s[n]/m,ans;
    	while(l<r){
    		int mid=(l+r)>>1;
    		solve(mid);
    		if(g[n]>m) l=mid+1;
    		else ans=m*(f[n]-mid*m)-s[n]*s[n],r=mid;
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    作业3——turtle
    作业2——Python基础
    作业——理解管理信息系统
    测试用例挑选策略
    UVALive 5903 Piece it together(二分图匹配)
    UVALive 4953 Wormly--【提醒自己看题要仔细】
    HDU 3111 Sudoku(精确覆盖)
    FZU 2165 v11(最小重复覆盖)+ codeforces 417D Cunning Gena
    UVALive 6577 Binary Tree 二叉树的LRU串
    codeforces 425C Sereja and Two Sequences(DP)
  • 原文地址:https://www.cnblogs.com/autoint/p/11284389.html
Copyright © 2011-2022 走看看