牛客國慶集訓派對Day2 E 資料排序(狀態壓縮dp)
阿新 • • 發佈:2018-12-13
雖然說得分可以一樣,但是我們還是可以僅僅只用兩個狀態0和1來表示這一題。
某一個位置為0表示當前這個數字還沒有被標號,也即當前數字比所有的已經標號的數字都要小。如果為1,那麼說明這個已經標號,並且這個數字與比它先標號的數字和與其相等的數字的衝突值已經計算過了。於是一個狀態,我們可以用一個15為的二進位制數字來表示。
令dp[i]表示當前的狀態,那麼當前狀態就可以轉移到後面的狀態。對於後一個狀態,相當於確定有哪些沒有被標號的數字會在下一次被標號。因此我們可以這個下次被標號的數字的集合s,對於s裡面每一個元素,在轉移的時候加上它與所有比它先標號的數字的衝突值。同時也要加上,這個集合s裡面所有數字標號相等時的衝突值。這樣,有轉移方程:
其中的w[s]表示新加入的這麼多點與之前加入的點的衝突值的和,b[s]表示s這麼多點一起加入的衝突值。具體在實現的時候,可以考慮預處理出單獨每一個數字加入的代價,然後在列舉子集的時候倒著來。另外,在計算單個數字的代價的時候,可以用上lowbit來加速。這樣,在不運算元集個數的情況下複雜度是O(N*2^N)的,實際算上自己個數之後,計算次數不會超過1500W。這樣優化常數之後,最快可以到80ms,排名Rank1,具體見程式碼:
#include<bits/stdc++.h> #define PI 3.1415926535 #define mod 998244353 #define LL long long #define pb push_back #define lb lower_bound #define ub upper_bound #define INF 0x3f3f3f3f #define sf(x) scanf("%d",&x) #define sc(x,y,z) scanf("%d%d%d",&x,&y,&z) #define clr(x,n) memset(x,0,sizeof(x[0])*(n+5)) #define file(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout) using namespace std; const int N = 1<<15; int dp[N],b[N],lg[N],st[N],p[N]; int w[15],t[15][15],n; int main() { sf(n); int up=(1<<n)-1; for(int i=0;i<=up;i++) { dp[i]=INF; for(int j=0;j<n;j++) if (i&(1<<j)) lg[i]=j; } for(int i=1;i<=2*n*(n-1);i++) { int x,y; sf(x); sf(y); x--; y--; t[x][y]++; } for(int i=0;i<=up;i++) for(int j=i,l=j&-j;j;j-=l,l=j&-j) for(int k=j-l,ll=k&-k;k;k-=ll,ll=k&-k) b[i]+=abs(t[lg[l]][lg[ll]]-t[lg[ll]][lg[l]]); dp[0]=0; for(int i=0;i<up;i++) { int res=up^i; for(int j=res,l=j&-j;j;j-=l,l=j&-j) { w[lg[l]]=0; for(int k=i,ll=k&-k;k;k-=ll,ll=k&-k) w[lg[l]]+=t[lg[ll]][lg[l]]; } int tt=0; for(int s=res;s;s=(s-1)&res) st[tt++]=s; while(tt--) { int tmp=st[tt]; p[tmp]=p[tmp^(1<<lg[tmp])]+w[lg[tmp]]; dp[i^tmp]=min(dp[i^tmp],dp[i]+p[tmp]+b[tmp]); } } printf("%d\n",dp[up]); return 0; }