斯坦纳树例题

P4294 [WC2008]游览计划

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;
}

P3264 [JLOI2015]管道连接

首先跑一遍斯坦纳树,然后应该二次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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值