1. 程式人生 > >【做題】uoj#370滑稽樹上滑稽果——巧妙dp

【做題】uoj#370滑稽樹上滑稽果——巧妙dp

放置 統計 turn typedef using tin 時間復雜度 轉移 OS

一個顯然的結論是最終樹的形態必然是一條鏈。具體證明只要考慮選定樹上的某一條鏈,然後把其他部分全部接在它後面,這樣答案一定不會變劣。

那麽,一開始的想法是考慮每一位的最後出現位置,但這並不容易實現。註意到最終序列是單調遞減的。我們在統計答案之前,把公共位先統計掉,即始終都是1的位。這樣,剩下的位的最終結果都是0。這樣,我們就避免了在統計時忽略某些數。那麽,我們記dp[i]表示當前的結果為i的最小費用。我們在轉移時枚舉下一個數字是什麽。這裏無需擔心數字的重復放置,因為它並不能讓當前的數字發生變化。那麽,我們就有如下第5檔部分分。

memset(dp,0x3f,sizeof dp);
for (int i = 1
; i <= n ; ++ i) dp[arr[i]] = arr[i]; for (int i = MAX - 1 ; i >= 1 ; -- i) for (int j = 1 ; j <= n ; ++ j) if ((i & arr[j]) < i) dp[i&arr[j]] = min(dp[i&arr[j]],dp[i] + (i&arr[j]));

註意到這裏需要(i&arr[j])<i,即\(C_uarr[j] \bigcap i \neq \emptyset\)。那麽,我們把所有\(C_uarr[j]\)

的子集存下來,然後轉移時枚舉子集就做到dp時復雜度與n無關了。
時間復雜度大概是:\(O(a^{log_23})\)
看起來很爆炸,但是非常不滿。

#include <bits/stdc++.h>
#define rint register int
using namespace std;
typedef long long ll;
const int N = 200010, MAX = 1 << 18;
int arr[N],n,avail[MAX + 5],com;
ll dp[MAX + 5];
#define rev(x) ((x) ^ (MAX - 1))
int main() {
  com = MAX - 1
; scanf("%d",&n); for (rint i = 1 ; i <= n ; ++ i) scanf("%d",&arr[i]), com &= arr[i]; for (rint i = 1 ; i <= n ; ++ i) arr[i] ^= com; for (rint i = 1 ; i <= n ; ++ i) { if (avail[rev(arr[i])]) continue; for (rint j = rev(arr[i]) ; j ; j = (j-1) & rev(arr[i])) avail[j] = 1; } memset(dp,0x3f,sizeof dp); for (rint i = 1 ; i <= n ; ++ i) dp[arr[i]] = arr[i]; for (rint i = MAX-1 ; i >= 1 ; -- i) if (dp[i] < dp[0]) { for (rint j = i ; j ; j = (j-1) & i) if (avail[j]) dp[i^j] = min(dp[i^j],dp[i] + (0ll^i^j)); } printf("%lld\n",1ll * com * n + dp[0]); return 0; }



小結:抓住題目的特殊性質是優化的關鍵。

【做題】uoj#370滑稽樹上滑稽果——巧妙dp