AtCoder Beginner Contest 224题解 A-G

AtCoder Beginner Contest 224(A-G)

知识点整理:

题号 知识点 备注
A
B
C 数学
D BFS 八数码问题变种
E 动态规划 329. 矩阵中的最长递增路径 变种
F 概率期望,算贡献,快速幂,逆元
G 数学,推公式
H 最小费用最大流

签到题、简单题


A - Tires

判断一个字符串是不是以erist结尾。

直接做即可

#include 

using namespace std;

int main() {
  	string s;
    cin >> s;
    int n = s.size();
    if (n >= 2 && s[n-1]=='r' && s[n-2]=='e') puts("er");
    else if (n >= 3 && s[n-3]=='i' && s[n-2]=='s' && s[n-1] == 't')
        puts("ist");
    return 0;
}

B - Mongeness

给你个矩阵,判断是否满足任意两个点 ( i 1 , j 1 ) (i1, j1) (i1,j1) ( i 2 , j 2 ) (i2, j2) (i2,j2) 满足

A i 1 , j 1 + A i 2 , j 2 ≤ A i 2 , j 1 + A i 1 , j 2 A_{i1,j1}+A_{i2,j2}\leq A_{i2,j1}+A_{i1,j2} Ai1,j1+Ai2,j2Ai2,j1+Ai1,j2

很简单, N N N的范围是50, 直接 O ( N 4 ) O(N^4) O(N4) 模拟

看官方题解,这种类型的矩阵叫Monge matrix, 还有一种性质可以 O ( n 2 ) O(n^2) O(n2)

#include 

using namespace std;

typedef long long ll;

const int N = 100;

int h, w;
ll A[N][N];

int main() {
  cin >> h >> w;
  for (int i = 1; i <= h; i++)
    for (int j = 1; j <= w; j++)
      cin >> A[i][j];

  bool flag = true;
  for (int i1 = 1; i1 <= h; i1++)
    for (int j1 = 1; j1 <= w; j1++)
      for (int i2 = i1 + 1; i2 <= h; i2++)
        for (int j2 = j1 + 1; j2 <= w; j2++)
          if (a[i1][j1] + a[i2][j2] > a[i2][j1] + a[i1][j2])
            flag = false;

  puts(flag ? "Yes" : "No");
  return 0;
}

C - Triangle?

给你 n n n个点,判断任取3个点可以构成多少个三角形。

看到 n ≤ 300 n\leq 300 n300 , 就知道可以三重循环做,判断三个点是否共线即可。

如果三个点坐标分别是 ( x 1 , y 1 ) , ( x 2 , y 2 ) , ( x 3 , y 3 ) (x_1, y_1),(x_2,y_2),(x_3,y_3) (x1,y1),(x2,y2),(x3,y3), 那么需要满足他们的斜率一致,即 y 2 − y 1 x 2 − x 1 = y 3 − y 2 x 3 − x 2 \frac{y_2-y_1}{x_2-x_1}=\frac{y_3-y_2}{x_3-x_2} x2x1y2y1=x3x2y3y2 ,特别判断下分母为零即可。

#include 
#include 
#include 

using namespace std;

const int N = 310;

typedef long long ll;

int n;
ll x[N], y[N];

int main() {
    cin >> n;

    for (int i = 1; i <= n; i++)
        cin >> x[i] >> y[i];

    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            for (int k = j + 1; k <= n; k++) {
                if (x[i] == x[j] && x[j] == x[k])
                    continue;

                if (y[i] == y[j] && y[j] == y[k])
                    continue;

                if ((y[j] - y[i]) * (x[k] - x[j]) != (x[j] - x[i]) * (y[k] - y[j]))
                    cnt++;
            }
        }
    }

    cout << cnt << endl;
    return 0;
}

中等题


D - 8 Puzzle on Graph

给你个有9个节点的图,其中8个节点填上了数字1~8,另外一个不填。

每次可以选择一个数字,把他沿着图移到相邻的空白格子上,问至少移动几次,使得前8个点中, i i i号点上的数字是 i i i , 如果无解输出-1.

一个经典问题八数码 的变形,只是把矩阵上相邻格子的移动变成沿着图移动,万变不离其宗,使用bfs搜索,每次记录下状态,队列如果为空,所有状态跑完还找不到解就是无解,否则总会找到最近的解。

#include 

using namespace std;

const int MAXN = 40;

int m, u, v;
int p;

struct Edge {
    int to, next;
} e[MAXN << 1];

int head[MAXN], idx = 1;

void add_edge(int u, int v) {
    e[idx].to = v;
    e[idx].next = head[u];
    head[u] = idx++;
}
void clear_graph() {
    memset(head, -1, sizeof(head));
    memset(e, 0, sizeof(e));
    idx = 1;
}

