Educational Codeforces Round 118 (Rated for Div. 2) ABCDE题解

A
问:
数比大小

解:
先把数变成 个位数+n个0
注意小数失精

B
问:
给一串数,让选若干对数字a和b,俩数都出现过,而且a%b没有出现,数可以重复选

解:
因为数可以重复,选最小的为b,任意一个数mod b一定都没出现

C
问:
屠龙游戏,给龙的血量和刀的属性,和攻击的次数,每次攻击龙会持续k时间掉血,攻击后属性重置。
每次攻击间隔相减判断和k的大小即可,注意最后一次一定是掉了k血。
求最小的k。

解:
因为越大一定越容易成功,考虑二分答案,O(n)check,总复杂度为O(nlogn)
D
问:

MEX(x1…xk)是 x1~xk中未出现的最小非负整数
如果一个序列被认为是MEX-CORRECT 的 那么
|xi-MEX(x1…xi)|<=1
(如果 i=1 就满足 |x1-MEX(x1)|<=1,如果i=2 要满足|x1-MEX(x1)|<=1和 |x2-MEX(x1,x2)|<=1)
现在 给出n个数 问一共有多少 MEX-CORRECT的序列

思:
显然是个DP的题,看眼数据,O(n)的dp,
先看题意,模拟一下发现只有两种数列。
①单调递增的
0 1 1 2 3 3 3 4
②中间调了一个数
0 1 2 2 3 3 3 5 3 5 5 5 3 3 5 3
不难发现,这种数列,前面是个①数列,直到加入了一个比最后一个数Xn大2的数,然后这个数列就只能选Xn或者Xn+2了

dp[i][0]表示结尾为i的第一种数列的所有方案数。

dp[x][1]表示最大值为x的第二种数列的所有方案数(已经存在x和x-2了)。

考虑如何转移 和 初始状态
转移:
首先明确,数列是有顺序的 我们按照1->n处理的时候,循环到i刚开始的时候,dp存的就是1-> i-1 的所有可行方案数,当前位置可以直接根据dp的值进行转移。

如果读到了一个数x

组成第一种数列:
他可以放在结尾是x的第一种数列和结尾为x-1的第一种数列

状态转移方程:
之前结尾就是x的数列现在结尾又多了个x,可以选或者不选,如果不选,还是之前那些方案,如果选了,相当于多了之前那些方案。
现在结尾未x的方案数还多了之前结尾为x-1的方案数。
有 dp[x][0] += dp[x][0] + dp[x-1][0]

组成第二种数列:
他可以放在最大值为x和x+2的第二种数列的结尾
他还可以放在结尾为x-2的第一种数列的结尾

状态转移方程:
放在最大值为x的数列结尾,多了一种选择
dp[x][1] += dp[x][1]
放在最大值为x+2的数列的结尾,也多了一种选择
dp[x+2][1] += dp[x+2][1]
放在结尾为x-2的第一种数列结尾
最大值为x第二种数列多了结尾为x-2的第一种数列的方案数
dp[x][1] += dp[x-2][0];

初始状态:
我们考虑什么都不选的时候,假设数列最开始有一个-1,方案数为1,只有一个-1就是可行数列是空的时候的情况。
第一个数是1或者0,如果是1,就相当于-1和1隔了两个数,从第二个情况转移过去,如果第一个数是0,也是从-1这个状态转移过去,所以默认一开始有个-1。

统计答案,即结尾为原数列存在的各个数的第一种序列的所有情况,最大数为 原数列存在的各个数的第二种序列的所有情况。
其实1-n不存在的也是0,直接全部都加上即可
注意:所有数+2,防止越界。

    #include <bits/stdc++.h>
    using namespace std;
    //#pragma GCC optimize (2)
    //#pragma GCC optimize (3)
    #pragma GCC optimize (Ofast)
    #define fastio ios_base::sync_with_stdio(0); cin.tie(NULL);
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define LL long long
    #define endl "\n"
    #define debug1 cout << "???" << endl;
    #define debug2(x) cout << #x << ": " << x << endl;
    const int INF = 0x3f3f3f3f;
    const int N = 5e5+7;
    const int MOD = 998244353;
    LL T, n, a[N], vis[N], ans, dp[N][2];
    int main()
    {
        fastio
        cin >> T;
        while(T--)
        {
            cin >> n;
            rep(i, 0, n+2)
                dp[i][0] = dp[i][1] = 0;
            ans = 0;
            dp[1][0] = 1;

            rep(i, 1, n)
            {
                cin >> a[i];
                a[i] += 2;

                dp[a[i]][0] += dp[a[i]][0] + dp[a[i]-1][0];
                dp[a[i]][1] += dp[a[i]][1] + dp[a[i]-2][0];
                dp[a[i] + 2][1] += dp[a[i] + 2][1] ;

                dp[a[i]][0] %= MOD;
                dp[a[i]][1] %= MOD;
                dp[a[i] + 2][1] %= MOD;
            }
            rep(i, 2, n+2)
            {
                ans += dp[i][0];
                ans %= MOD;
                ans += dp[i][1];
                ans %= MOD;
            }
            cout << ans << endl;
        }
        return 0;
    }

