USACO25OPEN题解

[USACO25OPEN] Hoof Paper Scissors Minus One B

题目描述

在一局蹄子剪刀布游戏中,Bessie 和 Elsie 可以出 \(N\)\(1 \leq N \leq 3000\))种不同的蹄子手势,编号为 \(1\dots N\),每个手势对应一种不同的材料。不同材料之间的相互关系有一个复杂的表格,基于该表格,可能会:

  • 一种手势获胜,另一种失败。
  • 两种手势平局。

蹄子剪刀布-1.0 的规则类似,但 Bessie 和 Elsie 可以各自出两个手势,每只蹄子出一个。在观察到她们所出的四个手势后,她们各自选择其中一个手势进行游戏。结果根据正常的蹄子剪刀布的规则决定。

给定 Elsie 计划在每局游戏中出的 \(M\)\(1 \leq M \leq 3000\))个手势组合,Bessie 想知道有多少种不同的手势组合可以确保战胜 Elsie。一个手势组合定义为一个有序对 \((L,R)\),其中 \(L\) 为奶牛用左蹄出的手势,\(R\) 为奶牛用右蹄出的手势。你能为每局游戏进行计算吗?

输入格式

输入的第一行包含两个空格分隔的整数 \(N\)\(M\),表示蹄子手势的数量,以及 Bessie 与 Elsie 进行的游戏局数。
以下 \(N\) 行,第 \(i\) 行由 \(i\) 个字符 \(a_{i,1}a_{i,2}\ldots a_{i,i}\) 组成,其中 \(a_{i,j} \in \{\texttt D,\texttt W,\texttt L\}\)。如果 \(a_{i,j} = \texttt D\),则手势 \(i\) 平于手势 \(j\)。如果 \(a_{i,j} = \texttt W\),则手势 \(i\) 胜于手势 \(j\)。如果 \(a_{i,j} = \texttt L\),则手势 \(i\) 负于手势 \(j\)。输入保证 \(a_{i,i} = \texttt D\)

以下 \(M\) 行,每行包含两个空格分隔的整数 \(s_1\)\(s_2\),其中 \(1 \leq s_1,s_2 \leq N\),表示 Elsie 在该局游戏中的手势组合。

输出格式

输出 \(M\) 行,其中第 \(i\) 行包含在第 \(i\) 局游戏中 Bessie 可以确保战胜 Elsie 的手势组合数量。

输入输出样例 #1

输入 #1

3 3
D
WD
LWD
1 2
2 3
1 1

输出 #1

0
0
5

说明/提示

在样例 1 解释:这对应于原始的蹄子剪刀布,我们可以设蹄子为 \(1\),布为 \(2\),剪刀为 \(3\)。布战胜蹄子,蹄子战胜剪刀,剪刀战胜布。Bessie 无法确保战胜蹄子 + 布或布 + 剪刀的组合。然而,如果 Elsie 出蹄子 + 蹄子,Bessie 可以采用以下任一组合进行反击。

  • 布 + 布
  • 布 + 剪刀
  • 布 + 蹄子
  • 蹄子 + 布
  • 剪刀 + 布

如果 Bessie 出这些组合中的任意一个,她可以确保通过出布来获胜。

  • 测试点 \(2\sim 6\)\(N,M\le 100\)
  • 测试点 \(7\sim 12\):没有额外限制。

代码

#include<stdio.h>
#include<iostream>
using namespace std;
typedef long long ll;
ll win[3005][3005];

int main() {
    ll n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= i; j++){
            char ch;
            cin >> ch;
            if(ch == 'W')   win[j][i] = 1;
            if(ch == 'L')   win[i][j] = 1;
        }
    }
    ll s1, s2;
    for(int i = 1; i <= m; i++){
        ll ans = 0;
        cin >> s1 >> s2;
        ll cnt = 0;
        for(int i = 1; i <= n; i++){
            if(win[s1][i] && win[s2][i])    cnt ++;
        }
        ans += cnt * (n - cnt) * 2;
        ans += cnt * cnt;
        cout << ans << endl;
    }
    return 0;
}

[USACO25OPEN] More Cow Photos B

题目描述

