[bzoj]2131: 免费的馅饼
原题链接:免费的馅饼
题意
接饼子游戏,饼子每秒下落一格,然后人每秒可以向左或者向右移动一格或者两格,当然也可以原地不动。
问最多接到的饼子价值。
分析
发现网上的做法都是dp+树状数组优化的。
这里提供一个另外的思路。
由于每秒向左向右移动的步数可以是$0,1,2$步,转化在坐标系中可以是一个夹角,右边界的斜率$k=\frac{\sqrt{5}}{5}$。
我们可以旋转坐标系,让$x$轴和右边界对齐,然后再旋转$y$轴,把原坐标系变为一个斜坐标系,这样子我们接完每个饼之后只能向$x,y$的正方向移动。
然后按照横坐标排序之后就变成一个求一个带权$LIS$的问题了。
怎么处理带权$LIS$呢,拆点,每个权值为$v_i$的点拆成$v_i$个点,这样就不带权了,然后求个$LIS$就行了。
但是这里的$n*v_i$是$10^8$还需要一步优化。
我们开一个$double->int$的$std::map$,按照$nlogn$的$LIS$算法,每次插入一个值就查找后继,删除掉跟插入一样多的后继,如果后继不够说明答案增加。
把全部点都插完之后直接遍历$std::map$然后把所有的权值累加就行了
代码
#include <bits/stdc++.h> #define ll long long #define Mid ((l+r)>>1) #define ull unsigned long long using namespace std; const double Pi=acos(1.0); int read(){ char c;int num,f=1; while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0'; while(c=getchar(), isdigit(c))num=num*10+c-'0'; return f*num; } const int N=1e5+1009; struct node{double x,y;int w;}a[N]; map<double,int>g; map<double,int>::iterator it,it2; int n,w; double Sin,Cos,Sin2,Cos2; bool cmp(node a,node b){return a.x<b.x;} int main() { Sin=1.0/sqrt(5); Cos=2.0/sqrt(5); Sin2=2*Cos*Cos-1; Cos2=2*Sin*Cos; w=read();n=read(); for(int i=1;i<=n;i++){ double y=read(),x=read(),p; a[i].w=read(); a[i].x=Cos*x+Sin*y;a[i].y=Cos*y-Sin*x; //先把坐标系旋转到x轴与k=1/sqrt(5)相同 x=a[i].x;y=a[i].y; a[i].x=x+y*Sin2/Cos2;a[i].y=y/Cos2; //再把直角坐标系映射到斜坐标系 } sort(a+1,a+1+n,cmp); for(int i=1;i<=n;i++){ int t=a[i].w; if(g.find(a[i].y)!=g.end())g[a[i].y]+=t; else g[a[i].y]=t; it=++g.find(a[i].y); while(t&&it!=g.end()){ if((it->second)<=t){ it2=it++; t-=(it2->second); g.erase(it2); }else{ (it->second)-=t; t=0; } } } int ans=0; for(it=g.begin();it!=g.end();it++) ans+=(it->second); printf("%d\n",ans); return 0; } /* 建立坐标系,发现每次移动都是45度角 旋转坐标系变为45度角,求最长不下降子序列即可 虽然带权,但是点权很小,直接把一个点拆成点权个数个点 */