A. Two Towers
给定两个包含字符B和R的字符串,可以对它们进行一种操作:将一个字符串的末尾字符移动到另一个字符串的末尾,这种操作可进行若干次。询问是否能使得两个字符串均满足相邻元素不相同(即RBRBRB…或BRBRBR…)
数据范围:题目包含 t ( 1 ≤ t ≤ 1000 ) t(1\le t\le 1000) t(1≤t≤1000)组数据,两个字符串长度分别为 n , m ( 1 ≤ n , m ≤ 20 ) n,m(1\le n,m \le 20) n,m(1≤n,m≤20)
input
4
4 3
BRBB
RBR
4 7
BRBR
RRBRBRB
3 4
RBR
BRBR
5 4
BRBRR
BRBR
output
YES
YES
YES
NO
思路: 考虑必定不满足题目条件的情况
- 任一字符串中存在两个及以上位置相邻元素相同的情况
- 任一字符串中存在相邻元素相同的情况,且两个字符串末尾元素相同(此时不能做移动操作)
- 两个字符串均存在邻元素相同的情况
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<unordered_map>
#include<map>
#include<set>
#include<functional>
#include<unordered_set>
#include<queue>
#include<stack>
#include<cmath>
#include<cctype>
#include<cstring>
#include<climits>
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
int check(string& s) {
int cnt = 0;
for (int i = 1; i < s.size(); i++) {
if (s[i] == s[i - 1]) ++cnt;
}
return cnt;
}
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
string s1, s2;
cin >> s1 >> s2;
if (check(s1) == 0 && check(s2) == 0) {
cout << "yes" << "\n";
continue;
}
if (check(s1) == 0) {
if (check(s2) > 1 || s1.back() == s2.back()) {
cout << "no" << "\n";
continue;
}
}
if (check(s2) == 0) {
if (check(s1) > 1 || s1.back() == s2.back()) {
cout << "no" << "\n";
continue;
}
}
if (check(s1) > 0 && check(s2) > 0) {
cout << "no" << "\n";
continue;
}
cout << "yes" << "\n";
}
}
B. Ideal Point
给定 n n n个区间 [ l , r ] [l,r] [l,r],并对区间 [ l , r ] [l,r] [l,r]中的每个位置进行加一(初始值为0),同时给定位置 k k k,你需要回答是否可以选择 n n n个区间中的任意个,使得操作后位置 k k k的值最大
数据范围:题目包含 t ( 1 ≤ t ≤ 1000 ) t(1\le t\le 1000) t(1≤t≤1000)组数据, 1 ≤ n , k ≤ 50 , 1 ≤ l i , r i ≤ 50 1 \le n, k \le 50,1 \le l_i, r_i \le 50 1≤n,k≤50,1≤li,ri≤50
input
4
4 3
1 3
7 9
2 5
3 6
2 9
1 4
3 7
1 3
2 4
3 5
1 4
6 7
5 5
output
YES
NO
NO
YES
在第一个例子中,第3点已经为最大值3了,所以你不必删除任何区间。
在第四个例子中,你可以删除除 [ 5 , 5 ] [5,5] [5,5]以外的所有区间。
思路: 删除所有不包含 k k k点的区间,使用差分数组进行区间加统计,最后遍历判断是否 k k k点为唯一最大值
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<unordered_map>
#include<map>
#include<set>
#include<functional>
#include<unordered_set>
#include<queue>
#include<stack>
#include<cmath>
#include<cctype>
#include<cstring>
#include<climits>
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
int t;
cin >> t;
while (t--) {
int n, k;
cin >> n >> k;
vector<int> a(100);
for (int i = 0; i < n; i++) {
int l, r;
cin >> l >> r;
if(l <= k && r >= k) a[l]++, a[r + 1]--;
}
int mx = 0, pos = 0;
bool flag = 1;
for (int i = 1; i < 100; i++) {
a[i] += a[i - 1];
if (a[i] > mx) {
mx = a[i];
pos = i;
flag = true;
}
else if (a[i] == mx) flag = 0;
}
if (flag && pos == k) cout << "yes" << "\n";
else cout << "no" << "\n";
}
}
C. Tea Tasting
给定长度为 n n n的数组 a , b a,b a,b, a [ i ] a[i] a[i]表示第 i i i杯茶有多少毫升, b [ i ] b[i] b[i]表示第 i i i个客人的一次能喝多少毫升的茶,初始时第 i i i个客人喝第 i i i杯茶,第二轮时数组左移,第 i + 1 i+1 i+1个客人喝第 i i i杯茶,第1个客人退出,第三轮第 i + 2 i+2 i+2个客人喝第 i i i杯茶,第2个客人退出,以此类推。询问每个客人各喝了多少毫升的茶
数据范围: t ( 1 ≤ t ≤ 1 0 4 ) t(1 \le t \le 10^4) t(1≤t≤104)组数据, 1 ≤ n ≤ 2 ⋅ 1 0 5 1 \le n \le 2 \cdot 10^5 1≤n≤2⋅105, 1 ≤ a i ≤ 1 0 9 1 \le a_i \le 10^9 1≤ai≤109, 1 ≤ b i ≤ 1 0 9 1 \le b_i \le 10^9 1≤bi≤109,保证 s u m ( n ) ≤ 2 ⋅ 1 0 5 sum(n)\le2 \cdot 10^5 sum(n)≤2⋅105
input
4
3
10 20 15
9 8 6
1
5
7
4
13 8 5 4
3 4 2 1
3
1000000000 1000000000 1000000000
1 1 1000000000
output
9 9 12
5
3 8 6 4
1 2 2999999997
思路: 第 i i i杯茶只会被第 i i i个及其之后客人的品尝,记录 b [ i ] b[i] b[i]数组的前缀和 s u m b sum_b sumb,遍历数组 a a a,对于第 i i i杯茶,二分找到最后会被哪一位客人 j j j 品尝,则对于 k ∈ [ i , j − 1 ] k\in[i,j-1] k∈[i,j−1]位客人均品尝了 b [ k ] b[k] b[k]毫升的茶,第 j j j 位客人品尝了 a [ i ] − ( s u m b [ j − 1 ] − s u m b [ i − 1 ] ) a[i]-(sum_b[j-1]-sum_b[i-1]) a[i]−(sumb[j−1]−sumb[i−1])毫升的茶。 [ i , j − 1 ] [i,j-1] [i,j−1]位客人需要进行区间加的操作,于是使用差分数组记录完整品茶的客人,第 j j j 位则客人直接进行累加。
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<unordered_map>
#include<map>
#include<set>
#include<functional>
#include<unordered_set>
#include<queue>
#include<stack>
#include<cmath>
#include<cctype>
#include<cstring>
#include<climits>
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<ll> a(n + 5), b(n + 5), s(n + 5), d(n + 5), ans(n + 5);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i], s[i] = s[i - 1] + b[i];
for (int i = 1; i <= n; i++) {
int l = 1, r = n + 1;
while (l < r) {
int mid = (l + r) >> 1;
if (s[mid] - s[i - 1] >= a[i]) r = mid;
else l = mid + 1;
}
if (l == n + 1) ++d[i];
else {
++d[i];
--d[l];
ans[l] += a[i] - (s[l - 1] - s[i - 1]);
}
}
for (int i = 1; i <= n; i++) {
d[i] += d[i - 1];
ans[i] += d[i] * b[i];
cout << ans[i] << " ";
}
cout << "\n";
}
}
D. Triangle Coloring
给你一个由 n n n个顶点和 n n n个边组成的无向图,其中 n n n能被 6 6 6整除。每条边都有一个权重 w i w_i wi,是一个正整数。
该图有如下结构:它被分成 n 3 \frac{n}{3} 3n个边数为3的环,第一个环由顶点 1 , 2 , 3 1,2,3 1,2,3组成,第二个环由顶点 4 , 5 , 6 4,5,6 4,5,6组成,以此类推。不同环的顶点之间没有边。
你必须把这个图的顶点涂成两种颜色,红色和蓝色。每个顶点应该正好有一种颜色,应该正好有 n 2 \frac{n}{2} 2n个红色顶点和 n 2 \frac{n}{2} 2n个蓝色顶点。如果符合这些约束条件,那么这种着色就被称为有效。
着色的权重是连接不同颜色的两个顶点的边的权重之和。
设 W W W为有效着色的最大可能权重。计算具有 W W W权重的有效着色的方案有多少种,并将方案数以 998244353 998244353 998244353为模数打印出来。
数据范围: 6 ≤ n ≤ 3 ⋅ 1 0 5 6 \le n \le 3 \cdot 10^5 6≤n≤3⋅105,保证 n % 6 = 0 n\%6=0 n%6=0, w 1 , w 2 , … , w n , 1 ≤ w i ≤ 1000 w_1, w_2, \dots, w_n,1 \le w_i \le 1000 w1,w2,…,wn,1≤wi≤1000
input
12
1 3 3 7 8 5 2 2 2 2 4 2
output
36
input
6
4 2 6 6 6 4
output
2
思路: 每个环最多贡献两条边的权重,此时环染色为蓝蓝红或者红红蓝,且要使图正好有 n 2 \frac{n}{2} 2n个红色顶点和 n 2 \frac{n}{2} 2n个蓝色顶点,则染色为蓝蓝红和红红蓝的环各占 n 6 \frac{n}{6} 6n个,要使权重之和最大,则不可能会有红红红或者蓝蓝蓝的环。所以所有环的染色情况为 C n 3 n 6 C_{\frac{n}{3}}^{\frac{n}{6}} C3n6n种,对于每一个环,需要选择最大的两条边,若三条边的权重 e 1 = e 2 = e 3 e_1=e_2=e_3 e1=e2=e3,则有三种选法;若三条边的权重 e 1 = e 2 < e 3 e_1=e_2<e_3 e1=e2<e3,则有两种选法。由乘法原理将每个环的颜色选法相乘,最后乘上 C n 3 n 6 C_{\frac{n}{3}}^{\frac{n}{6}} C3n6n即可。
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<unordered_map>
#include<map>
#include<set>
#include<functional>
#include<unordered_set>
#include<queue>
#include<stack>
#include<cmath>
#include<cctype>
#include<cstring>
#include<climits>
using namespace std;
const int N = 2e5 + 5, mod = 998244353;
typedef long long ll;
ll qmi(int a, int k, int p)
{
ll res = 1;
while (k)
{
if (k & 1)res = (ll)res * a % p;
a = (ll)a * a % p;
k >>= 1;
}
return res;
}
ll C(int a, int b, int p)
{
if (b > a)return 0;
ll res = 1;
for (int i = 1, j = a; i <= b; i++, j--)
{
res = res * j % p;
res = res * qmi(i, p - 2, p) % p;
}
return res;
}
ll lucas(int a, int b, int p)
{
if (a < p && b < p)return C(a, b, p);
return (ll)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
int t = 1;
while (t--) {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
ll ans = 1;
for (int i = 1; i <= n; i++) {
if (i % 3 == 0) {
int b[3] = { a[i - 2], a[i - 1], a[i] };
sort(b, b + 3);
if (b[0] == b[1] && b[1] == b[2]) ans = (ans * 3) % mod;
if (b[0] == b[1] && b[1] != b[2]) ans = (ans * 2) % mod;
}
}
ans = (ans * lucas(n / 3, n / 6, mod)) % mod;
cout << ans << "\n";
}
}
E. Explosions?
给定一个长度为 n n n数组 a a a,每个位置 i i i 有一个怪物 a [ i ] a[i] a[i],你有一个基本法术,消耗 1 M P 1MP 1MP,可以将 a [ i ] a[i] a[i]的生命值减一( a [ i ] > 0 a[i]>0 a[i]>0时使用),该法术你可以施放任意次。另外,你有一个特殊的卷轴,上面有 "爆炸 "法术,你只能使用一次。爆炸将消耗 a [ i ] a[i] a[i]点 M P MP MP,直接将 a [ i ] a[i] a[i]的生命值清零,并对相邻元素造成溅射伤害,伤害值为 a [ i ] − 1 a[i]-1 a[i]−1,若爆炸造成的伤害足以杀死 i − 1 i-1 i−1(或 i + 1 i+1 i+1)的怪物,即当前的 a [ i − 1 ] ≤ a [ i ] − 1 a[i-1]\le a[i]-1 a[i−1]≤a[i]−1(或 a [ i + 1 ] ≤ a [ i ] − 1 a[i+1]\le a[i]-1 a[i+1]≤a[i]−1),那么该怪物也会死亡,产生二次爆炸,伤害为 a [ i − 1 ] − 1 a[i-1]-1 a[i−1]−1或 a [ i + 1 ] − 1 a[i+1]-1 a[i+1]−1 以此类推,直到爆炸结束。询问杀死所有怪物,你需要的最低总MP是多少?
数据范围: t ( 1 ≤ t ≤ 1 0 4 ) t(1 \le t \le 10^4) t(1≤t≤104)组数据, 1 ≤ n ≤ 3 ⋅ 1 0 5 1 \le n \le 3 \cdot 10^5 1≤n≤3⋅105, 1 ≤ a [ i ] ≤ 1 0 6 1 \le a[i] \le 10^6 1≤a[i]≤106,保证 s u m ( n ) ≤ 3 ⋅ 1 0 5 sum(n)\le3 \cdot 10^5 sum(n)≤3⋅105
input
5
3
1 1 1
4
4 1 2 1
4
5 10 15 10
1
42
9
1 2 3 2 2 2 3 2 1
output
3
6
15
42
12
思路: 要想做到实施爆破操作后全图清空,首先要使用基本法术将数组处理为 “山峰“ (严格递增后严格递减)。由题易得,我们需要得到对每一个 a [ i ] a[i] a[i]计算进行爆破后清空全图的代价,取其中的最小值。我们使用一个严格单调递增的单调栈,栈中每个元素为一个二元组 ( v a l , c n t ) (val,cnt) (val,cnt)示为一个集合,集合中有 c n t cnt cnt个数,最大值为 v a l val val,最小值为 v a l − c n t + 1 val - cnt + 1 val−cnt+1(最小值 ≥ 0 \ge0 ≥0),它们严格递增且差值为1,遍历数组 a a a时
- 若当前集合的最小值 v a l − c n t + 1 val-cnt+1 val−cnt+1大于栈顶元素的 v a l val val,则直接插入 ( a [ i ] , 1 ) (a[i],1) (a[i],1)
- 若当前集合的最小值 v a l − c n t + 1 ≤ val-cnt+1\le val−cnt+1≤栈顶元素的 v a l val val,则不断出栈,进行集合的合并,并计算合并的代价
以下图为例: a = [ 1 , 2 , 3 , 4 , 3 , 4 , 5 , 2 ] a=[1,2,3,4,3,4,5,2] a=[1,2,3,4,3,4,5,2]
当前进栈元素集合的最小值为3, ≤ \le ≤栈顶元素的 v a l val val,出栈,集合合并,代价为 4 − 2 = 2 4-2=2 4−2=2
当前进栈元素集合的最小值为2, ≤ \le ≤栈顶元素的 v a l val val,出栈,集合合并,代价为 3 − 1 = 2 3-1=2 3−1=2
当前进栈元素集合的最小值为1, ≤ \le ≤栈顶元素的 v a l val val,出栈,集合合并,代价为 2 − 0 = 2 2-0=2 2−0=2
当前进栈元素集合的最小值为0, ≤ \le ≤栈顶元素的 v a l val val,出栈,集合合并,代价为 1 − 0 = 1 1-0=1 1−0=1
此时集合中的元素为 { 0 , 0 , 1 , 2 , 3 } \{0,0,1,2,3\} {0,0,1,2,3}
后面进栈 3,4,5,2 如法炮制即可
注意 ( 2 , 3 ) (2,3) (2,3)和 ( 3 , 5 ) (3,5) (3,5)合并时, ( 2 , 3 ) (2,3) (2,3)的集合元素是 { 0 , 1 , 2 } \{0,1,2\} {0,1,2}, ( 3 , 5 ) (3,5) (3,5)的集合元素是 { 0 , 0 , 1 , 2 , 3 } \{0,0,1,2,3\} {0,0,1,2,3},合并代价为 1 + 2 + 3 = 6 1+2+3=6 1+2+3=6,使用等差数列求和公式计算代价。两个集合合并后为 { 0 , 0 , 0 , 0 , 0 , 0 , 1 , 2 } \{0,0,0,0,0,0,1,2\} {0,0,0,0,0,0,1,2}
由此便可得到将数组的前缀
[
1
,
i
]
,
i
∈
[
1
,
n
]
[1,i],i\in[1,n]
[1,i],i∈[1,n]处理为严格递增所需的代价
L
[
i
]
L[i]
L[i]。同理,我们将数组反转再跑一次单调栈便可得到将后缀
[
i
,
n
]
,
i
∈
[
1
,
n
]
[i,n],i\in[1,n]
[i,n],i∈[1,n]处理为严格递减所需的代价
R
[
i
]
R[i]
R[i],所以对每一个
a
[
i
]
a[i]
a[i]计算进行爆破后清空全图的代价即为
L
[
i
]
+
R
[
i
]
+
a
[
i
]
L[i]+R[i]+a[i]
L[i]+R[i]+a[i]
a
n
s
=
m
i
n
i
∈
[
1
,
n
]
{
L
[
i
]
+
R
[
i
]
+
a
[
i
]
}
ans=\mathop{min}\limits_{i\in[1,n]}\{L[i]+R[i]+a[i]\}
ans=i∈[1,n]min{L[i]+R[i]+a[i]}
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<unordered_map>
#include<map>
#include<set>
#include<functional>
#include<unordered_set>
#include<queue>
#include<stack>
#include<cmath>
#include<cctype>
#include<cstring>
#include<climits>
using namespace std;
const int N = 2e5 + 5, mod = 998244353;
typedef long long ll;
ll calc(ll x, ll cnt) {
ll st = x - cnt + 1;
return cnt * (st + x) / 2;
}
int main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<ll> a(n + 1), L(n + 1), R(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
auto solve = [&](vector<ll>& cost) {
vector<pair<ll, ll>> st;
ll sum = 0;
for (int j = 1; j <= n; j++) {
ll cnt = 1;
while (st.size() && st.back().first > a[j] - cnt) {
// 等差求和计算代价
int mi = max(0ll, a[j] - cnt); // 集合中最小值不小于0
sum += calc(st.back().first, min(st.back().second, st.back().first))
- calc(mi, min(mi, st.back().second));
// 对(val = 3, cnt = 6)等差求和,实际上是0+0+0+1+2+3,所以cnt = min(cnt, val)
// 集合合并
cnt += st.back().second;
st.pop_back();
}
cost[j] = sum;
st.push_back({ a[j], cnt });
}
};
solve(L);
reverse(a.begin() + 1, a.end());
solve(R);
reverse(R.begin() + 1, R.end());
reverse(a.begin() + 1, a.end());
ll ans = (1ll << 60);
for (int i = 1; i <= n; i++) {
ans = min(ans, L[i] + R[i] + a[i]);
}
cout << ans << "\n";
}
}