AcWing Round #13

A. 排列

题目链接


请你构造一个长度为 n n n 的数组 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an

要求:

  • 该数组是一个 1 ∼ n 1\sim n 1n 的排列。
  • 对于所有 1 ≤ i ≤ n 1\leq i\leq n 1in,满足 a i ≠ i a_i\neq i ai=i
输入格式

第一行包含整数 T T T,表示共有 T T T 组测试数据。

每组数据占一行,包含一个整数 n n n

输出格式

每组数据输出一行结果,包含 n n n 个空格隔开的整数 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an

如果方案不唯一,输出任意合理方案均可。

保证一定有解。

数据范围

本题共两个测试点。
小测试点,如样例所示。
大测试点满足: 1 ≤ T ≤ 100 1\leq T\leq 100 1T100 2 ≤ n ≤ 100 2\leq n\leq 100 2n100

输入样例:
2
2
5
输出样例:
2 1
2 1 5 3 4

依次后移一位即可,这玩意 WA 了五发,废了废了。

Code

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int main()
{
    int T;
    cin >> T;
    while (T -- )
    {
        int n;
        cin >> n;
        for (int i = 0; i < n; i ++ )
            cout << (i + 1) % n + 1 << ' ';
        cout << endl;
    }
    return 0;
}

B.机器人走迷宫

题目链接


有一个 n × m n\times m n×m 个单元格构成的迷宫,其中空单元格用.表示,障碍物用#表示。

迷宫中有一个机器人,它的起点位置用S表示,目标位置用E表示,这两个地点均没有障碍。

机器人只能沿上下左右四个方向移动。

给定一串由数字 0 ∼ 3 0\sim 3 03 构成的字符串,表示机器人的行动指令列表。

机器人将按照列表中的指令,依次进行移动。

在执行指令的过程中:

如果机器人走出迷宫边界或者碰到障碍物,则机器人会损坏。
如果机器人到达目标位置,则停止行动,不再接受后续指令。
现在,哪个数字 ( 0 ∼ 3 ) (0\sim 3) (03) 对应哪种行动(上下左右)还未分配。

请问,共有多少种分配方案,能够使得机器人顺利到达目标位置。

输入格式

第一行包含整数 T T T,表示共有 T T T 组测试数据。

每组数据第一行包含两个整数 n n n m m m

接下来 n n n 行,每行包含 m m m 个字符,表示迷宫。所有字符均为 ., #, S, E之一,其中 SE出现且仅出现一次。

最后一行包含一个字符串 s s s 表示指令列表,每个字符均为 0 ∼ 3 0\sim 3 03 之一。

输出格式

每组数据输出一行结果,表示能够使得机器人顺利到达目标位置的行动指令分配方案数量。

数据范围

前三个测试点满足 2 ≤ n , m ≤ 10 2\leq n,m\leq 10 2n,m10
所有测试点满足 1 ≤ T ≤ 10 1\leq T\leq 10 1T10 2 ≤ n , m ≤ 50 2\leq n,m\leq 50 2n,m50 1 ≤ ∣ s ∣ ≤ 100 1\leq |s|\leq 100 1s100
同一测试点内,所有 n × m n\times m n×m 的和不超过 2500 2500 2500

输入样例:
2
5 6
.....#
S....#
.#....
.#....
...E..
333300012
6 6
......
......
..SE..
......
......
......
01232123212302123021
输出样例:
1
14

算法:

枚举 + 全排列

判断机器人是否能够到达终点不难,思路同经典的 BFS 最短路模型判断一致、问题在于如何全排列方向。

原始的(上下左右)用0, 1, 2, 3表示,可以构造一个数组a[]a[0]表示(上),a[1]表示下……。这样我们只需要将这个下标进行全排列即可,可以用 STL 内的next_permutation函数全排列a[]的元素。

对于给定的指令串path,我们用path[i] - '0'的方式索引对应的方向即可。

Code

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 55;

int n, m;
char g[N][N];
string path;

bool check(vector<int> &q)
{
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    
    int x, y;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            if (g[i][j] == 'S')
                x = i, y = j;
    
    for (auto c : path)
    {
        int t = q[c - '0'];
        x += dx[t], y += dy[t];
        if (x < 0 || x >= n || y < 0 || y >= m || g[x][y] == '#') 
            return false;
        if (g[x][y] == 'E') return true;
    }
    return false;
}

int main()
{
    int T;
    cin >> T;
    while (T -- )
    {
        cin >> n >> m;
        for (int i = 0; i < n; i ++ )   cin >> g[i];
        cin >> path;
        int res = 0;
        vector<int> q{0, 1, 2, 3};
        for (int i = 0; i < 24; i ++ )
        {
            if (check(q)) res ++ ;
            next_permutation(q.begin(), q.end());
        }
        cout << res << endl;
    }
    return 0;
}

C.最大路径权值

题目链接


给定一个 n n n 个点 m m m 条边的有向图。

