1. 程式人生 > 其它 >noip 模擬 33 Connect

noip 模擬 33 Connect

本場考試最有意義的一道題,思路根據題解和 ICEY 鑽研了好久但最後分道揚鑣了,寫篇題解紀念一下。
先構造出最終狀態,最後的狀態一定是 \(1\)\(n\) 的一條鏈,並且不在鏈上的每個聯通塊最多隻和鏈上的一個點有連邊。 形如:

我們定義狀態:
\(dp_{u,s}\) 為當前鏈的末端點為 \(u\),已聯通的點的集合為 \(s\)
當轉移到 \(u\) 點時,有兩種選擇方案,

1.不新增枝葉,新增鏈。
2.新增枝葉,不新增鏈。

枝葉就是聯通塊,對於每個點的可行性枝葉可以預處理出來,同時注意新增的聯通塊不能包含 \(n\) 節點,因為 \(n\) 一定在鏈上。另外根據最終狀態的的定義,新新增的聯通塊不能包含已聯通的點。

先說一下預處理,
先把每個聯通塊狀態的權值更新

	for(re int s=1;s<(1<<n);s++) {
		for(re int i=1;i<=n;i++) if(s&(1<<i-1)) {
			for(re int j=head[i],to;j;j=edge[j].nxt) {
				to=edge[j].var;
				if((to>i)&&(s&(1<<to-1))) cses[s]+=edge[j].cst;
			} 
		} 
	} 

下面是把每個點所能達到的聯通塊狀態處理出來

	for(re int i=1,lim;i<=n;i++) {
		lim=(1<<i-1);
		vis[i][lim]=1;
		for(re int s=lim;s<(1<<n);s++) if(vis[i][s]) {
			for(re int j=1;j<=n;j++) if(!(s&(1<<j-1))&&(ds[j]&s)) {
				if(!vis[i][s|(1<<j-1)]) {
					if(i==n) zi[i][++zi[i][0]]=s|(1<<j-1);
					else if(!((s|(1<<j-1))&(1<<n-1))) zi[i][++zi[i][0]]=s|(1<<j-1);
					vis[i][s|(1<<j-1)]=1;
				}
			} 
		} 
	} 

轉移就很好說了,分情況轉移

	dp[1][1]=1;
	int lim=(1<<(n-1));
	for(re int sum_tmp=1;sum_tmp<lim;sum_tmp++) 
	for(re int now=1;now<n;now++) if(dp[now][sum_tmp]) {  
		for(re int i=1,tmp;i<=zi[now][0];i++)  {   // Situation 1
			tmp=zi[now][i];
			if((tmp&(sum_tmp^(1<<now-1)))) continue;
			dp[now][sum_tmp|tmp]=max(dp[now][sum_tmp|tmp],dp[now][sum_tmp]+cses[tmp]);
		}
		for(re int i=head[now],to;i;i=edge[i].nxt) {    // Situation 2 
			to=edge[i].var;
			if((sum_tmp&(1<<to-1))) continue;
			dp[to][sum_tmp|(1<<to-1)]=max(dp[to][sum_tmp|(1<<to-1)],dp[now][sum_tmp]+edge[i].cst);
		}
	}

注意最後的答案更新,\(n\) 點還要分情況討論一下

Code

#include <bits/stdc++.h>
#define re register
#define db double
#define pir make_pair
#define max(x,y) (x)>(y)? (x):(y)
using namespace std; 
const int maxn=16;
const int INF=1e9;