今天奶牛们的心情特别顽皮!Farmer John 只是想拍摄一张奶牛们排成一行的照片,但她们总是在他得到机会按下快门之前移动位置。

具体地说,FJ 的 \(N\) 头奶牛(\(1 \le N \le 10^5\))每一头的身高都是 \(1\)\(N\) 的整数。FJ 想要拍摄奶牛以一种特定的顺序排成一行的照片。如果奶牛们排成一行时从左到右有身高 \(h_1, \dots, h_K\),他希望奶牛们的身高拥有以下三个性质:

  • 他希望奶牛们的身高先递增再递减。形式化地说,必须存在一个整数 \(i\) 使得 \(h_1 \le \dots \le h_i \ge \dots \ge h_K\)
  • 他不希望任何奶牛与另一头身高完全相同的奶牛相邻。形式化地说,对于所有 \(1 \le i < K\)\(h_i \neq h_{i+1}\)
  • 他希望照片是对称的。形式化地说,如果 \(i + j = K+1\),则 \(h_i = h_j\)

FJ 希望照片中包含尽可能多的奶牛。具体地说,FJ 可以移除一些奶牛并重新排列余下的奶牛。计算 FJ 在满足他的限制的情况下可以在照片中包含的奶牛的最大数量。

输入格式

你需要回答多个测试用例。
输入的第一行包含一个整数 \(T\)\(1 \leq T \leq 10^5\)),为测试用例的数量。以下为 \(T\) 个测试用例。

每一个测试用例的第一行包含一个整数 \(N\)。第二行包含 \(N\) 个整数,为可用的 \(N\) 头奶牛的身高。奶牛们的身高在 \(1\)\(N\) 之间。

输入保证所有测试用例的 \(N\) 之和不超过 \(10^6\)

输出格式

输出 \(T\) 行,第 \(i\) 行包含第 \(i\) 个测试用例的答案。每行包含一个整数,表示 FJ 可以在照片中包含的奶牛的最大数量。

输入输出样例 #1

输入 #1

2
4
1 1 2 3
4
3 3 2 1

输出 #1

3
1

说明/提示

对于第一个测试用例,FJ 可以选择身高为 \(1\)\(1\)\(3\) 的奶牛,并重新排列为 \([1,3,1]\),满足所有条件。对于第二个测试用例,FJ 可以选择身高为 \(3\) 的奶牛以组成一张合法的照片。

  • 测试点 \(2\sim3\)\(T\le 100\)\(N \le 7\)
  • 测试点 \(4\sim5\)\(T \le 10^4\),所有奶牛的身高不超过 \(10\)
  • 测试点 \(6\sim11\):没有额外限制。

代码

#include<stdio.h>
#include<iostream>
using namespace std;
const int N = 100005;
int cnt[N];

int main() {
    int t;
    cin >> t;
    while(t--){
        int n, a;
        int max_val = 0, ans = 0;
        cin >> n;
        for(int i = 1; i <= n; i++)     cnt[i] = 0;
        for(int i = 1; i <= n; i++){
            cin >> a;
            if(cnt[a] <= 1)     cnt[a] ++;
            max_val = max(max_val, a);
        }
        for(int i = 1; i < max_val; i++){
            if(cnt[i] == 2)     ans += 2;
        }
        cout << ans + 1 << endl;
    }
    return 0;
}

[USACO25OPEN] Compatible Pairs S

题目描述

在乡村深处,Farmer John 的奶牛们不仅仅是普通的农场动物——她们是一个奶牛地下情报网络的一部分。每头奶牛都有一个识别码,由奶牛密码专家精心分配。然而,由于 Farmer John 相当随意的标记系统,一些奶牛最终得到了相同的识别码。

Farmer John 注意到有 \(N\)\(1\leq N\leq 2\cdot10^5\))个不同的识别码,对于每一个不同的识别码 \(d_i\)\(0\leq d_i\leq 10^9\)),有 \(n_i\)\(1\leq n_i\leq 10^9\))头奶牛共用该识别码。

