【解题报告】Codeforces Round #354 (Div. 2)

题目链接


A. Nicholas and Permutation(Codeforces 676A)

思路

显然,将最大值或最小值中的一个交换到数组的某一端(若不放在端点上,则放在更靠近端点的位置总能得到更优的解)能让最大值与最小值之间的距离最大。我们可以通过计算计算出最优的方法。但是因为只有四种组合(最小值最左,最小值最右,最大值最左,最大值最右),所以可以直接枚举组合,并更新最优解。

代码
#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn = 110;
int n, b, c, minIdx, maxIdx, a[maxn];

int abs(int x) {
    return x > 0 ? x : -x;
}

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        if(a[i] == 1) {
            minIdx = i;
        }
        if(a[i] == n) {
            maxIdx = i;
        }
    }
    b = max(abs(minIdx - n), abs(minIdx - 1));
    c = max(abs(maxIdx - n), abs(maxIdx - 1));
    printf("%d\n", max(b, c));
    return 0;
}

B.Pyramid of Glasses(Codeforces 676B)

思路

因为 n10 ,所以酒杯的数量 num55 (等差数列求和)。这样的数量足以让我们用暴力的方法解决此题。首先最暴力的办法是每次往最顶端的酒杯倒一杯酒,然后就循环或递归访问每个酒杯,以计算出每个酒杯的酒的余量。其次比较不那么暴力的方法是一次性把所有的酒倒进最顶端的酒杯中,然后也是循环或递归访问每个酒杯,以计算出每个酒杯的余量。显然,相比之下,不论在时间复杂度还是在实现复杂度上,后者会优于前者。但前者更容易被想到。

代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 15;
const double eps = 1e-10;
int n, t, cnt;
double h[maxn][maxn];

int cmp(double x) {
    return x < - eps ? -1 : x > eps;
}

void dfs(int x, int y, double come) {
    if(x > n) {
        return;
    }
    if(cmp(come) == 0) {
        return;
    }
    if(cmp(h[x][y] - 1) == 0) {
        dfs(x + 1, y, come / 2);
        dfs(x + 1, y + 1, come / 2);        
    }
    else {
        if(cmp(h[x][y] + come - 1) < 0) {
            h[x][y] += come;
        }
        else {
            int to = h[x][y] + come - 1;
            cnt++;
            h[x][y] = 1;
            dfs(x + 1, y, to / 2);
            dfs(x + 1, y + 1, to / 2);
        }
    }
}

int main() {
    scanf("%d%d", &n, &t);
    cnt = 0;
    while(t--) {
        dfs(1, 1, 1.0);
    }
    printf("%d\n", cnt);
    return 0;
}

C. Vasya and String(Codeforces 676C)

思路

刚开始一直往动态规划那方面想,所以浪费了不少时间。但实际上本题从“枚举子串”的方面想才是对的(看来凡事还是先上暴力思维会比较不容易走偏)。我们可以先以 O(n2) 的复杂度枚举出子串,然后以 O(n) 的复杂度计算出该子串是否能通过 k 次“改变”变成字符统一的字符串。在枚举中更新满足条件的子串的最大长度即可。但是 O(n3) 的总复杂度是不发承受的,那么我们能不能仅 O(n) 地扫描一遍字符串就完成上述全部工作呢?事实上是可以的。假如 s 的子串 s[p..q] 是满足条件的字符串,而 s[p..q+1] 是不满足条件的字符串,那么下一步应当是令 p=p+1 q=p (枚举以新的 p 开头的子串)。实际上,这样使得集合 {s[p+1..j],p+1jq} 中的全部字符串都重复枚举了(因为不可能得到更优解)。也就是说尾指针 q 的回溯是没必要的。这种利用无法达到最优解的特性避免回溯尾指针的枚举子串的方法就是“尺取法”(名字取自《挑战程序设计竞赛》)。这样我们用 O(n) 的复杂度枚举子串,然后在头尾指针移动的过程中动态更新子串中 a 的数量 sa b 的数量 sb (因为“尺取法”有每次只用让指针移动一个字符),根据 min(sa,sb) k 的关系来判断子串是否满足条件并更新答案,题目得解。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 5;
char s[maxn];
int n, k, l, r, sa, sb, ans = 0;

int main() {
    scanf("%d%d%s", &n, &k, s);
    sa = (s[l=0] == 'a');
    sb = (s[r=0] == 'b');
    while(r < n) {
        if(sa <= k || sb <= k) {
            ans = max(ans, r - l + 1);
        }
        else {
            sa -= (s[l] == 'a');
            sb -= (s[l++] == 'b');
        }
        sa += (s[++r] == 'a');
        sb += (s[r] == 'b');        
    }
    printf("%d\n", ans);
    return 0;
}