int bfs(string st) {
    queue<string> q;

    map<string, int> d;

    d[st] = 0;
    string end = "#123456780";
    q.push(st);

    while (q.size()) {
        string t = q.front();
        q.pop();

        int dis = d[t];
        if (t == end) {
            return dis;
        }
        int pos = t.find('0');

        for (int i = head[pos]; ~i; i = e[i].next) {
            int j = e[i].to;
            swap(t[j], t[pos]);
            if (d.count(t) == 0) {
                d[t] = dis + 1;
                q.push(t);
            }
            swap(t[j], t[pos]);
        }
    }
    return -1;

}
int main() {
    cin >> m;
    clear_graph();

    for (int i = 1; i <= m; i++) {
        cin >> u >> v;
        add_edge(u, v);
        add_edge(v, u);
    }

    string state = "#000000000";
    for (int i = 1; i <= 8; i++) {
        cin >> p;
        state[p] = i + '0';
    }
    int res = bfs(state);
    cout << res << endl;

    return 0;
}

E - Integers on Grid

题意:

给你一个矩阵,矩阵里面填了数,从一个格子出发,每一步可以沿着行或者列跳到比他数字大的格子,问每个点最多能跳多少步?

题解

一开始我用的是记忆化搜索,按数字倒序,因为数字大的搜完了,数字小的一定走到数字大的,发现会tle。仔细想了下这种方案依然是 O ( n 2 ) O(n^2) O(n2)的,要考虑怎么优化。

看了官方题解,发现可以用动态规划做:

d p [ i ] dp[i] dp[i]表示第 i i i个点能最远走多少步。那么朴素的做法是 d p [ i ] = m a x { d p [ j ] + 1 } , i 能 走 到 j dp[i]=max\{dp[j]+1\}, i能走到j dp[i]=max{dp[j]+1},ij

因为从每个点出发,要么横着走,要么竖着走,所以我们拆分这两种情况, d p [ i ] dp[i] dp[i]可以横着转移来,或者竖着转移来,也就是 j j j i i i同行或同列。那么把 j j j构成的集合拆开,得到

r m a x [ r ] = m a x { d p [ j ] + 1 } , j 点 在 第 r 行 , 且 权 值 大 于 i 点 rmax[r]=max\{dp[j]+1\},j点在第r行,且权值大于i点 rmax[r]=max{dp[j]+1},jri

c m a x [ c ] = m a x { d p [ j ] + 1 } , j 点 在 第 c 列 , 且 权 值 大 于 i 点 cmax[c]=max\{dp[j]+1\},j点在第c列,且权值大于i点 cmax[c]=max{dp[j]+1},jci

d p [ i ] = m a x ( r m a x [ r i ] , c m a x [ c i ] ) dp[i]=max(rmax[r_i], cmax[c_i]) dp[i]=max(rmax[ri],cmax[ci]). 我们可以对点权倒序排序,确保每个 j j j点都能被 i i i点走过去。

计算 r m a x , c m a x rmax,cmax rmax,cmax 时候也是,因为是倒着排序的,所以只要存在一个 d p [ j ] dp[j] dp[j], 权值都是大于当前正在遍历的点。总时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn).

#include 

using namespace std;

using ll = long long;
using pii = pair<int, int>;

const int MAXN = 2e5+10;
const int MOD = 1e9 + 7;

int h, w, n, r[MAXN], c[MAXN], a[MAXN];

// key=权值,val=点序号
map<int, vector<int>> mp;

int dp[MAXN], rmax[MAXN], cmax[MAXN];

int main() {
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> h >> w >> n;
    for (int i = 1; i <= n; i++) {
        cin >> r[i] >> c[i] >> a[i];
        mp[-a[i]].push_back(i);
    }

    for (auto [_, i] : mp) {
        for (auto j : i)
            dp[j] = max(rmax[r[j]], cmax[c[j]]);
        for (auto j : i) {
            rmax[r[j]] = max(rmax[r[j]], dp[j] + 1);
            cmax[c[j]] = max(cmax[c[j]], dp[j] + 1);
        }
    }

    for (int i = 1; i <= n; i++)cout << dp[i] << endl;
    return 0;
}

F - Problem where +s Separate Digits

题意:

给你一个字符串,可以在任意空格里填上加号,得到一个公式,问所有可行的公式的和是多少?

题解

临场我想到的是后缀和加DP的思路,但是时间复杂度是 O ( n 2 ) O(n^2) O(n2), 肯定不可行。

看官方题解 把问题转化为一个概率期望的模型:

对一个字符串随机选空格中间加上+号,求最后得到的表达式的值的期望,再乘以 2 ∣ S ∣ − 1 2^{|S|-1} 2S1

接下来,我们分析每个数位对期望的贡献。

一个数字 x x x在原来字符串里是倒数第 k k k位,他可能以个位、十位、百位……等出现,那么它的取值就可能是 x , 10 x , 100 x . . . x,10x,100x... x,10x,100x... 分别计算每种情况出现的概率:

  • x x x 若出现在个位,需要其后面一格有加号,概率为 1 / 2 1/2 1/2
  • x x x 若出现在十位,需要其后面一格无加号,后两格有加号,概率为 1 / 4 1/4 1/4

……

  • x x x 若出现在第 k − 1 k-1 k1位,需要后面 k − 2 k-2 k2个格都无加号,最后一格有加号,概率为 1 / 2 k − 1 1/2^{k-1} 1/2k1
  • x x x 若出现在第 k k k位,需要后面 k − 1 k-1 k1个格都无加号,概率为 1 / 2 k − 1 1/2^{k-1} 1/2k1
