应该是一个dp 考虑设f[i] 表示第i天所能获得的最多的钱数 如何体现出兑换出来的货币是一个问题,考虑多加一个状态表示第i天所能获得的最多的A券数?由于题目中保证了每次操作必然卖完金券 或 用所有的钱来买金券 因A和B成比例所以知道A就可以知道B 所以不需要设B。但是我意识到一个问题其实没有必要吧 的确两个状态可以进行互推但是考虑我们设出来一个进行状态转移我们只需找到那天买入时的钱数直接默认买入然后当前直接卖出即可。
推了一波式子(我们显然可以的到对于第j天我们买A券的钱数:SA=(f[j]*A[j]*R[j]/(A[j]*R[j]+B[j]));那么SB:(f[j]-SA); 那么买入A的数量就是除以一个Aj 买入B的数量/Bj 直接进行转移即可复杂度n^2考虑优化。
//#include<bits/stdc++.h> #include<iostream> #include<cmath> #include<ctime> #include<algorithm> #include<cctype> #include<utility> #include<queue> #include<map> #include<set> #include<bitset> #include<deque> #include<vector> #include<cstdio> #include<cstdlib> #include<iomanip> #include<stack> #include<string> #include<cstring> #define INF 1000000000 #define ll long long #define db double #define max(x,y) ((x)>(y)?(x):(y)) #define min(x,y) ((x)>(y)?(y):(x)) using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=100010; int n; db s; db A[MAXN],B[MAXN],R[MAXN]; //注意到每次操作要不使用完所有人民币要不卖出所有的金券 db f[MAXN];//f[i]表示第i天所得到的最多钱数 int main() { freopen("1.in","r",stdin); n=read();scanf("%lf",&s); for(int i=1;i<=n;++i)scanf("%lf%lf%lf",&A[i],&B[i],&R[i]); f[0]=s; for(int i=1;i<=n;++i) { for(int j=0;j<i;++j)f[i]=max(f[i],f[j]); for(int j=1;j<i;++j) { db SA=(f[j]*A[j]*R[j]/(A[j]*R[j]+B[j])); db SB=(f[j]-SA); SA=SA/A[j];SB=SB/B[j]; f[i]=max(SA*A[i]+SB*B[i],f[i]); } } /*for(int j=1;j<=n;++j) { db SA=(f[j]*A[j]*R[j]/(A[j]*R[j]+B[j]))/A[j]; printf("%.3lf ",f[j]); }*/ printf("%.4lf",f[n]); return 0; }
我们把状态转移方程单拉出来看看有没有可以优化的地方:
for(int j=0;j<i;++j)f[i]=max(f[i],f[j]); for(int j=1;j<i;++j) { db SA=(f[j]*A[j]*R[j]/(A[j]*R[j]+B[j])); db SB=(f[j]-SA); SA=SA/A[j];SB=SB/B[j]; f[i]=max(SA*A[i]+SB*B[i],f[i]); }
看起来这么丑的状态转移看不出来优化的地方 放到一个式子里才可以优化好吧...那就放到一起。
经过我的不懈努力 把上述式子优化加压 到了一行之中:
for(int j=0;j<i;++j)f[i]=max(f[i],f[j]); for(int j=1;j<i;++j) f[i]=max(f[j]*R[j]/(A[j]*R[j]+B[j])*A[i]+f[j]/(A[j]*R[j]+B[j])*B[i],f[i]);
显然的是 这是看不出来怎么优化的(突然我想到学长说的话最上面的那个式子是可以优化。我给忘了。
SA SB 显然是只跟j有关的项 然后发现斜率有两个 比较崩溃...先办法转成一个 我没想到 有等效替代这回事设x[i]=A[i]/B[i] 那么上述式子就开业利用(SA*x[i]+SB)*B[i]这个东西来看了 B[i]是常数项所以此时斜率就只有一个了这个x斜率是不单调的但是SA和SB一定是单调不降的考虑如何进行优化。复习了一下才想到优化 f[i]=(SA*x[i]+SB)*B[i];f[i]/b[i]-SA*x[i]=SB.优化好了 xi为斜率 SA为横坐标 SB为纵坐标 我们让截距最大就好了 最大的话不要下凸壳 维护一个上凸壳就好了。
考虑xi一直变 着意味着我们要在凸壳上二分但是考虑SA 和SB也不一定是单调的 因为有可能SA多了SB少了什么的 动态插入点 有点意思这玩意要平衡树才能支持快速插入故不太好写...
听说这玩意能CDQ 我有点懵...CDQ是没错的这个的确是左对右的一发贡献。所以好像我们维护左边的凸壳不断地对右边进行贡献就好了 嗯 很稳。省掉了动态插入点删除点。
整体复杂度 nlogn 吧...这题绝对的神仙利用CDQ美妙的性质搞出来了 关键是对于一个决策点i来说它前面的都是对他合法的转移 所以可以用CDQ 写了5h 痛苦的写完了 细节比较ex。
//#include<bits/stdc++.h> #include<iostream> #include<cmath> #include<ctime> #include<algorithm> #include<cctype> #include<utility> #include<queue> #include<map> #include<set> #include<bitset> #include<deque> #include<vector> #include<cstdio> #include<cstdlib> #include<iomanip> #include<stack> #include<string> #include<cstring> #define INF 1000000000 #define ll long long #define db double #define max(x,y) ((x)>(y)?(x):(y)) #define min(x,y) ((x)>(y)?(y):(x)) using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=100010; int n; db s; int q[MAXN],L,R; db f[MAXN],g[MAXN];//g[i]表示第i天所能得到的最多的A券 //冗杂的代码从不是我所期望的. struct wy { int id; db a,b,r,x; int friend operator<(wy w,wy y){return w.x==y.x?w.id<y.id:w.x>y.x;} }t[MAXN],tmp[MAXN]; inline void swap(int &x,int &y){int tmp=x;x=y;y=tmp;} inline double slope(int l,int r)//求斜率 { db Br=g[t[r].id]/t[r].r,Bl=g[t[l].id]/t[l].r; return (Br-Bl)/(g[t[r].id]-g[t[l].id]); } inline void CDQ(int l,int r) { if(l==r) { f[l]=max(f[l],f[l-1]); g[l]=f[l]*t[l].r/(t[l].a*t[l].r+t[l].b); return; } int mid=(l+r)>>1; int i=l,j=mid+1; for(int k=l;k<=r;++k) { if(t[k].id<=mid)tmp[i++]=t[k]; else tmp[j++]=t[k]; } for(int k=l;k<=r;++k)t[k]=tmp[k]; CDQ(l,mid); //此时考虑左边对右边的贡献后再分治右边 L=1;R=0; for(int k=l;k<=mid;++k)//得到左边的凸壳 { while(L<R&&slope(q[R],k)>slope(q[R-1],q[R]))--R; q[++R]=k; while(L<R&&g[t[q[R]].id]==g[t[q[R-1]].id]) { db D1=g[t[q[R]].id]/t[q[R]].r; db D2=g[t[q[R-1]].id]/t[q[R-1]].r; if(D1>D2)swap(q[R],q[R-1]); --R; } } //左对右的贡献 for(int k=mid+1;k<=r;++k) { while(L<R&&slope(q[L],q[L+1])>t[k].x)++L; f[t[k].id]=max(f[t[k].id],g[t[q[L]].id]*t[k].a+g[t[q[L]].id]/t[q[L]].r*t[k].b); } CDQ(mid+1,r); i=l;j=mid+1; for(int k=l;k<=r;++k) { if(j>r||(i<=mid&&g[t[i].id]<g[t[j].id]))tmp[k]=t[i],++i; else tmp[k]=t[j],++j; } for(int k=l;k<=r;++k)t[k]=tmp[k]; } int main() { freopen("1.in","r",stdin); n=read();scanf("%lf",&s); for(int i=1;i<=n;++i) { db a,b,r; scanf("%lf%lf%lf",&a,&b,&r); t[i]=(wy){i,a,b,r,-a/b}; } f[1]=s;g[1]=s*t[1].r/(t[1].a*t[1].r+t[1].b); sort(t+1,t+1+n); CDQ(1,n); //for(int i=1;i<=n;++i)printf("%.4lf ",f[i]); printf("%.4lf",f[n]); return 0; }
一个人可以不骄傲,但是却不能没有傲骨。