2019中国大学生程序设计竞赛-女生专场 题解

本文精选了算法竞赛中常见的题目类型及其解决方案,包括优先队列应用、树链剖分、字符串操作、图形区域计算等,提供了详细的代码实现及思路解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1001.Ticket
签到
1002. Gcd
签到
1003. Function

解法:我们用一个优先队列存fi(x + 1) - fi(x),初始化我们把所有方程的x设置为1,并且全部存进优先队列,最小值优先,每次出队,我们都把自变量调大一个单位继续进队,依次进出队m - n 次,最后取出优先队列所有函数值并求和即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10;
ll d[maxn];
int vis[maxn], n, m;
struct node {
    int a, b, c, x;
    ll v;
    bool operator<(const node& t) const {
        return v > t.v;
    }
};
priority_queue<node> q;
ll gao(int a, int b ,int c, int x) {
    return a * x * x + b * x + c;
}
int main() {
    int a, b, c;
    cin>>n>>m;
    for (int i = 1; i <= n; i++) {
        cin>>a>>b>>c;
        ll v = gao(a, b, c, 2) - gao(a, b, c, 1);
        q.push(node{a, b, c, 1, v});
    }
    for (int i = 0; i < m - n; i++) {
        node tmp = q.top();
        q.pop();
        tmp.x++;
        tmp.v = gao(tmp.a, tmp.b, tmp.c, tmp.x + 1) - gao(tmp.a, tmp.b, tmp.c, tmp.x);
        q.push(tmp);
    }
    ll ans = 0;
    while (!q.empty()) {
        node tmp = q.top();
        ans += gao(tmp.a, tmp.b, tmp.c, tmp.x);
        q.pop();
    }
    cout<<ans<<endl;
}

1004. Tree
裸树链剖分

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10;
ll sum[maxn *4];
int mx[maxn * 4], top[maxn], dep[maxn], son[maxn];
int sz[maxn], cnt, id[maxn], f[maxn], n, a[maxn];
vector<int> G[maxn];
void dfs(int u, int fa) {
    f[u] = fa;
    dep[u] = dep[fa] + 1;
    sz[u] = 1;
    for (auto v : G[u])
        if (v != fa) {
            dfs(v, u);
            sz[u] += sz[v];
            if (sz[v] > sz[son[u]])
                son[u] = v;
        }
}
#define ls o * 2
#define rs o * 2 + 1
void update(int o, int l, int r, int k, int v) {
    if (l == r) {
        mx[o] = sum[o] = v;
        return;
    }
    int m = (l + r) / 2;
    if (k <= m)
        update(ls, l, m, k, v);
    else
        update(rs, m + 1, r, k, v);
    mx[o] = max(mx[ls], mx[rs]);
    sum[o] = sum[ls] + sum[rs];
}
void dfs2(int u, int rt) {
    top[u] = rt;
    id[u] = ++cnt;
    update(1, 1, n, cnt, a[u]);
    if (son[u])
        dfs2(son[u], rt);
    for (auto v : G[u])
        if (v != f[u] && v != son[u])
            dfs2(v, v);
}
void up(int o, int l, int r, int ql, int qr) {
    if (mx[o] <= 1)
        return;
    int m = (l + r) / 2;
    if (l >= ql && r <= qr) {
        if (l == r) {
            sum[o] = mx[o] = sqrt(mx[o]);
            return;
        }
        if (mx[ls] > 1)
            up(ls, l, m, ql, qr);
        if (mx[rs] > 1)
            up(rs, m + 1, r, ql, qr);
        mx[o] = max(mx[ls], mx[rs]);
        sum[o] = sum[ls] + sum[rs];
        return;
    }
    if (ql <= m)
        up(ls, l, m, ql, qr);
    if (qr > m)
        up(rs, m + 1, r, ql, qr);
    mx[o] = max(mx[ls], mx[rs]);
    sum[o] = sum[ls] + sum[rs];
}
ll qu(int o, int l, int r, int ql, int qr) {
    if (l >= ql && r <= qr)
        return sum[o];
    int m = (l + r) / 2;
    ll res = 0;
    if (ql <= m)
        res += qu(ls, l, m, ql, qr);
    if (qr > m)
        res += qu(rs, m + 1, r, ql, qr);
    return res;
}
void up_path(int x, int y) {
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]])
            swap(x, y);
        up(1, 1, n, id[top[x]], id[x]);
        x = f[top[x]];
    }
    if (id[x] > id[y])
        swap(x, y);
    up(1, 1, n, id[x], id[y]);
}
ll qu_path(int x, int y) {
    ll res = 0;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]])
            swap(x, y);
        res += qu(1, 1, n, id[top[x]], id[x]);
        x = f[top[x]];
    }
    if (id[x] > id[y])
        swap(x, y);
    return res + qu(1, 1, n, id[x], id[y]);
}
int main() {
    int m, opt, u, v;
    cin>>n>>m;
    for (int i = 1; i <= n; i++)
        cin>>a[i];
    for (int i = 1; i < n; i++) {
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1, 0);
    dfs2(1, 1);
    while (m--) {
        cin>>opt>>u>>v;
        if (!opt)
            up_path(u, v);
        else
            printf("%lld\n", qu_path(u, v));
    }
}

1005. Checkout

