AC详细带有注释代码
#include <bits/stdc++.h>
using namespace std;
#define debug(x) cout << #x << " = " << x << "\n";
typedef long long ll;
typedef pair<int, int> PII;
template <typename T>
inline void read(T &n)
{
n = 0; int f = 1; char ch = getchar();
while(!isdigit(ch))
{if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch))
{n = (n<<3) + (n<<1) + (ch&15); ch = getchar();}
n *= f;
}
const int N = 10 + 10;
const int dx[] = {0, 0, 1, -1};//上下左右四个方向
const int dy[] = {1, -1, 0, 0};
int a[N * N], dp[N * N][1 << 10], imp[N * N];
//a代表每个顶点是什么,讲二维转换成一维
//dp[i][S]代表以i为根,连接集合S的最小代价
//imp[i]存储着每个景点的位置
PII pre[N * N][1 << 10];
//用于回溯斯坦纳树经过的点, pre[i][S]存储着以i为根,连接集合S是由谁转移来的
bool vis[N * N];//dij需要的vis
char ans[N][N];//答案输出
int n, m, k, cnt;
priority_queue<PII, vector<PII>, greater<> > q;
PII legal(int code, int num)//code输入当前顶点,num代表是上下左右哪个方向,判断相邻的点是否越界了
{
PII t;
int x = code / m, y = code % m;
int xx = x + dx[num], yy = y + dy[num];
if(xx >= 0 && xx < n && yy >= 0 && yy < m)
t.first = 1;
else
t.first = 0;
t.second = xx * m + yy;
return t;
}
void dij(int s)//对已有的点进行松弛
{
memset(vis, 0, sizeof(vis));
while(!q.empty())
{
PII t = q.top();
q.pop();
int u = t.second, w = t.first;
if(vis[u])
continue;
vis[u] = 1;
for(int i = 0; i < 4; i ++)
{
PII re = legal(u, i);
if(!re.first)
continue;
if(dp[re.second][s] > dp[u][s] + a[re.second])
{
dp[re.second][s] = dp[u][s] + a[re.second];
q.push({dp[re.second][s], re.second});
pre[re.second][s] = {u, s};//记录其是由谁转移而来
}
}
}
return ;
}
void dfs(int x, int S)//回溯答案
{
if(!pre[x][S].second)//如果当前点无法从其他点转移而来,就退出dfs
return ;
if(a[x] != 0)//如果当前点不是景点,则该点是斯坦纳点
ans[x / m][x % m] = 'o';
if(pre[x][S].first == x)//如果x是由两个子树转移而来,则需要dfs另一颗子树
dfs(x, S ^ pre[x][S].second);
dfs(pre[x][S].first, pre[x][S].second);//dfs搜其他点的树
}
int main()
{
memset(dp, 0x3f, sizeof(dp));
memset(ans, '_', sizeof(ans));//初始化
cin >> n >> m;
for(int i = 0; i < n; i ++)
for(int j = 0; j < m; j ++)
{
cin >> a[i * m + j];
if(a[i * m + j] == 0)
{
ans[i][j] = 'x';
dp[i * m + j][1 << (++ cnt - 1)] = 0;
imp[cnt] = i * m + j;
}
}
for(int S = 1; S < (1 << cnt); S ++)
{
for(int i = 0; i < n * m; i ++)
{
for(int sub = S & (S - 1); sub; sub = (sub - 1) & S)
{
if(dp[i][S] > dp[i][sub] + dp[i][sub ^ S] - a[i])
{
dp[i][S] = dp[i][sub] + dp[i][sub ^ S] - a[i];
pre[i][S] = {i, sub};//记录是两个子树转移
}
}
if(dp[i][S] != 0x3f3f3f3f)
q.push({dp[i][S], i});
}
dij(S);
}
cout << dp[imp[1]][(1 << cnt) - 1] << "\n";//输出最小和
dfs(imp[1], (1 << cnt) - 1);//回溯
for(int i = 0; i < n; i ++)
{
for(int j = 0; j < m; j ++)
cout << ans[i][j];
cout << "\n";
}
return 0;
}
首先跑一遍斯坦纳树,然后应该二次dp,定义 DP 数组 Ans (S),表示使状态 S 中所有点连通的最小费用,注意,这个 DP 的过程是有条件的:
首先枚举 S 集合(指定点集合),这个将这个状态 S 进行更新的条件是 S 必须:对于一个频率,要么包含该频率中的所有指定点,要么不包含于这个频率的任意结点!
接着枚举 S 的子集,这个子集 S1 也是要有条件的,也是 必须:对于一个频率,要么包含该频率中的所有指定点,要么不包含于这个频率的任意结点!
AC代码:
#include <bits/stdc++.h>
using namespace std;
#define debug(x) cout << #x << " = " << x << "\n";
typedef long long ll;
typedef pair<int, int> PII;
template <typename T>
inline void read(T &n)
{
n = 0; int f = 1; char ch = getchar();
while(!isdigit(ch))
{if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch))
{n = (n<<3) + (n<<1) + (ch&15); ch = getchar();}
n *= f;
}
const int N = 1e3 + 10;
vector<PII> a[N];
int dp[N][1 << 11], ans[1 << 11], num[N], sum[N];
bool vis[N], isleg[1 << 11];
int n, m, k, u, v, w, cnt = 0;
priority_queue <PII, vector<PII>, greater<> > q;
struct Node
{
int col;
int id;
}imp[N];
void dij(int s)
{
memset(vis, 0, sizeof(vis));
while(!q.empty())
{
PII t = q.top();
q.pop();
int u = t.second, w = t.first;
if(vis[u])
continue;
vis[u] = 1;
for(int i = 0; i < a[u].size(); i ++)
{
int v = a[u][i].second, c = a[u][i].first;
if(dp[v][s] > dp[u][s] + c)
{
dp[v][s] = dp[u][s] + c;
q.push({dp[v][s], v});
}
}
}
return ;
}
bool check(int s)//检查集合s是否合法,包含了某个频率的所有点就为合法
{
memset(num, 0, sizeof(num));
for(int i = 0; i < cnt; i ++)
{
if(s & (1 << i))
num[imp[i].col] ++;
}
for(int i = 1; i <= 10; i ++)
{
if(num[i] && num[i] != sum[i])
return false;
}
return true;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
memset(dp, 0x3f, sizeof(dp));
cin >> n >> m >> k;
while(m --)
{
cin >> u >> v >> w;
a[u].push_back({w, v});
a[v].push_back({w, u});
}
while(k --)
{
cin >> u >> v;
dp[v][1 << cnt] = 0;
imp[cnt].col = u;
imp[cnt ++].id = v;
sum[u] ++;
}
for(int s = 1; s < (1 << cnt); s ++)
{
for(int i = 1; i <= n; i ++)
{
for(int sub = s & (s - 1); sub; sub = (sub - 1) & s)
dp[i][s] = min(dp[i][s], dp[i][sub] + dp[i][sub ^ s]);
if(dp[i][s] != 0x3f3f3f3f)
q.push({dp[i][s], i});
}
dij(s);
}
memset(ans, 0x3f, sizeof(ans));
for(int s = 1; s < (1 << cnt); s ++)
{
for(int i = 1; i <= n; i ++)
ans[s] = min(ans[s], dp[i][s]);
}//当前ans为默认最小的时全部相连
for(int i = 1; i < (1 << cnt); i ++)
if(check(i))
isleg[i] = 1;//拿出所有合法集合
for(int s = 1; s < (1 << cnt); s ++)//枚举
{
if(isleg[s])
for(int sub = s; sub; sub = (sub - 1) & s)
if(isleg[sub])
ans[s] = min(ans[s], ans[sub] + ans[s ^ sub]);
}
cout << ans[(1 << cnt) - 1] << "\n";
return 0;
}