noip 模擬 33 Connect
阿新 • • 發佈:2021-08-08
本場考試最有意義的一道題,思路根據題解和 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\) 不深搜。