原题题解和数据下载 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)\),满足:

\[\frac {\sum_{i\in S}t_i} {\sum_{i\in S}p_i}>r_j
\]

等价于\(\sum_{i\in S}t_i- {\sum_{i\in S}p_i} \cdot r_j>0\)。也就是:

\[\sum_{i\in S}(t_i-p_i \cdot r_j)>0 \Rightarrow
\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(斜率优化/动态凸包/分治优化)的更多相关文章

  1. bzoj 3924 幻想乡战略游戏 —— 动态点分治

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3924 参考了博客:https://blog.csdn.net/qq_34564984/art ...

  2. bzoj 3730: 震波 动态点分治_树链剖分_线段树

    ##### 题目描述 : 在一片土地上有N个城市,通过N-1条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为1,其中第i个城市的价值为value[i].不幸的是,这片土地常常发生地震,并且随着 ...

  3. BZOJ 1767] [Ceoi2009] harbingers (斜率优化)

    [BZOJ 1767] [Ceoi2009] harbingers (斜率优化) 题面 给定一颗树,树中每个结点有一个邮递员,每个邮递员要沿着唯一的路径走向capital(1号结点),每到一个城市他可 ...

  4. 【BZOJ 2300】 2300: [HAOI2011]防线修建 (动态凸包+set)

    2300: [HAOI2011]防线修建 Description 近来A国和B国的矛盾激化,为了预防不测,A国准备修建一条长长的防线,当然修建防线的话,肯定要把需要保护的城市修在防线内部了.可是A国上 ...

  5. BZOJ 2300: [HAOI2011]防线修建( 动态凸包 )

    离线然后倒着做就变成了支持加点的动态凸包...用平衡树维护上凸壳...时间复杂度O(NlogN) --------------------------------------------------- ...

  6. BZOJ [HAOI2011]防线修建(动态凸包)

    听说有一种很高端的东西叫动态凸包维护dp就像学一下,不过介于本人还不会动态凸包就去学了下,还是挺神奇的说,维护上下凸包的写法虽然打得有点多不过也只是维护复制黏贴的事情而已罢了. 先说下动态凸包怎么写吧 ...

  7. 【BZOJ1492】【Luogu P4027】 [NOI2007]货币兑换 CDQ分治,平衡树,动态凸包

    斜率在转移顺序下不满足单调性的斜率优化\(DP\),用动态凸包来维护.送命题. 简化版题意:每次在凸包上插入一个点,以及求一条斜率为\(K\)的直线与当前凸包的交点.思路简单实现困难. \(P.s\) ...

  8. [NOI2007]货币兑换Cash(DP+动态凸包)

    第一次打动态凸包维护dp,感觉学到了超级多的东西. 首先,set是如此的好用!!!可以通过控制一个flag来实现两种查询,维护凸包和查找斜率k 不过就是重载运算符和一些细节方面有些恶心,90行解决 后 ...

  9. 【bzoj3672】[Noi2014]购票 斜率优化dp+CDQ分治+树的点分治

    题目描述  给出一棵以1为根的带边权有根树,对于每个根节点以外的点$v$,如果它与其某个祖先$a$的距离$d$不超过$l_v$,则可以花费$p_vd+q_v$的代价从$v$到$a$.问从每个点到1花费 ...

随机推荐

  1. K进制数

    题目描述 考虑包含N位数字的K-进制数. 定义一个数有效, 如果其K-进制表示不包含两连续的0. 考虑包含N位数字的K-进制数. 定义一个数有效, 如果其K-进制表示不包含两连续的0. 例: 1010 ...

  2. Deflation Methods for Sparse PCA

    目录 背景 总括 Hotelling's deflation 公式 特点 Projection deflation 公式 特点 Schur complement deflation Orthogona ...

  3. 整数划分 poj3181

    分析 因为n,m分别最大1000,100 所以结果会超过ll,要用两个来存大数的两部分 代码 #include<iostream> #include<algorithm> #i ...

  4. c++入门之内置数组和array比较

    array是C++11中新提出来的容器类型,与内置数组相比,array是一种更容易使用,更加安全的数组类型,可以用来替代内置数组.作为数组的升级版,继承了数组最基本的特性,也融入了很多容器操作,下面介 ...

  5. PS调出最美海滨城市俯拍照

    原图 一.找一张漂亮的风景照片,美丽的海滩. 二.打开PS做效果把图片放进去然后ctrl+j复制一层,添加滤镜-模糊-特殊模糊. 三.然后在这个图层的基础上添加滤镜-滤镜库-干画笔效果. 四.这个时候 ...

  6. 每周分享之cookie详解

    本章从JS方向讲解cookie的使用.(实质上后端代码也是差不多用法,无非读取和设置两块) 基本用法:document.cookie="username=pengpeng"; 修改 ...

  7. tomcat启动参数

    /usr/java/jdk1..0_191-amd64/bin/java -Djava.util.logging.config./conf/logging.properties -Djava.util ...

  8. 使用docker化的nginx 反向代理 docker化的GSCloud 的方法

    1. 首先将nginx 的image pull 下来. docker pull nginx 2. 将最近的可用的 参数文件 复制过来当一个目录 mkdir /nginx ssh root@linuxs ...

  9. windows浏览器访问虚拟机开的rabbitmq服务,无法访问

    根据这个博主的建议 https://blog.csdn.net/csdnliuxin123524/article/details/78207427 换了一个浏览器上火狐浏览器输入“localhost: ...

  10. Spring boot 全局配置文件application.properties

    #更改Tomcat端口号 server.port=8090 #修改进入DispatcherServlet的规则为:*.htmlserver.servlet-path=*.html#这里要注意高版本的s ...