2022“杭电杯”中国大学生算法设计超级联赛(7)

2022“杭电杯”中国大学生算法设计超级联赛(7)

[题目链接](Search Result (hdu.edu.cn))

D Black Magic

题目大意

给出n个方块,每个方块的左和右都可能是黑或白。将这些方块排成一列,如果两个相邻方块相连接的面都是黑色,那么这两个方块会连在一起。求连通块的最大和最小数量。

题解

思维题。

最大数量:所有L可以全放在最左边,所有R可以全放在最右边,E和B相互间隔放在中间。

最小数量:所有B可以拼在一起,然后再加一个左一个右连成一块,之后把一个L和一个R分为一组拼起来,E放在最后。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int t, e, l, r, b, res1, res2;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> e >> l >> r >> b;
        res1 = res2 = e + l + r + b;
        if (b)
        {
            if (l && r)
                res1 -= b + 1, l--, r--;
            else if (l)
                res1 -= b, l--;
            else if (r)
                res1 -= b, r--;
            else
                res1 -= b - 1;
        }
        if (l && r)
            res1 -= min(l, r);
        if (b - e - 1 > 0)
            res2 -= b - e - 1;
        cout << res1 << " " << res2 << endl;
    }
    return 0;
}

H Triangle Game

题目大意

一个非退化三角形,三边边长分别为a,b,c。现 Kate 和 Emilico 二人做游戏,每轮需要令三角形的一边长度减去一正整数,使这个三角形退化的一方负。Kate 先手,双方均采用最优策略,问 Kate 是否会获胜。

题解

官方题解证明。

在这里插入图片描述

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int t, a, b, c, ans;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> a >> b >> c;
        ans = (a - 1) ^ (b - 1) ^ (c - 1);
        if (ans == 0)
            cout << "Lose" << endl;
        else
            cout << "Win" << endl;
    }
    return 0;
}

C Counting Stickmen

题目大意

有一棵无根树,问能数出多少个火柴人。火柴人有一个头,两个手,一个身体,两条腿。

题解

可以枚举每个点作为火柴人的脖子。

当选定u点是脖子时,对于u的子节点,都可以做头;度数大于等于2的子节点,可以做手;度数大于等于3的子节点,可以做身体。

对于每个u点,可以枚举子节点做身体。统计剩下的子节点中,能做手的方案,再乘以做头的方案。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 5;
const int mod = 998244353;
ll t, n, h, b, d[maxn], hand[maxn], body[maxn], res;
vector<int> g[maxn];
ll C(ll x)
{
    return x * (x - 1) / 2;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> n;
        res = 0;
        for (int i = 1; i <= n; i++)
        {
            g[i].clear();
            d[i] = hand[i] = body[i] = 0;
        }
        for (int i = 1; i < n; i++)
        {
            int u, v;
            cin >> u >> v;
            g[u].push_back(v);
            g[v].push_back(u);
            d[u]++, d[v]++;
        }
        for (int i = 1; i <= n; i++)
        {
            hand[i] = d[i] - 1;
            if (d[i] > 2)
                body[i] = C(d[i] - 1);
        }
        for (int i = 1; i <= n; i++)
        {
            if (d[i] < 4)
                continue;
            h = b = 0;
            for (auto e : g[i])
            {
                h += hand[e];
                b += body[e];
            }
            for (auto e : g[i])
            {
                res = (res + body[e] * (C(h - hand[e]) - (b - body[e])) % mod * (d[i] - 3)) % mod;
            }
        }
        cout << res << endl;
    }
    return 0;
}

B Independent Feedback Vertex Set

题目大意

有一个图,现在要从中找到一个独立点集(点集中的点没有边连接),使得剩下的点可以组成一个森林。每个点都有点权,求最大独立点集的权值。

题解

该题的加边的方式比较特别,加入的边和原边构成一个三元环,答案必须包含每个三元环中的恰好一个点,因为一个点都不选会破坏森林约束,选两个及以上则会破坏独立集约束。

所以初始的三元环中,1,2,3三个点, 有且仅有一个点在独立点集中。

