1. 程式人生 > >【AtCoder】【DP】【思維】Prefix Median(AGC012)

【AtCoder】【DP】【思維】Prefix Median(AGC012)

模的是這位神犇的程式碼:Atcoder AGC012F : Prefix Median

題意:

在動態中位數那道題上做了一些改動。給你一個序列a,可以將a重新任意排序,然後對於a序列構造出b序列。
假設a序列有2*n-1個元素,b序列有n個元素。
其中b[i]=Median(a[1],a[2],a[3]...a[2i-1])。求能夠構造出多少個不同的b序列。

資料範圍:

1<=N<=50,1<=ai<=2N-1

思路:

這道題真的是究極神題...雖然說程式碼實現比較簡單,但是分析的過程是噁心到吐的。
首先,我們需要分析一堆性質:
首先將a序列排序,便於討論

  1. \(a[i]<=b[i]<=a[2*N-i]\)
  2. \(當i<j時,不存在i使得:b[j]<b[i]<b[j+1]或者是b[j+1]<=b[i]<=b[j]\)

先拋開性質不談,首先有很明顯的一個性質:i從左往右走的時候,假設已經選取的元素的集合為c(c是排好了序的)。每加入兩個元素,b[i]的取值的下標在c中只會向左或者是向右移動一格,或者是保持不變。(可以通過列舉法簡單證得,即新加入的兩個元素x,y都小於b[i-1],或者是都大於b[i+1],或者是一個大於,一個小於的情況)。

然後來看上面的性質。因為求b[i]時的序列中小於等於b[i]至少有i個元素,大於等於b[i]的至少有i個元素,所以說在a陣列中的b[i]至少在a[i]~a[2N-i]之間。性質1是沒有問題的;
然後是性質2。因為最開始所說的“明顯的性質”,所以說b[j]與b[j+1]在已經選取的數字的序列之的位置相差不會超過1,也就是說中間不會間隔任何的數字。但同時還需要注意一點,就是這裡是'<'和'>',也就是說是可以取等的,也就是位置保持不變的情況。那麼性質2也有了。

接下來就是怎麼實現了。
由於開頭賦的題解對於程式碼的部分的具體細節解釋的不是很詳細,我這裡就貿然做一些補充了(〃'▽'〃)
首先定義狀態dp[i][L][R]表示現在確定的是b[i],可選元素中小於等於b[i+1]的有L個,大於b[i+1]的有R個。然後考慮轉移。轉移的話就是列舉b[i]選取的位置,然後刪去相應的元素就可以了。然而並沒有這麼簡單!!!具體細節將在程式碼中進行詳細的說明。

程式碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 100
#define MO 1000000007
using namespace std;
typedef long long LL;
int a[MAXN+5],n;
LL f[MAXN+5][MAXN+5][MAXN+5];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=2*n-1;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+2*n);
    f[n][1][0]=1;
    //初始狀態中,小於等於b[n]的有1個,大於b[n]的有0個(雖然稍微有些違背定義,但是暫且先這樣定義吧)
    LL t;
    for(int i=n-1;i>=1;i--)
    {
        int al=(a[i]!=a[i+1]),ar=(a[2*n-i]!=+a[2*n-i-1]);
        //這裡是向兩邊進行擴充套件,也就是運用性質1進行擴充套件
        //因為相同的數進行的擴充套件是沒有意義的,所以說需要這樣進行能否進行有效的擴充套件的判斷
        for(int l=0;l<=2*n-1;l++)
            for(int r=0;l+r<=2*n-1;r++)
                if(f[i+1][l][r])
                {
                    t=f[i+1][l][r];
                    for(int dl=1;dl<=l+al;dl++)//選擇小於b[i+1]的元素
                    {
                        f[i][l+al-dl+1][r+ar+(dl>1)]+=t;
                        //+al是進行對於L的拓展
                        //-dl是刪去選了的元素與b[i+1]之間的元素
                        //+1是因為刪多了一個,那就是已經選取的b[i],所以說要加回來
                        //右邊+(dl>1)是因為:
                        //當dl==1的時候,選取的就是b[i+1]這個元素,那就是什麼都不會改變的
                        //當dl>1的時候,選取的就是<b[i+1]中的一個元素,那麼b[i+1]就會成為>b[i]中的一個元素,又因為可以取整,所以說要加上去
                        f[i][l+al-dl+1][r+ar+(dl>1)]%=MO;
                    }
                    for(int dr=1;dr<=r+ar;dr++)//選擇大於等於b[i+2]的元素
                    {
                        f[i][l+al+1][r+ar-dr]+=t;
                        //左邊+1是因為加入b[i]這個可選元素,與上面的比較類似
                        //右邊就是正常的轉移,這還比較簡單
                        f[i][l+al+1][r+ar-dr]%=MO;
                    }
                }
    }
    LL ans=0;
    for(int l=0;l<=2*n-1;l++)
        for(int r=0;l+r<=2*n-1;r++)
            ans=(1LL*ans+1LL*f[1][l][r])%MO;//對於每一個可能的情況都需要計算答案
    printf("%lld\n",ans);
    return 0;
}

以上只是個人的理解,如果出了什麼偏差,還請讀者自行腦補ヽ(・ω・´メ)