P3959 [NOIP2017 提高组] 宝藏 题解

P3959 [NOIP2017 提高组] 宝藏 题解

搜索魅力时刻

怎么说,四种做法

  • 比较??的模拟退火
  • 跑得快但是 正确性有问题的 状压DP
  • 跑得慢但是 一定正确的 状压 DP
  • 时间复杂度很玄学的DFS+剪枝

我就选择了搜索的做法

先打个暴搜,70pts

点击查看暴搜代码
#include <bits/stdc++.h>
using namespace std;
const int inf = 998244353;
int n, m;
int dis[100][100];
struct node
{
    // int vis;
    int step;
};
node nds[114514];
int tmp;
int ans = inf;
void dfs(int num)
{
    if (tmp > ans)
    {
        return;
    }
    if (num == n)
    {
        ans = min(ans, tmp);
    }
    else
    {
        for (int yy = 1; yy <= n; yy++) //由谁拓展
        {
            if (nds[yy].step)
            {
                for (int tt = 1; tt <= n; tt++) //拓展到谁
                {
                    if (nds[tt].step == 0 && yy != tt && dis[yy][tt] != inf)//如果能去
                    {
                        tmp += nds[yy].step * dis[yy][tt];
                        nds[tt].step = nds[yy].step + 1;
                        dfs(num + 1);
                        tmp -=nds[yy].step * dis[yy][tt];
                        nds[tt].step=0;
                    }
                }
            }
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m;
    memset(dis, inf, sizeof(dis));
    int a, b, c;
    for (int yy = 1; yy <= m; yy++)
    {
        cin >> a >> b >> c;
        if (dis[a][b] < c)
        {
            continue;
        }
        if (dis[a][b] > c)
        {
            dis[b][a] = c;
            dis[a][b] = c;
        }
    }
    for (int ww = 1; ww <= n; ww++) //枚举起点
    {
        nds[ww].step = 1;
        dfs(1);
        nds[ww].step = 0;
    }
    cout<<ans;
    return 0;
}

接下来就是想着怎么优化了

自然,我们可以想到这题可以加上一个最优化剪枝,然后在记录一下哪些点被拓展过。
就可以得到如下代码

点击查看优化代码-1
#include <bits/stdc++.h>
using namespace std;
const int inf = 198244353;
int n, m;
int dis[100][100], sho[100];
int dd[55];
struct node
{
    // int vis;
    int step;
};
node nds[114514];
int tmp;
int ans = inf;
int short_;
void dfs(int num)
{
    // cout<<(3*ans)/2<<endl;

    if (num == n)
    {
        ans = min(ans, tmp);
    }
    else
    {
        for (int ysy = 1; ysy <= num; ysy++) //由谁拓展
        {
            int yy = dd[ysy];
            //最优化剪枝
            if (tmp + short_ * nds[yy].step > ans)//如果当前代价加上后面的最小代价还是比之前的答案大,那么一定不优
            {
                return;
            }
            for (int tt = 1; tt <= n; tt++) //拓展到谁
            {
                if (nds[tt].step == 0 && yy != tt && dis[yy][tt] != inf)
                {
                    tmp += nds[yy].step * dis[yy][tt];
                    nds[tt].step = nds[yy].step + 1;
                    short_ -= sho[tt];//减去这个点的最小代价
                    dd[num + 1] = tt;
                    dfs(num + 1);
                    dd[num + 1] = 0;
                    short_ += sho[tt];
                    tmp -= nds[yy].step * dis[yy][tt];
                    nds[tt].step = 0;
                }
            }
        }
    }
}
int main()
{
    // freopen("P3959_1.in", "r", stdin);
    // freopen("", "w", stdout);
    ios::sync_with_stdio(false);
    cin >> n >> m;
    memset(dis, inf, sizeof(dis));
    int a, b, c;
    for (int yy = 1; yy <= m; yy++)
    {
        cin >> a >> b >> c;
        // if (dis[a][b] < c)
        // {
        // continue;
        // }
        // if (dis[a][b] > c)
        dis[b][a] = min(dis[b][a], c);
        dis[a][b] = min(dis[a][b], c);
    }

    for (int yy = 1; yy <= n; yy++)
    {
        int ww = inf;
        for (int ee = 1; ee <= n; ee++)//获取每个点的最小代价,也就是那个最短的出边
        {
            if (yy != ee)
            {
                ww = min(ww, dis[yy][ee]);
            }
        }
        short_ += ww;
        sho[yy] = ww;
    }
    for (int ww = 1; ww <= n; ww++) //枚举起点
    {
        tmp = 0;
        nds[ww].step = 1;
        short_ -= sho[ww];
        dd[1] = ww;
        dfs(1);
        dd[1] = 0;
        short_ += sho[ww];
        nds[ww].step = 0;
        // cout << ans << "\n";
    }
    cout << ans;
    return 0;
}

官方数据都过了,但是 Hack 数据会有 \(10\) 各点左右 超时,那我们该怎么进一步优化呢?

首先我们可以发现,当我们拓展完一个节点后,又会回到之前拓展过的节点上,重新遍历他们的出度,但是事实上,这些点中的出度已经有很多被遍历过了,所以我们可以用当前弧优化的思想 对于每一个点记录一下这次搜索之中已经遍历到哪个出度了。

加上这个优化的代码
#include <bits/stdc++.h>
using namespace std;
const int inf = 998244353;
int n, m;
int dis[100][100], sho[100];
int du[100];
int to_[100][100];
int dd[55];
struct node
{
    int vis=1;
    int step;
};
node nds[114];
int tmp;
int ans = inf;
int short_;
int cmm;
bool cmp(int a, int b)
{
    return dis[cmm][a] < dis[cmm][b];
}
void dfs(int num)
{
    // cout<<(3*ans)/2<<endl;

    if (num == n)
    {
        ans = min(ans, tmp);
    }
    else
    {
        int tt, yy;
        for (int ysy = 1; ysy <= num; ysy++) //由谁拓展
        {

            yy = dd[ysy];
            if (tmp + short_ * nds[yy].step > ans)
            {
                return;
            }
            // cout<<du[yy]<<"   |||   "<<endl;
            for (int att = nds[yy].vis; att <= du[yy]; att++) //拓展到谁
            {
                nds[yy].vis++;
                tt = to_[yy][att];
                // cout<<tt;
                if (nds[tt].step == 0&&dis[yy][tt]!=inf)
                {
                    tmp += nds[yy].step * dis[yy][tt];
                    nds[tt].step = nds[yy].step + 1;
                    short_ -= sho[tt];
                    dd[num + 1] = tt;
                    dfs(num + 1);
                    dd[num + 1] = 0;
                    short_ += sho[tt];
                    tmp -= nds[yy].step * dis[yy][tt];
                    nds[tt].step = 0;
                }
            }
            nds[yy].vis=1;//C
        }
    }
}
int main()
{
    // freopen("P3959_22.in", "r", stdin);
    // freopen("", "w", stdout);
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int ww = 0; ww <= n + 1; ww++)
    {
        for (int ee = 0; ee <= n + 1; ee++)
        {
            dis[ww][ee] = inf;
        }
        du[ww]=0;
    }
    int a, b, c;
    for (int yy = 1; yy <= m; yy++)
    {
        cin >> a >> b >> c;
        // if (dis[a][b] < c)
        // {
        // continue;
        // }
        // if (dis[a][b] > c)
        if (dis[a][b] == inf)
        {
            du[a]++;
            du[b]++;
            to_[a][du[a]] = b;
            to_[b][du[b]] = a;
        }
        dis[b][a] = min(dis[b][a], c);
        dis[a][b] = min(dis[a][b], c);
    }

    for (int yy = 1; yy <= n; yy++)
    {
        // cout<<du[yy]<<endl;;
        cmm = yy;
        sort(to_[yy] + 1, to_[yy] + 1 + du[yy], cmp);
        int ww = inf;
        for (int ee = 1; ee <= n; ee++)
        {
            if (yy != ee)
            {
                ww = min(ww, dis[yy][ee]);
            }
        }
        short_ += ww;
        sho[yy] = ww;
    }
    for (int ww = 1; ww <= n; ww++) //枚举起点
    {
        tmp = 0;
        nds[ww].step = 1;
        short_ -= sho[ww];
        dd[1] = ww;
        dfs(1);
        dd[1] = 0;
        short_ += sho[ww];
        nds[ww].step = 0;
        // cout << ans << "\n";
    }
    cout << ans;
    return 0;
}

加上这个优化之后,我们就可以过掉这个题了,但是,还是太慢了,需要足足 \(1.3s\) 才能跑完官方的数据。

事实上,我们还有 \(1\) 个很可以优化的地方:对于最先开始遍历的那些点,我们在循环之中已经把他们都处理完了,下次搜索就不需要再考虑他们,于是,我们再次用上当前弧优化的思想,加上一个传参 来记录有多少个被拓展的点已经被处理完了,加上这个优化之后 ,真就是暴力碾标算,只用 \(130ms\) 就可以跑完官方的所有数据

最终版的代码
#include <bits/stdc++.h>
using namespace std;
const int inf = 998244353;
int n, m;
int dis[100][100], sho[100];
int du[100];
int to_[100][100];
int dd[55];
/*
dis:邻接矩阵
sho:每个点最短的出边
du:每个点的出度(边已去重)
to_:每个点能到的点
dd:被拓展的点的顺序
*/
struct node
{
    int vis = 1;
    int step;
};
node nds[114];//每个洞穴
int tmp;//dfs用的,记录中间答案
int ans = inf;//最终答案
int short_;//每个点最短的出度之和,用于最优化剪枝
int cmm;//sort用
bool cmp(int a, int b)
{
    return dis[cmm][a] < dis[cmm][b];
}
void dfs(int now_,int num)
{
    // cout<<(3*ans)/2<<endl;

    if (num == n)
    {
        ans = min(ans, tmp);
    }
    else
    {
        int tt, yy;
        for (int ysy = now_; ysy <= num; ysy++) //由谁拓展
        {

            yy = dd[ysy];
            if (tmp + short_ * nds[yy].step > ans)
            {
                return;
            }
            for (int att = nds[yy].vis; att <= du[yy]; att++) //拓展到谁
            {
                nds[yy].vis++;
                tt = to_[yy][att];
                if (tt == 0)
                {
                    cout << "Warning!";
                    cout << yy << "'s " << att << '\n';
                }
                if (nds[tt].step == 0)
                {

                    tmp += nds[yy].step * dis[yy][tt];
                    nds[tt].step = nds[yy].step + 1;
                    short_ -= sho[tt];
                    dd[num + 1] = tt;
                    dfs(ysy,num + 1);
                    dd[num + 1] = 0;
                    short_ += sho[tt];
                    tmp -= nds[yy].step * dis[yy][tt];
                    nds[tt].step = 0;
                }
            }
            nds[yy].vis = 1;//这个点处理完了,将其当前弧优化归零
        }
    }
}
int main()
{
    // freopen("P3959_22.in", "r", stdin);
    // freopen("", "w", stdout);
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int ww = 0; ww <= n + 1; ww++)
    {
        for (int ee = 0; ee <= n + 1; ee++)
        {
            dis[ww][ee] = inf;
        }
        dis[ww][ww]=0;
        du[ww] = 0;
    }
    int a, b, c;
    for (int yy = 1; yy <= m; yy++)
    {
        cin >> a >> b >> c;
        if (dis[a][b] == inf && c < inf)
        {
            du[a]++;
            du[b]++;
            to_[a][du[a]] = b;
            to_[b][du[b]] = a;
        }
        dis[b][a] = min(dis[b][a], c);
        dis[a][b] = min(dis[a][b], c);
    }

    for (int yy = 1; yy <= n; yy++)
    {
        cmm = yy;
        sort(to_[yy] + 1, to_[yy] + 1 + du[yy], cmp);//排序,降低复杂度,因为越短的边成为答案的概率越大
        int ww = inf;
        for (int ee = 1; ee <= n; ee++)//找到最小的出度(其实这里可以直接用上排序后的结果)
        {
            if (yy != ee)
            {
                ww = min(ww, dis[yy][ee]);
            }
        }
        short_ += ww;
        sho[yy] = ww;
    }
    for (int ww = 1; ww <= n; ww++) //枚举起点
    {
        tmp = 0;
        nds[ww].step = 1;
        short_ -= sho[ww];
        dd[1] = ww;
        dfs(1,1);
        dd[1] = 0;
        short_ += sho[ww];
        nds[ww].step = 0;
        // cout << ans << "\n";
    }
    cout << ans;
    return 0;
}

提交记录:

posted @ 2024-10-11 18:13  sea-and-sky  阅读(13)  评论(0编辑  收藏  举报