NZOJ 模拟赛8

T1 布尔(CF2030C)

CF2030C

爱丽丝和鲍勃正在玩一个游戏。游戏中有一个由 n 个布尔值组成的列表,每个布尔值要么为真,要么为假,以长度为 n 的二进制字符串表示(其中 1 表示为真,0 表示为假)。最初,布尔值之间没有运算符。

爱丽丝和鲍勃将轮流在布尔之间放置 or 或 and,爱丽丝先放。因此,由于有 n 个布尔值,游戏将由 n-1 个回合组成。爱丽丝的目标是最后的语句结果为 "真",而鲍勃的目标是最后的语句结果为 "假"。给定布尔值列表,判断如果双方都以最佳方式下棋,爱丽丝是否会赢。

要对最终表达式进行求值,请重复执行以下步骤,直到该语句只包含一个 "true "或 "false":

  • 如果语句中包含 and 运算符,则任选一个,并用其求值替换其周围的子表达式。
  • 否则,语句包含 or 运算符。任选一个,并用其运算结果替换其周围的子表达式。

例如,表达式 true 或 false 和 false 的运算结果为 true or(false and false)= true 或 false = true。可以看出,任何复合语句的结果都是唯一的。

二进制字符串是仅由字符 0 和 1 组成的字符串。

经过尝试容易发现,当存在连续两个 \(\texttt{TRUE}\) 时,先手往中间放一个 \(\texttt{or}\),后手无论如何放置,先手在另一侧放置一个 \(\texttt{or}\) 一定可以保证结果为 \(\texttt{TRUE}\);如果头尾存在 \(\texttt{TRUE}\),那么先手直接放置 \(\texttt{or}\) 就可以必胜;反之必败。

参考代码

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, m;
char s[N];

void solve()
{
    cin >> n;
    cin >> s;
    for (int i = 1; i < n; i ++ )
        if (s[i - 1] == '1' && s[i] == '1')
            return puts("YES"), void();
    if (s[0] == '1' || s[n - 1] == '1') return puts("YES"), void();
    puts("NO");
}

int main()
{
    int T = 1;
    cin >> T;
    while (T -- ) solve();
    return 0;
}

T2 Max × Sum(ABC376E)

ABC376E

给你长度为 N 的序列: A = (A1, A2, ..., AN) 和 B = (B1, B2, ..., BN) 。
设 S 是大小为 K 的 {1, 2, ..., N} 的子集。求以下表达式的最小可能值:

\[\displaystyle \left(\max_{i \in S} A_i\right) \times \left(\sum_{i \in S} B_i\right) \]

考虑贪心,按照 \(A_i\) 的大小排序,如果可以确定 \(\max\limits_{i \in S} A_i\),我们发现 \(\sum\limits_{i \in S} B_i\) 也是唯一确定的,一定是前 \(i\) 个元素的前 \(k\) 小值的和,否则不优,因为我们想要乘积最小,因此我们维护一个优先队列存储前 \(k\) 小值即可。

参考代码

#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 2e5 + 10;
int n, k, a[N], b[N];
PII c[N];

int main()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    for (int i = 1; i <= n; i ++ ) cin >> b[i];
    for (int i = 1; i <= n; i ++ ) c[i] = {a[i], b[i]};
    sort(c + 1, c + n + 1);
    priority_queue<ll> heap;
    ll ans = 1e18, sum = 0;
    for (int i = 1; i <= k; i ++ ) heap.push(c[i].second), sum += c[i].second;
    ans = min(ans, sum * c[k].first);
    for (int i = k + 1; i <= n; i ++ )
    {
        ll t = heap.top();
        if (t > c[i].second) heap.pop(), sum -= t, heap.push(c[i].second), sum += c[i].second;
        ans = min(ans, sum * c[i].first);
    }
    cout << ans << endl;
    return 0;
}

T3 竞赛(CF2024D)

CF2024D

现在已经是 3024 年,问题的创意早已枯竭,奥林匹克竞赛现在以修改后的个人形式进行。奥林匹克竞赛由 n 个问题组成,编号从 1 到 n 。其中第 i 个问题有自己的分数 ai 和一定的参数 bi (1 ≤ bi ≤ n)。

最初,测试系统会给参与者第 1 个问题。当参与者得到第 i 个问题时,他们有两个选择:

  • 他们可以提交问题并获得 ai 分;
  • 他们可以跳过这个问题,在这种情况下,他们将永远无法提交这个问题。

然后,测试系统会从索引为 j 的问题中为参与者选择下一个问题,这样,参与者就可以提交下一个问题:

  • 如果他提交了第 i 个问题,系统就会查看索引为 j < i 的问题;
  • 如果他跳过了第 i 个问题,则会查看索引为 j ≤ bi 的问题。

在这些问题中,它会选择一个索引最大的问题,这个索引以前没有给过参与者(他以前既没有提交也没有跳过这个问题)。如果没有这样的问题,则该参赛者的比赛结束,其成绩等于所有提交问题的分数总和。特别是,如果参赛者提交了第一个问题,那么他们的比赛就结束了。需要注意的是,参赛者最多只能收到每个问题 1 次。

Prokhor 已经为奥林匹克竞赛做了充分准备,现在他可以提交任何问题。请帮助他确定他能获得的最高分数。

我们发现如果可以提交第 \(i\) 个问题,那么一定可以提交所有 \(1 \le j < i\) 中未被跳过的问题 \(j\),因此,我们的问题转化为了我们能够提交问题 \(i\),并且前 \(i\) 个问题我们跳过的分数为多少。

