1. 程式人生 > 實用技巧 >【Atcoder MSPC】E M's Solution

【Atcoder MSPC】E M's Solution

題目

給出二維平面內的\(n\)個帶權節點,初始時\(x\)軸和\(y\)軸已標記

你可以新增\(k\)個平行於座標軸的標記軸

一個點的代價定義為該點到最近的標記軸的距離和該點權值的乘積

對於$k = 0 \to n $ ,依次輸出最小代價和

$1 \le n \le 15 \ , \ -10^4 \le x_i ,y_i \le 10^4 ,\ 0\le p_i \le 10^6 $

題解

標記直線至少過一個已知點(我不會證)

這樣合理的直線只有\(2n\)

注意到對於一個點,不會被兩條標記直線經過

因此可以\(O(3^n \times n^2 )\) 加剪枝

也可以考慮將\(2n\)條可能標記直線分配給\(n\)

個點

\(dp[S][k]\)為已經分配的集合為\(S\),分配的個數為\(k\)的最小代價

預處理\(cost[S]\)表示和選取一條標記線的集合\(S\)最小代價和

列舉子集轉移即可

複雜度\(O(n^22^n+n3^n)\)

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int N=15,M=1<<15;
const ll inf=1e12;
int n,ax[N],ay[N],b[N];
ll dp[M][N+1],cost[M];
int main(){
	freopen("E.in","r",stdin);
	freopen("E.out","w",stdout);
	scanf("%d",&n);
	for(int i=0;i<n;++i)scanf("%d%d%d",&ax[i],&ay[i],&b[i]);
	for(int i=1;i<1<<n;++i){
		ll sum;cost[i]=inf;
		sum=0;
		for(int j=0;j<n;++j)if(i&(1<<j))
			sum+=1ll*b[j]*abs(ax[j]);
		cost[i]=min(cost[i],sum);
		sum=0;
		for(int j=0;j<n;++j)if(i&(1<<j))
			sum+=1ll*b[j]*abs(ay[j]);
		cost[i]=min(cost[i],sum);
	}
	for(int i=1;i<1<<n;++i){
		ll &tmp=dp[i][0];
		tmp=inf;
		for(int t=i;t;t=(t-1)&i)
			tmp=min(tmp,cost[i^t]+cost[t]);
	}
	
	for(int i=0;i<1<<n;++i){
		ll sum=0;
		for(int j=0;j<n;++j){
			sum=0;
			for(int k=0;k<n;++k)if(i&(1<<k))
				sum+=1ll*b[k]*abs(ax[j]-ax[k]);
			cost[i]=min(cost[i],sum);
		}
		for(int j=0;j<n;++j){
			sum=0;
			for(int k=0;k<n;++k)if(i&(1<<k))
				sum+=1ll*b[k]*abs(ay[j]-ay[k]);
			cost[i]=min(cost[i],sum);
		}
	}

	
	for(int i=0;i<1<<n;++i)
	for(int j=1;j<=n;++j){
		ll &tmp=dp[i][j];
		tmp=inf;
		for(int t=i;t;t=(t-1)&i)
			tmp=min(tmp,dp[i^t][j-1]+cost[t]);
	}
	
	for(int i=0;i<=n;++i)cout<<dp[(1<<n)-1][i]<<endl;
	return 0;
}//tkys_Austin