首页 > 其他分享 >P3959 [NOIP2017 提高组] 宝藏 题解

P3959 [NOIP2017 提高组] 宝藏 题解

时间:2024-10-11 18:10:58浏览次数:10  
标签:NOIP2017 int 题解 yy P3959 100 du inf dis

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

提交记录:

标签:NOIP2017,int,题解,yy,P3959,100,du,inf,dis
From: https://www.cnblogs.com/sea-and-sky/p/18459032

相关文章

  • 有关 OneDrive 中 Copilot 的常见问题解答
    很多小伙伴已经用上了OneDrive中的Copilot功能:Copilot重磅更新!OneDrive全新功能炸裂一个技巧实现在SharePoint中使用Copilot针对大家提问比较多的问题,在此做一个汇总:1、OneDrive中的Copilot目前在哪里可用?OneDrive中的Copilot目前在 OneDriveWeb上仅适用于商......
  • P5547 [BJ United Round #3] 三色树 题解
    Description请你对满足以下要求的\(n\)个节点的无标号无根树计数:每个节点是三种颜色之一:红,蓝,黄红色节点度数不超过\(4\),蓝色和黄色节点度数均不超过\(3\)黄色节点不能相邻注意无标号无根树的意义是:如果两颗树可以通过重新编号的方法使得对应点颜色相同,对应连边一致......
  • 颠倒原理题解
    颠倒原理/reverse时间限制:1000ms空间限制:512MB题目描述\(GreenDuck\)想学习转置原理,但由于它太难了,因此他转而学习更为简单的和图的染色有密切联系的“颠倒原理”\((reverseprinciple)\)。颠倒原理中有个重要的操作叫做“颠倒操作”。对于一个无向连通图\(G\),其节点要么......
  • 数据结构题解报告
    [GDOI2016]疯狂动物城对于大多树上区间问题往往加个树剖就能变成普通区间问题,只是说复杂度会加个\(log\),出题人这么做的理由可能是想锻炼一下评测姬吧选手的码力吧。而强制在线只需要可持久化数据结构即可。本题同理可视作区间问题用线段树维护,考虑推式子降次以便于维护\(ans......
  • [AGC054D] (ox) 题解
    感觉看到交换就应该要想到逆序对。思路一个前置小知识,我们把一个排列用相邻交换复原的最小次数是逆序对数量。考虑没有ox的情况。我们顺着扫一遍字符串。把左括号正一,右括号看作负一,当前缀和小于零以后,我们把后面最近的左括号提过来,这样可以构造出交换次数最少的合法括号串......
  • 【学校训练记录】十月个人训练赛1题解
    A只需按照题目意思扩展h倍即可,先记录初始字符,打印时扩展为2*h根据题目公式打印`include<bits/stdc++.h>defineintlonglongusingnamespacestd;constintMAXN=100005;intn;inta[MAXN];charmp[105][105];signedmain(){inth,w;cin>>h>>w;for(inti=......
  • CF959F Mahmoud and Ehab and yet another xor task 题解
    题目传送门前置知识线性基解法将操作离线下来,并按\(\{l\}\)升序排序,接着顺序插入线性基并处理询问。对于未成功插入线性基的元素\(k\)一定能被线性基内选出若干元素得到。故在\(x\)能被表出的情况下,设\(1\siml\)中成功插入线性基的元素个数为\(siz\),对于剩下\(......
  • IEEE全球极限编程大赛10.0题目题解:给出数字N,A,B,求出A,B之间与N互质的数的和(数据范围大)
    题目题目来源第10届IEEE极限编程大赛https://www.hackerrank.com/contests/ieeextreme-challenges/challenges/inti-setsInordertomotivatehisPeruvianstudents,ateacherincludeswordsintheQuechualanguageinhismathclass.Today,hedefinedacurious......
  • AT_abc283_g [ABC283G] Partial Xor Enumeration 题解
    题目传送门前置知识线性基解法考虑线性基。因为有可空子序列也参与运算,所以第\(1\)小的数是\(0\);但线性基中是不允许有异或和为\(0\)的存在,故线性空间内第\(k-1\)小的数就是所求的第\(k\)小的数。令每一个二进制位仅在它这一位的基底上出现,其他位上的基底直接异或......
  • Codeforces Round 972 (Div. 2)题解记录
    A.SimplePalindromeaeiou,如果这里后面+u则会多出2,+o则会多3,通过分析加相同的字母比加之前存在的不同字母赚发现同一个太多了,又会增太大,遂平均分配,使增多幅度上升的缓慢#include<bits/stdc++.h>usingnamespacestd;typedeflonglongll;llgcd(llx,lly){ if(y==0) r......