考虑最短路模型,我们如果跳过了问题 \(i\),那么我们失去的分数为 \(a_i\),但是我们也因此可以回答 \(b_i\) 题前的所有题,我们想要分数尽可能高,那么就是想办法尽可能的能够回答更多的问题,所以我们给 \(i \to b_i\) 连一条权值为 \(a_i\) 的有向边。由于所有的问题,如果我们能够回答第 \(i\) 个问题,我们都可以先不考虑第 \(i\) 个问题而考虑更前面的问题,因此我们给 \(i \to i - 1\) 连一条权值为 \(0\) 的有向边,代表我可以考虑这个问题更前列的问题,跑一次最短路,就可以知道我想回答 \(i\) 问题前所有的问题需要的代价,注意到所有被跳过的问题只会被考虑一次,否则到达同一个点不满足最短路模型,因此我们的答案为 \(i\) 问题前所有问题的分数减去到达 \(i\) 问题的代价。

参考代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> PII;
const int N = 4e5 + 10;
int n, a[N], b[N];
int h[N], e[N << 1], w[N << 1], ne[N << 1], idx;
ll dist[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void dijkstra()
{
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0, heap.push({0, 1});
    while (heap.size())
    {
        PII t = heap.top();
        heap.pop();
        ll d = t.first;
        int u = t.second;
        if (st[u]) continue;
        st[u] = 1;
        for (int i = h[u]; ~i; i = ne[i])
        {
            int j = e[i];
            if (d + w[i] < dist[j])
            {
                dist[j] = d + w[i];
                if (!st[j]) heap.push({dist[j], j});
            }
        }
    }
}

int main()
{
    cin >> n;
    ll sum = 0, ans = 0;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    for (int i = 1; i <= n; i ++ )
    {
        cin >> b[i];
        if (i - 1) add(i, i - 1, 0);
        add(i, b[i], a[i]);
    }
    dijkstra();
    for (int i = 1; i <= n; i ++ )
    {
        sum += a[i];
        ans = max(sum - dist[i], ans);
    }
    cout << ans << endl;
    return 0;
}

T4 三队划分(ABC375E)

ABC375E

有 N 人分成三个小组。
人数编号为 1, 2, ..., N ,团队编号为 1, 2, 3 。目前, i 属于 Ai 小组。
每个人都有一个名为强度的值, i 的强度为 Bi 。一个团队的强度被定义为其成员强度的总和。
确定是否可能有零个或更多的人交换团队,从而使所有团队的实力相等。如果可能,求最少需要多少人换队才能达到这个目的。
除了队伍 1 、 2 、 3 以外,您不能创建新的团队。

3 ≤ N ≤ 100
Ai ∈ {1, 2, 3}
对于每个 x ∈ {1, 2, 3} ,都存在一些 i 使得 Ai = x 。
1 ≤ Bi,且所有 Bi 的和不超过 1500。
所有输入值都是整数。

考虑动态规划,首先对于所有的人如果强度之和不是 \(3\) 的倍数,那么一定不存在结果。

类似于背包问题,我们记 \(dp[i][j][k]\) 表示考虑前 \(i\) 个人,队伍 \(1\) 的力量和为 \(j\),队伍 \(2\) 的力量和为 \(k\),队伍 \(3\) 的力量和可以被 \(i, j\) 唯一确定。

对于下一个状态,我们考虑将第 \(i + 1\) 个人分别插入进 \(3\) 支队伍,如果这个人本来就在这个队伍,那么贡献为 \(0\),否则贡献为 \(1\)

例如第 \(i + 1\) 个人在第 \(1\) 支队伍,那么有:

\[dp[i + 1][j + B_i][k] = dp[i][j][k] \]

\[dp[i + 1][j][k + B_i] = dp[i][j][k] + 1 \]

\[dp[i + 1][j][k] = dp[i][j][k] + 1 \]

他们分别为插入第 \(1, 2, 3\) 支队伍的代价转移,对所有合法的状态取一个 \(\min\) 即可,初始化 \(dp\) 数组为极大值,如果结束后该状态不存在则输出 \(-1\),否则答案为最小操作次数。

参考代码

#include<bits/stdc++.h>
using namespace std;
const int N = 510, M = 110;
int n, sum, a[N], b[N], dp[M][N][N];

int main()
{
    memset(dp, 0x3f, sizeof dp);
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i] >> b[i], sum += b[i];
    if (sum % 3) return puts("-1"), 0;
    sum /= 3, dp[0][0][0] = 0;
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 0; j <= sum; j ++ )
        {
            for (int k = 0; k <= sum; k ++ )
            {
                if (a[i] == 1)
                {
                    if (j + b[i] <= sum) dp[i][j + b[i]][k] = min(dp[i][j + b[i]][k], dp[i - 1][j][k]);
                    if (k + b[i] <= sum) dp[i][j][k + b[i]] = min(dp[i][j][k + b[i]], dp[i - 1][j][k] + 1);
                    dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j][k] + 1);
                }
                else if (a[i] == 2)
                {
                    if (j + b[i] <= sum) dp[i][j + b[i]][k] = min(dp[i][j + b[i]][k], dp[i - 1][j][k] + 1);
                    if (k + b[i] <= sum) dp[i][j][k + b[i]] = min(dp[i][j][k + b[i]], dp[i - 1][j][k]);
                    dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j][k] + 1);
                }
                else
                {
                    if (j + b[i] <= sum) dp[i][j + b[i]][k] = min(dp[i][j + b[i]][k], dp[i - 1][j][k] + 1);
                    if (k + b[i] <= sum) dp[i][j][k + b[i]] = min(dp[i][j][k + b[i]], dp[i - 1][j][k] + 1);
                    dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j][k]);
                }
            }
        }
    }
    if (dp[n][sum][sum] == 0x3f3f3f3f) puts("-1");
    else cout << dp[n][sum][sum];
    return 0;
}
posted @ 2024-10-21 11:13  YipChip  阅读(56)  评论(0编辑  收藏  举报