第四届「帆软杯」东南大学大学生程序设计竞赛(冬季)- 复赛 官方题解

dd 2023-03-15 23:44:26 2023-06-03 23:59:26

整体难度:

题目名称 难度
G. 摩斯密码 *800
C. try-catch *900
A. 数学问题 *1000
I. 完美解场 *1200
E. 分段函数 *1300
B. 数数 *1800
D. 王嘉然的选课问题
F. 排队
H. 珂朵莉的问题 *2500

A.数学问题

由数学知识可知对于一个小数 cc ,必定存在 (c×106)/106c106|(c \times 10^6)/10^6 - c | \leq 10^{-6} ,而数据又可知 c2000c \leq 2000a,b2×109a,b\leq 2\times 10^9,按此方法构造可得答案,注意分数的约分 , 时间复杂度 O(tlog(max(a,b)))O(t\log(\max(a,b)))

B. 数数

使用st表或者线段树得到每个询问区间的最大值,并且预处理出每个最大值的区间数,询问时对答案减去1即可。 预处理的过程可以使用单调栈,线段树上二分法,时间复杂度O(n+q)O(n+q)(单调栈)。

单调栈:

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

typedef long long LL;

const int N = (int)(1e5) + 10;

int a[N], stk[N], n, q, top, l, r;
LL ans[N];
int lg[N], st[20][N];

int mx(int x, int y) { return (x > y) ? x : y; }

int mn(int x, int y) { return (x < y) ? x : y; }

int Ask_ST(int l, int r) {
    int k = lg[r - l + 1];
    return mx(st[k][l], st[k][r - (1 << k) + 1]);
}

void Get_ST() {
    for (int i = 1; i <= n; ++i) {
        st[0][i] = a[i];
    }
    for (int i = 0; (1 << i) <= n; ++i) lg[1 << i] = i;
    for (int i = 2; i <= n; ++i) lg[i] = (lg[i] == 0) ? lg[i - 1] : lg[i];
    for (int i = 1; (1 << i) <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            st[i][j] = mx(st[i - 1][j], st[i - 1][mn(n, j + (1 << (i - 1)))]);
        }
    }
}

int main() {
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
    }
    Get_ST();
    stk[top = 0] = 0;
    memset(ans, 0, sizeof(ans));
    for (int i = 1; i <= n; ++i) {
        while (top > 0 && a[stk[top]] <= a[i]) {
            ans[a[stk[top]]] += (LL)(stk[top] - stk[top - 1]) * (LL)(i - stk[top]);
            --top;
        }
        stk[++top] = i;
    }
    while (top > 0) {
        ans[a[stk[top]]] += (LL)(stk[top] - stk[top - 1]) * (LL)(n + 1 - stk[top]);
        --top;
    }
    while (q--) {
        scanf("%d%d", &l, &r);
        printf("%lld\n", ans[Ask_ST(l, r)] - 1ll);
    }
    return 0;
}

线段树二分:

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;

const ll N = 100010;
ll n, m, q, a[N], b[N], ans[N];
struct SegmentTree {
    ll l, r, max;
} tree[N << 3];

inline ll read() {
    ll x = 0, tmp = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            tmp = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return tmp * x;
}

inline void write(ll x) {
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    ll y = 10, len = 1;
    while (y <= x) {
        y = (y << 3) + (y << 1);
        len++;
    }
    while (len--) {
        y /= 10;
        putchar(x / y + 48);
        x %= y;
    }
}

inline void pushup(ll p) { tree[p].max = max(tree[p << 1].max, tree[p << 1 | 1].max); }

void build(ll p, ll l, ll r) {
    tree[p].l = l;
    tree[p].r = r;
    if (l == r) {
        tree[p].max = a[l];
        return;
    }
    ll mid = (l + r) >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    pushup(p);
}

ll query(ll p, ll l, ll r) {
    if (l <= tree[p].l && tree[p].r <= r)
        return tree[p].max;
    ll mid = (tree[p].l + tree[p].r) >> 1, ans = 0;
    if (l <= mid)
        ans = query(p << 1, l, r);
    if (r > mid)
        ans = max(ans, query(p << 1 | 1, l, r));
    return ans;
}

inline ll findleft(ll x, ll val) {
    ll l = 1, r = x - 1, ans = x;
    while (l <= r) {
        ll mid = (l + r) >> 1;
        if (query(1, mid, ans - 1) <= val) {
            ans = mid;
            r = mid - 1;
        } else
            l = mid + 1;
    }
    return ans;
}

inline ll findright(ll x, ll val) {
    ll l = x + 1, r = n, ans = x;
    while (l <= r) {
        ll mid = (l + r) >> 1;
        if (query(1, ans + 1, mid) <= val) {
            ans = mid;
            l = mid + 1;
        } else
            r = mid - 1;
    }
    return ans;
}

