C++动态规划及单调队列的优化—————拥挤的奶牛(挤奶牛Crowded Cows)和弹簧高跷(POGO的牛Pogo-Cow)

题目描述:

FJ的n头奶牛(1<=n<=50000)在被放养在一维的牧场。第i头奶牛站在位置x(i),并且x(i)处有一个高度值h(i)(1<=x(i),h(i)<=1000000000)。

一头奶牛感觉到拥挤当且仅当它的左右两端都有一头奶牛所在的高度至少是它的2倍,且和它的距离最多为D。尽管感到拥挤的奶牛会产生更少的牛奶,FJ还是想知道一共有多上感到拥挤的奶牛。请你帮助他。

输入:

第一行:两个整数n和D。

第二行到第n+1行:每一行有两个数表示x(i)和h(i)。

输出:

一个数k表示感到拥挤的奶牛的数量。

输入样例:

6 4
10 3
6 2
5 3
9 7
3 6
11 2

输出样例:

2

思路分析:

当我们看到这一道题时,我们应该知道这是求区间内的最大值,我们就应该想到用单调队列(如果不懂单调队列)解决了,我们可以维护一个不下降队列,从左到右依次遍历,再用一个数组存储满足条件的情况,最后倒着遍历用ans记录个数就可以得出答案了。

代码实现:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
struct node{
    int x,y;
}s[50005];
int n,m,tail,head,a[50005],tail1,head1,a1[50005],ans,p[50005];
bool cmp(node q,node r)
{
    return q.x<r.x;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&s[i].x,&s[i].y);
    head=1;
    head1=1;
    sort(s+1,s+1+n,cmp);
    for(int i=1;i<=n;i++)
    {
        while(head<=tail&&(s[a[head]].x+m)<s[i].x)
            head++;
        if(s[a[head]].y>=s[i].y*2)
            p[i]++;
        while(head<=tail&&s[a[tail]].y<=s[i].y)
            tail--;
        tail++;
        a[tail]=i;
    }
    for(int i=n;i>=1;i--)
    {
        while(head1<=tail1&&(s[a1[head1]].x-m)>s[i].x)
            head1++;
        if(s[a1[head1]].y>=s[i].y*2)
        {
            if(p[i])
                ans++;
        }
        while(head1<=tail1&&s[a1[tail1]].y<=s[i].y)
            tail1--;
        tail1++;
        a1[tail1]=i;
    }
    printf("%d",ans);
}

总结:

这一题就是单调队列的模板题,只要分析清楚就会有答案。

题目描述:

在草场上有一条直线,直线上有若干个目标点。每个目标点都有一个分值和一个座标。现在你可以选择其中任意一个目标点开始跳,只能沿一个方向跳,并且必须跳到另一个目标点。且每次跳的距离都不能少于上一次的距离。请问你能得到的最大分值是多少?

输入:

第一行一个整数N(1<=N<=1000).接下来从第二行到第N+1行,每一行包含两个整数x(i)和p(i),每个整数在区间[0,1000000]之间。

输出:

输出格式:最大能获得的分值。

输入样例:

6 
5 6 
1 1 
10 5 
7 6 
4 8 
8 10

输出样例:

25

思路分析:

由于这道题对座标的涉及性较大,所以我们先用sort对其进行座标从小到大的顺序来排序。因为它只能走一个方向且跳跃的距离是不下降的序列。所以我们就可以用i作为起始点,j作为中间点,k为终点来进行dp(其实还用一种叫DFS也出来了),还是因为距离,我们的动态数组所以是二维(不然就无法保证距离了)。可是O(n^{^{3}})的时间复杂度绝对要凉呀!开始我也为此踌躇了许久,直到同校大佬的思路(其实还用单调队列的方法,只不过并没有理解)启发了我,我们可以先枚举中间点再找起始点,这样距离就可以帮我们节省了许多时间(时间复杂度为O(n^{_{2}})了),向前跳一遍,向后跳一边就可以求出答案了。

代码实现:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,dp[1005][1005],ans;
struct node{
    int x,p;
}a[1005];
bool cmp(node x,node y)
{
    return x.x<y.x;
}
int read()
{
    int x=0,f=1;
    char s=getchar();
    while(s<'0'||s>'9')
    {
        if(s=='-')
            f=-1;
        s=getchar();
    }
    while(s>='0'&&s<='9')
    {
        x*=10;
        x+=s-'0';
        s=getchar();
    }
    return x*f;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i].x=read();
        a[i].p=read();
    }
    sort(a+1,a+1+n,cmp);
    for(int j=1;j<=n;j++)
    {
        int k=j-1,sum=a[j].p;
        for(int i=j+1;i<=n;i++)
        {
            while(k&&(a[i].x-a[j].x>=a[j].x-a[k].x))
            {
                sum=sum>dp[j][k]+a[j].p?sum:dp[j][k]+a[j].p;
                k--;
            }
            dp[i][j]=dp[i][j]>sum?dp[i][j]:sum;
            ans=ans>sum+a[i].p?ans:sum+a[i].p;
        }
    }
    memset(dp,0,sizeof(dp));
    for(int j=n;j>=1;j--)
    {
        int k=j+1,sum=a[j].p;
        for(int i=j-1;i>=1;i--)
        {
            while(k<=n&&(a[j].x-a[i].x>=a[k].x-a[j].x))
            {
                sum=sum>dp[j][k]+a[j].p?sum:dp[j][k]+a[j].p;
                k++;
            }
            dp[i][j]=dp[i][j]>sum?dp[i][j]:sum;
            ans=ans>sum+a[i].p?ans:sum+a[i].p;
        }
    }
    printf("%d",ans);
}

总结:

这一题的巧妙之处在于DP的维度判断以及循环的变量位置,只要好好思考就还是能够做出来的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章