图中可能包含重边和自环,也可能不连通。

给每个点分配一个小写字母。

我们定义一条路径的权值为出现频率最高的字母的出现次数。

例如,如果一条路径上的字母是 abaca,则该路径的权值为 3。

请你找到给定图中权值最大的路径,输出这个最大路径权值。

输入格式

第一行包含两个整数 n n n m m m

第二行包含一个由小写字母构成的字符串 s s s,其中第 i i i 个字母表示第 i i i 个点上的字母。

接下来 m m m 行,每行包含两个整数 x , y x,y x,y 表示存在一条从点 x x x 到点 y y y 的边。

所有点的编号为 1 ∼ n 1\sim n 1n

输出格式

输出最大路径权值。

如果这个权值是无穷大,则输出 − 1 −1 1

数据范围

前三个测试点满足 1 ≤ n , m ≤ 10 1\leq n,m\leq 10 1n,m10
所有测试点满足 1 ≤ n , m ≤ 3 × 105 , 1 ≤ x , y ≤ n 1\leq n,m\leq 3\times 105,1\leq x,y\leq n 1n,m3×1051x,yn

输入样例1:
5 4
abaca
1 2
1 3
3 4
4 5
输出样例1:
3
输入样例2:
6 6
xzyabc
1 2
3 1
2 3
5 4
4 3
6 4
输出样例2:
-1
前置知识:

1、拓扑图和拓扑排序

2、DAG 图定义:如果一个有向图无法从任意顶点出发经过若干条边回到该点,则这个图就是有向无环图。所以一个 DAG 图是没有自环回路的。

题目分析:

如果题目中存在自环回路,环上所有点的字母都会出现无穷多次,就不可能存在出现次数最多的情况。

若没有自环和回路,路径长度一定是有限值,一定有解。

路径长度最大为 n n n

如何判断有无环存在:

常用方法:

1、Topsort

2、有向图的强连通分量

如果不存在环,怎么求最长路径?

每个点会分配一个小写字母,出现次数最多的字母,一共只有26种,而每个点的权值只有 26 26 26 种,可以考虑枚举每个字母为最大值的情况,最后求一个max

a为例

如果一个点的字母是a,则点权为 1 1 1,否则为 0 0 0

a - > b - > a -> c -> a
1 ->  0  -> 1 -> 0 -> 1

即在这个拓扑图上求最长路,可以用递推DP 来求,

对于已经求好的拓扑序列,倒序使用递推求最长路即可。

这里稍微解释一下为什么可以这么做:

首先要理解一下拓扑序列的含义,这里我们可以使用拓扑排序的方式来判断该图是否是 DAG 图,如果是,则队列q[]中存放的就是一个拓扑序列,那么我们可以从后往前倒推,用闫氏 DP 分析法来分析:

闫氏 DP 分析法:

状态表示(在拓扑序列中):

集合: f ( t ) f(t) f(t) 表示所有以 t t t 为起点的路径构成的集合
属性:集合中所包含路径长度的最大值

状态计算:

f ( t ) = m a x ( f ( j ) + w t → j ) ,   j ∈ A f(t) = max(f(j) + w_{t\rightarrow j}),\ j\in A f(t)=max(f(j)+wtj), jA,其中 A A A 表示点 t t t 的所有邻边构成的集合。

这样,我们在拓扑序列中从后往前递推,就可以算出权值最大的路径最大路径权值了。

时间复杂度

拓扑排序预处理 + 26 × n 26\times n 26×n,每次递推时间复杂度为 O ( n ) O(n) O(n),故总时间复杂度为 O ( 26 ⋅ n ) O(26\cdot n) O(26n)

Code

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 300010, M = N;

int n, m;
char w[N];
int h[N], e[M], ne[M], idx;
int q[N], d[N];
int f[N]; // 每个点的最长路

void add(int a, int b)  
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

bool topsort()
{
    int hh = 0, tt = -1;
    for (int i = 1; i <= n; i ++ )
        if (!d[i]) 
            q[ ++ tt] = i;
    
    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            -- d[j];
            if (!d[j]) 
                q[ ++ tt] = j;
        }
    }
    return tt == n - 1;
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    scanf("%s", w + 1); // 读入每个点的字母
    
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        ++ d[b];
    }
    
    if (!topsort()) puts("-1");
    else
    {
        int res = 0; // 答案,最大值
        for (char c = 'a'; c <= 'z'; c ++ )
            for (int i = n - 1; i >= 0; i -- )
            {
                // q[i]为当前点
                int t = q[i]; 
                // 看一下当前这个点权值为多少。
                int v = w[t] == c;
                // f[t] 表示以t为起点的长度的最大值
                f[t] = v;
                // t不一定有后继,如果有后继,则遍历所有t的出边
                for (int j = h[t]; ~j; j = ne[j])
                {
                    int k = e[j];
                    f[t] = max(f[t], v + f[k]);
                }
                res = max(res, f[t]);
            }
        printf("%d\n", res);
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值