2019.10.24 CSP%你賽第二場d1t3

題目描述 Description

精靈心目中亙古永恆的能量核心崩潰的那一刻,Bzeroth 大陸的每一個精靈都明白,他們的家園已經到了最後的時刻。
就在這危難關頭,諸神天降神諭,傳下最終兵器——潘少拉魔盒。然而當精靈們準備打開魔盒時,魔盒的守護靈出如今精靈們面前:「若是大家想要拯救世界,必需要先解決這個困難的問題:定義一個 N 階數列 A 爲神奇數列當且僅當對全部2≤i≤N−1 ,都有 Ai1+Ai+12×Ai。如今有一個N階正整數列B ,請計算將 B 數列均勻隨機打亂以後,獲得的數列是神奇數列的機率 P 。你只須要輸出P×(N!)mod998244353 的結果便可。(顯然 P×(N!) 必定是個整數)。」
ios

輸入描述 Input Description

第一行爲 1 個正整數 N。
第二行爲 N 個正整數 Ai。
git

輸出描述 Output Description

輸出 P×(N!)mod998244353 的結果。函數

樣例輸入 Sample Input

4
1 2 1 3
spa

樣例輸出 Sample Output

8

數據範圍及提示 Data Size & Hint

對於 50%的數據:3 ≤ N ≤ 10。
對於 80%的數據:3 ≤ N ≤ 20。
對於 100%的數據:3 ≤ N ≤ 40,1Ai109 。code


容易知道,這個題其實是想讓咱們求給定的集合可以拼成多少個神奇數列。xml

通過推一波式子,咱們能夠知道a[i+1]-a[i]>=a[i]-a[i-1],因此它的差分序列單調遞增,即其爲一個下凸函數。。blog

所以,對於一個神奇數列,知足它必定是一個下凸函數。排序

考慮在這個函數上進行dp:ip

對於一個區間dp[i][k],表示區間最左端取值爲a[i],最右端取值爲a[k],則進行轉移時每次往左端或右端放入一個點。get

可是因爲剛纔的式子,咱們必須記錄端點以前的位置的取值,因此咱們用dp[i][j][k][l]表示某個下凸函數的一段區間最左端取值爲a[i],a[j],最右端取值爲a[l],a[k]的神奇序列個數。

考慮將原來的集合排序,因此易知下凸函數的頂點必定是整個集合的最小值。

由此,咱們每次考慮放入一個新值a[pos]知足pos=max(i,k)+1,緣由是排好序的序列單調遞增,又由於a[i],a[k]必定含有當前區間值最大的點(由於是下凸函數),因此咱們只須要取兩者下標最大值+1做爲新值下標。因此能夠用剛纔的方法檢驗是否答案合法。

上代碼:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long LL;
#define mem(a,b) memset(a,b,sizeof(a))
typedef pair<int,int> PII;
typedef pair<int,PII> PIP;
const int MOD=998244353,maxn=42;
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    return x*f;
}
int n,a[maxn],sum=1,jc[maxn],dp[maxn][maxn][maxn][maxn],ans;
int main()
{
    n=read(); 
    jc[0]=1;
    for(int i=1;i<=n;i++)a[i]=read(),jc[i]=((LL)jc[i-1]*(LL)i)%MOD;//階乘預處理
    sort(a+1,a+n+1);
    for(int i=2;i<=n;i++)
    {
        if(a[i]!=a[i-1])break;
        sum++;
    }//取出區間最小值
    for(int i=sum+1;i<=n;i++)a[i-sum+1]=a[i];//把其它最小值扔到後面
    dp[1][0][1][0]=1;
    n=n-sum+1;//去掉與最小值相同的值
    for(int i=1;i<=n;i++)
        for(int j=0;j<i;j++)
            for(int k=1;k<=n;k++)
                for(int l=0;l<k;l++) 
                {
                    int pos=max(i,k)+1;//找到新加入的數
                    if(pos==n+1){ans=(ans+dp[i][j][k][l])%MOD;continue;}//若是整個下凸函數已經構成了長度爲n的區間
                    if(2*a[i]<=a[pos]+a[j] || i==1)dp[pos][i][k][l]=(dp[pos][i][k][l]+dp[i][j][k][l])%MOD;//檢查i端是否合法
                    if(2*a[k]<=a[pos]+a[l] || k==1)dp[i][j][pos][k]=(dp[i][j][pos][k]+dp[i][j][k][l])%MOD;//檢查k端是否合法
                }
    printf("%d\n",((LL)ans*(LL)jc[sum])%MOD);//由於最小值一共有sum個取值,因此這sum個取值進行全排列再乘以答案便可           
    return 0;
}
相關文章
相關標籤/搜索