int main() {
    n = read();
    q = read();
    for (ll i = 1; i <= n; i++) b[i] = a[i] = read();
    sort(b + 1, b + 1 + n);
    m = unique(b + 1, b + 1 + n) - b - 1;
    for (ll i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + 1 + m, a[i]) - b;
    build(1, 1, n);
    for (ll i = 1; i <= n; i++) {
        ll l = findleft(i, a[i] - 1), r = findright(i, a[i]);
        ans[a[i]] += (r - i + 1) * (i - l + 1);
    }
    while (q--) {
        ll l = read(), r = read();
        write(ans[query(1, l, r)] - 1);
        putchar('\n');
    }
    return 0;
}

C.try-catch

本场签到题之一。

观察可知try_catch是无用的,所有把try当做(catch当作),做括号匹配即可,时间复杂度O(tn)O(tn)

D.王嘉然的选课问题

搬运题,见C1. Sheikh (Easy version)

E. 分段函数

二分即可,这题本来是作为一道简单题设计的,但是因为许多选手未做过交互题或者是因为本题二分有点困难的原因,比赛时通过人数远低于预期,时间复杂度O(tlog(n))O(t\log(n))

代码如下:

#include <bits/stdc++.h>
using namespace std;
int main() {
    int t;
    cin >> t;
    while (t--) {
        long long n, m, k, dy;
        cin >> n >> m;
        cout << "? " << n << " " << m << endl;
        cin >> dy;
        k = dy / (m - n - 1);
        long long L = n, R = m, Mid;
        while (L + 1 < R) {
            Mid = (L + R) >> 1;
            cout << "? " << L << " " << Mid << endl;
            cin >> dy;
            if (dy == k * (Mid - L))
                L = Mid;
            else
                R = Mid;
        }
        cout << "! " << k << " " << L << endl;
    }
    return 0;
}

F.排队

考虑非法的情况种类数。

首先对于两个队列分别考虑,由排列知识可知每个队列的非法方案数为所有出现超过一次的数字的次数阶乘的积。但是在上面的计算中,可能算重复了一些情况,减去重复的情况数,即可得到非法情况数,进而得到合法的情况数,时间复杂度O(nlog(n))O(n\log(n))

#include <bits/stdc++.h>
using namespace std;
#define fer(i, a, n) for (int i = (a); i <= (n); ++i)
#define se second
typedef long long ll;
const ll m = 998244353;
const int maxn = 3e5 + 5;
ll p[maxn];
typedef pair<ll, ll> pll;
pll a[maxn];
map<pll, ll> mp;
map<ll, ll> mp1, mp2;
signed main() {
    p[0] = 1;
    fer(i, 1, 3e5) p[i] = (p[i - 1] * i) % m;
    int n;
    cin >> n;
    fer(i, 1, n) cin >> a[i].first >> a[i].second;
    sort(a + 1, a + n + 1);
    fer(i, 1, n)++ mp[a[i]], ++mp1[a[i].first], ++mp2[a[i].second];
    ll sum1 = 1, sum2 = 1, sum3 = 1;
    for (auto& x : mp1) sum1 = (sum1 * p[x.se]) % m;
    for (auto& x : mp2) sum2 = (sum2 * p[x.se]) % m;
    for (auto& x : mp) sum3 = (sum3 * p[x.se]) % m;
    int f = 1;
    fer(i, 2, n) if (a[i - 1].second > a[i].second) f = 0;
    cout << (p[n] - sum1 - sum2 + (f ? sum3 : 0) + 2 * m) % m;
}

G. 摩斯密码

按照题目打表即可。

H.珂朵莉的问题

首先,一个小于 10910^9 数的质因子数 10\leq 10 个 ,因为权值与每个数字的质因子有关,需要对每个数字进行质因数分解, 使用 PollardRho算法Pollard-Rho算法 ,对每个数字的质因数分解的时间复杂度不会超过 ai0.25a_i^{0.25} 。接下来考虑如何求权值的最大值,在使用在线方法难以处理的情况下,考虑离线做法莫队。 易知,在增加区间长度的时候最大值很好更新,但是减少区间长度的情况下难以更新最大值,可以使用不删除莫队 ,根据每个数字的质因数分解结果更新最大值信息即可 ,注意对分解出来的质因数离散化,时间复杂度为 O(10nn+n×ai0.25+10n×log10n)O(10n\sqrt{n}+n\times a_i^{0.25}+10n\times \log{10n})

代码如下:

#include <bits/stdc++.h>
using namespace std;
#define fer(i, a, n) for (int i = (a); i <= (n); ++i)
#define rep(i, n, a) for (int i = (n); i >= (a); --i)
typedef long long ll;
ll max_factor;
inline ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
inline ll qp(ll x, ll p, ll mod) {
    ll ans = 1;
    while (p) {
        if (p & 1)
            ans = ans * x % mod;
        x = x * x % mod;
        p >>= 1;
    }
    return ans;
}
inline bool mr(ll x, ll b) {
    ll k = x - 1;
    while (k) {
        ll cur = qp(b, k, x);
        if (cur != 1 && cur != x - 1)
            return false;
        if ((k & 1) == 1 || cur == x - 1)
            return true;
        k >>= 1;
    }
    return true;
}
inline bool isprime(ll x) {
    if (x == 46856248255981ll || x < 2)
        return false;
    if (x == 2 || x == 3 || x == 7 || x == 61 || x == 24251)
        return true;
    return mr(x, 2) && mr(x, 61);
}
inline ll f(ll x, ll c, ll n) { return (x * x + c) % n; }
inline ll PR(ll x) {
    ll s = 0, t = 0, c = 1ll * rand() % (x - 1) + 1;
    int stp = 0, goal = 1;
    ll val = 1;
    for (goal = 1;; goal <<= 1, s = t, val = 1) {
        for (stp = 1; stp <= goal; ++stp) {
            t = f(t, c, x);
            val = val * abs(t - s) % x;
            if ((stp % 127) == 0) {
                ll d = gcd(val, x);
                if (d > 1)
                    return d;
            }
        }
        ll d = gcd(val, x);
        if (d > 1)
            return d;
    }
}
inline void fac(ll x) {
    if (x <= max_factor || x < 2)
        return;
    if (isprime(x)) {
        max_factor = max_factor > x ? max_factor : x;
        return;
    }
    ll p = x;
    while (p >= x) p = PR(x);
    while ((x % p) == 0) x /= p;
    fac(x), fac(p);
}
const int maxn = 1e5 + 5;
vector<ll> prime;
int a[maxn], n, q;
vector<ll> divisor[maxn];
ll cnt[maxn], __cnt[maxn], res, tmp, pos[maxn], l = 1, r, __l, last_block = -1, siz, ans[maxn];
struct qus {
    int l, r, k;
    bool operator<(const qus& q) { return pos[l] ^ pos[q.l] ? pos[l] < pos[q.l] : r < q.r; }
} qq[maxn];
void input() {
    cin >> n;
    siz = sqrt(n);
    fer(i, 1, n) cin >> a[i], pos[i] = i / siz;
    fer(i, 1, n) {
        srand(time(0));
        ll tmp = a[i];
        while (tmp != 1) {
            max_factor = 0;
            fac(tmp);
            prime.push_back(max_factor);
            while (tmp % max_factor == 0) tmp /= max_factor;
            divisor[i].push_back(max_factor);
        }
    }
    sort(prime.begin(), prime.end());
    prime.erase(unique(prime.begin(), prime.end()), prime.end());
    cin >> q;
    fer(i, 1, q) cin >> qq[i].l >> qq[i].r, qq[i].k = i;
    sort(qq + 1, qq + q + 1);
    fer(i, 1, n) {
        int now = divisor[i].size();
        fer(j, 0, now - 1) {
            divisor[i][j] = lower_bound(prime.begin(), prime.end(), divisor[i][j]) - prime.begin();
        }
    }
}
void getans() {
    fer(i, 1, q) {
        if (pos[qq[i].l] == pos[qq[i].r]) {
            ll mx = 0;
            fer(j, qq[i].l, qq[i].r) {
                int now = divisor[j].size();
                fer(o, 0, now - 1)++ __cnt[divisor[j][o]],
                    mx = max(mx, prime[divisor[j][o]] * __cnt[divisor[j][o]]);
            }
            fer(j, qq[i].l, qq[i].r) {
                int now = divisor[j].size();
                fer(o, 0, now - 1) __cnt[divisor[j][o]] = 0;
            }
            ans[qq[i].k] = mx;
            continue;
        }
        if (pos[qq[i].l] ^ last_block) {
            res = 0;
            while (l <= r) {
                int now = divisor[r].size();
                fer(j, 0, now - 1) cnt[divisor[r][j]] = 0;
                --r;
            }
            l = (qq[i].l / siz + 1) * siz, r = l - 1;
            last_block = pos[qq[i].l];
        }
        while (r < qq[i].r) {
            ++r;
            int now = divisor[r].size();
            fer(j, 0, now - 1) {
                ++cnt[divisor[r][j]];
                res = max(res, cnt[divisor[r][j]] * prime[divisor[r][j]]);
            }
        }
        tmp = res, __l = l;
        while (__l > qq[i].l) {
            --__l;
            int now = divisor[__l].size();
            fer(j, 0, now - 1) {
                ++__cnt[divisor[__l][j]];
                tmp = max(tmp, (__cnt[divisor[__l][j]] + cnt[divisor[__l][j]]) * prime[divisor[__l][j]]);
            }
        }
        ans[qq[i].k] = tmp;
        while (__l < l) {
            int now = divisor[__l].size();
            fer(j, 0, now - 1) { __cnt[divisor[__l][j]] = 0; }
            ++__l;
        }
    }
}
signed main() {
    input();
    getans();
    fer(i, 1, q) cout << ans[i] << '\n';
}

I.完美解场

因为每个随从只能攻击一次,易得如果 敌方随从数+圣盾随从数>我方随从数敌方随从数+圣盾随从数 > 我方随从数一定不能完成解场 。若不满足上述条件,由上述条件可得贪心:如果那我方攻击力低的随从破圣盾一定是更优的,先拿我方低攻击力的随从破圣盾。再使用暴力搜索枚举我方随从攻击敌方随从的方式,枚举所有情况,即可得到答案,设询问组数为 tt ,时间复杂度为 O(tm(n!))O(tm(n!))