2019.9.22

允我心安 提交于 2019-12-04 02:20:51

1.LA 4254 Processor

题意:

\(n\)个任务,每个任务有\(3\)个参数\(r_i\)\(d_i\)\(w_i\),表示必须在时刻\([r_i,d_i]\)之内执行,工作量为\(w_i\)。处理器的速度可以为任意值,任务不一定要连续执行,可以分成若干块,求最大速度的最小值。

分析:

要求最大速度的最小值,一般都要想到二分(但也有例外,如之前的那道正睿的第一天考试的第一题,二分就不能做,而是要用三分或者贪心)。

然后这道题的关键就变成了如何判断一个处理顺序是否合法,计算时就要用到贪心的思想(很多用到二分的题一般都是二分+贪心,字符串的话还会有二分+哈希

因为我们会一开始按照r值的大小对该数组进行排序,所以我们率先处理d值小的。(至于为什么这样做是对的,请感性理解)

\(Code\):

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<queue>
using namespace std;

const int maxn=1e4+10;
struct Node
{
    int r,d,w;
    bool operator < (const Node& a) const
    {
        return d>a.d;//d值小的优先级高
    }
}node[maxn];
bool cmp(Node a,Node b)
{
    return a.r<b.r;
}
template<class T>void read(T &x)
{
    bool f=0;char ch=getchar();x=0;
    for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    if(f) x=-x;
}
bool check(int speed)
{
    priority_queue<Node> pq;
    int cnt=0;
    int last=1;
    while(cnt<n||!pq.empty())
    {
        while(cnt<n&&node[++cnt].r==last) pq.push_back(node[cnt]);//此时该工作已经可以
        int s=speed;                                              //开始执行了
        while(!pq.empty()&&s)
        {
            Node x=pq.top();
            pq.pop();
            int tmp=min(s,x.w);
            s-=tmp;//这一秒内完成x的工作量还是只能完成一部分,还是不仅可以完成x的工作量而且有余
            x.w-=tmp;
            if(x.w>0) pq.push(x);
        }//我们是在模拟每一秒的工作
        ++last;
        if(!pq.empty()&&pq.top().d==last) return false;
        if(pq.empty()&&cnt==n) return true;
    }
}

int main()
{
    ios::sync_with_stdio(false);
    int T;
    read(T);
    while(T--)
    {
        int n;
        read(n);
        for(int i=1;i<=n;++i) read(node[i].r);read(node[i].d);read(node[i].w);
        sort(node+1,node+n+1,cmp);
        int l=1,r=100000;
        int ans;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(check(mid)) r=mid-1;
            else l=mid+1;//因为这个是需要的是进一法,若是去尾法的话,就应该是ans=mid,最后输
            //出ans
        }
        printf("%d\n",l);
    }
    return 0;
}

终于达成目标:写了一道二分+贪心的题了

2.UVa11627 Slalom

题意:

在一场滑雪比赛中,你需要通过\(n\)个旗门(均可看成水平线段)。第\(i\)个旗门左端的坐标为(\(x_i,y_i\)),所有旗门的宽度均为\(W\)。旗门海拔高度严格递减,即对所有\(1<=i<n\)满足\(y_i<y_{i+1}\)[\(y_i\)表示\(i\)距离山顶的距离]。你有S双滑雪板,第\(j\)双的速度为\(s_j\)(即向下滑行速度为\(s_j\)米/秒)。你的水平速度在任何时刻都不能超过\(v_h\)米/秒,但可以任意变速。如果起点和终点的水平坐标可以任意选择,用哪些滑雪板可以顺利通过所有旗门?输出可以通过所有旗门的滑雪板的最大速度。

我并没有很看得懂这个题意描述,于是找了另一个老师重新解释了一下题意,如下:

\(n\)个旗门,位置在\((x_i,y_i)\)\((x_i+W,y_i)\),并且\(y_i\)递增。
\(S\)个滑雪板,第\(j\)个滑雪板在\(y\)方向上的速度是\(s_j\)米每秒。
\(x\)方向的速度不是固定的,只要求不超过\(v_h\)每秒并且可以变速。
问按这样的滑行规则,问选哪种滑雪板是可以过了所有的门的,就是到门的那个\(y\)坐标的时候,\(x\)在那个区间,并且所用时间是最短的,起点和终点坐标可以任选。

分析:

二分法。

显然,当一个滑雪板的垂直速度越小时,它就越容易到达下一个旗门,所以我们按照滑雪板的垂直速度按照从小到大的顺序的先进行排序。

事实上从上一个旗门到达下一个旗门因为\(x\)方向最大速度的限制,所以到达时间是一个区间,而在\(y\)方向上又有一个确定的时间段,那么我们只要判断这两个区间是否有交集即可判断是否可以从上一个旗门到达下一个旗门。

本代码并没有过,但还是发现了很多小问题,代码中会提到,怀疑数据有锅,上一个过了的还是四年以前

\(Code:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;

const int maxn=1e6+10;
struct Node
{
    double x,y;
}node[maxn];
int vmax,W;
int n;
int S;
int s[maxn];//关于s的数据类型,如果在这里定义double型,输出时用%d它输出的会是0,我也不太知道为
//什么
bool check(int t)
{
    double v=s[t];
    double l=node[1].x,r=node[1].x+W;
    for(int i=2;i<=n;++i)
    {
        double tmp=vmax*(node[i].y-node[i-1].y)/v;//除去vmax的部分计算的是从i-1到达i
        l-=tmp,r+=tmp;                            //所用时间,tmp指的是x轴方向上行走的
        l=max(l,node[i].x),r=min(r,node[i].x+W);  //距离
        if(r<l) return false;//是r<l
    }
    return true;
}
int main()
{
    ios::sync_with_stdio(false);
    int T;
    cin>>T;
    while(T--)
    {
        cin>>W>>vmax>>n;
        for(int i=1;i<=n;++i) cin>>node[i].x>>node[i].y;
        cin>>S;//一开始忘记了输入S
        for(int i=1;i<=S;++i) cin>>s[i];
        sort(s+1,s+S+1);
        int l=0,r=S;
        while(l+1<r)//二分
        {
            int mid=(l+r)>>1;
            if(check(mid)) l=mid;
            else r=mid;
        }
        if(l<=0) printf("IMPOSSIBLE\n");
        else printf("%d\n",s[l]);
    }
    return 0;
}

据说,只有\(10\%\)的程序员能把二分写对

事实证明,没有万能的二分模板。

当然\(l\)\(r\)更改的位置要视情况而定

以下代码全为求最大值

第一种,将区间分为\([l,mid]\)\([mid+1,r]\)两部分

int solve()
{
    int l=ans1,r=ansmax;
    while(l<r)
    {
        int mid=l+r>>1;//位运算的优先级更低
        if(check(mid)) l=mid+1;
        else r=mid;
    }
    return l;
}

第二种,将区间分为\([l,mid-1]\)\([mid,r]\)两个部分

int solve()
{
    int l=ans1,r=ansmax;
    while(l<r)
    {
        int mid=(l+r+1)>>1;//为了防止求最大值时的无限循环,上一个若是求最小值时,也需要这样操作
        if(check(mid)) l=mid;
        else r=mid-1;
    }
    return l;
}

第三种,最常用

int solve()
{
    int l=ans1-1,r=ansmax+1;
    while(l+1<r)
    {
        int mid=(l+r)>>1;
        if(check(mid)) l=mid;
        else r=mid;
    }
    return l;
}

还有一种,不知道是不是万能的

int solve()
{
    int l=ans1,r=ansmax;
    int ans;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(check(mid)) ans=mid,l=mid+1;
        else r=mid-1;
    }
    return ans;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!