2025年icpc全国邀请赛(南昌)部分题解
| 序号 | 题目 | 题目链接 | 算法 |
|---|---|---|---|
| A | Nezha Naohai | 📝 查看题目 | 签到题 |
| D | Virtuous Pope | 📝 查看题目 | 离散化 差分 前缀和 |
| M | Divide coins | 📝 查看题目 | 思维 打表找规律 |
| F | Caloric Difference | 📝 查看题目 | 贪心 数学 |
| K | Rotation | 📝 查看题目 | 思维 数学 |
| G | Exploration | 📝 查看题目 | dp |
| I | Dating Day | 📝 查看题目 | 组合数 容斥原理 双指针 |
A. Nezha Naohai
题目大意
给四个正整数 \(a, b, c, d\),求 \((a + b + c) \times d\)。
思路
很简单的签到题,直接计算输出就行,代码就省略了。
D. Virtuous Pope
题目大意
给定一个长方体,长宽高分别为:\(a,b,c\),长方体的一个顶点在 \((0,0,0)\),一个顶点在 \((a, b, c)\)。现在给出三维空间中的 \(n\) 条线段,求任何垂直于某条坐标轴的平面中最多可以和多少条线段相交。
思路
垂直于某条坐标轴的平面,就是只取某条坐标轴的某点上的平面如:x = 2, y = 1, z = 5,将每条线段映射到三条坐标轴上,求取哪个点的线段最多,那么就取过该点的垂直平面,答案就是该点上的线段个数。因为长宽高的数据范围为 \(10^9\),所以不能直接在坐标轴上做差分前缀和,但是线段个数最多只有 \(10^5\),所以我们可以先做离散化,然后在新的序列上做差分前缀和。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <iomanip>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
#define endl '\n'
#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define all(a) a.begin(), a.end()
#define lowbit(x) x & -x
#define ent cout << '\n'
#define out(x) cout << x << ' '
#define out2(x, y) cout << x << " ~ " << y << ' '
#define me(a, b) memset(a, b, sizeof a)
#define mc(a, b) memcpy(a, b, sizeof a)
#define pk push_back
#define ur(x) sort(all(x)), x.erase(unique(all(x)), x.end())
#define fi first
#define se second
#define si(x) int(x.size())
#define chi(x) (x - '0')
#define ull unsigned long long
#define Mp make_pair
using namespace std;
const ll inf = 1e18;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
const int mod = 998244353;
const int P = 1e9 + 7;
const int dx[] = {0, 0, 1, -1};
const int dy[] = {-1, 1, 0, 0};
const int N = 1e6 + 10;
const int M = 1000 + 10;
int n, a, b, c;
void solve()
{
cin >> n >> a >> b >> c;
vector<int> x, y, z;
vector<PII> xx(n + 1), yy(n + 1), zz(n + 1);
for (int i = 1; i <= n; i ++) {
int x1, y1, z1, x2, y2, z2;
cin >> x1 >> y1 >> z1 >> x2 >> y2 >> z2;
x.pk(x1);
x.pk(x2);
y.pk(y1);
y.pk(y2);
z.pk(z1);
z.pk(z2);
if (x1 > x2) swap(x1, x2);
xx[i] = {x1, x2};
if (y1 > y2) swap(y1, y2);
yy[i] = {y1, y2};
if (z1 > z2) swap(z1, z2);
zz[i] = {z1, z2};
}
ur(x);
ur(y);
ur(z);
map<int, int> idx, idy, idz;
for (int i = 0; i < si(x); i ++) idx[x[i]] = i;
for (int i = 0; i < si(y); i ++) idy[y[i]] = i;
for (int i = 0; i < si(z); i ++) idz[z[i]] = i;
vector<int> sx(si(x) + 2, 0), sy(si(y) + 2, 0), sz(si(z) + 2, 0);
for (int i = 1; i <= n; i ++) {
auto [l, r] = xx[i];
l = idx[l] + 1, r = idx[r] + 1;
sx[l] ++, sx[r + 1] --;
l = yy[i].fi, r = yy[i].se;
l = idy[l] + 1, r = idy[r] + 1;
sy[l] ++, sy[r + 1] --;
l = zz[i].fi, r = zz[i].se;
l = idz[l] + 1, r = idz[r] + 1;
sz[l] ++, sz[r + 1] --;
}
int res = 0;
for (int i = 1; i <= si(x); i ++) {
sx[i] += sx[i - 1];
res = max(res, sx[i]);
}
for (int i = 1; i <= si(y); i ++) {
sy[i] += sy[i - 1];
res = max(res, sy[i]);
}
for (int i = 1; i <= si(z); i ++) {
sz[i] += sz[i - 1];
res = max(res, sz[i]);
}
cout << res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while (t --) solve();
return 0;
}
M. Divide coins
题目大意
初始 \(n\) 枚硬币都是正面朝上的,小 T 会恰好翻转其中任意 \(K\) 枚硬币,小 J 可以对每枚硬币执行以下操作 :
- 1:放在第一堆(不翻转)
- 2:放在第一堆并翻转
- 3:放在第二堆(不翻转)
- 4:放在第二堆并翻转
所有硬币完成操作后,如果两堆硬币中正面朝上的硬币个数相同则小 J 获胜,允许某堆硬币个数为0。求构造一种操作序列,让小 T 无论翻转哪 \(K\) 枚硬币都能保证小 J 一定获胜。
思路
最暴力的方法从 \(n = 1, k = 1\) 开始枚举观察规律,可以发现只要将 \(K\) 枚硬币不翻转放入第一堆,将剩下硬币翻转放入第二堆即可。
假设第一堆有 \(a\) 枚硬币,其中 \(x\) 枚硬币是由小 T 翻转过的,第二堆有 \(n - a\) 枚硬币,其中 \(k - x\) 枚硬币是由小 T 翻转过的,假如第一堆的硬币都不做任何操作直接放入,那么正面朝上的硬币就是 \(a - x\) 枚,让 \(n - a\) 枚硬币全部翻转放入第二堆,朝上的个数就是 \(k - x\) 枚,要使第二堆的正面朝上的硬币一样多,只要取 \(a = K\)。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <iomanip>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
#define endl '\n'
#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define all(a) a.begin(), a.end()
#define lowbit(x) x & -x
#define ent cout << '\n'
#define out(x) cout << x << ' '
#define out2(x, y) cout << x << " ~ " << y << ' '
#define me(a, b) memset(a, b, sizeof a)
#define mc(a, b) memcpy(a, b, sizeof a)
#define pk push_back
#define ur(x) sort(all(x)), x.erase(unique(all(x)), x.end())
#define fi first
#define se second
#define si(x) int(x.size())
#define chi(x) (x - '0')
#define ull unsigned long long
#define Mp make_pair
using namespace std;
const ll inf = 1e18;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
const int mod = 998244353;
const int P = 1e9 + 7;
const int dx[] = {0, 0, 1, -1};
const int dy[] = {-1, 1, 0, 0};
const int N = 1e6 + 10;
const int M = 1000 + 10;
int n, k;
void solve()
{
cin >> n >> k;
for (int i = 0; i < k; i ++) cout << 1;
for (int i = k; i < n; i ++) cout << 4;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while (t --) solve();
return 0;
}
F. Caloric Difference
题目大意
规划 \(n\) 天的饮食计划,记第 \(i\) 天摄入 \(r_i\) 卡路里,消耗 \(c_i\) 质量,其中 \(c_i = p \times c_{i - 1} + (1 - p) \times r_{i - 1}\)。现在有 \(k\) 天摄入的 \(r_i\) 卡路里已确定,要确定剩下的 \(n - k\) 天的 \(r_i\) ,使 \(\sum_{i = 1}^{n}(c_i - r_i)\) 最大,其中需满足 \(r_i \in [l, r]\)。
思路
其中假设我们已知了 \(p \times (\sum_{i = 1}^{n}c_{i - 1} - \sum_{i = 1}^{n}r_{i - 1})\)(因为这些数在计算到第 \(i\) 位时已经被求出来了),要使结果最大,那么只有取 \(r_i\) 最小,就是 \(r_i = l\) 。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <iomanip>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
#define endl '\n'
#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define all(a) a.begin(), a.end()
#define lowbit(x) x & -x
#define ent cout << '\n'
#define out(x) cout << x << ' '
#define out2(x, y) cout << x << " ~ " << y << ' '
#define me(a, b) memset(a, b, sizeof a)
#define mc(a, b) memcpy(a, b, sizeof a)
#define pk push_back
#define ur(x) sort(all(x)), x.erase(unique(all(x)), x.end())
#define fi first
#define se second
#define si(x) int(x.size())
#define chi(x) (x - '0')
#define ull unsigned long long
#define Mp make_pair
using namespace std;
const ll inf = 1e18;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
const int mod = 998244353;
const int P = 1e9 + 7;
const int dx[] = {0, 0, 1, -1};
const int dy[] = {-1, 1, 0, 0};
const int N = 1e6 + 10;
const int M = 1000 + 10;
int n, k;
void solve()
{
cin >> n >> k;
double r0, c0, p, L, R;
cin >> r0 >> c0 >> p >> L >> R;
vector<double> r(n + 1, 0);
while (k --) {
int id;
double val;
cin >> id >> val;
r[id] = val;
}
double res = 0;
vector<double> c(n + 1);
c[0] = c0;
r[0] = r0;
for (int i = 1; i <= n; i ++) {
if (r[i] == 0.0) r[i] = L;
c[i] = p * c[i - 1] + (1.0 - p) * r[i - 1];
res += c[i] - r[i];
}
cout << fixed << setprecision(10) << res << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t --) solve();
return 0;
}
K. Rotation
题目大意
给定 \(n\) 个石像一开始面向某个方向,设定顺时针顺序为:前面、右面、后面、左面,有两种操作:
- 除第 \(i\) 个以外的石像顺时针转动一次
- 全部石像顺时针转动一次
求最小操作次数使所有石像都面向前面。
思路
假设每个石像执行第一种操作 \(k_i\) 次,使所有石像都面向同一个方向,最后再执行第二种操作 \(x\) 次使全部石像面向前面。
那么有 \(\sum_{i = 1}^{n}k_i + x = res\),假设 \(t = \sum_{i = 1}^{n}k_i\)
实际上先执行哪个操作并没有先后顺序,我们可以先执行第二种操作 \(x\) 次,使每个石像方向改变 \((a_i + x) \% 4 = s_i\)。
因为第 \(i\) 个石像执行第一种操作 \(k_i\) 次,那么其他石像就会转动 \(k_i\) 次,所以对于每个石像转动次数为 \(t - k_i\),所以 \((s_i + t - k_i) \% 4 == 0\) ,转换一下即 \(s_i + t \equiv k_i \ mod \ 4\)。
假设 \(r = t \% 4\),\(r_i = k_i \% 4\),那么有 \(\sum_{i = 1}^{n}r_i \equiv r \ mod \ 4\),代入上面的公式:\(s_i + r \equiv r_i \ mod \ 4\),再求和就会有 \(\sum_{i = 1}^{n}(s_i + r) \equiv \sum_{i = 1}^{n}(k_i \% 4) \equiv \sum_{i = 1}^{n}r_i \equiv r \ mod \ 4\)
因为 \(x\) 和 \(r\) 都小于4,我们只需枚举一遍即可,保证上面的公式成立即是合法操作,取一个最小。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <iomanip>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
#define endl '\n'
#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define all(a) a.begin(), a.end()
#define lowbit(x) x & -x
#define ent cout << '\n'
#define out(x) cout << x << ' '
#define out2(x, y) cout << x << " ~ " << y << ' '
#define me(a, b) memset(a, b, sizeof a)
#define mc(a, b) memcpy(a, b, sizeof a)
#define pk push_back
#define ur(x) sort(all(x)), x.erase(unique(all(x)), x.end())
#define fi first
#define se second
#define si(x) int(x.size())
#define chi(x) (x - '0')
#define ull unsigned long long
#define Mp make_pair
using namespace std;
const ll inf = 1e18;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
const int mod = 998244353;
const int P = 1e9 + 7;
const int dx[] = {0, 0, 1, -1};
const int dy[] = {-1, 1, 0, 0};
const int N = 1e6 + 10;
const int M = 1000 + 10;
int n;
void solve()
{
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i ++) cin >> a[i];
int res = INF;
for (int i = 0; i < 4; i ++) {
vector<int> s(n, 0);
for (int j = 0; j < n; j ++) s[j] = (a[j] + i) % 4;
for (int j = 0; j < 4; j ++) {
int tol = 0;
for (int k = 0; k < n; k ++) tol += (s[k] + j) % 4;
if (tol % 4 == j) res = min(res, tol + i);
}
}
cout << res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while (t --) solve();
return 0;
}
G. Exploration
题目大意
给定一个带权有向图,询问 \(q\) 次,每次询问从 \(p\) 点开始出发,初始有一个耐力值 \(x\),每通过一条边权为 \(d\) 的边,耐力值会变为 \(\lfloor \frac{x}{d} \rfloor\),求最少经过多少边可以使耐力值 \(x\) 置0。
思路
边权 \(d\) 最小为2,\(x\) 最大为 \(10^9\),那么最多进行30次就可以将 \(x\) 置0,所以我们可以用dp来解决,先预处理出每个点出发进行某次后边权最大是多少。
定义dp数组 \(dp[i][j]\):从 \(i\) 点出发进行第 \(j\) 次后最大边权乘积。
转移公式:\(dp[i][j] = max(dp[i][j], dp[v][j - 1] * d)\)
初始化:\(dp[i][0] = 1\)
在每次询问时,从1开始枚举到30,查看最小多少次可以让 \(dp[p][i] > x\)。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <iomanip>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
#define endl '\n'
#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define all(a) a.begin(), a.end()
#define lowbit(x) x & -x
#define ent cout << '\n'
#define out(x) cout << x << ' '
#define out2(x, y) cout << x << " ~ " << y << ' '
#define me(a, b) memset(a, b, sizeof a)
#define mc(a, b) memcpy(a, b, sizeof a)
#define pk push_back
#define ur(x) sort(all(x)), x.erase(unique(all(x)), x.end())
#define fi first
#define se second
#define si(x) int(x.size())
#define chi(x) (x - '0')
#define ull unsigned long long
#define Mp make_pair
using namespace std;
const ll inf = 1e18;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
const int mod = 998244353;
const int P = 1e9 + 7;
const int dx[] = {0, 0, 1, -1};
const int dy[] = {-1, 1, 0, 0};
const int N = 1e6 + 10;
const int M = 1000 + 10;
int n, m, q;
vector<PII> g[N];
ll dp[N][30];
void solve()
{
cin >> n >> m >> q;
while (m --) {
int u, v, w;
cin >> u >> v >> w;
g[u].emplace_back(v, w);
}
for (int i = 1; i <= n; i ++) dp[i][0] = 1;
for (int i = 1; i < 31; i ++) {
for (int j = 1; j <= n; j ++) {
for (auto [v, w] : g[j]) {
dp[j][i] = max(dp[j][i], min(dp[v][i - 1] * w, INF * 1ll));
}
}
}
while (q --) {
int p, x;
cin >> p >> x;
for (int i = 1; i < 31; i ++)
if (dp[p][i] > x) {
cout << i << '\n';
break;
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while (t --) solve();
return 0;
}
I. Dating Day
题目大意
给定一个01串,选择一个区间 \([l, r]\),满足区间内有 \(k\) 个1,要求重排区间内的字符,求一共可能会有多少个不同的01序列。
思路
用双指针枚举有 \(k\) 个1的区间,重排区间内的字符,即是在 \(r - l + 1\) 内选 \(k\) 个位上填写 1,可以用组合数。但如果存在两个区间有交集,那么根据容斥原理,需要减去两个区间重复计算的组合。
首先我们枚举区间时要枚举到最大区间,我们以样例的第一个测试点为例:101011,k=2中,101符合要求,1010也符合要求,但我们要选1010,因为明显的,重排1010的组合一定包含了重排101的组合,所以要枚举最大区间。
接着解决区间重复计算问题,依旧以样例的第一个测试点为例,重排1010和0101时会提供重复计算的组合:110011、100111,仔细观察可以发现第1位和第5位上的1都没动,只有第3位上的1发生了移动,移动的范围就是两个区间的交集,由此我们可以想到重复计算的就是交集内重排的组合,那么当区间变大,k变大时,区间内会有多少个1呢,答案是有k-1个1,很容易证明。由此我们根据容斥原理,每计算新的一个区间,如果和上一个区间有交集,就减去交集内重排的组合数。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <iomanip>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
#define endl '\n'
#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define all(a) a.begin(), a.end()
#define lowbit(x) x & -x
#define ent cout << '\n'
#define out(x) cout << x << ' '
#define out2(x, y) cout << x << " ~ " << y << ' '
#define me(a, b) memset(a, b, sizeof a)
#define mc(a, b) memcpy(a, b, sizeof a)
#define pk push_back
#define ur(x) sort(all(x)), x.erase(unique(all(x)), x.end())
#define fi first
#define se second
#define si(x) int(x.size())
#define chi(x) (x - '0')
#define ull unsigned long long
#define Mp make_pair
using namespace std;
const ll inf = 1e18;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
const int mod = 998244353;
const int P = 1e9 + 7;
const int dx[] = {0, 0, 1, -1};
const int dy[] = {-1, 1, 0, 0};
const int N = 1e6 + 10;
const int M = 1000 + 10;
ll fact[N], infact[N];
ll qmi(ll a, ll b)
{
ll res = 1;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void init()
{
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++) fact[i] = fact[i - 1] * i % mod;
infact[N - 1] = qmi(fact[N - 1], mod - 2);
for (int i = N - 2; i; i --) infact[i] = infact[i + 1] * (i + 1) % mod;
}
ll C(int a, int b)
{
if (b < 0 || a - b < 0) return 0;
return fact[a] * infact[b] % mod * infact[a - b] % mod;
}
void solve()
{
int n, k;
string s;
cin >> n >> k >> s;
int sum = 0;
for (auto it : s) sum += chi(it);
if (sum < k) return void(cout << 0 << '\n');
int cnt = 0, i, j;
int l = -1, r = -1;
ll res = 1;
for (i = 0, j = 0; i < n; i ++) {
cnt += s[i] - '0';
if (cnt == k + 1) {
int len = i - j;
res = (res + C(len, k) - 1) % mod;
if (l != -1) {
int L = max(l, j), R = min(i - 1, r);
len = R - L + 1;
res = (res - C(len, k - 1) + 1 + mod) % mod;
}
l = j, r = i - 1;
while (cnt == k + 1) cnt -= s[j ++] - '0';
}
}
if (cnt == k) {
int len = i - j;
res = (res + C(len, k) - 1) % mod;
if (l != -1) {
int L = max(l, j), R = min(i - 1, r);
len = R - L + 1;
res = (res - C(len, k - 1) + 1 + mod) % mod;
}
}
cout << res << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
init();
int t = 1;
cin >> t;
while (t --) solve();
return 0;
}

浙公网安备 33010602011771号