百度之星2016初赛第二场(Astar Round 2B)

来源:互联网 发布:淘宝模特小茹 编辑:程序博客网 时间:2024/06/04 20:14

这场比赛难度比起第一场有所提升.
依旧按照我做题的顺序给出题解.

题目链接

1003 瞬间移动

我的做法比较简单粗暴,枚举步数然后用组合数求.复杂度是O(n)的.
很多题解都说可以打表找出规律之后O(1)用组合数计算,奥妙重重呀.

1006 中位数计数

首先看清楚题目条件,n个数都是独一无二的,于是就只用考虑长度为奇数的区间了.暴力O(n2)即可.

1005 区间交

首先区间的交的左端点是所有左端点的最大值,右端点是所有右端点的最小值.又因为数字都是非负的,所以在左端点确定的情况下右端点越往右越好.这个贪心还是比较显然的.
具体怎么实现,可以用Splay来维护,但还有一种更机智的用堆的做法,先将区间双关键字排序,考虑当前区间的L作为最终区间的交的L,从前面的区间中取出第k1大的R值,这可以用一个小顶堆维护k1个最大值来实现.注意k=1的情况.
这样做的复杂度是O(nlgn).

#include<cstdio>#include<queue>#include<algorithm>using namespace std;const int N=1e5+5;int n,K,m,num[N];typedef __int64 ll;ll sum[N];typedef pair<int,int> P;priority_queue<int,vector<int>,greater<int> >pque;P itv[N];void rd(int &res){    res=0;    char c;    while(c=getchar(),c<48);    do res=(res<<3)+(res<<1)+(c^48);        while(c=getchar(),c>47);}inline void Max(ll &a,ll b){    if(b>a)a=b;}inline void Min(int &a,int b){    if(b<a)a=b;}void solve(){    sum[0]=0;    for(int i=1;i<=n;++i){        rd(num[i]);        sum[i]=sum[i-1]+num[i];    }    for(int i=0;i<m;++i){        rd(itv[i].first);        rd(itv[i].second);    }    ll ans=0;    if(K==1){        for(int i=0;i<m;++i)            Max(ans,sum[itv[i].second]-sum[itv[i].first-1]);        printf("%I64d\n",ans);        return;    }    sort(itv,itv+m);    while(!pque.empty())pque.pop();    for(int i=0;i<K-1;i++)        pque.push(itv[i].second);    for(int i=K-1;i<m;++i){        int L=itv[i].first,R=itv[i].second,            elm=pque.top();        pque.pop();        Min(R,elm);        if(L<=R)Max(ans,sum[R]-sum[L-1]);        pque.push(max(elm,itv[i].second));    }    printf("%I64d\n",ans);}int main(){    while(~scanf("%d%d%d",&n,&K,&m))solve();    return 0;}/*    May.26.16    Tags:Greedy,Data Structure    Submissions:1    Exe.Time 1606MS    Exe.Memory 4360K    Code Len. 1344B*/

1001 区间的价值

这题比赛时到最后也没交过…
题目中很关键的条件是数据随机,而这种题目我几乎没有做过…连个水分的方法都没有想出来…
赛后看到很多人的做法都是找到区间最大值,更新答案后递归到两个子区间,这样做期望是O(nlgn)的,实现并没有什么技术含量.
ShinFeb有并查集+线段树的O(nlgn)玄学做法.

1004 货物运输

这题第一个想法肯定是二分答案,然后对于每个区间,如果建立的传送站为(x,y)的话,满足条件的(x,y)组成的是一个对角线与坐标轴平行的正方形区域,只要求所有这些区域交集是否为空即可.
然而我并不会正方形求交….>_<
好吧其实这已经几乎是正解了.
设二分的答案为len,那么对每个RL>len的区间[L,R],需满足|Lx|+|Ry|len,这等价于

Lx+RylenLxR+ylenL+x+RylenL+xR+ylen

然后化为y关于x的四条不等式,这下判定有无解就方便了.