D. Theseus and labyrinth(Codeforces 676D)

思路

若没有方块的旋转,则这个问题就是简单的用 BFS 求最短步数的问题。现在有了旋转,相当于总共有 4 n×m 的网格,分别对应着原来的网格不旋转 (s=0) ,旋转 1 (s=1) ,旋转 2 (s=2) 和旋转 3 (s=3) 的情况。那么我们可以将这 4 个网格看成三维的网格,也就是说可以将状态空间看成 n×m×4 的三维空间。那么主人公处在某个状态 (x,y,s) 的时候可以向五个方向移动,分别为上 (x1,y,s) ,下 (x+1,y,s) ,左 (x,y1,s) ,右 (x,y+1,s) 和旋转1次 (x,y,(s+1)mod4) ,而且每次移动的代价都是相同的(等待 1 秒)。设计好状态以后在三维空间内用 BFS 求最短步数即可。另外还有一个小问题,就是如何简洁而快速地判断是否能向某个方向移动。我们可以建立一个映射 b[u]=v ,表示当方块里的内容为 u 时,向上右下左(不是上下左右)四个方向移动是否可行(将四个方向的信息压缩成整数 v ),为此我们要将上右下左四个方向分别编号为 0,1,2,3 。例如 b[+]=15 (二进制表示为 1111 )表示当方块的内容为 + 时上右下左四个方向都能移动。 b[>]=1<<2 (二进制表示为 0010 )表示只能向右移动,为什么要向左移动2位呢?因为“右”的编号为 2 <script id="MathJax-Element-440" type="math/tex">2</script> 。这样,知道了方方块内的内容和移动的方向就能知道向这个方向移动是否可行。

代码
#include <bits/stdc++.h>
using namespace std;

// 搜索树中的节点
struct node {
    int x, y, s, d;
    node(int x, int y, int s, int d): x(x), y(y), s(s), d(d) {}
};

const int maxn = 1010;
// 上右下左四个方向的坐标变化 
const int dx[] = {-1, 0, 1, 0};
const int dy[] = {0, 1, 0, -1};
// 判重用的数组
bool vis[maxn][maxn][5];
char G[maxn][maxn];
int n, m, sx, sy, tx, ty, b[150];
queue <node> q;

// 初始化映射
void init() {
    b['^'] = 1 << 0;
    b['>'] = 1 << 1;
    b['v'] = 1 << 2;
    b['<'] = 1 << 3;
    b['L'] = ~b['<'];
    b['R'] = ~b['>'];
    b['U'] = ~b['^'];
    b['D'] = ~b['v'];
    b['-'] =  b['<'] | b['>'];
    b['|'] =  b['^'] | b['v'];
    b['+'] =  b['|'] | b['-'];
}

// 状态的转移
void expand(node& u, int dir) {
    int x, y, s, d, i, j;
    if(dir == -1) {
        x = u.x;
        y = u.y;
        s = (u.s + 1) % 4;
        d = u.d + 1;
        if(vis[x][y][s]) {
            return;
        }
        q.push(node(x, y, s, d));
        vis[x][y][s] = true;
    }
    else {
        // 判断本方块往dir方向是否有通路
        i = b[G[u.x][u.y]];
        j = 1 << ((dir - u.s + 4) % 4);
        if((i & j) == 0) {
            return;
        }
        x = u.x + dx[dir];
        y = u.y + dy[dir];
        s = u.s;
        d = u.d + 1;
        // 防止访问越界
        if(x < 1 || x > n || y < 1 || y > m) {
            return;
        }
        // BFS的判重
        if(vis[x][y][s]) {
            return;
        }
        // 判断新方块的dir方向的反方向是否有通路
        i = b[G[x][y]];
        j = 1 << ((dir + 2 - s + 4) % 4);
        if((i & j) == 0) {
            return;
        }
        q.push(node(x, y, s, d));
        vis[x][y][s] = true;
    }
} 

int bfs(node s) {
    q.push(s);
    while(!q.empty()) {
        node u = q.front();
        q.pop();
        if(u.x == tx && u.y == ty) {
            return u.d;
        }
        // 共有5种方式向BFS的队列加入新的节点
        // 0, 1, 2, 3分别表示上右下左4个方向 
        // -1表示所有的方块顺时针方向旋转90度
        for(int i = -1; i < 4; i++) {
            expand(u, i);
        }
    }
    return -1;
}

int main() {
    init();
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%s", G[i] + 1);
    }
    scanf("%d%d%d%d", &sx, &sy, &tx, &ty);
    vis[sx][sy][0] = true;
    printf("%d\n", bfs(node(sx, sy, 0, 0)));
    return 0;
}

(其它题目略)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值