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 */

 

posted @ 2021-07-26 08:06  lei_yu  阅读(154)  评论(0编辑  收藏  举报