#include<cstdio>#include<algorithm>using namespace std;const int N=1e6+5,INF=1e9;int n,m,dat[N][2];inline void Max(int &a,int b){    if(b>a)a=b;}inline void Min(int &a,int b){    if(b<a)a=b;}bool judge(int len){    int mi0=-INF,mx0=INF,mi1=-INF,mx1=INF;    for(int i=0;i<m;++i){        int L=dat[i][0],R=dat[i][1];        if(R-L<=len)continue;        Max(mi0,L+R-len);        Min(mx0,L+R+len);        Max(mi1,-L+R-len);        Min(mx1,-L+R+len);    }    return mi0<=mx0&&mi1<=mx1;}void rd(int &res){    res=0;    char c;    while(c=getchar(),c<48);    do res=(res<<3)+(res<<1)+(c^48);        while(c=getchar(),c>47);}int main(){    while(~scanf("%d",&n)){        rd(m);        for(int i=0;i<m;++i){            for(int j=0;j<2;++j)                rd(dat[i][j]);            if(dat[i][0]>dat[i][1])swap(dat[i][0],dat[i][1]);        }        int L=0,R=n,ans;        while(L<=R){            int mid=L+R>>1;            if(judge(mid)){                ans=mid;                R=mid-1;            }            else L=mid+1;        }        printf("%d\n",ans);    }    return 0;}

1002 刷题计划

一道蛮好的数形结合+01背包题.
然而这里我懒得画图了…>_<
把所有满足条件的方案看成平面上的点,横坐标为选出的bi之和,纵坐标为选出的ci之和.
因为要使横纵坐标的乘积最小,所以最优解肯定在所谓的”下凸壳”上.
现在考虑如果已知满足条件的下凸壳上的两个点L,R,如何求一个横坐标在两者之间的下凸壳上的点mid.
这里的特殊指的是要使L,R,mid组成的三角形面积最大,这样才能保证复杂度.
可以直接用向量叉积来算这个面积,那么我们就要最大化:

Lmid×LR=(R.yL.y)(mid.xL.x)(R.xL.x)(mid.yL.y).

去掉常数后即最小化(好吧是我推的时候脑残搞成了最小化,反正都一样):
(L.yR.y)bi+(R.xL.x)ci.

这个东西可以改b,c的系数然后01背包搞吧…
求出mid之后再递归去求解两个子区间即可.
注意最开始要先找到两边的点,即求bi最小和ci最小的点.
然而这种做法的复杂度是多少?奥妙重重呀.

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int N=405,SIGMA=805;int n,m,sum_A,A[N],B[N],C[N];typedef __int64 ll;const ll INF=1ll<<60;ll ans,val[SIGMA];struct Point{    ll x,y;    inline bool operator ==(const Point &tmp)const{        return x==tmp.x&&y==tmp.y;    }}dp[SIGMA];inline void Min(ll &a,ll b){    if(b<a)a=b;}Point DP(ll kb,ll kc){    dp[0]=(Point){0,0};    val[0]=0;    for(int i=1;i<=sum_A;++i){        dp[i]=(Point){INF,INF};        val[i]=INF;    }    for(int i=0;i<n;++i){        for(int j=sum_A;j>=A[i];--j){            ll value=val[j-A[i]]+kb*B[i]+kc*C[i];            if(value<val[j]){                val[j]=value;                dp[j]=(Point){dp[j-A[i]].x+B[i],dp[j-A[i]].y+C[i]};            }        }    }    int res_id=m;    Point res=dp[m];    for(int i=m+1;i<=sum_A;++i){        if(val[i]<val[res_id]){            res_id=i;            res=dp[i];        }    }    return res;}void rec(Point L,Point R){    Point mid=DP(L.y-R.y,R.x-L.x);    if(mid==L||mid==R)return;    Min(ans,mid.x*mid.y);    rec(L,mid);    rec(mid,R);}int main(){    while(~scanf("%d%d",&n,&m)){        sum_A=0;        for(int i=0;i<n;++i){            scanf("%d%d%d",&A[i],&B[i],&C[i]);            sum_A+=A[i];        }        Point L=DP(1,0),R=DP(0,1);        ans=min(L.x*L.y,R.x*R.y);        rec(L,R);        printf("%I64d\n",ans);    }    return 0;}/*    May.26.16    Tags:Mathematics,dp    Submissions:1    Exe.Time 826MS    Exe.Memory 1424K    Code Len. 1266B*/

其实我之前还做过一道类似的好题:HNOI2014 画框.
还有一个经典问题用的也是这个思路:最小乘积生成树.

1 0