1. 程式人生 > 實用技巧 >[東南大學第2屆程式設計秋季賽 G] Hmmm: A Tricky Counting Challenge - 結論,思維,dp

[東南大學第2屆程式設計秋季賽 G] Hmmm: A Tricky Counting Challenge - 結論,思維,dp

Description

定義一個長度為 \(n (n \ge 2)\)正整數數列 \(a_1,a_2,...,a_n\)權值

\[F(a_1,a_2,...,a_n)=\sum_{i=2}^{n-1} \max(\min(\max_{j=1}^{i-1} (a_j-a_i), \max_{j=i+1}^n (a_j-a_i)), 0) \]

給定一個長度為 \(n\) 的正整數數列 \(a_1,a_2,...,a_n\)

定義一種交換操作:任意選擇兩個下標 \(i,j\) 滿足 \(1 \le i < j \le n\),交換 \(a_i,a_j\) 的值。

定義一個自然數 \(z\)

可達的,當且僅當可以通過任意多次可以為 \(0\)交換操作,使得 \(F(a_1,a_2,...,a_n)=z\) 成立。換言之,\(z\) 是可達的的充要條件是對原數列可以通過若干次交換操作來得到一個新數列,使得新數列的權值等於 \(z\)。求可達的自然數共有多少個。

簡而言之,即求對於給定數列通過任意次交換操作得到的所有數列中,數列權值的不同可能取值有多少種。

第一行一個整數 \(n (1 \le n \le 500)\) 表示數列的長度。

第二行 \(n\) 個正整數,第 \(i\) 個正整數表示 \(a_i ( 1 \le a_i \le 50)\)。注意數列中可能會有相同的數。

Solution

以下數列 \(\{a_i\}\) 指的均是原數列的任意一種排列。設

\[b_1=a_1,b_n=a_n,b_i = \max(\min(\max_{j=1}^{i-1} a_j, \max_{j=i+1}^n a_j), a_i) (2 \le i \le n) \]

\(\{a_i\}\) 得到 \(\{b_i\}\) 的過程稱為一次變換。對於任意一個數列 \(a_i\),對 \((a_i,b_i)\) 進行排序使 \(b_i\) 單調不升並且 \(b_i\) 相同時 \(a_i\) 單調不降,結果記做 \((a'_i,b'_i)\),則 \(b'_i\) 仍是 \(a'_i\) 經過變換後的序列。利用動態規劃統計所有可能的 \(\{b'_i\}\)

和有多少種取值即可。即用 \(a_i\) 中的元素去填 \(b_i\),滿足填出的 \(b_i\) 單調不降,並且第 \(i\) 大的元素至少要在第 \(i\) 個位置之後才可以出現。設 \(f[t][i][j]\) 表示考慮了 \(\{a_i\}\) 中前 \(t\) 大的元素,每個元素可以不使用或使用一次或多次,填了前 \(b[1..i]\),這 \(i\) 個數的和是 \(j\),轉移類似完全揹包,用 std::bitset 優化即可。

改編自 [CCO 2017] 接雨滴

#include <bits/stdc++.h>
using namespace std;
#define int long long 
bitset <30005> f[505],ans;
int n,a[505],sum,res;
signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i], sum+=a[i];
    sort(a+1,a+n+1,greater<int>());
    f[1][a[1]]=1;
    for(int i=2;i<=n;i++,ans|=f[n]) for(int j=i;j<=n;j++) f[j]|=f[j-1]<<a[i];
    for(int i=sum;i<30005;i++) res+=ans[i];
    cout<<res;
}