所以一共只有三种情况,在每种情况中,逐个遍历加入的边。对于加入的边,当加入点x,而y和z都不在独立点集中时,x一定得加入独立点集;否则x一定不能加入独立点集。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e5 + 5;
ll t, n, temp, res;
ll w[maxn], u[maxn], v[maxn];
bool vis[maxn];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        res = 0;
        cin >> n;
        for (ll i = 1; i <= n; i++)
            cin >> w[i];
        for (ll i = 4; i <= n; i++)
        {
            ll x, y;
            cin >> x >> y;
            u[i] = x, v[i] = y;
        }
        for (ll i = 1; i <= 3; i++)
        {
            for (ll j = 1; j <= n; j++)
                vis[j] = 0;
            temp = w[i];
            vis[i] = 1;
            for (ll j = 4; j <= n; j++)
            {
                if (vis[u[j]] == 0 && vis[v[j]] == 0)
                    vis[j] = 1, temp += w[j];
            }
            res = max(res, temp);
        }
        cout << res << endl;
    }
    return 0;
}

F Sumire

题目大意

给定一个公式,∑fk(i,B,d) (l<=i<=r),其中f(x,B,d)表示,x在B进制的情况下,出现了多少个d。

题解

数位DP,用dp[i] [j] [0/1] [0/1]表示统计到第i位,从第i位开始的剩余的数字中,有j个位上是d,是否取数字上界,以及是否有前导零。

用记忆化搜索维护转移。转移时需分四种情况考虑:

1、当前位卡住上界。
2、当前位为0。
3、当前位为d。
4、以上都不满足(大大减少遍历的次数)。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e2 + 5;
const ll mod = 1e9 + 7;
ll t, k, B, d, l, r, ans;
ll a[maxn], dp[maxn][maxn][2][2];
ll qpow(ll a, ll n, ll mod)
{
    ll res = 1;
    while (n)
    {
        if (n & 1)
            res = (res * a) % mod;
        a = (a * a) % mod;
        n >>= 1;
    }
    return res;
}
ll dfs(ll pos, ll cnt, bool limit, bool zero)
{
    if (pos < 0 && cnt == 0)
        return 1;
    if (pos < 0)
        return 0;
    if (cnt < 0)
        return 0;
    if (dp[pos][cnt][limit][zero] != -1)
        return dp[pos][cnt][limit][zero];
    dp[pos][cnt][limit][zero] = 0;
    ll up = limit ? a[pos] : B - 1, now = 0, used = 0;
    ll cost = (now == d);
    if (zero && d == 0)
        cost = 0;
    dp[pos][cnt][limit][zero] = (dp[pos][cnt][limit][zero] + dfs(pos - 1, cnt - cost, limit && now == up, zero && now == 0)) % mod;
    used++;
    if (now != d && d <= up)
    {
        now = d;
        used++;
        dp[pos][cnt][limit][zero] = (dp[pos][cnt][limit][zero] + dfs(pos - 1, cnt - 1, limit && now == up, 0)) % mod;
    }
    if (now != up)
    {
        now = up;
        used++;
        dp[pos][cnt][limit][zero] = (dp[pos][cnt][limit][zero] + dfs(pos - 1, cnt, limit && now == up, 0)) % mod;
    }
    dp[pos][cnt][limit][zero] = (dp[pos][cnt][limit][zero] + dfs(pos - 1, cnt, 0, 0) * max(0ll, up - used + 1) % mod) % mod;
    return dp[pos][cnt][limit][zero];
}
ll solve(ll x)
{
    ll len = 0;
    while (x)
    {
        a[len++] = x % B;
        x /= B;
    }
    memset(dp, -1, sizeof dp);
    ans = 0;
    for (ll i = 1; i <= len; i++)
        ans = (ans + dfs(len - 1, i, 1, 1) * qpow(i, k, mod) % mod) % mod;
    return ans;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> k >> B >> d >> l >> r;
        cout << (solve(r) - solve(l - 1) + mod) % mod << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值