这些奶牛们只能一对一地进行通信,她们的秘密加密方法有一个严格的规则:两头奶牛只有当她们不是同一头奶牛且她们的识别码之和等于 \(A\)\(B\) 时(\(0\leq A\leq B\leq 2\cdot10^9\)),她们才能交换信息。每头奶牛同时只能参与一个对话(即,没有奶牛可以属于多对)。

输入格式

输入的第一行包含 \(N\)\(A\)\(B\)

接下来 \(N\) 行每行包含 \(n_i\)\(d_i\)。所有 \(d_i\) 均不相同。

输出格式

输出一行,包含同时可以组成的独立通信对的最大数量。

注意这个问题涉及到的整数可能需要使用 64 位整数类型(例如,C/C++ 中的 "long long")。

输入输出样例 #1

输入 #1

4 4 5
17 2
100 0
10 1
200 4

输出 #1

118

输入输出样例 #2

输入 #2

4 4 5
100 0
10 1
100 3
20 4

输出 #2

30

说明/提示

样例一解释

一头识别码为 \(0\) 的奶牛可以与另一头识别码为 \(4\) 的奶牛通信,因为她们的识别码之和为 \(4\)。由于总共有 \(100\) 头识别码为 \(0\) 的奶牛和 \(200\) 头识别码为 \(4\) 的奶牛,因此可以组成至多 \(100\) 对此识别码组合的通信对。

一头识别码为 \(4\) 的奶牛也可以与另一头识别码为 \(1\) 的奶牛通信(和为 \(5\))。有 \(10\) 头识别码为 \(1\) 的奶牛和 \(100\) 头余下未配对的识别码为 \(4\) 的奶牛,组成另外 \(10\) 对。

最后,一头识别码为 \(2\) 的奶牛可以与另一头相同识别码的奶牛通信。由于总共有 \(17\) 头识别码为 \(2\) 的奶牛,因此可以另外组成至多 \(8\) 对。

总共组成 \(100+10+8=118\) 对通信对。可以证明这是最大可能的对数。

样例二解释

将识别码 \(0\)\(4\) 配对可以组成 \(20\) 对,而将识别码 \(1\)\(3\) 配对可以组成 \(10\) 对。可以证明这是最优配对方案,总共组成 \(30\) 对。

测试点性质

  • 测试点 \(3\sim4\)\(A=B\)
  • 测试点 \(5\sim7\)\(N\le 1000\)
  • 测试点 \(8\sim12\):无额外限制。

代码

做题思路:对所有相同识别码的奶牛建点。用map存储奶牛数量。

如果发现当前存在两个识别码和为\(a\)或者\(b\)的奶牛对,那么在这两个点之间连一条边。且每个点最多有两条边,可能还存在0或1条边。因此从连了1条边的点出发, 开始扩散,贪心逐渐累加统计结果。

考虑ID码为\(2/a\)或者\(2/b\)的自环情况,可以等所有不同的点配对完之后,剩下的数量自我内部消化配对。
84pt问题:
没有考虑到当\(A==B\)时会存在重边情况,多加一个判断即可AC。

//p12026
#include<stdio.h>
#include<iostream>
#include<map>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
const int N = 200005;
map<ll, ll> mp, idx;
vector<ll> edge1[N], vc;
queue<ll> q;
ll cnt[N], a[5];
bool flag[N];


int main() {
    ll n;
    ll x, y, c;
    cin >> n >> a[1] >> a[2];
    for(ll i = 1; i <= n; i++){
        cin >> x >> y;
        mp[y] = x;
        cnt[i] = x;
        idx[y] = i;
        if(a[2] == a[1])  c = 1;
        else c = 2;
        for(ll j = 1; j <= c; j++){
            if(a[j] >= y && mp[a[j] - y]){
                //连边
                if(y != a[j] - y){
                    edge1[idx[a[j]-y]].push_back(idx[y]);
                    edge1[idx[y]].push_back(idx[a[j]-y]);
                }
                else    vc.push_back(idx[y]);
            }
        }
    }
    ll ans = 0;
    for(ll i = 1; i <= n; i++){
        if(edge1[i].size() == 1){
            q.push(i);
            flag[i] = 1;
        }
    }
    while(!q.empty()){
        ll t = q.front();
        q.pop();
        for(ll i = 0; i < edge1[t].size(); i++){
            ll temp = edge1[t][i];
            if(cnt[temp] > cnt[t]){
                ans += cnt[t];
                cnt[temp] -= cnt[t];
                cnt[t] = 0;
            }
            else{
                ans += cnt[temp];
                cnt[t] -= cnt[temp];
                cnt[temp] = 0;
            }
            if(!flag[temp]){
                q.push(temp);
                flag[temp] = 1;
            }
        }
    }
    for(ll i = 0; i < vc.size(); i++)      ans += cnt[vc[i]] / 2;
    cout << ans << endl;
    return 0;
}

