• 11/29 NOIP 模拟赛


    总结:

    T1 升降梯上(updown),考场 AC

    T2 重叠的图像(Frame Up),读错题了,惨爆 44pts

    T3 小奇回地球(earth),hzwer 大佬的神仙题,完全不会做,但找不到 OJ 能测...

    T4 道路(road),最小生成树板子。然而刚开始没看出来,浪费了很长时间。好在最后 AC 了

    T1 升降梯上(updown)

    题目描述

    有一个 (N) 层的塔,升降梯在每层都有一个停靠点。手柄有 (M) 个控制槽,第 (i) 个控制槽旁边标着一个数 (C_i),满足 (C_1<C_2<C_3<...<C_M)。如果 (C_i>0),表示手柄扳动到该槽时,电梯将上升 (C_i) 层;如果 (C_i<0),表示手柄扳动到该槽时,电梯将下降 (-C_i) 层:并且一定存在一个 (C_i=0),手柄最初就位于此槽中。注意升降梯只能在 (1-N) 层之间移动

    电梯每移动 (1) 层,需要花费 (2) 秒钟时间,而手柄从一个控制槽扳到相邻的槽,需要花费 (1) 秒钟时间。探险队员现在在 (1) 层,并且想尽快到达 (N) 层,他们想知道 (1) 层到 (N) 层至少需要多长时间?

    输入格式

    第一行两个正整数 (N), (M)

    第二行 (M) 个整数 (C_1, C_2, C_3, ..., C_m)

    输出格式

    输出一个整数表示答案,即至少需要多长时间。若不可能到达输出 (-1)

    数据范围

    对于 (30\%) 的数据,满足 (1 leq N leq 10, space 2 leq M leq 5)

    对于 (100\%) 的数据,满足 (1 leq N leq 1000, space 2 leq M leq 20, space -N<C_1<C_2<...<C_M<N)

    Solution

    由一个普通的 (mathrm{dijkstra}) 稍加更改得到。因为手柄的位置也应该关心,所以记 (mathrm{vis}) 时也应该标记当前手柄位置。那么入队条件变成了更新最短路径手柄位置未标记。相应的,当前的步数也应该入队。

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    #include <cmath>
    #define LL long long
    using namespace std;
    
    const int N = 233333;
    LL n, m, st = 0, c[N], dis[N], vis[N], use[N][30];
    struct node //dijkstra 板子重载运算符,按照 dis 从小到大排序
    {
        LL x, dis, now;
        friend bool operator < (node a, node b)
        {
            return a.dis > b.dis;
        }
    };
    
    LL dijkstra()
    {
        priority_queue <node> q;
        memset(use, 0, sizeof(use));
        memset(dis, 0x7f7f7f, sizeof(dis));
        memset(vis, 0, sizeof(vis));
        q.push((node) { 1, 0, st });
        dis[1] = 0, use[1][st] = 1;
        while(!q.empty())
        {
            node tmp = q.top();
            q.pop();
            if(vis[tmp.x]) continue;
            vis[tmp.x] = 1;
            for(int i = 1; i <= m; i++)
            {
                if(c[i] == 0) continue;
                int len = abs(tmp.now - i) + (abs(c[i])) * 2, nxt = tmp.x + c[i]; //计算代价
                if(nxt > n || nxt < 1) continue; //判断越界
                if(len + tmp.dis < dis[nxt] || !use[nxt][i])
                {
                    dis[nxt] = min(dis[nxt], len + tmp.dis);
                    use[nxt][i] = 1;
                    if(!vis[nxt]) q.push((node) { nxt, len + tmp.dis, i });
                }
            }
        }
        if(dis[n] == dis[0]) return -1;
        return dis[n];
    }
    
    int main()
    {
        freopen("updown.in", "r", stdin);
        freopen("updown.out", "w", stdout); 
        scanf("%lld%lld", &n, &m);
        for(int i = 1; i <= m; i++)
        {
            scanf("%lld", &c[i]);
            if(c[i] == 0) st = i;
        }
        printf("%lld
    ", dijkstra());
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    

    T2 重叠的图像(frame)

    题面都在上面的链接里了...

    Solution

    和容易想到暴枚 (26!),很明显会 TLE(但也能拿很多分啊喂)

    考虑加一些限制条件,形如 (A) 的图像在 (B) 的图像上方。这东西有什么用呢?如果满足 (A)(B) 上面就从 (B) 连一条边到 (A),那么从下往上放图像的过程中,只有放了 (B) 才能放 (A).

    是不是很熟悉?这就是拓扑排序的过程!每次从队列里取出一个元素,这个元素是不确定的,这就造就了此题的多解。然而麻烦就麻烦再这里。因为多解,所以需要 (mathrm{dfs}) 暴力枚举所有可能情况,然后发现 (mathrm{stl}) 又不好用了,只能手写队列拓扑。细节贼多,都在代码里。

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int N = 2333;
    char a[N][N];
    int n, m, Num = 0, tot = 0, res = 0;
    int vis[N], lx[N], rx[N], ly[N], ry[N], e[N][N];
    int q[N], du[N], use[N];
    string ans[2333333]; //答案数组一定要开足够大!
    
    void dfs(int num, int stp)
    {
        if(stp == Num)
        {
            res++;
            ans[res] = "";
            for(int i = 1; i <= stp; i++) ans[res] += (char)(use[i]);
            return ;
        }
        //这里不能对 q 排序,要不然 dfs 回溯就乱套了
        for(int i = 1; i <= num; i++) //取第 i 个元素
        {
            int x = q[i], cnt = num - 1;
            for(int j = i; j < num; j++) q[j] = q[j + 1];
            for(int j = 'A'; j <= 'Z'; j++)
            {
                if(vis[j] && e[x][j])
                {
                    du[j]--;
                    if(du[j] == 0) q[++cnt] = j;
                }
            }
            use[stp + 1] = x;
            dfs(cnt, stp + 1);
            //回溯
            for(int j = num; j > i; j--) q[j] = q[j - 1];
            q[i] = x;
            for(int j = 'A'; j <= 'Z'; j++)
                if(vis[j] && e[x][j]) du[j]++;
        }
    }
    
    int main()
    {
        scanf("%d%d", &n, &m);
        memset(e, 0, sizeof(e));
        memset(lx, 0x3f, sizeof(lx));
        memset(ly, 0x3f, sizeof(ly));
        memset(vis, 0, sizeof(vis));
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
            {
                cin >> a[i][j];
                if(a[i][j] >= 'A' && a[i][j] <= 'Z')
                {
                    if(!vis[a[i][j]])
                    {
                        vis[a[i][j]] = 1;
                        Num++;
                    }
                    //标记每个字母对应的矩阵
                    lx[a[i][j]] = min(lx[a[i][j]], i);
                    ly[a[i][j]] = min(ly[a[i][j]], j);
                    rx[a[i][j]] = max(rx[a[i][j]], i);
                    ry[a[i][j]] = max(ry[a[i][j]], j);
                }
            }
        for(int i = 'A'; i <= 'Z'; i++) //添加限制条件
        {
            if(!vis[i]) continue;
            for(int j = ly[i]; j <= ry[i]; j++)
            {
                char ch1 = a[lx[i]][j], ch2 = a[rx[i]][j];
                if(vis[ch1] && ch1 != i) e[i][ch1] = 1;
                if(vis[ch2] && ch2 != i) e[i][ch2] = 1;
            }
            for(int j = lx[i]; j <= rx[i]; j++)
            {
                char ch1 = a[j][ly[i]], ch2 = a[j][ry[i]];
                if(vis[ch1] && ch1 != i) e[i][ch1] = 1;
                if(vis[ch2] && ch2 != i) e[i][ch2] = 1;
            }
        }
        memset(du, 0, sizeof(du));
        for(int i = 'A'; i <= 'Z'; i++)
        {
            if(!vis[i]) continue;
            for(int j = 'A'; j <= 'Z'; j++)
            {
                if(!vis[j]) continue;
                if(e[i][j] == 1) du[j]++;
            }
        }
        for(int i = 'A'; i <= 'Z'; i++)
        {
            if(!vis[i]) continue;
            if(du[i] == 0) //入队要全!否则可能搜不出解
            {
                tot++;
                q[tot] = i;
            }
        }
        dfs(tot, 0);
        sort(ans + 1, ans + res + 1); //按照字典序从小到大输出
        for(int i = 1; i <= res; i++) cout << ans[i] << endl;
        return 0;
    }
    

    T3 小奇回地球(earth)

    啊这题完全不会呢...放上大佬 (mathrm{hzwer})题解

    考场代码也放不了了,因为考场上根本就没写代码 /dk

    T4 道路(raod)/ 安慰奶牛

    题目描述

    (mathrm{John}) 变得非常懒,他不想再继续维护供奶牛之间供通行的道路。

    道路被用来连接 (N(5 leq N leq 10000)) 个牧场,牧场被连续地编号为 (1..N. space)每一个牧场都是一个奶牛的家。

    (mathrm{FJ}) 计划除去 (P(N-1 leq P leq 100,000)) 条道路中尽可能多的道路, 但是还要保持牧场之间的连通性。你首先要决定那些道路是需要保留的 (N-1) 条道路。第 (j) 条双向道路连接了牧场 (S_j)(E_j(1 leq S_j leq N, space 1 leq E_j leq N, space S_j eq E_j)),而且走完它需要 (L_j (0 leq L_j leq 1,000)) 的时间。没有两个牧场是被一条以上的道路所连接。

    奶牛们非常伤心,因为她们的交通系统被削减了. 你需要到每一个奶牛的住处去安慰她们. 每次 你到达第i个牧场的时候(即使你已经到过),你必须花去 (C_i (1 leq C_i leq 1000)) 的时间和奶牛交谈。你每个晚上都会在同一个牧场(这是供你选择的)过夜,直到奶牛们都从悲伤中缓过神来。在早上起来和晚上回去睡觉的时候,你都需要和在你睡觉的牧场的奶牛交谈一次。这样你才能完成你的交谈任务。

    假设 (mathrm{Farmer John}) 采纳了你的建议,请计算出使所有奶牛都被安慰的最少时间。

    输入格式

    (1) 行,用空格隔开的两个整数 (N)(P)

    (2-N+1) 行,第 (i+1) 行包含了 (1) 个整数:(C_i)

    (N+2-N+P+1) 行,第 (N+j+1) 行包含用空格隔开的三个整数: (S_j, space E_j, space L_j)

    输出格式

    (1) 行,一个整数,所需要的总时间(包含和在你所在的牧场的奶牛的两次谈话时间)

    Solution

    才开始以为是个最小生成树 + 树形dp,写完发现被 (mathrm{Hack}) 了。仔细一看原来是个最小生成树板子题...这题真是人均切啊,不得不 (mathrm{orz}) 各位巨佬 %%%

    除根节点外,每个点被访问的次数 (=) 它儿子的个数 (+1). 根节点还会被额外访问 (1) 次。

    稍微画几个图比较一下,可以得出每条边只会经过两次,即进子树一次,出子树一次。

    综上,一条权值为 (w) 的边(设其连接的两个点编号为 (a, space b))对答案的贡献就是 (w imes 2 + C_a + C_b). 直接以贡献为权值最小生成树即可。

    最后还要选一个根节点加上,显然选花费最小的。

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define LL long long
    using namespace std;
    
    const int N = 2333333;
    struct edge { LL a, b, c; } e[N];
    LL n, m, Sum = 0, cnt = 0, ans = 1e18, c[N], f[N], head[N];
    
    bool cmp(edge a, edge b) { return a.c < b.c; }
    LL find(LL x) { return f[x] == x ? x : f[x] = find(f[x]); }
    
    int main()
    {
        freopen("road.in", "r", stdin);
        freopen("road.out", "w", stdout);
        scanf("%lld%lld", &n, &m);
        memset(head, 0, sizeof(head));
        for(int i = 1; i <= n; i++) scanf("%lld", &c[i]), f[i] = i;
        for(int i = 1; i <= m; i++)
        {
            scanf("%lld%lld%lld", &e[i].a, &e[i].b, &e[i].c);
            e[i].c = e[i].c * 2 + c[e[i].a] + c[e[i].b]; //根据分析修改权值
        }
        sort(e + 1, e + m + 1, cmp); //最小生成树板子
        for(int i = 1; i <= m; i++)
        {
            LL fa = find(e[i].a), fb = find(e[i].b);
            if(fa != fb) f[fa] = fb, Sum += e[i].c;
        }
        //找一个权值最小的点加上
        for(int i = 1; i <= n; i++) ans = min(ans, c[i]);
        printf("%lld", ans + Sum);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    
  • 相关阅读:
    在一个页面用Ajax访问另一个页面弹出询问框怎么办
    还有一张呢
    系统相关信息查看
    linux dd 命令使用说明
    Github 使用说明
    Hadoop HA 资料
    fedora 在virtualbox安装guest additions
    git 使用记录
    linux console 小技巧
    Agent admitted failure to sign using the key 错误及解决方法
  • 原文地址:https://www.cnblogs.com/Andy-park/p/14067023.html
Copyright © 2020-2023  润新知