【BZOJ 1701】Cow School(斜率优化/动态凸包/分治优化)
原题题解和数据下载 Usaco2007 Jan
题意
小牛参加了n个测试,第i个测试满分是\(p_i\),它的得分是\(t_i\)。老师去掉\(t_i/p_i\)最小的d个测试,将剩下的总得分/总满分作为小牛的得分。
小牛想知道多少个d存在比老师计算的分数更高的选择测试的方案,并输出这些d。
题解
基础思路
排好序后,$ \frac {t_1} {p_1} < \frac {t_2} {p_2}<..< \frac {t_n} {p_n}$。
如果d==j,老师给的分数是\(r_j=\frac {t_{j+1}+t_{j+2}+..t_{n}} {p_{j+1}+p_{j+2}+..p_{n}}=\frac{st_j}{sp_j}\)。
要找的就是是否存在一个集合\(S(|S|=n-j)\),满足:
\]
等价于\(\sum_{i\in S}t_i- {\sum_{i\in S}p_i} \cdot r_j>0\)。也就是:
\sum_{i\in S}t_i\cdot sp_j-p_i\cdot st_j>0
\]
贪心地找最大的 n-j 个加起来,复杂度是\(O(n^2\log n)\)。
法1 平衡树维护动态凸包
一个优化的方法是,考虑\(z=t_i\cdot sp_j-p_i\cdot st_j\)在\(1..j\)里最大值\(g[j]\) 和\(j+1..n\)里的最小值\(f[j]\)。
如果\(f[j]\) 小于\(g[j]\),那么意味着从\(j+1..n\)中取出最小值换为\(1..j\)中的最大值,可以更优。
\(g[j]=\max\{a_j\cdot x_i+b_j\cdot y_i\}\)实际上就是斜率优化的形式。
这里\(x_i=p_i,y_i=t_i,a_j=-st_j,b_j=sp_j\)。
直线为\(y=st_j/sp_j\cdot x+z/sp_j\)。
所以每个点坐标为\((p_i,t_i)\),要找一个点,斜率为\(-a_j/b_j\)的直线经过它时,纵截距最大。需要维护一个上凸壳。x不是单调的,需要用平衡树来维护这个凸壳。
\(f[j]\)同理求。总复杂度\(O(n\log n)\)。
法2 普通维护凸包
实际上还可以更优。
求\(g[j]\)时加入上凸壳的点\((p_j,t_j)\)和原点连线的斜率\(t_j/p_j\),一定比之前加入的任意点\((p_{j'},t_{j'})\)的要大,于是新点一定可以保留在凸壳上。直线斜率\(st_j/sp_j\)一定比\(t_j/p_j\)大。于是当前点右边的点就没有用了。因为直线斜率递增,所以最优解的位置递减。
求\(f[j]\)时加入下凸壳的点\((p_j,t_j)\)和原点连线的斜率\(t_j/p_j\),一定比之前加入的任意点\((p_{j'},t_{j'})\)的要小,于是新点一定可以保留在凸壳上。直线斜率\(st_j/sp_j\)一定比\(t_j/p_j\)大。于是当前点左边的点就没有用了。因为直线斜率递增,所以最优解的位置递增。
于是只要用\(Graham\)算法维护凸壳即可。点排序后复杂度是\(O(n)\)。
法3 分治dp
由于决策单调,计算\(f[j],g[j]\)时还可以分治计算。
代码
//平衡树动态维护凸包
#include <bits/stdc++.h>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define rep(i,l,r) for(int i=l,_=r;i<_;++i)
#define per(i,l,r) for(int i=r-1,_=l;i>=_;--i)
typedef long long ll;
typedef double dd;
const int INF=0x3f3f3f3f;
const int N=50100;
struct nd{
ll t,p;
bool operator < (const nd&b)const{
return t*b.p<p*b.t;
}
}a[N];
struct DynmcCnvx{
int rot,fa[N],c[N][2];
dd lk[N],rk[N],x[N],y[N];
void zigzag(int x,int &rot){
int y=fa[x],z=fa[y];
int p=(c[y][1]==x),q=p^1;
if (y==rot) rot=x;
else if (c[z][0]==y) c[z][0]=x;
else c[z][1]=x;
fa[x]=z; fa[y]=x; fa[c[x][q]]=y;
c[y][p]=c[x][q]; c[x][q]=y;
}
void splay(int x,int &rot){
while (x!=rot){
int y=fa[x],z=fa[y];
if (y!=rot) zigzag(((c[y][0]==x)^(c[z][0]==y))?x:y,rot);
zigzag(x,rot);
}
}
void insert(int &t,int anc,int now){//加入平衡树
if (!t) t=now, fa[t]=anc;
else insert(c[t][x[now]>x[t]],t,now);
}
void update(int t){//加入点(x[t],y[t]),维护上凸壳。
splay(t,rot);
if (c[t][0]){//向左求凸包
int left=prev(rot);
splay(left,c[rot][0]); c[left][1]=0;
lk[t]=rk[left]=getk(left,t);
}
else lk[t]=INF;
if (c[t][1]){//向右求凸包
int right=succ(rot);
splay(right,c[rot][1]); c[right][0]=0;
rk[t]=lk[right]=getk(t,right);
}
else rk[t]=-INF;
if (lk[t]<=rk[t]){//在原凸包内部的情况,直接删掉该点
rot=c[t][0]; c[rot][1]=c[t][1]; fa[c[t][1]]=rot; fa[rot]=0;
lk[rot]=rk[c[t][1]]=getk(rot,c[t][1]);
}
}
dd getk(int i,int j){//求斜率
if (x[i]==x[j]) return -INF;
return (y[j]-y[i])/(x[j]-x[i]);
}
int prev(int rot){//求可以和当前点组成凸包的右边第一个点
int t=c[rot][0],tmp=t;
while (t){
if (getk(t,rot)<=lk[t]) tmp=t,t=c[t][1];
else t=c[t][0];
}
return tmp;
}
int succ(int rot){//求可以和当前点组成凸包的左边第一个点
int t=c[rot][1],tmp=t;
while (t){
if (getk(rot,t)>=rk[t]) tmp=t,t=c[t][0];
else t=c[t][1];
}
return tmp;
}
int find(int t,dd k){//找到当前斜率的位置,即找到最优值
if (!t) return 0;
if (lk[t]>=k && k>=rk[t]) return t;
return find(c[t][lk[t]>=k],k);
}
void Init(){
rot=0;mem(fa,0);mem(c,0);
}
dd GetMax(dd a,dd b){//max{ax+by}
int j=find(rot,-a/b);
return a*x[j]+b*y[j];
}
void InsertPoint(int i,dd _x,dd _y){//插入点(x,y)
x[i]=_x,y[i]=_y;
insert(rot,0,i);
update(i);
}
}s;
dd st,sp;
dd f[N],g[N];
int ans[N],cnt;
int n;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
rep(i,1,n+1)
cin>>a[i].t>>a[i].p;
sort(a+1,a+n+1);
per(i,1,n+1){
s.InsertPoint(i,-a[i].p,-a[i].t);
f[i-1]=-s.GetMax(-(st+=a[i].t),sp+=a[i].p);
}
s.Init();
rep(i,1,n){
s.InsertPoint(i,a[i].p,a[i].t);
g[i]=s.GetMax(-(st-=a[i].t),sp-=a[i].p);
if(g[i]>f[i]) ans[cnt++]=i;
}
cout<<cnt<<endl;
rep(i,0,cnt)cout<<ans[i]<<endl;
return 0;
}
//直接维护凸包
#include <bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(int i=l,_=r;i<_;++i)
#define per(i,l,r) for(int i=r-1,_=l;i>=_;--i)
typedef long long ll;
const int N=50100;
struct nd{
ll t,p;
bool operator < (const nd&b)const{
return t*b.p<p*b.t;
}
}a[N];
struct Po{
ll x,y;
Po(ll x=0,ll y=0):x(x),y(y){}
Po operator -(const Po&b)const {return Po(x-b.x,y-b.y);}
Po operator +(const Po&b)const {return Po(x+b.x,y+b.y);}
ll operator ^(const Po&b)const {return x*b.y-y*b.x;}
ll operator *(const Po&b)const {return x*b.x+y*b.y;}
}p[N];
ll xmul(const Po&a,const Po&b,const Po&o){
return (a-o)^(b-o);
}
struct DownCnvx{
Po q[N];int top;
//顺时针方向维护下凸壳
void Insert(Po p){
while(top && q[top].x<=p.x) --top;
while(top>1 && xmul(q[top],p,q[top-1])>=0)--top;
q[++top]=p;
}
ll GetMin(Po p){
while(top>1 && p*q[top]>=p*q[top-1])--top;
return q[top]*p;
}
}d;
struct UpCnvx{
Po q[N];int top;
//顺时针方向维护上凸壳
void Insert(Po p){
while(top && q[top].x>=p.x) --top;
while(top>1 && xmul(q[top],p,q[top-1])>=0)--top;
q[++top]=p;
}
ll GetMax(Po p){
while(top>1 && p*q[top]<=p*q[top-1])--top;
return q[top]*p;
}
}u;
ll st,sp;
ll f[N],g[N];
int cnt,ans[N];
int n;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
rep(i,1,n+1)
cin>>a[i].t>>a[i].p;
sort(a+1,a+n+1);
per(i,1,n+1){
d.Insert(Po(a[i].p,a[i].t));
f[i-1]=d.GetMin(Po(-(st+=a[i].t),sp+=a[i].p));
}
rep(i,1,n+1){
u.Insert(Po(a[i].p,a[i].t));
g[i]=u.GetMax(Po(-(st-=a[i].t),sp-=a[i].p));
if(g[i]>f[i]) ans[cnt++]=i;
}
cout<<cnt<<endl;
rep(i,0,cnt)cout<<ans[i]<<endl;
return 0;
}
//分治优化
#include <bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(int i=l,_=r;i<_;++i)
#define per(i,l,r) for(int i=r-1,_=l;i>=_;--i)
typedef long long ll;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const int N=50100;
struct nd{
ll t,p;
bool operator < (const nd&b)const{
return t*b.p<p*b.t;
}
}a[N];
ll st[N],sp[N];
ll f[N],g[N];
int cnt,ans[N];
int n;
void solveMax(int l,int r,int optL,int optR){
if(l>r)return;
int j=l+r>>1,u=optL;
rep(i,optL,min(optR,j)+1){
ll tmp=sp[j]*a[i].t-st[j]*a[i].p;
if(tmp>g[j])g[j]=tmp,u=i;
}
solveMax(l, j-1, optL, u);
solveMax(j+1, r, u, optR);
}
void solveMin(int l,int r,int optL,int optR){
if(l>r)return;
int j=l+r>>1,u=optL;
rep(i,max(optL,j+1),optR+1){
ll tmp=sp[j]*a[i].t-st[j]*a[i].p;
if(tmp<f[j])f[j]=tmp,u=i;
}
solveMin(l, j-1, optL, u);
solveMin(j+1, r, u, optR);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
rep(i,1,n+1)
cin>>a[i].t>>a[i].p;
sort(a+1,a+n+1);
per(i,1,n+1){
st[i-1]=st[i]+a[i].t;sp[i-1]=sp[i]+a[i].p;
f[i]=LINF;g[i]=-LINF;
}
solveMax(1,n,1,n);
solveMin(1,n,1,n);
rep(i,1,n)
if(f[i]<g[i])ans[cnt++]=i;
cout<<cnt<<endl;
rep(i,0,cnt)cout<<ans[i]<<endl;
return 0;
}
【BZOJ 1701】Cow School(斜率优化/动态凸包/分治优化)的更多相关文章
- bzoj 3924 幻想乡战略游戏 —— 动态点分治
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3924 参考了博客:https://blog.csdn.net/qq_34564984/art ...
- bzoj 3730: 震波 动态点分治_树链剖分_线段树
##### 题目描述 : 在一片土地上有N个城市,通过N-1条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为1,其中第i个城市的价值为value[i].不幸的是,这片土地常常发生地震,并且随着 ...
- BZOJ 1767] [Ceoi2009] harbingers (斜率优化)
[BZOJ 1767] [Ceoi2009] harbingers (斜率优化) 题面 给定一颗树,树中每个结点有一个邮递员,每个邮递员要沿着唯一的路径走向capital(1号结点),每到一个城市他可 ...
- 【BZOJ 2300】 2300: [HAOI2011]防线修建 (动态凸包+set)
2300: [HAOI2011]防线修建 Description 近来A国和B国的矛盾激化,为了预防不测,A国准备修建一条长长的防线,当然修建防线的话,肯定要把需要保护的城市修在防线内部了.可是A国上 ...
- BZOJ 2300: [HAOI2011]防线修建( 动态凸包 )
离线然后倒着做就变成了支持加点的动态凸包...用平衡树维护上凸壳...时间复杂度O(NlogN) --------------------------------------------------- ...
- BZOJ [HAOI2011]防线修建(动态凸包)
听说有一种很高端的东西叫动态凸包维护dp就像学一下,不过介于本人还不会动态凸包就去学了下,还是挺神奇的说,维护上下凸包的写法虽然打得有点多不过也只是维护复制黏贴的事情而已罢了. 先说下动态凸包怎么写吧 ...
- 【BZOJ1492】【Luogu P4027】 [NOI2007]货币兑换 CDQ分治,平衡树,动态凸包
斜率在转移顺序下不满足单调性的斜率优化\(DP\),用动态凸包来维护.送命题. 简化版题意:每次在凸包上插入一个点,以及求一条斜率为\(K\)的直线与当前凸包的交点.思路简单实现困难. \(P.s\) ...
- [NOI2007]货币兑换Cash(DP+动态凸包)
第一次打动态凸包维护dp,感觉学到了超级多的东西. 首先,set是如此的好用!!!可以通过控制一个flag来实现两种查询,维护凸包和查找斜率k 不过就是重载运算符和一些细节方面有些恶心,90行解决 后 ...
- 【bzoj3672】[Noi2014]购票 斜率优化dp+CDQ分治+树的点分治
题目描述 给出一棵以1为根的带边权有根树,对于每个根节点以外的点$v$,如果它与其某个祖先$a$的距离$d$不超过$l_v$,则可以花费$p_vd+q_v$的代价从$v$到$a$.问从每个点到1花费 ...
随机推荐
- Success Rate CodeForces - 807C (数学+二分)
You are an experienced Codeforces user. Today you found out that during your activity on Codeforces ...
- 培训班课程课时及费用管理系统V3.0,适合钢琴培训班、艺术培训班等
联系QQ 564955427. ACM3.02 文件下载 还有: 预收课时版 特点: 1. 适合主要业务是一对一课程和部分集体课培训的中小培训班(非连锁管理).考 ...
- message:GDI+ 中发生一般性错误。
图片类型的文件保存的时候出了问题,可能是路径出错,也可能是保存到的文件夹不存在导致(发布项目的时候如果文件夹是空的,文件夹将不存在)
- Django lazy load 懒加载 倒序查询
Django orm默认懒加载 Django orm默认使用的懒加载,即使用的时候才去访问数据库,且每次默认取最少的数据,当然这样有好处也有坏处... 坏处: 会导致频繁的查询数据库,如涉及到外键 ...
- python 的内存回收,及深浅Copy详解
一.python中的变量及引用 1.1 python中的不可变类型: 数字(num).字符串(str).元组(tuple).布尔值(bool<True,False>) 接下来我们讲完后你就 ...
- #Leetcode# 1009. Complement of Base 10 Integer
https://leetcode.com/problems/complement-of-base-10-integer/ Every non-negative integer N has a bina ...
- IdentityServer4【QuickStart】之切换到混合流并且添加API访问
切换到混合流并且添加API访问 前面的示例中我们开发了API访问和用户认证,现在我们要将两个合并到一起. OpenID Connect&OAuth 2.0组合的美妙之处是,你可以使用单一协议和 ...
- flutter屏幕适配
现在的手机品牌和型号越来越多,导致我们平时写布局的时候会在个不同的移动设备上显示的效果不同, 比如我们的设计稿一个View的大小是300px,如果直接写300px,可能在当前设备显示正常,但到了其他设 ...
- Leetcode SingleNumber I & II & III 136/137/260
SingleNumber I: 题目链接:https://leetcode-cn.com/problems/single-number/ 题意: 给定一个非空整数数组,除了某个元素只出现一次以外,其余 ...
- 每日一小时linux(1)--sysRq
参考https://www.ibm.com/developerworks/cn/linux/l-cn-sysrq/index.html SysRq 是什么 你是否遇到服务器不能通过 SSH 登录,也不 ...