1. 程式人生 > >C++動態規劃及單調佇列的優化————擁擠的奶牛(擠奶牛Crowded Cows)和彈簧高蹺(POGO的牛Pogo-Cow)

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的維度判斷以及迴圈的變數位置,只要好好思考就還是能夠做出來的。