2024ICPC武汉邀请赛个人题解
题目排序由个人认为难度排序
题解火车头
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
#include <unordered_map>
#include <cstring>
#include <cstdio>
#include <string>
#include <queue>
#include <stack>
#include <map>
#include <list>
#include <bitset>
#include <cmath>
#include <numeric>
#define endl '\n'
#define ft first
#define sd second
#define yes cout << "yes\n"
#define no cout << "no\n"
#define Yes cout << "Yes\n"
#define No cout << "No\n"
#define YES cout << "YES\n"
#define NO cout << "NO\n"
#define pb push_back
#define eb emplace_back
#define all(x) x.begin(), x.end()
#define unq_all(x) x.erase(unique(all(x)), x.end())
#define sort_all(x) sort(all(x))
#define reverse_all(x) reverse(all(x))
#define INF 0x7fffffff
#define INFLL 0x7fffffffffffffffLL
#define RED cout << "\033[91m"
#define GREEN cout << "\033[92m"
#define YELLOW cout << "\033[93m"
#define BLUE cout << "\033[94m"
#define MAGENTA cout << "\033[95m"
#define CYAN cout << "\033[96m"
#define RESET cout << "\033[0m"
// 红色
#define DEBUG1(x) \
RED; \
cout << #x << " : " << x << endl; \
RESET;
// 绿色
#define DEBUG2(x) \
GREEN; \
cout << #x << " : " << x << endl; \
RESET;
// 蓝色
#define DEBUG3(x) \
BLUE; \
cout << #x << " : " << x << endl; \
RESET;
// 品红
#define DEBUG4(x) \
MAGENTA; \
cout << #x << " : " << x << endl; \
RESET;
// 青色
#define DEBUG5(x) \
CYAN; \
cout << #x << " : " << x << endl; \
RESET;
// 黄色
#define DEBUG6(x) \
YELLOW; \
cout << #x << " : " << x << endl; \
RESET;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<string, string> pss;
typedef pair<string, int> psi;
typedef pair<string, ll> psl;
typedef vector<bool> vb;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<string> vs;
typedef vector<pii> vpii;
typedef vector<pll> vpll;
typedef vector<pss> vpss;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
typedef queue<int> qi;
typedef queue<ll> ql;
typedef queue<pii> qpii;
typedef queue<pll> qpll;
typedef queue<psi> qpsi;
typedef queue<psl> qpsl;
typedef priority_queue<int> pqi;
typedef priority_queue<ll> pql;
typedef priority_queue<string> pqs;
typedef priority_queue<pii> pqpii;
typedef priority_queue<psi> pqpsi;
typedef priority_queue<pll> pqpl;
typedef priority_queue<psi> pqpsl;
typedef map<int, int> mii;
typedef map<int, bool> mib;
typedef map<ll, ll> mll;
typedef map<ll, bool> mlb;
typedef map<char, int> mci;
typedef map<char, ll> mcl;
typedef map<char, bool> mcb;
typedef map<string, int> msi;
typedef map<string, ll> msl;
typedef map<int, bool> mib;
typedef unordered_map<int, int> umii;
typedef unordered_map<ll, ll> uml;
typedef unordered_map<char, int> umci;
typedef unordered_map<char, ll> umcl;
typedef unordered_map<string, int> umsi;
typedef unordered_map<string, ll> umsl;
template <typename T>
inline void read(T &x)
{
T f = 1;
x = 0;
char ch = getchar();
while (0 == isdigit(ch))
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (0 != isdigit(ch))
x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
x *= f;
}
template <typename T>
inline void write(T x)
{
if (x < 0)
{
x = ~(x - 1);
putchar('-');
}
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
I. Cyclic Apple Strings
题目大意
给你一个由 0 , 1 0,1 0,1组成的字符串,可以任选一个任意长度的子段,将其向左旋转任意长度,例如 110 110 110向左旋转一次就是 101 101 101。问最少需要多少次操作使得字符串单调递增。
解题思路
对于任意一个连续的 1 1 1和连续的 0 0 0组成子串,我们都可以在一次操作内将所有 0 0 0旋转到 1 1 1的前面。所以,只需要统计在 1 1 1后面的连续 0 0 0段数量即可。即只需要找出有多少个 10 10 10就行。
题解
/*#####################################BEGIN#####################################*/
void solve()
{
string s;
cin >> s;
int ans = 0;
for (int i = 1; i < s.size(); i++)
{
if (s[i] == '0' && s[i - 1] == '1')
ans++;
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(nullptr), std::cout.tie(nullptr);
int _ = 1;
// std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
K. Party Games
题目大意
给你 n n n个整数 1 , 2 , 3 , … , n 1,2,3,\dots,n 1,2,3,…,n,每次可以从最左端或者最右端拿走一个数字,如果当前所剩的数异或和为 0 0 0或被拿完则输了。问谁会赢。
解题思路
由异或和的性质可以得知,当二进制位上的 1 1 1的数量都为偶数时,异或和为 0 0 0。所以从 0 ∼ n 0\sim n 0∼n连续进行异或和,每隔 4 4 4个数就会出现一次异或和为 0 0 0。
0 , 1 , 2 , 3 0,1,2,3 0,1,2,3的二进制形式为 000 0 2 , 000 1 2 , 001 0 2 , 001 1 2 0000_2,0001_2,0010_2,0011_2 00002,00012,00102,00112。
4 , 5 , 6 , 7 4,5,6,7 4,5,6,7的二进制形式为 010 0 2 , 010 1 2 , 011 0 2 , 011 1 2 0100_2,0101_2,0110_2,0111_2 01002,01012,01102,01112。
每隔四位都会使得所有位上的 1 1 1的数量为偶数。
所以对于整个序列来说,我们只需要考虑对前 3 3 3个和最多后 4 4 4个数字博弈。
可以写成 000 1 2 , 001 0 2 , 001 1 2 , x x 0 0 2 , x x 0 1 2 , x x 1 0 2 , x x 1 1 2 0001_2,0010_2,0011_2,xx00_2,xx01_2,xx10_2,xx11_2 00012,00102,00112,xx002,xx012,xx102,xx112。
先从简单向复杂推导。
f ( 0 ) f(0) f(0),当 n m o d 4 = 0 n\mod 4=0 nmod4=0时,当前的序列为 000 1 2 , 001 0 2 , 001 1 2 , x x 0 0 2 0001_2,0010_2,0011_2,xx00_2 00012,00102,00112,xx002。先手只要拿掉 x x 0 0 2 xx00_2 xx002就必胜。
f ( 1 ) f(1) f(1),当 n m o d 4 = 1 n\mod 4=1 nmod4=1时,当前的序列为 000 1 2 , 001 0 2 , 001 1 2 , x x 0 0 2 , x x 0 1 2 0001_2,0010_2,0011_2,xx00_2,xx01_2 00012,00102,00112,xx002,xx012。先手只要拿掉 000 1 2 0001_2 00012就必胜。
f ( 2 ) f(2) f(2),当 n m o d 4 = 2 n\mod 4=2 nmod4=2时,当前的序列为 000 1 2 , 001 0 2 , 001 1 2 , x x 0 0 2 , x x 0 1 2 , x x 1 0 2 0001_2,0010_2,0011_2,xx00_2,xx01_2,xx10_2 00012,00102,00112,xx002,xx012,xx102。先手不可能拿掉 x x 1 0 2 xx10_2 xx102,这样就会使得 f ( 2 ) → f ( 1 ) f(2)\rightarrow f(1) f(2)→f(1),导致后手必胜,所以只能拿 000 1 2 0001_2 00012,后手这时只需要拿走 001 0 2 0010_2 00102,就会使得异或和为 0 0 0,所以无论如何后手必胜。
f ( 3 ) f(3) f(3),当 n m o d 4 = 3 n\mod 4=3 nmod4=3时,当前的序列为 000 1 2 , 001 0 2 , 001 1 2 , x x 0 0 2 , x x 0 1 2 , x x 1 0 2 , x x 1 1 2 0001_2,0010_2,0011_2,xx00_2,xx01_2,xx10_2,xx11_2 00012,00102,00112,xx002,xx012,xx102,xx112。异或和已经为 0 0 0,先手不能拿掉任何数,后手必胜。
题解
/*#####################################BEGIN#####################################*/
const string t1 = "Fluttershy";
const string t2 = "Pinkie Pie";
void solve()
{
int n;
cin >> n;
cout << (n % 4 < 2 ? t1 : t2) << endl;
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(nullptr), std::cout.tie(nullptr);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
B. Countless Me
题目大意
给你 n n n个数字组成的数组 a a a,你可以对任意两个数字 a i , a j a_i,a_j ai,aj进行一次操作,使得 a i + x , a j − x a_i+x,a_j-x ai+x,aj−x。最多进行 n n n次操作。确保每次运算后所有 a i , a j a_i,a_j ai,aj 都保持非负整数。我们的目标是最小化 a n s = a 1 ∣ a 2 ∣ a 2 … ∣ a n ans=a_1|a_2|a_2\dots|a_n ans=a1∣a2∣a2…∣an的值。
解题思路
很容易可以证明,我们可以在这 n n n次操作内把数组内所有元素变成不超过 s = ∑ i = 1 n a i s=\sum ^{n}_{i=1}a_i s=∑i=1nai的任意数。
所以我们需要考虑的是,如和将 s s s分配到 n n n个位置上,使得 a n s ans ans最小。
贪心的考虑,我们肯定是要让手里待分配的数字变小,使情况简化,所以从高位开始分配。
从最高位开始贪心,如果不分配当前位数 i i i的二进制值,转到 i − 1 i-1 i−1位可以把 s s s分配完的话,就不进行分配(),否则就从 s s s中分配出 x = m i n ( n , ⌊ s 2 i ⌋ ) x=min(n,\lfloor \frac{s}{2^i} \rfloor) x=min(n,⌊2is⌋)个 2 i 2^i 2i,只有 n n n个位置,所以最多只能分配 n n n个。
题解
/*#####################################BEGIN#####################################*/
void solve()
{
ll n;
cin >> n;
ll a;
ll s = 0;
for (int i = 0; i < n; i++)
{
cin >> a;
s += a;
}
ll ans = 0;
for (int i = 30; i >= 0; i--)
{
if (s > n * ((1 << i) - 1))
{
ans |= 1 << i;
ll x = min(n, s >> i);
s -= x * (1 << i);
}
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(nullptr), std::cout.tie(nullptr);
int _ = 1;
// std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
F. Custom-Made Clothes
题目大意
有一个由正整数 a i , j ( 1 ≤ a i , j ≤ n × n ) a_{i,j}(1\le a_{i,j}\le n\times n) ai,j(1≤ai,j≤n×n)组成的 n × n n\times n n×n大小的矩阵,对于矩阵中的任意一个元素都有 a i , j ≥ a i − 1 , j ( i > 1 ) , a i , j ≥ a i , j − 1 ( j > 1 ) a_{i,j}\ge a_{i-1,j}(i\gt1),a_{i,j}\ge a_{i,j-1}(j\gt1) ai,j≥ai−1,j(i>1),ai,j≥ai,j−1(j>1)。你不知道这个矩阵里面每个位置的具体值,但你每次可以进行一次查询,查询矩阵中给定位置 a i , j a_{i,j} ai,j 是否小于等于指定整数 x x x,如果小于等于会返回 1 1 1,否则返回 0 0 0。
通过不超过 50000 50000 50000次,寻找 k k k值。 k k k值被定义为对 n × n n×n n×n个 元素进行降序排序时, 第 k k k个元素的值。
解题思路
二分+暴力检查。
考虑到是否有 k k k个元素大于当前选择的值是具有单调性性的,我们可以直接二分寻找最终答案。每次暴力查询矩阵中是否有 k k k个元素大于它。
题解
/*#####################################BEGIN#####################################*/
int n, k;
bool query(int i, int j, int x)
{
cout << "? " << i << " " << j << " " << x << endl;
int res;
cin >> res;
return res;
}
bool check(int x)
{
int cnt = 0;
for (int i = 1, j = n; i <= n; i++)
{
while (j >= 1 && !query(i, j, x))
j--;
cnt += n - j;
}
return cnt >= k;
}
void solve()
{
cin >> n >> k;
int l = 1, r = n * n;
int ans = 0;
while (l <= r)
{
int mid = l + r >> 1;
if (check(mid))
l = mid + 1;
else
{
r = mid - 1;
ans = mid;
}
}
cout << "! " << ans << endl;
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(nullptr), std::cout.tie(nullptr);
int _ = 1;
// std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
D. ICPC
题目大意
在一张长桌上, n n n 个座位排成一排,从左到右的第 i t h i_{th} ith 个座位上摆放着 a i a_i ai 份量的菜肴。由由子从 s t h s_{th} sth座位开始,每秒结束时,她可以移动到与当前座位相邻的任何座位(她也可以选择留在当前座位)。她所到达的座位上的任何菜肴都会被她吃掉。
由由子想知道她能吃的菜肴的最大总份量。对于满足 1 ≤ s ≤ n , 1 ≤ t ≤ 2 n 1≤s≤n,1≤t≤2n 1≤s≤n,1≤t≤2n的所有正整数 s , t s,t s,t ,你需要计算如果由由子从 s t h s_{th} sth座位开始,移动 t t t秒,她可以吃掉的菜肴的最大总份量。
让 F s , t F_{s,t} Fs,t表示由由子从座位 s s s开始,在 t t t秒内可以吃掉的菜肴的最大总份量。为了验证你已经计算出 1 ≤ s ≤ n , 1 ≤ t ≤ 2 n 1≤s≤n,1≤t≤2n 1≤s≤n,1≤t≤2n的所有 F s , t F_{s,t} Fs,t 的值,请输出整数 ⨁ s = 1 n ( s + ⨁ t = 1 2 n t ⋅ F s , t ) ⨁_{s=1}^{n}(s+⨁_{t=1}^{2n}t⋅F_{s,t}) ⨁s=1n(s+⨁t=12nt⋅Fs,t) 。
解题思路
很简单的二维 d p dp dp。对于所有移动,要么一直走到头不折返,要么只折返一次然后走到头。
设 g s , t g_{s,t} gs,t为在位置 s s s移动不超过 t t t次且不折返的吃掉菜肴的最大值,先利用前缀和可以简单算出 g s , t g_{s,t} gs,t。
对于折返一次的走法,设在走 i i i步后折返,则最大值为 g s + i , t − i g_{s+i,t-i} gs+i,t−i。
综上,可以推出状态转移方程 F s , t = m a x ( g s + i , t − ∣ i ∣ ) , 1 ≤ s + i ≤ n , t − ∣ i ∣ ≥ 1 F_{s,t}=max(g_{s+i,t-|i|}),1\le s+i\le n,t-|i|\ge 1 Fs,t=max(gs+i,t−∣i∣),1≤s+i≤n,t−∣i∣≥1。
由于 n ≤ 5000 n\le 5000 n≤5000,所以 d p dp dp的时间复杂度不能超过 n 2 n^2 n2,只能进行两层循环,所以需要压缩一下。
题解
/*#####################################BEGIN#####################################*/
const int N = 5e3 + 5;
int a[N];
ll F[N][2 * N];
ll g[N][2 * N];
ll sum[N];
int n;
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
}
for (int s = 1; s <= n; s++)
{
for (int t = 1; t <= 2 * n; t++)
{
int l = max(1, s - t);
int r = min(n, s + t);
g[s][t] = max(sum[s] - sum[l - 1], sum[r] - sum[s - 1]);
}
}
// 向左移动然后折返
for (int s = 1; s <= n; s++)
{
for (int t = 1; t <= 2 * n; t++)
{
F[s][t] = max(F[s - 1][t - 1], g[s][t]);
}
}
// 向右移动然后折返
for (int s = n; s >= 1; s--)
{
for (int t = 1; t <= 2 * n; t++)
{
g[s][t] = max(g[s][t], g[s + 1][t - 1]);
F[s][t] = max(F[s][t], g[s][t]);
}
}
ll ans = 0;
for (int i = 1; i <= n; i++)
{
ll temp = 0;
for (int j = 1; j <= 2 * n; j++)
{
temp ^= j * F[i][j];
}
ans ^= (i + temp);
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(nullptr), std::cout.tie(nullptr);
int _ = 1;
// std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
E. Boomerang
M. Merge
E和M抽空补吧,剩下的A,C,G,H,J,L先算了。
GYM链接:2024 ICPC National Invitational Collegiate Programming Contest, Wuhan Site