[NOI2007] Currency exchange-various forms of slope optimization DP

topic

[NOI2007] Currency Exchange

answer

First of all, the question has been prompted, every time you either do nothing, or run out of money, or sell out the tickets.

So it’s easy to think of the DP formula: suppose f[i]that the maximum profit after the i day is the maximum amount of money on hand after the i day, and this money information can be used to transfer the subsequent DP, so what can be done on the i day Don't do it, that is f[i]=f[i-1], you can use the money you earned on a previous day to spend all the tickets, that is f[i]=max(f[i-1],f[j]/(aj*rj+bj)*rj*ai+f[j]/(aj*rj+bj)*bi),j<i.

Direct enumeration will definitely not work. Observe the formula and find that the slope can be optimized. Remember g[j]=f[j]/(aj*rj+bj), consider that if k is better than j in the enumeration, then g [j]*rj*ai+g [j]*bi <g [k]*rk*ai+g [k]*bi,

Note that g is not necessarily monotonic g[j]<g[k]here , so we assume  that there is(g [j] * rj-g [k] * rk) / (g [j] -g [k])> - bi / ai .

Since both g and -bi/aiare not monotonic, there are several ways to optimize:

CDQ divide and conquer

We still consider the contribution of the left half to the right half.

The left half is sorted according to g , so each query on the right half becomes an ordinary convex upper dichotomy.

The total complexity is O(n log^2 n) .

It can be implemented in a little more detail. The sorting of the left half of g can be merged from the bottom, and the right half can be sorted by -bi/ ai merge, and then the two pointers sweep on the convex hull on the left.

This complexity is O(n log n) .

This method is not optimal, so the code is not included.

Balance tree maintenance convex hull

General practice of slope optimization:

Each time it is equivalent to querying the first line of slope <-bi/ai on the convex hull . This obviously can directly balance the tree dichotomy.

When you insert a point after the calculation, you can violently delete the points on both sides that are not on the convex hull.

The total complexity is O(n log n) .

This should be the practice of most people, treap, splay belong to this kind of practice, faster than CDQ but not easy to play.

map instead of balanced tree

This approach is not recommended. It is purely because the author is too lazy to build the balance tree by himself and replaces it with the red-black tree (map) that comes with STL. The idea is the same, but two maps are used, one for the slope and the other for the coordinates. Delete When you click, create a new point and use the find function to locate it, and then swipe left and right through the pointer. (After pressing the line), it is much shorter than the balanced tree:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#define ll long long
#define MAXN 100005
#define INF 0x7f7f7f7f
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
int n,del[MAXN],tp;
double a[MAXN],b[MAXN],r[MAXN],f[MAXN],g[MAXN],S;
map<double,int>kp;
map<double,double>mp;
signed main()
{
	n=read(),S=read();
	for(int i=1;i<=n;i++)scanf("%lf%lf%lf",&a[i],&b[i],&r[i]);
	kp[INF]=0,mp[0]=INF,f[1]=S,kp[r[1]]=1,g[1]=S/(a[1]*r[1]+b[1]),mp[g[1]]=r[1];
	map<double,int>::iterator kt;
	map<double,double>::iterator mt,tt;
	for(int i=2;i<=n;i++){
		double k=-b[i]/a[i],y;
		kp[k],kt=kp.find(k),kt++;
		if(kp[k]==0)kp.erase(k);
		f[i]=max(f[i-1],g[kt->second]*(r[kt->second]*a[i]+b[i]));
		g[i]=f[i]/(r[i]*a[i]+b[i]),tp=0,y=g[i]*r[i];
		if(mp.find(g[i])!=mp.end()){
			int l=kp[mp[g[i]]];
			if(y>g[l]*r[l])kp.erase(mp[g[i]]);
			else continue;
		}mp[g[i]]=y,mt=mp.find(g[i]);
		while(mt!=mp.begin()){mt--;
			double kj=mt->second;int j=kp[kj];
			double ki=(y-g[j]*r[j])/(g[i]-g[j]);
			if(ki>=kj)del[++tp]=j;
			else {mp[g[i]]=ki;break;}
		}mt=mp.find(g[i]),k=mt->second,mt++;
		while(mt!=mp.end()){
			double kj=mt->second;int j=kp[kj];
			double ki=(g[j]*r[j]-y)/(g[j]-g[i]);
			if(kj>=k){del[++tp]=i;break;}
			tt=mt,tt++;
			if(tt==mp.end()||tt->second<ki){
				kp.erase(mt->second),mt->second=ki,kp[ki]=j;break;
			}else del[++tp]=j,mt++;
		}
		for(int j=1;j<=tp;j++){
			if(del[j]==i)mp.erase(g[i]);
			else kp.erase(mp[g[del[j]]]),mp.erase(g[del[j]]);
		}if(mp.find(g[i])!=mp.end())kp[k]=i;
	}
	printf("%.3f\n",f[n]);
	return 0;
}

(Ultimate) Li Chao line segment tree maintenance

This approach is too good, I am still triumphant after playing the map, and when I see this solution, I immediately shut myself down.

The method comes from the blog of Luogu giant panyf : https://www.luogu.com.cn/blog/221955/solution-p4027

Just post the code of the boss below:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+3;
#define db double
db x[N],y[N],a[N],b[N],r[N],c[N],d[N];
int u,s[N*4];
#define f(i,t) (y[t]+x[t]*c[i])
void upd(int k,int t,int l,int r){
    if(l==r){if(f(l,t)>f(l,s[k]))s[k]=t;return;}
    int m=l+r>>1;
    if(f(m,t)>f(m,s[k]))swap(t,s[k]);
    f(l,t)>f(l,s[k])?upd(k*2,t,l,m):upd(k*2+1,t,m+1,r);
}//李超树插入
db qry(int k,int l,int r){
    if(l==r)return f(u,s[k]);
    int m=l+r>>1;
    return max(f(u,s[k]),u>m?qry(k*2+1,m+1,r):qry(k*2,l,m));
}//李超树查询
int main(){
    int n,i;
    db f,g;
    scanf("%d%lf",&n,&f);
    for(i=1;i<=n;++i)scanf("%lf%lf%lf",a+i,b+i,r+i),c[i]=a[i]/b[i],d[i]=c[i];
    sort(c+1,c+n+1);//离散化
    for(i=1;i<=n;++i){
        u=lower_bound(c+1,c+n+1,d[i])-c,f=max(f,b[i]*qry(1,1,n));
        g=a[i]*r[i]+b[i],x[i]=f*r[i]/g,y[i]=f/g,upd(1,i,1,n);
    }
    printf("%.3lf",f);
    return 0;
}

The constant is small, the code is short, and the accuracy is not stuck, it is the optimal solution!

Guess you like

Origin blog.csdn.net/weixin_43960287/article/details/113958583