解法:我们用map[u]存u节点所有儿子权值信息,然后进行一次dfs,枚举每个节点u当领导的贡献,首先 u 的原领导 rt 离职了,那么ans -= mp[rt][a[rt]],然后我们要把 u 的儿子和 rt 的儿子合并,枚举 u 所有儿子权值 val,ans += mp[u][val] * mp[rt][val],不过 u 作为 rt 的儿子和 u 的儿子合并多计算了一次贡献,所以ans -= mp[u][a[u]]
update:上述做法没考虑周全,已被评论区数据hack(感谢),如果 rt 还有父亲 root,那么我们还用统计 rt 变成 u 之后对 root 以及 root 的儿子产生的贡献的变化,这个问题很简单,交给你啦
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10;
map<int, int> mp[maxn];
int a[maxn], f[maxn];
vector<int> G[maxn];
ll sum, ans[maxn];
void dfs(int u, int fa) {
    f[u] = fa;
    for (auto v : G[u])
    if (v != fa) {
        dfs(v, u);
        if (a[v] == a[u])
            sum++;
        sum += mp[u][a[v]];
        mp[u][a[v]]++;
    }
}
void dfs2(int u) {
    int rt = f[u];
    if (rt) {
        ll res = sum - mp[rt][a[rt]];
        for (auto tmp : mp[u])
            res += 1ll * tmp.second * mp[rt][tmp.first];
        res -= mp[u][a[u]];
        int root = f[rt];
        if (root) {
            res -= (mp[root][a[rt]] - 1);
            if (a[root] == a[rt])
                res--;
            if (a[u] == a[root])
                res++;
            res += mp[root][a[u]];
        }
        ans[rt] = max(ans[rt], res);
    }
    if (G[u].size() == 1 && u != 1) {
        ans[u] = sum - mp[rt][a[u]] + 1;
        if (a[rt] == a[u])
            ans[u]--;
    }
    for (auto v : G[u])
        if (v != f[u])
            dfs2(v);
}
int main() {
    int n, m, u, v;
    cin>>n>>m;
    for (int i = 1; i <= n; i++)
        cin>>a[i];
    for (int i = 1; i < n; i++) {
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1, 0);
    dfs2(1);
    for (int i = 1; i <= n; i++)
        printf("%lld%c", ans[i], i == n ? '\n' : ' ');
}

1006. String

解法:我们设d[i][j][p]为前 i 个字符,含有 p 段字符串,最后一个字母是 j + ‘a’ 所需要最少的操作数,对于第 i 个字符 c,我可以不修改它,d[i][c][p] = min(d[i][c][p], d[i - 1][c][p]),对于所有 j (j != c),d[i][c][p] = min(d[i][c][p], d[i - 1][j][p - 1]),我们修改它,那么最好是直接整段修改到 pre= max(0, i - L),枚举字符 c,对于所有 j != c,显然d[i][c][p] = min(d[i][c][p], d[pre][j][p - 1] + 1),我们可以预处理出d[pre][j][p - 1] 的前缀最大值,后缀最大值,再进行转移,复杂度 n * 26 * K。//最后答案应该还有枚举段数,之前写瑕疵了,感谢网友的指出
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int d[maxn][26][11], sum[28], suf[28];
char s[maxn];
void up(int &x, int y) {
    x = min(x, y);
}
int main() {
    int n, l, k;
    cin>>n>>l>>k>>s + 1;
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < 26; j++)
            for (int p = 0; p <= k; p++)
                d[i][j][p] = 1e9;

    d[1][s[1] - 'a'][1] = 0;
    for  (int i = 0; i < 26; i++)
        up(d[1][i][1], 1);

    for (int i = 2; i <= n; i++)
    for (int p = 1; p <= min(i, k); p++) {
        up(d[i][s[i] - 'a'][p], d[i - 1][s[i] - 'a'][p]);
        for (int j = 0; j < 26; j++)
            if (j != s[i] - 'a')
                up(d[i][s[i] - 'a'][p], d[i - 1][j][p - 1]);
        int pre = max(0, i - l);

        suf[25] = d[pre][25][p - 1];
        for (int j = 24; j >= 0; j--)
            suf[j] = min(suf[j + 1], d[pre][j][p - 1]);

        int mn = d[pre][0][p - 1];
        for (int j = 0; j < 26; j++) {
            up(d[i][j][p], d[pre][j][p] + 1);
            int t1 = 1e9, t2 = 1e9;
            if (j)
                up(t1, mn);
            if (j != 25)
                up(t2, suf[j + 1]);
            mn = min(mn, d[pre][j][p - 1]);
            up(d[i][j][p], min(t1, t2) + 1);
        }
    }
    int ans = 1e9;
    for (int i = 0; i < 26; i++)
        for (int j = 1; j <= k; j++)
            up(ans, d[n][i][j]);
    cout<<ans<<endl;
}
/*
4 1 4
bbbb
*/

1007. Circle
咕咕咕

1008. Clock
咕咕咕

1009. Union
没读题(应该不会)

1010. Tangram

解法:我们发现,加一条线,最多有5条线相交,那么 ans += 6,再加一条线,就会有6条线与之相交,那么ans += 7,那么很显然,加n条边的总区域数是:7 + (6 + n + 5) * n / 2。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main() {
    ll n;
    while (cin>>n) {
        ll res = 7 + (11 + n) * n / 2;
        cout<<res << '\n';
    }
}

1011. Tetris
咕咕咕

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙橘子猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值