[Atcoder]AtCoder Beginner Contest 234 题解

前几题直接代码。
A - Weird Function

int f(int x) {
    return x * x + 2 * x + 3;
}
cout <<  f(f(f(t)+t)+f(f(t))) << endl;

B - Longest Segment

double ans = 0;
for(int i = 1; i <= n; i++) {
    for(int j = i + 1; j <= n; j++) {
        ans = max(ans, 1.0 * (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
    }
}
printf("%.10f\n", sqrt(ans));

C - Product
输出只由0和2组成的正整数中,第k大的数。
就是把k二进制展开就行,但是把1变成2就行。

cin >> n;
vector<int> ans;
while(n) {
    ans.push_back(n % 2 * 2);
    n /= 2;
}
if(ans.size() == 0) cout << 0 << endl;
else {
    for(int i = ans.size() - 1; i >= 0; i--) {
        cout << ans[i];
    }
    cout << endl;
}

D - Prefix K-th Max
给一个排列,对于每个大于k的位置,问前面一系列数中第k大的数是多少。
对顶堆可以nlogn,但是懒得写,直接树状数组二分也行。

#include <bits/stdc++.h>

#define Mid ((l + r) / 2)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
const int N = 5e5 + 1009;
int n, k, a[N];
int tree[N];
void add(int x, int y) {
    for( ; x <= n; x += x & -x)
        tree[x] += y;
}
int query(int x) {
    int ans = 0;
    for( ; x; x -= x & -x)
        ans += tree[x];
    return ans;
}
signed main() {
#ifndef ONLINE_JUDGE
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
#endif
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> k;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <  k; i++) add(n - a[i] + 1, 1);
    for(int i = k; i <= n; i++) {
        add(n - a[i] + 1, 1);
        int l = 1, r = n;
        while(l <= r) {
            if(query(Mid) >= k) r = Mid - 1;
            else l = Mid + 1;
        }
        cout << n - r << endl;
     }
    return 0;
}

E - Arithmetic Number
一个数叫做等差数当且仅当满足下面两个其中之一:

  1. 它是1位或者2位数
  2. 对于每个\(i, a_i - a_{i - 1} = a_{i + 1} - a_{i}\)
    输出大于等于x的最小的等差数。

首先,如果是只有1或2位数,直接输出就行。
不然枚举暴力枚举第一位和第二位就可以确定后面的数,时间复杂度\(T(9 * 10 * 17)\)

#include <bits/stdc++.h>
#define int long long
#define Mid ((l + r) / 2)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
int n, x;
char c[209];
signed main() {
#ifndef ONLINE_JUDGE
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
#endif
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> c;
    n = strlen(c);
    for(int i = 0; i < n; i++) {
        x = x * 10 + c[i] - '0';
    }
    if(n == 1 || n == 2) {
        cout << c << endl;
    } else {
        int ans = 1ll << 61;
        for(int i = 0; i < 10; i++) {
            for(int j = -9; j <= 9; j++) {
                int t = i, now = 0;
                int f = 1;
                for(int k = 1; k <= n; k++) {
                    if(t > 9 || t < 0) {
                        f = 0;
                        break;
                    }
                    now = now * 10 + t;
                    t = t + j;
                }
                if(f && now >= x) {
                    ans = min(ans, now);
                }
            }

        }
        cout << ans << endl;
    }
    return 0;
}

F - Reordering
给定一个字符串\(S\),问有多少个不同的字符串可以由S的一个子串排列组合构成。

既然都是子串排列组合了,跟\(S\)就没啥关系了,只需要知道每个字母有多少个就行了。
然后我们统计方案只要用这个数量数组来就行了。
首先注意到组成的长度不同,方案肯定不同,可以用长度划分开方案:\(f[i][j]\)表示前\(i\)个字母组成长度为\(j\)字符串的种数。然后枚举这一个字母取多少个,然后插入新串的若干个位置,剩下的空位把原来的长度为\(j\)的串按顺序放入,就可以保证方案不同了。

#include <bits/stdc++.h>
#define int long long
#define Mid ((l + r) / 2)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
const int mod = 998244353;
const int N = 2e5 + 1009;
int n, cnt[29], f[29][5009];
char c[5009];
int inv[N], fac[N];
void init() {
    inv[0] = inv[1] = fac[0] = fac[1] = 1;
    for(int i = 2; i < N; i++) inv[i] = (mod - mod / i) * inv[mod % i] % mod;
    for(int i = 2; i < N; i++) inv[i] = inv[i] * inv[i - 1] % mod;
    for(int i = 2; i < N; i++) fac[i] = fac[i - 1] * i % mod;
}
int C(int n, int m) {
    return fac[n] * inv[n - m] % mod * inv[m] % mod;
}
signed main() {
#ifndef ONLINE_JUDGE
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
#endif
    ios::sync_with_stdio(0);
    cin.tie(0);
    init();
    cin >> c; n = strlen(c);
    for(int i = 0; i < n; i++) {
        cnt[c[i] - 'a' + 1]++;
    }
    int sum = 0;
    f[0][0] = 1;
    for(int i = 1; i <= 26; i++) {
        for(int j = 0; j <= sum; j++) {
            for(int k = 0; k <= cnt[i]; k++) {
                f[i][j + k] = (f[i][j + k] + f[i - 1][j] * C(j + k, k) % mod) % mod;
            }
        }
        sum += cnt[i];
    }
    int ans = 0;
    for(int i = 1; i <= n; i++) {
        ans = (ans + f[26][i]) % mod;
    }
    cout << ans << endl;
    return 0;
}

G - Divide a Sequence
给定一个数组,对于一种划分成\(k\)段的方案\(P\),\(f(P)\)为每一段的最大值减最小值的乘积:\(\prod_{i = 1}^{k} max(B_i) - min(B_i)\),\(B_i\)为第\(i\)段这个集合。
问对于所有的划分方案,\(\sum f(P)\)。

\(n^2\)的做法挺好想的,f[i]表示前i个数任意划分的乘积的和,只需要枚举最后一段的长度就行了。

\[f(i) = \sum (f(j - 1) \times (max_{k = j}^{i} a_k - min_{k = j}^{i} a_k)) \]

代码:

  f[0] = 1;
  for(int i = 1; i <= n; i++) {
      int maxn = a[i];
      int minn = a[i];
      for(int j = i; j >= 1; j--) {
          maxn = max(maxn, a[j]);
          minn = min(minn, a[j]);
          f[i] = (f[i] + f[j - 1] * (maxn - minn) % mod) % mod;
      }
  }
  cout << f[n] << endl;

显然,跑的太慢了,考虑优化,重点就在第二个循环。
我们给每个位置一个后缀max和min的值,每次加入i位置的值,有若干个位置的max和min会更新。
显然,max是递减的,min是递增的。
由于这两个问题等价,我们下面只考虑max的处理。
对于每次加入一个新的数字,只有末尾的若干个max值会被变成\(a(i)\),同时对\(f(i)\)的影响就是\(\delta max_j * f(j)\)。
当一段数字都变成\(a(i)\)之后,其实在之后如果要改变就会一起改变相同的\(\delta max\)了,所以可以直接把所有的\(f\)值放在一起。
这个过程就是单调栈的处理方式,我们用单调栈维护每个位置的\(sumf\)和\(max\),就可以了。

这里只计算了变大的值,不变的部分直接\(f(i) += f(i - 1)\)就行。

#include <bits/stdc++.h>
#define int long long
#define Mid ((l + r) / 2)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
const int mod = 998244353;
const int N = 3e5 + 1009;
int n, a[N], f[N], submax[N], submin[N];
struct node {
    int f, v;
};
stack<node> maxst, minst;
signed main() {
#ifndef ONLINE_JUDGE
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
#endif
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    maxst.push({1, a[1]});
    minst.push({1, a[1]});
    for(int i = 2; i <= n; i++) {
        int maxs = 0, mins = 0;
        f[i] = f[i - 1];
        while(!maxst.empty() && maxst.top().v < a[i]) {
            f[i] = (f[i] + maxst.top().f * (a[i] - maxst.top().v) % mod) % mod;
            maxs = (maxs + maxst.top().f) % mod;
            maxst.pop();
        }
        while(!minst.empty() && minst.top().v > a[i]) {
            f[i] = (f[i] + minst.top().f * (minst.top().v - a[i]) % mod) % mod;
            mins = (mins + minst.top().f) % mod;
            minst.pop();
        }
        maxst.push({(maxs + f[i - 1]) % mod, a[i]});
        minst.push({(mins + f[i - 1]) % mod, a[i]});
    }
    cout << f[n] << endl;
    return 0;
}

Ex - Enumerate Pairs
很奇妙的题,看题解做出来的,这里根据自己的理解简要翻译一下官方题解。
我们把\((x, y)\)放到\((x/k, y/k)\)(整除)这个集合中,对于每个点,跟他距离小于等于k的点只会出现在

\[(x/k+i,y/k+j), i\in[-1, 1], j\in[-1, 1] \]

中。
这是显然的,假如\(x,y\)的差距超过k,那么欧几里得距离一定大于k。
之后只要爆搜这些集合就可以找到所有的点对了,复杂度是\(O(N + M)\)。

方法很简单,但是复杂度不显然,下面是复杂度证明。
\(B_{X,Y}\)是集合\((X,Y)\)的点数,\(f(x)\)是包含x个点的集合的点的合法点对数量。
显然\(f(x)\le x^2\),现在要证明\(f(x)\)的下界为\(x^2\),目的是为了说明\(B_{X,Y}^2\)和\(f(B_{X,Y})\)同阶。

一个边长为\(\frac{K}{\sqrt(2)}\)的矩形中的任意两点距离小于\(K\)(最远的点对距离都为\(K\))。
而一个边长为\(K\)的矩形可以由4个这样的矩形覆盖,假如矩形中有x个点,那么根据鸽巢原理,一定有\(\frac{x}{4}\)个点在其中一个小矩形中,那么小矩形中的点对数大于\(\frac{\frac{x}{4} * (\frac{x}{4} - 1)}{2}\)。这个数量级为\(O(n^2)\),所以\(B_{X,Y}^2\)和\(f(B_{X,Y})\)同阶。

由于\(\sum f(B_{X,Y})\le M\),得到\(O(\sum B_{X,Y}^2) = O(M)\)

证明这个复杂度有什么用呢?

我们的算法计算的都是

\[\sum_{X, Y} \sum_{i = -1}^{1} \sum_{j = -1}^{1} B_{X,Y} \times B_{X + i, Y + j} \]

根据均值不等式:

\[B_{X,Y} \times B_{X + i, Y + j} \le \frac{1}{2} \times (B_{X,Y} ^ 2 + B_{X + i, Y + j}^2) \]

所以

\[\sum_{X, Y} \sum_{i = -1}^{1} \sum_{j = -1}^{1} B_{X,Y} \times B_{X + i, Y + j} \le \sum_{X, Y} \sum_{i = -1}^{1} \sum_{j = -1}^{1} \frac{1}{2} \times (B_{X,Y} ^ 2 + B_{X + i, Y + j}^2) \]

\[\le \sum_{X, Y} \frac{9}{2} \times (B_{X,Y} ^ 2) \]

所以:

\[O(\sum_{X, Y} \sum_{i = -1}^{1} \sum_{j = -1}^{1} B_{X,Y} \times B_{X + i, Y + j}) = O(N + M) \]

下面是代码:

#include <bits/stdc++.h>
#define int long long
#define Mid ((l + r) / 2)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
const int N = 2e5 + 1009;
const int lim = 2e9;
const int dx[] = {-1, -1, -1, 0, 0, 0, 1, 1, 1};
const int dy[] = {-1, 0, 1, -1, 0, 1, -1, 0, 1};
int n, k;
struct node {
    int x, y;
} p[N];
int dis(int a, int b) {
    return (p[a].x - p[b].x) * (p[a].x - p[b].x) + (p[a].y - p[b].y) * (p[a].y - p[b].y);
}
map<int, vector<int> > g;
int get(int x, int y) {
    return x * lim + y;
}
vector<node> ans;
signed main() {
#ifndef ONLINE_JUDGE
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
#endif
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> k;
    for(int i = 1; i <= n; i++) {
        int x, y;
        cin >> x >> y;
        p[i] = {x, y};
        g[get(x / k, y / k)].push_back(i);
    }
    for(int i = 1; i <= n; i++) {
        int x = p[i].x / k;
        int y = p[i].y / k;
        for(int j = 0; j < 9; j++) {
            int xx = x + dx[j];
            int yy = y + dy[j];
            for(auto t : g[get(xx, yy)]) {
                if(i >= t) continue;
                if(dis(i, t) <= k * k) {
                    ans.push_back({i, t});
                }

            }
        }
    }
    sort(ans.begin(), ans.end(), [](const node &a, const node &b) {
        if(a.x == b.x) return a.y < b.y;
        else return a.x < b.x;
    });
    cout << ans.size() << endl;
    for(auto x : ans) {
        cout << x.x << " " << x.y << endl;
    }
    return 0;
}
上一篇:7 指针 定义使用 占用内存空间


下一篇:JavaWeb基本概念及web服务器