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]\)

思路

\[\sum_{i = 1}^{n}(c_i - r_i) = \sum_{i = 1}^{n}c_i - \sum_{i = 1}^{n}r_i \]

\[\sum_{i = 1}^{n}c_i = \sum_{i = 1}^{n}(p \times c_{i - 1} + (1 - p) \times r_{i - 1}) = \sum_{i = 1}^{n}(p \times c_{i - 1}) + \sum_{i = 1}^{n}((1 - p) \times r_{i - 1}) = p \times \sum_{i = 1}^{n}c_{i - 1} + (1 - p) \times \sum_{i = 1}^{n}r_{i - 1} \]

\[\begin{align} \sum_{i = 1}^{n}c_i - \sum_{i = 1}^{n}r_i &= p \times \sum_{i = 1}^{n}c_{i - 1} + (1 - p) \times \sum_{i = 1}^{n}r_{i - 1} - \sum_{i = 1}^{n}r_i \\ &= p \times \sum_{i = 1}^{n}c_{i - 1} + (1 - p) \times \sum_{i = 1}^{n}r_{i - 1} - \sum_{i = 1}^{n}r_{i - 1} - r_i \\ &= p \times \sum_{i = 1}^{n}c_{i - 1} - p \times \sum_{i = 1}^{n}r_{i - 1} - r_i \\ &= p \times (\sum_{i = 1}^{n}c_{i - 1} - \sum_{i = 1}^{n}r_{i - 1}) - r_i \end{align} \]

其中假设我们已知了 \(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;
}
posted @ 2025-05-28 21:54  Natural_TLP  阅读(261)  评论(0)    收藏  举报