T180748 网格图小节点数斯坦纳树
普通的斯坦纳树是一个NP问题,在多项式时间复杂度范围内无法解决,因此这里的斯坦纳树是很暴力的算法——状压。
显然我们只需要把当前有哪些关键点已经在生成树中记录下来。为了能够转移,我们还必须记录一下当前的根节点是什么。
我们设$f(i,state)$表示根节点为$i$,经过的关键点集合至少为$state$时的最少花费。"至少"表明我们在转移时肯定会有一些非最优的状态,但是也总会有最优的状态出现。
其中一种转移是合并,即两个根节点相同的树合并成一颗新树。
即$f(i,state)=f(i,state')+f(i,state'')$,其中$state'\cup state''=state$,不过显然最优的情况肯定是$state'\cap state''=\varnothing$时出现。因此可以直接枚举子集降低复杂度。
另一种操作是换根,即$f(i,state)=f(j,state)+a(i,j)$,由于普通节点的数量没有限制,增加的代价即两个相邻点的距离应当为它们在图中的最短路,因此我们不知道经过的节点,就无法考虑到换到新的节点时关键点集合的变化(这里至少的定义就发挥作用了)。但是通过预处理$f(i,1<<(i-1))=0$,转移中总有一种情况可以得到最优解。
此处换根的操作其实也可以看成用新合并出的$f(i,state)$松弛所有的$f(i,state)$。所以相当于求一遍最短路。利用普通最短路算法,时间复杂度在$O(n^2)$左右
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 int f[105][1<<10],b[10005],t[10001]; 7 inline int r() 8 { 9 int s=0,k=1;char c=getchar(); 10 while(!isdigit(c)) 11 { 12 if(c=='-')k=-1; 13 c=getchar(); 14 } 15 while(isdigit(c)) 16 { 17 s=s*10+c-'0'; 18 c=getchar(); 19 } 20 return s*k; 21 } 22 int n,m,k; 23 int a[1005][1005]; 24 void dij(int s) 25 { 26 memset(b,0,sizeof(b)); 27 for(int i=1;i<=n;i++) 28 { 29 int u=0; 30 for(int j=1;j<=n;j++) 31 if(!b[j]&&(!u||f[j][s]<f[u][s]))u=j; 32 if(f[u][s]>1e9)return; 33 b[u]=1; 34 for(int j=1;j<=n;j++) 35 f[j][s]=min(f[j][s],f[u][s]+a[u][j]); 36 } 37 } 38 int main() 39 { 40 n=r(); 41 m=r(); 42 k=r(); 43 memset(a,0x3f,sizeof(a)); 44 memset(f,0x3f,sizeof(f)); 45 for(int i=1;i<=n;i++) 46 a[i][i]=0; 47 int x,y,z; 48 for(int i=1;i<=m;i++) 49 { 50 x=r();y=r();z=r(); 51 if(a[x][y]>z)a[x][y]=a[y][x]=z; 52 } 53 54 for(int i=1;i<=k;i++)t[i]=r(); 55 56 for(int l=1;l<=n;l++) 57 for(int i=1;i<=n;i++) 58 for(int j=1;j<=n;j++) 59 if(a[i][j]>a[i][l]+a[l][j]&&i!=j&&j!=l&&i!=l) 60 a[i][j]=a[i][l]+a[l][j]; 61 62 for(int i=1;i<=(1<<k)-1;i++)//当前的所有状态 63 { 64 for(int j=i;j;j=(j-1)&i)//子集 65 { 66 for(int l=1;l<=n;l++)//树根可以任意 67 if(f[l][j]<1e9)f[l][i]=min(f[l][i],f[l][j]+f[l][i-j]); 68 } 69 if(!(i&(i-1)))//只有一个节点 70 for(int j=0;j<k;j++)if(i&(1<<j))//找到当前点,初始化 71 { 72 f[t[j+1]][i]=0; 73 break; 74 } 75 dij(i); 76 } 77 int ends=1<<k,ans=1e9; 78 ends-=1; 79 for(int i=1;i<=n;i++) 80 ans=min(ans,f[i][ends]); 81 cout<<ans; 82 }
和普通的斯坦纳树不同,本题问的是最小的节点树。
因此在转移的时候将会有所不同。
当根节点为普通空地时,合并会导致根节点被重复计算一次,因此必须要把答案减一。
而若根节点为电器时,因为本身就不需要电线,所以不需要减一。
在松弛其他节点时,也可以通过BFS/SPFA来松弛,这样时间复杂度是近似$O(n^2)$的。
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 #include<algorithm>
5 #include<queue>
6 using namespace std;
7 int n,m,t;
8 char c[1001][1001];
9 char s[1001];
10 int f[55][55][1<<8];
11 int dx[5]={0,1,-1,0,0};
12 int dy[5]={0,0,0,1,-1};
13 struct node
14 {
15 int x,y;
16 };
17 queue<node>q;
18 int cnt;
19 int main()
20 {
21 cin>>n>>m>>t;
22 for(int i=1;i<=n;i++)
23 for(int j=1;j<=m;j++)
24 for(int k=0;k<=(1<<t)-1;k++)
25 f[i][j][k]=1e9;
26 for(int i=1;i<=n;i++)
27 {
28 cin>>(s+1);
29 for(int j=1;j<=m;j++)
30 {
31 c[i][j]=s[j];
32 if(c[i][j]=='+')
33 f[i][j][1<<(cnt++)]=1;
34 }
35 }
36 for(int k=0;k<=(1<<t)-1;k++)
37 {
38 for(int i=1;i<=n;i++)
39 for(int j=1;j<=m;j++)
40 {
41 if(c[i][j]=='#')continue;
42 for(int l=k;l;l=(l-1)&k)
43 if(f[i][j][l]<1e9&&f[i][j][k-l]<1e9)
44 f[i][j][k]=min(f[i][j][k],f[i][j][l]+f[i][j][k^l]-1);
45 if(f[i][j][k]<1e9)
46 q.push((node){i,j});
47 }
48 while(!q.empty())
49 {
50 node tmp=q.front();
51 q.pop();
52 int x=tmp.x,y=tmp.y;
53 //cout<<"松弛"<<x<<" "<<y<<endl;
54 for(int i=1;i<=4;i++)
55 {
56 int xx=x+dx[i],yy=y+dy[i];
57 if(c[xx][yy]!='#'&&f[xx][yy][k]>f[x][y][k]+1)
58 {
59 f[xx][yy][k]=f[x][y][k]+1;
60 q.push((node){xx,yy});
61 }
62 }
63 }
64 }
65 int ans=1e9;
66 //cout<<ends<<endl;
67 for(int i=1;i<=n;i++)
68 for(int j=1;j<=m;j++)
69 ans=min(ans,f[i][j][(1<<t)-1]);
70 cout<<ans-t;
71
72 }
73 /*
74 4 4 3
75 #..+
76 +...
77 #...
78 +...
79 */
本文来自博客园,作者:lei_yu,转载请注明原文链接:https://www.cnblogs.com/lytql/p/15059726.html