4 条题解
-
4
设 表示前个数划分完后的最小代价
表示前 个数的和
这个是典型的一维动态规划,而且它的是关于的,因此我们想到要用斜率优化
然后我们容易证明
则它满足四边形不等式
因此这个递推式子有决策单调性
然后我们化简一下式子
我们如果要用斜率优化,我们要满足为一个关于的东西且它得和一个关于的东西相乘,为一个关于的东西且它得和一个关于的东西相乘,为一个关于的东西,为一个关于的东西
那么我们可以得出
这样我们的问题就变为如何让这个一次函数的截距最小
我们设 且比优
即$f[j]-2*s[j]*s[i]+s[j]*s[j]<f[k]-2*s[k]*s[i]+s[k]*s[k]$
化简可得
那么只要满足这个式子就可以保证比优
假设有三个点
为 的斜率
为 的斜率
假设 为
优于优于 ,优于 优于优于
可以发现这三个情况都是劣的
所以我们可以把它扔掉
所以我们要维护的点是在一个下凸包
然后他的斜率是单增的
然后我们最优的点就是最小的那个满足,然后他又有决策单调性
所以我们可以用一个单调队列,队头用去判,如果不行就扔,队尾维护一个下凸包就好
然后这个题就做完了
#include <bits/stdc++.h> using namespace std; const long long N = 5e5 + 10; long long n, c, a[N], s[N], f[N], st[N], h, t; void read(){ for(long long i = 1;i <= n; i++){ cin >> a[i]; s[i] = s[i-1] + a[i]; } } double X(long long i){ return s[i]; } double Y(long long i){ return f[i] + s[i] * s[i]; } double k(long long a,long long b){ if(X(a)-X(b) == 0) return 1e18; return (Y(a)-Y(b))/(X(a)-X(b)); } void compute(){ memset(f,0,sizeof f); memset(st,0,sizeof st); h = t = 0; st[t++] = 0; for(long long i = 1;i <= n; i++){ while(t - h > 1 && k(st[h],st[h+1]) <= s[i] * 2) h++; f[i] = f[st[h]] + (s[st[h]] - s[i]) * (s[st[h]] - s[i]) + c; while(t - h > 1 && k(st[t-1],st[t-2]) >= k(st[t-1],i)) t--; st[t++] = i; } cout << f[n] << '\n'; } int main(){ while(cin >> n >> c){ read(); compute(); } return 0; } -
-3
不用斜率优化的做法:
表示前i个数划分完的最小代价
首先我们可以发现对于所有的0 可以直接去掉 因为对答案没有任何贡献
剩下的部分每个数都至少为1
如果C=0 那么显然将它划分为n段是最优的
否则若将元素和分别为和的两端合并 需要的代价,能节省C的花销
考虑暴力的dp转移中 ->这条转移:
若在j~i中存在一个数k,使得
则该转移一定不优于 ->+-> 也就是说可以不考虑
因为“剩下的部分每个数都至少为1” 所以当时,一定存在上文中的k
所以从到i转移即可
复杂度
(如果倒着从到转移能有更优的常数)
同时,该做法还可以推广到次方的情况
#include<bits/stdc++.h> using namespace std; #define int long long const int inf=1e18+1,mod=1e9+7; int n,C,a[501000],dp[501000]; int sum[501000]; int b[501000],tot; signed main() { ios::sync_with_stdio(0); cin.tie(0); while(cin>>n>>C){ int tot=0; for(int i=1;i<=n;i++){ dp[i]=inf; cin>>a[i]; if(a[i]) b[++tot]=a[i]; } for(int i=1;i<=tot;i++){ a[i]=b[i]; sum[i]=sum[i-1]+a[i]; for(int j=max(1ll,(int)(i-sqrt(2*C)-1));j<=i;j++){ dp[i]=min(dp[i],dp[j-1]+(sum[i]-sum[j-1])*(sum[i]-sum[j-1])+C); } } cout<<dp[tot]<<"\n"; } return 0; } -
-4
进行一个式子的神秘的推。
大佬们太苣了,都会斜率优化,小蒟蒻不会怎么办?
受着由于某OJ疑似数据范围没写西格玛,遂认为应该写线性做法,于是我认为转移一定存在某种规律使得转移集合可以被缩减,手动尺取了半天猜测了一个结论(果然还是基于人类智慧)。
对于转移到 的每个 , 单调不降。
考虑证明,从当前最优状态入手。(先贴个图)

假设 的最优情况是由 转移而来, 在 的前一个位置,中间部分和为 ,则有(方便起见, 同时表示这个位置和这个位置上的值,公式中 都可以消掉,忽略)
化简得
接着考虑 的情况
(1):
(2):
令 (3)=(2)-(1):
取最小值,(3)=
说明二式恒大于一式,无限向下递推该式可得如下结论:对于任意最优转移 不存在最优转移 。
故结论得证,代码真的很简单啊,复杂度基于zt的证明最劣 。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=5e5+17; int n,c,f[N],q[N],a[N]; int sum(int l,int r){ int res=(q[r]-q[l-1]); return res*res; }int ans; signed main(){ // freopen("ex.in","r",stdin); // freopen("ex.ans","w",stdout); ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); int cnt; while(cin >> n){ ans=0;cin>> c; for(int i=1;i<=n;i++){ cin >> a[i];q[i]=q[i-1]+a[i]; } int t=0; for(int i=1;i<=n;i++){ f[i]=1e14;int tmp; for(int j=t;j<i;j++){ int re=f[j]+sum(j+1,i)+c; if(re<=f[i]){ f[i]=re;tmp=j; } }t=tmp; } cout << f[n] << '\n'; } return 0; } -
-4
设 f[i] 表示前 i 个数划分完后的最小代价
复杂度
考虑优化
对于 k < j < i ,假设选 j 比选 k 更优,则:
$f[j]+(sum[i]-sum[j])^2+C < f[k]+(sum[i]-sum[k])^2+C$
整理可得:
$\frac {f[j]+sum[j]^2 - (f[k]+sum[k]^2)}{sum[j]-sum[k]} < 2·sum[i]$
这样形成一个类似于求斜率的式子
令 则
注意到不等式右边是递增的。(斜率优化的重要特点:单调)
令 g[k][j]表示刚刚的斜率式,即 g[k][j]=
第一:如果 ,此时 比 优,则在后面 更大时, 也更大,不等式肯定也成立, 同样比 优,则 点可以淘汰
第二:对于 ,如果 ,那么 永远不可能成为最优解的决策,可以直接将它踢出我们的最优解集。为什么呢?
如果 ,那么就是说 点要比 点优,排除 点。
如果 ,那么 点此时是比 点要更优,但是同时 。这说明还有 点会比 点更优,同样排除 点。
所以相当于维护一个下凸壳,斜率在逐渐增大。用单调队列维护。
- 1
信息
- ID
- 290
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 8
- 标签
- (无)
- 递交数
- 75
- 已通过
- 12
- 上传者