AtCoder Regular Contest 114 题解

A A A

50 50 50 以内的质数个数为15.

暴力搜索, 然后判断即可.

int n, p[N], a[N], tot;
ll ans = 1e18;
bool v[N], cho[N];

void dfs(int x, ll y) {
    if (y >= ans)
        return;
    if (x == tot + 1) {
        FOR(i, n) if (__gcd((ll)a[i], y) == 1) return;
        ans = y;
        return;
    }
    dfs(x + 1, y * p[x]);
    dfs(x + 1, y);
}

void solve() {
    n = 50;
    rep(i, 2, n) if (!v[i]) {
        for (int j = i * i; j <= n; j += i)
            v[j] = 1;
    }
    qr(n);
    FOR(i, n) {
        qr(a[i]);
        int x = a[i];
        for (int j = 2; j <= x; j++)
            if (x % j == 0) {
                while (x % j == 0)
                    x /= j;
                if (!cho[j])
                    cho[j] = 1, p[++tot] = j;
            }
    }
    dfs(1, 1);
    pr2(ans);
}

B B B

每个环都可选可不选…

int n, a[N], tot, deg[N], vis[N];

void solve() {
    qr(n);
    FOR(i, n) qr(a[i]), deg[a[i]]++;
    static int q[N], l, r;
    l = 1;
    r = 0;
    FOR(i, n) if (!deg[i]) q[++r] = i;
    while (l <= r) {
        int x = q[l++], y = a[x];
        vis[x] = 1;
        if (!--deg[y])
            q[++r] = y;
    }
    ll ans = 1;
    FOR(i, n) if (!vis[i]) {
        int x = a[i], y;
        ans = ans * 2 % mod;
        while (!vis[x])
            vis[x] = 1, x = a[x];
    }
    pr2(ans - 1);
}

C C C

显然我们从小到大处理每一个值.

如果对于 i < j , a i = a j , ∀ i < k < j , a k > a i i < j, a_i = a_j, \forall i < k < j, a_k > a_i i<j,ai=aj,i<k<j,ak>ai, 我们连一条边 ( i , j ) (i,j) (i,j).

这样一个排列的答案 = 生成图的连通块数.

我们枚举每一个对 ( i , j ) (i, j) (i,j), 考虑存在边 ( i , j ) (i,j) (i,j) 的方案数.

∑ x = 1 m ( m − x ) j − i − 1 m n − ( j − i ) − 1 \sum_{x=1}^m (m-x)^{j-i-1} m ^{n -(j-i)-1} x=1m(mx)ji1mn(ji)1.

前面这一部分我们可以 O ( n m ) O(nm) O(nm) 预处理.

然后我们可以暴力枚举 ( i , j ) (i,j) (i,j), 当然也可以直接枚举 j − i j-i ji.

int n, m;
ll s[N], p[N];

void solve() {
    qr(n, m);
    p[0] = 1;
    FOR(i, n) p[i] = p[i - 1] * m % mod;
    rep(i, 0, m - 1) {
        ll t = 1;
        FOR(j, n) ad(s[j], t), t = t * i % mod;
    }
    ll ans = p[n] * n % mod;
    FOR(i, n) {
        FOR(j, i - 1) { dl(ans, s[i - j] * p[n - (i - j) - 1] % mod); }
    }
    pr2(ans);
}

D D D

先对 a a a 排个序.

b i b_i bi 为 第 i i i 个点的目标位置.

显然有 b 1 ≤ b 2 ≤ . . . ≤ b n b_1\le b_2\le ...\le b_n b1b2...bn.

我们对 a i , b i a_i, b_i ai,bi 维护每个位置的出现次数, 然后可以发现最后出现次数为奇数的集合正好为 t t t.(我们设 v i v_i vi 表示第 i i i 个位置的两边是否相同, 那么加入一条路径相当于 v a i xor = 1 , v b i xor = 1 v_{a_i} \text {xor}= 1,v_{b_i} \text{xor} = 1 vaixor=1,vbixor=1)

但是 b b b 是未知的, 我们可以发现 可重集 ∪ { a i } ∪ { t i } \cup \{a_i\} \cup \{t_i\} {ai}{ti} 中出现出现奇数次的 一定在 b b b 中出现过.

这样我们就可以 dp \text {dp} dp 啦: 定义 f [ i ] [ j ] f[i][j] f[i][j] 表示 前 i i i a a a, 配对前 j j j b b b.

每次转移既可以和 b b b 配对, 也可以和下一个 a a a 配对.

复杂度 O ( n k ) O(nk ) O(nk)

int n, m, a[N], t, c[N * 2];
ll f[N][N];
map<int, int> s;

void solve() {
    qr(n, m);
    FOR(i, n) qr(a[i]), s[a[i]] ^= 1;
    int v;
    FOR(i, m) qr(v), s[v] ^= 1;
    sort(a + 1, a + n + 1);
    for (auto i : s)
        if (i.se)
            c[++t] = i.fi;
    if (t > n || (n - t) & 1) {
        puts("-1");
        return;
    }
    memset(f, 63, sizeof f);
    f[0][0] = 0;
    rep(i, 0, n - 1) rep(j, 0, min(t, i)) {
        ll& v = f[i][j];
        if (v >= INF)
            continue;
        cmin(f[i + 2][j], v + a[i + 2] - a[i + 1]);
        cmin(f[i + 1][j + 1], v + abs(a[i + 1] - c[j + 1]));
    }
    pr2(f[n][t]);
}

E E E

显然有 5 类不同的东西:

左/右边的列, 上/下的行, 中间的行列.

我们把他们依次给他们标号, 然后给所有物品排列.

然后根据期望的线性性, 我们只需要考虑 1 ∼ 4 1\sim 4 14 每种在第一个 5 5 5 前面个数的期望即可.

但是这样不符合题目中抛弃一半的意思, 所以我们给 1 ∼ 4 1\sim 4 14 每种内部在标号, 标号小的可以把标号大的删去.

c n t i cnt_i cnti 表示每一类的数量.

那么对于一类中标号为 j j j 的被选到当且仅当 它是 [ 1 , j ] [1,j] [1,j] 和所以第 5 类 中第一个被选到的, 故概率为 1 j + c n t 5 \dfrac 1 {j + cnt_5} j+cnt51
A n s = ∑ i = 1 4 ∑ j = 1 c n t i 1 j + c n t 5 Ans = \sum_{i=1}^4 \sum_{j = 1}^{cnt_i} \dfrac 1 {j + cnt_5} Ans=i=14j=1cntij+cnt51

#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
typedef long long ll;
#define rep(i, a, b) for (int i = a; i <= b; i++)

int n, m, u, v, w, z, s, inv[200010], S;
ll calc(int x) {
    ll s = 0;
    while (x)
        s += inv[x-- + S];
    return s;
}
int main() {
    cin >> n >> m >> u >> v >> w >> z;
    if (u > w)
        swap(u, w);
    if (v > z)
        swap(v, z);
    inv[0] = inv[1] = 1;
    rep(i, 2, n + m) inv[i] = (ll)inv[mod % i] * (mod - mod / i) % mod;
    S = w - u + z - v;
    cout << (calc(u - 1) + calc(n - w) + calc(v - 1) + calc(m - z) + 1) % mod << "\n";
}

F F F

显然这种题还是不要做了

可以发现 Q ≥ P Q \ge P QP, k k k 越小 Q Q Q 越小.

对于一个划分, Q Q Q 显然就是每段的第一个元素排序后拼接起来的结果.

然后,我们来讨论一下 P 1 P_1 P1:

  • P 1 ≤ k P_1 \le k P1k, 显然每一段的开头分别为 [ 1 , k ] [1,k] [1,k], 否则的话, 第一个位置可以更大…

  • 否则, 我们要做到字典序最小必须保证 P , Q P,Q P,Q 的相同前缀尽可能的长.于此同时, 希望后面部分的划分尽可能少.

    为了满足第二部分, 我们前面应该求一个最长下降子序列.

    f i f_i fi 表示选定 P 1 , P i P_1, P_i P1,Pi 后最长下降子序列的长度.

    那么如果 i i i 后的段开头一定 < P i < P_i <Pi. (否则 P , Q P,Q P,Q 的相同前缀长度缩小.)

    如果后面 < P i <P_i <Pi 的个数 ≥ m − f i \ge m - f_i mfi, 为了优先满足第一部分, 我们应该选择 < P i <P_i <Pi 的下表更大的 m − f i m-f_i mfi 个作为剩下的段开头.

    综上, 我们按 P i P_i Pi 从小到大枚举, 然后用树状数组 / 平衡树 维护一下下标集合, 即可求出最大公共前缀.

    剩下的部分排序即可解决.

TP void cmax(o& x, o y) {
    if (x < y)
        x = y;
}

int n, m, a[N], b[N], f[N], sta[N], top, p[N];

template <typename T> struct BIT {
    T* c;
    int n, lg; // require you to define 0 as the initial value !!
    BIT(int _n)
        : n(_n) {
        c = new T[n];
        lg = __lg(n);
        c--;
        FOR(i, n) c[i] = T(0);
    }
    void add(int x, T y) {
        for (; x <= n; x += x & -x)
            c[x] = c[x] + y;
    }
    int Kth(int k) {
#define bin(x) (1 << (x))
        int x = 0;
        REP(i, 0, lg) if (bin(i) + x <= n && c[bin(i) + x] < k) k -= c[x += bin(i)];
        return x + 1;
    }
};

bool vis[N];

void solve() {
    qr(n, m);
    sta[0] = 0;
    FOR(i, n) {
        qr(a[i]);
        b[a[i]] = i;
        int x = n - a[i] + 1;
        if (sta[top] < x)
            sta[++top] = x, f[i] = top;
        else if (a[i] <= a[1])
            f[i] = lower_bound(sta + 1, sta + top + 1, x) - sta, sta[f[i]] = x;
        else
            f[i] = -n;
    }
    if (top >= m) {
        FOR(i, n) pr1(a[i]);
        puts("");
        return;
    }
    BIT<int> tr(n);
    int pos = 0, mx = 0; //最大的同步右端点
    FOR(i, n) {
        int x = b[i], y = tr.Kth(i - (m - f[x]));
        tr.add(x, 1);
        if (y == n + 1)
            continue;
        if (x < y) {
            if (--y > pos)
                pos = y, mx = f[x];
            else if (y == pos)
                cmax(mx, f[x]);
        }
    }
    iota(p, p + n - pos + 1, pos);
    sort(p + 1, p + n - pos + 1, [](int x, int y) { return a[x] < a[y]; });
    int res = m - mx;
    debug(pos);
    debug(res);
    vis[n + 1] = 1;
    FOR(i, pos) pr1(a[i]);
    FOR(i, res) vis[p[i]] = 1;
    REP(i, 1, res) {
        int x = p[i];
        do
            pr1(a[x++]);
        while (!vis[x]);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值