[USACO25OPEN] Moo Decomposition G

题目描述

给定一个由 \(\texttt{M}\)\(\texttt{O}\) 组成的长字符串 \(S\) 和一个整数 \(K \geq 1\)。计算将 \(S\) 分解为若干子序列的方式数,其中每个子序列形如 \(\texttt{MOOO...O}\)(恰好包含 \(K\)\(\texttt{O}\)),结果对 \(10^9+7\) 取模。

由于字符串非常长,我们不直接给出 \(S\)。而是给定一个整数 \(L\)\(1 \leq L \leq 10^{18}\))和一个长度为 \(N\) 的字符串 \(T\)\(1 \leq N \leq 10^6\))。字符串 \(S\)\(T\) 重复 \(L\) 次拼接而成。

输入格式

第一行包含 \(K\)\(N\)\(L\)

第二行包含长度为 \(N\) 的字符串 \(T\),每个字符是 \(\texttt{M}\)\(\texttt{O}\)

保证 \(S\) 的分解方式数不为零。

输出格式

输出字符串 \(S\) 的分解方式数,对 \(10^9+7\) 取模。

输入输出样例 #1

输入 #1

2 6 1
MOOMOO

输出 #1

1

输入输出样例 #2

输入 #2

2 6 1
MMOOOO

输出 #2

6

输入输出样例 #3

输入 #3

1 4 2
MMOO

输出 #3

4

输入输出样例 #4

输入 #4

1 4 100
MMOO

输出 #4

976371285

说明/提示

样例一解释:唯一分解方式是将前三个字符组成一个 \(\texttt{MOO}\),后三个字符组成另一个 \(\texttt{MOO}\)

样例二解释:共有六种不同的分解方式(大写字母组成一个 \(\texttt{MOO}\),小写字母组成另一个 \(\texttt{MOO}\)):

  1. \(\texttt{MmOOoo}\)
  2. \(\texttt{MmOoOo}\)
  3. \(\texttt{MmOooO}\)
  4. \(\texttt{MmoOOo}\)
  5. \(\texttt{MmoOoO}\)
  6. \(\texttt{MmooOO}\)

样例四解释:注意:结果需对 \(10^9+7\) 取模。

  • 测试点 \(5\sim7\)\(K=1\)\(L=1\)
  • 测试点 \(8\sim10\)\(K=2\)\(N \leq 1000\)\(L=1\)
  • 测试点 \(11\sim13\)\(K=1\)
  • 测试点 \(14\sim19\)\(L=1\)
  • 测试点 \(20\sim25\):无额外限制。

代码

分析题意,要求将\(S\)分解为若干个子序列。那么序列\(T\)一定满足序列中\(O\)的个数为\(M\)的个数的\(K\)倍,并且最开始字符一定为\(M\),否则会有多出来的字符,无法完全分解。

对于单个序列\(T\),先进行拆解。因为后面的\(M\)不能用前面剩下的\(O\),只能用后面的\(O\),所以在两个\(M\)中间的\(O\)有且只能和前面的\(M\)配对。

考虑从后往前倒推的情况,那么每个\(M\)所匹配的\(O\)只能从后面剩下的还没匹配的\(O\)选。

因此代码思路为:

  1. 从后往前对\(m\)\(o\)分别计数,当遇到\(m\)时,选择\(k\)\(o\)匹配。