数字 位数 概率 取值 贡献
4 个位 1 4 4
3 个位 1/2 3 1.5
十位 1/2 30 15
2 个位 1/2 2 1
十位 1/4 20 5
百位 1/4 200 50
1 个位 1/2 1 0.5
十位 1/4 10 2.5
百位 1/8 100 12.5
千位 1/8 1000 125

总的贡献是217,乘以8得到最终答案1736.

所以, x x x 这个数对期望的贡献是:
x × ( ∑ i = 1 k − 1 1 0 i − 1 2 i + 1 0 k − 1 2 k − 1 ) x\times (\sum_{i=1}^{k-1} \frac{10^{i-1}}{2^i}+\frac{10^{k-1}}{2^{k-1}}) x×(i=1k12i10i1+2k110k1)

后面这一坨是个等比数列,可以用公式 O ( 1 ) O(1) O(1) 算出来。所以整个时间复杂度是 O ( n ) O(n) O(n) .

#include 

using namespace std;

using ll = long long;
using pii = pair<int, int>;

const int MAXN = 2e5 + 5;
const int MOD = 998244353;

string s;

ll qpow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}
int main() {
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> s;

    int n = s.size();
    ll res = 0;
    ll inv8 = qpow(8, MOD - 2);

    for (int i = n - 1; i >= 0; i--) {
        int x = s[i] - '0';
        int k = n - i;

        ll a = 9 * qpow(5, k - 1) % MOD;
        a = (a - 1 + MOD) % MOD;
        a = a * x % MOD;
        a = a * inv8 % MOD;

        res = (res + a) % MOD;
    }
    res = res * qpow(2, n - 1) % MOD;
    cout << res << endl;
    return 0;
}

高级题

G - Roll or Increment

题意:

给你一个 N N N面骰子,一开始朝上的数字是 S S S, 你可以进行如下操作:

  1. A A A日元,让朝上的数字变成当前朝上的数字加1,如果朝上的数字是 N N N,则不可以进行此操作

  2. B B B日元,重新扔一次

以最省钱的策略操作,问把朝上的数字变成 T T T的消费的期望。

题解

首先我们看下, 什么是最省钱的策略操作

分析这两种方案,我们有如下推论:

  • 如果用了方案1,那么就不要再用方案2,否则不如直接重新扔
  • 根据上面那句话推论知:如果在 P P P 点选择了加1,那么 P P P T − 1 T-1 T1 就都不要重扔

那么我们需要在 [ 1 , T ] [1,T] [1,T]中找到一个数 X X X, 使得 T T T前面 X X X个数都选择方案1,其他重新扔。那么接下来要分析如何求 X X X.

考虑两种情况:

  1. S S S落在选择方案1的区间里,那么只使用方案一,变成 T T T即可,花的钱就是 A ( T − S ) A(T-S) A(TS),这个方案跟 X X X没关系。
  2. S S S落在外面,那么先重新扔,扔到 [ T − X + 1 , T ] [T-X+1, T] [TX+1,T]上,再执行方案一。这种情况下,我们可以算出扔进那个区间的概率是 X / N X/N X/N, 那么期望费用就是 B N / X BN/X BN/X, 接下来考虑方案1, 因为扔到每一个格子的概率是相等的,而要走的步数是个等差数列,最后算得期望花销是 A ( X − 1 ) / 2 + B N / X A(X-1)/2+BN/X A(X1)/2+BN/X.

情况2跟 X X X就有关系了,我们算下什么时候这个值是最小的,显然 X = 2 B N / A X=\sqrt{2BN/A} X=2BN/A 的时候是最好的,但是这个数可能是小数,所以算下接近的数,再跟情况一比较一下就可以了。整个都是数学计算,时间复杂度 O ( 1 ) O(1) O(1).

#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long ll;

ll N, S, T, A, B;

double f(ll x) {
    return A * (x - 1) / 2.0 + B * N * 1.0/ x;
}
int main() {
    cin >> N >> S >> T >> A >> B;

    ll minx = sqrt(2.0 * B * N / A);

    double res = 1e18;

    if (T >= S) res = min(res, A *1.0* (T - S));

    // minx可能不是整数,两边都算算
    for (ll i = minx-1; i <= minx+1; i++) {
        if (i >= 1 && i <= T) // 边界情况,i=1时相当于不执行方案1一直重扔,i=x时相当于扔到T左边就一直加1
            res = min(res, f(i));
    }

    cout << fixed << setprecision(8) << res << endl;
}

H - Security Camera 2

题意:

给你一个二分图,左边有 L L L个点,右边有 R R R个点,每个点安装一个摄像头需要花费若干钱。

给你一个 L ∗ R L*R LR的矩阵,要求你花最少的钱,使得 < L i , R j > <Li,Rj>这条边上有 C i j C_{ij} Cij个摄像头。

不会

你可能感兴趣的:(夜深人静写算法,another,oj,1024程序员节)