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\n", 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 \neq 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 \times 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;
}
posted @ 2020-11-29 21:21  Nyxia  阅读(110)  评论(0编辑  收藏  举报