设当前o的数量为\(cnto\),根据题意,\(cnto>=k\)。那么当前m对答案的贡献值为\(C_{cnto}^k\)。并且\(cnto-=k\)重复此操作直到便利至字符串\(T\)的起始位置。统计单个\(T\)的答案贡献ans。

  1. 注意涉及到组合数取模,因此不能直接除以\(k!\)

    利用小费马定理\(a^{-1} \equiv a^{p-2} \mod p\),可以把除以\(a\),转换成乘\(a^{p-2}\),利用快速幂计算

  2. 因为有\(L\)个字符串,那么对答案的贡献为\(ans^L\),同样利用快速幂计算

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
typedef long long ll;
const ll MOD = 1e9+7;

ll fast_mi(ll a, ll b){
    ll ans = 1;
    ll val = a;
    while(b){
        if(b & 1){
            ans = ans * val % MOD;
        }
        val = val * val % MOD;
        b >>= 1;
    }
    return ans;
}

ll cal(ll n, ll k){
    ll ans = 1;
    for(int i = 1; i <= k; i++){
        ans = ans * n % MOD * fast_mi(i, MOD - 2) % MOD;
        n --;
    }
    return ans;
}

int main() {
    ll k, n, l;
    string t;
    cin >> k >> n >> l;
    cin >> t;
    ll idx = n-1;
    ll cnt_o = 0;
    ll ans = 1;
    while(idx >= 0){
        if(t[idx] == 'M'){
            ans = (ans * cal(cnt_o, k)) % MOD;
            cnt_o -= k;
            idx --;
        }
        while(idx >= 0 && t[idx] == 'O'){
            cnt_o ++;
            idx --;
        }
    }
    cout << fast_mi(ans, l) << endl;
    return 0;
}

[USACO25OPEN] It's Mooin' Time III B

题目描述

Elsie 正在试图向 Bessie 描述她最喜欢的 USACO 竞赛,但 Bessie 很难理解为什么 Elsie 这么喜欢它。Elsie 说「现在是哞哞时间!谁想哞哞?拜托,我只想参加 USACO」。

Bessie 仍然不理解,于是她将 Elsie 的描述转文字得到了一个长为 \(N\)\(3 \leq N \leq 10^5\))的字符串,包含小写字母字符 \(s_1s_2 \ldots s_N\)。Elsie 认为一个包含三个字符的字符串 \(t\) 是一个哞叫,如果 \(t_2 = t_3\)\(t_2 \neq t_1\)

一个三元组 \((i, j, k)\) 是合法的,如果 \(i < j < k\) 且字符串 \(s_i s_j s_k\) 组成一个哞叫。对于该三元组,FJ 执行以下操作计算其值:

  • FJ 将字符串 \(s\) 在索引 \(j\) 处弯曲 90 度
  • 该三元组的值是 \(\Delta ijk\) 的面积的两倍。

换句话说,该三元组的值等于 \((j-i)(k-j)\)

Bessie 向你进行 \(Q\)\(1 \leq Q \leq 3 \cdot 10^4\))个查询。在每个查询中,她给你两个整数 \(l\)\(r\)\(1 \leq l \leq r \leq N\)\(r-l+1 \ge 3\)),并询问满足 \(l \leq i\)\(k \leq r\) 的所有合法三元组 \((i, j, k)\) 的最大值。如果不存在合法的三元组,输出 \(-1\)

注意这个问题涉及到的整数可能需要使用 64 位整数类型(例如,C/C++ 中的 long long)。

输入格式

输入的第一行包含两个整数 \(N\)\(Q\)
以下一行包含 \(s_1 s_2, \ldots s_N\)

以下 \(Q\) 行每行包含两个整数 \(l\)\(r\),表示每个查询。

输出格式

对于每一个查询输出一行,包含对于该查询的答案。

输入输出样例 #1

输入 #1

12 5
abcabbacabac
1 12
2 7
4 8
2 5
3 10

输出 #1

28
6
1
-1
12

说明/提示

样例解释

对于第一个查询,\((i,j,k)\) 必须满足 \(1 \le i < j < k \le 12\)。可以证明,对于某个合法的 \((i,j,k)\)\(\Delta ijk\) 的最大面积在 \(i=1\)\(j=8\) 以及 \(k=12\) 时取到。注意 \(s_1 s_8 s_{12}\) 是字符串 "acc",根据前述定义是一个哞叫。\(\Delta ijk\) 的直角边长为 \(7\)\(4\),从而它的面积的两倍将等于 \(28\)