E
问:
给一张地图,有一个终点,判断机器人能否从任意一个点走到终点,机器人每次可以收到一个指令,他一定不会走指令的方向,如果有别的方向可以走,他就会随机走一个,求所有可以到终点的点。

解:
考虑每一个点:
如果当前点可以走到终点,需要满足,这个点有k个方向可以走,至少k-1个方向是一定可以保证能通向终点的,那么我们只需要让机器人不走不通的方向即可。

要判断这个点能否走,需要已经知道他另外几个方向能否走,所以我们维护一个可以走的状态,从这些状态向没有走的地方拓展,然后判断是不是在答案里。

实际上就是拓扑排序

如何维护这种可以走的状态呢?肯定是从终点开始才能开始得到那些点是可以走的。需要用一个bfs从终点开始维护所有一定可以走的路径。

预处理每个点可以走的方向。
把终点丢到BFS队列里,然后队列里都是一定可以走到的,队列每个点拓展的时候,拓展到的点直接度数减一,如果度数小于等于一就说明这个点也可以走,丢到队列里即可。

注意:
不能用cout << endl 1e5个endl就得跑两秒,用\n。
因为不能直接开 1e6 x 1e6大的数组,需要开N个vector,每次初始化resize一下,最后记得clear,就可以当二维数组用了

#pragma GCC optimize (2)
#pragma GCC optimize (3)
#pragma GCC optimize (Ofast)
#define fastio ios_base::sync_with_stdio(0); cin.tie(NULL);
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define LL long long
#define debug1 cout << "???" << endl;
#define debug2(x) cout << #x << ": " << x << endl;
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1e6+7;
int T, n, m, lx, ly,dir[2][4] = {-1, 0, 1, 0, 0, 1, 0, -1};
char ch;
vector <int> a[N], vis[N], w[N], pre[N];
int DFS(int x, int y);
void Solve()
{
    //vis 1 一定可以到终点 a 1可以走
    cin >> n >> m;

    rep(i, 0, n+1)
    {
        w[i].resize(m+2);
        a[i].resize(m+2);
        vis[i].resize(m+2);
        pre[i].resize(m+2);
    }

    rep(i, 1, n)
    {

        rep(j, 1, m)
        {
            cin >> ch;
            if(ch == '.')
                a[i][j] = 1;
            else if(ch == '#')
                a[i][j] = 0;
            else if(ch == 'L')
            {
                a[i][j] = 1;
                vis[i][j] = 1;
                lx = i, ly = j;
            }
        }
    }
    //求每个点可以走的方向数
    rep(i, 1, n)
    {
        rep(j, 1, m)
        {
            if(!a[i][j]) continue;
            rep(k, 0, 3)
            {
                int tx = i + dir[0][k], ty = j + dir[1][k];
                if(a[tx][ty])
                    w[i][j]++;
            }
        }
    }

    queue <pair<int, int>> q;
    q.push(make_pair(lx, ly));
    a[lx][ly] = 2;
    while(!q.empty())
    {
        int x = q.front().first, y = q.front().second; q.pop();
        pre[x][y] = 1;
        rep(i, 0, 3)
        {
            int tx = x + dir[0][i], ty = y + dir[1][i];
            if(!a[tx][ty]) continue;
            if(--w[tx][ty] <= 1)
            {
                vis[tx][ty] = 1;
                if(!pre[tx][ty])
                    q.push(make_pair(tx, ty));
            }
        }
    }
    rep(i, 1, n)
    {
        rep(j, 1, m)
        {
            if(a[i][j] == 1)
            {
                if(vis[i][j] == 1)
                    cout << '+';
                else
                    cout << '.';
            }
            else if(a[i][j] == 2)
                cout << 'L';
            else
                cout << '#';
        }
        cout << '\n';
    }
    //每次结束后清空
    rep(i, 0, n+1)
    {
        a[i].clear();
        vis[i].clear();
        w[i].clear();
        pre[i].clear();
    }
}
int main()
{
    fastio
    cin >> T;
    while(T--)
        Solve();
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值