inline int read() {
   int s=0,w=1; char ch=getchar();
   while(ch<'0'||ch>'9') { if(ch=='-') w=-1;ch=getchar(); }
   while(ch>='0'&&ch<='9') { s=s*10+ch-'0'; ch=getchar(); }
   return s*w;
}
struct EDGE { int var,cst,nxt; } edge[(maxn*maxn)<<1];
int n,m,head[maxn],cnt,cses[1<<maxn],ans,res;
bool vis[maxn][1<<maxn];int ds[maxn];
inline void add(int a,int b,int c) { edge[++cnt]=(EDGE){ b,c,head[a] }; head[a]=cnt; }
int zi[maxn][1000010];
int dp[maxn][1<<maxn];
signed main(void) {
   //freopen("connect4.in","r",stdin);
   n=read(),m=read();
   for(re int i=1,u,e,w;i<=m;i++) {
   	u=read(),e=read(),w=read();
   	res+=w;
   	ds[u]|=(1<<e-1); ds[e]|=(1<<u-1);
   	add(u,e,w); add(e,u,w);
   }
   for(re int s=1;s<(1<<n);s++) {
   	for(re int i=1;i<=n;i++) if(s&(1<<i-1)) {
   		for(re int j=head[i],to;j;j=edge[j].nxt) {
   			to=edge[j].var;
   			if((to>i)&&(s&(1<<to-1))) cses[s]+=edge[j].cst;
   		} 
   	} 
   } 
   for(re int i=1,lim;i<=n;i++) {
   	lim=(1<<i-1);
   	vis[i][lim]=1;
   	for(re int s=lim;s<(1<<n);s++) if(vis[i][s]) {
   		for(re int j=1;j<=n;j++) if(!(s&(1<<j-1))&&(ds[j]&s)) {
   			if(!vis[i][s|(1<<j-1)]) {
   				if(i==n) zi[i][++zi[i][0]]=s|(1<<j-1);
   				else if(!((s|(1<<j-1))&(1<<n-1))) zi[i][++zi[i][0]]=s|(1<<j-1);
   				vis[i][s|(1<<j-1)]=1;
   			}
   		} 
   	} 
   } 
   dp[1][1]=1;
   int lim=(1<<(n-1));
   for(re int sum_tmp=1;sum_tmp<lim;sum_tmp++) 
   for(re int now=1;now<n;now++) if(dp[now][sum_tmp]) {
   	for(re int i=1,tmp;i<=zi[now][0];i++)  {
   		tmp=zi[now][i];
   		if((tmp&(sum_tmp^(1<<now-1)))) continue;
   		dp[now][sum_tmp|tmp]=max(dp[now][sum_tmp|tmp],dp[now][sum_tmp]+cses[tmp]);
   	}
   	for(re int i=head[now],to;i;i=edge[i].nxt) {
   		to=edge[i].var;
   		if((sum_tmp&(1<<to-1))) continue;
   		dp[to][sum_tmp|(1<<to-1)]=max(dp[to][sum_tmp|(1<<to-1)],dp[now][sum_tmp]+edge[i].cst);
   	}
   }
   for(re int s=1;s<(1<<n);s++) if(dp[n][s]) {
   	int ls=0;
   		for(re int i=1,tmp;i<=zi[n][0];i++) {
   			tmp=zi[n][i];
   			if((tmp&(s^(1<<n-1)))) continue;
   			ls=max(ls,cses[tmp]);
   		}
   	ans=max(ans,ls+dp[n][s]); 
   }
   printf("%d\n",res-ans+1);
}

奠死掉的dfs... 深搜比迴圈慢,因為這個被T飛了

inline void dfs(int now,int sum_tmp,int val) {
	//if(v[pir(pir(now,sum_tmp),val)]) return;
	//v[pir(pir(now,sum_tmp),val)]=1;
	//if(v[pir(now,sum_tmp)]>val) return;
	//v[pir(now,sum_tmp)]=val;
	if(dp[now][sum_tmp]>val) return;
	dp[now][sum_tmp]=val;
	if(now==n) { 
		int ls=0;
		for(re int i=1,tmp;i<=zi[now][0];i++) {
			tmp=zi[now][i];
			if((tmp&(sum_tmp^(1<<now-1)))) continue;
			ls=max(ls,cses[tmp]);
		}
		//cout<<ans<<endl; 
		ans=max(ans,ls+val); 
		return; 
	}
	for(re int i=head[now],to;i;i=edge[i].nxt) {
		to=edge[i].var;
		if((sum_tmp&(1<<to-1))) continue;
		dfs(to,sum_tmp|(1<<to-1),val+edge[i].cst);
	}
	for(re int i=1,tmp;i<=zi[now][0];i++)  {
		tmp=zi[now][i];
		if((tmp&(sum_tmp^(1<<now-1)))||(tmp&(1<<n-1))) continue;
		dfs(now,sum_tmp|tmp,val+cses[tmp]);
	}
	
}

考試總結: 動態開點注意空間,能 \(DP\) 不深搜。