对于第三个查询,\((i,j,k)\) 必须满足 \(4 \le i < j < k \le 8\)。可以证明,对于某个合法的 \((i,j,k)\)\(\Delta ijk\) 的最大面积在 \(i=4\)\(j=5\) 以及 \(k=6\) 时取到。

对于第四个查询,不存在满足 \(2 \le i < j < k \le 5\)\((i,j,k)\) 使得 \(s_i s_j s_k\) 是一个哞叫,所以该查询的输出为 \(-1\)

测试点性质

  • 测试点 \(2\sim3\)\(N,Q\le 50\)
  • 测试点 \(4\sim6\)\(Q=1\),唯一的询问满足 \(l=1\)\(r=N\)
  • 测试点 \(7\sim 11\):没有额外限制。

代码

前提:\(i\)\(k\)的差值越大,长度越大;\(j\)越靠近\(i\)\(k\)的中点,得到的值越大。

  1. 预处理所有字符的位置数组。如对于字符'a',其位置数组如果为[1,3,4,7],代表其在原字符串的第1、3、4、7位置出现;

  2. 枚举三元组\(\{i, j, k\}\)中的\(i\)字符,可能存在值为\([a,z]\),要求\(i\)字符位置最接近\(l\)但是大于等于\(l\)

  3. 枚举三元组\(\{i,j,k\}\)中的\(k\)字符,可能值为除了\(i\)字符以外的\([a,z]\),要求\(k\)字符位置最接近\(r\)但是小于等于\(r\)

  4. 二分寻找最接近\(i\)\(k\)中点,并且和\(k\)字符一样的\(j\)字符位置;

  5. 比较得到该区间内的最大值,输出当前查询答案。

总时间复杂度:\(O(Q*26*26*log(n)*log(n)*log(n))\)

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
#include<math.h>
using namespace std;
typedef long long ll;
vector<int> pos[30];

int main() {
    int n, q;
    int l, r;
    string s;
    cin >> n >> q;
    cin >> s;
    for(int i = 0; i < n; i++){
        int j = s[i] - 'a';
        pos[j].push_back(i + 1);
    }
    while(q --){
        cin >> l >> r;
        ll ans = 0;
        //left
        for(int i = 0; i <= 25; i++){
            //找到pos[i]序列中第一个大于等于l的元素(位置i)
            auto p1 = lower_bound(pos[i].begin(), pos[i].end(), l) - pos[i].begin();
            if(p1 + pos[i].begin() == pos[i].end() || pos[i][p1] < l ||pos[i][p1] > r-2)  continue;
            ll i_pos = pos[i][p1];
            //right
            for(int j = 0; j <= 25; j++){
                if(j == i)      continue;
                //找到pos[j]序列中第一个小于等于r的元素(位置k)
                auto p3 = upper_bound(pos[j].begin(), pos[j].end(), r) - pos[j].begin() - 1;
                if(p3 < 0 || pos[j][p3] < l + 2 || pos[j][p3] > r)  continue;
                ll k_pos = pos[j][p3];
                ll mid = (i_pos + k_pos) / 2;
                //找到pos[j]序列中在(i,k)区间内,最接近mid位置的元素(位置j)
                auto p2 = lower_bound(pos[j].begin(), pos[j].end(), mid) - pos[j].begin();
                if(p2 > 0 && abs(pos[j][p2-1] - mid) < abs(pos[j][p2] - mid) && pos[j][p2-1] > l)  p2--;
                ll j_pos;
                if(l < pos[j][p2] && pos[j][p2] < r)    j_pos = pos[j][p2];
                else    continue;
                ans = max(ans, (j_pos - i_pos) * (k_pos - j_pos));
            }
        }
        if(ans)    cout << ans << endl;
        else    cout << -1 << endl;
    }
    return 0;
}
posted @ 2026-01-04 15:04  hsy2093  阅读(10)  评论(0)    收藏  举报