2024 i c p c 南京站 \Huge{2024icpc南京站} 2024icpc南京站
文章目录
- [C. Primitive Root](#C. Primitive Root)
- [F. Equivalent Rewriting](#F. Equivalent Rewriting)
- [G. Knapsack](#G. Knapsack)
- [I. Counter](#I. Counter)
比赛地址:The 2023 ICPC Asia Nanjing Regional Contest (The 2nd Universal Cup. Stage 11: Nanjing)
官方题解:2023 ICPC 亚洲区域赛南京站 - SUA Wiki
C. Primitive Root
题意
给定质数 P P P和非负整数 m m m,有多少非负整数 g g g满足$ g≤m ~&&~ g⊕(P−1)≡1 (mod P)$?这里 ⊕ ⊕ ⊕是按位异或(XOR) 运算符。
思路
跟据题目给出的公式,我们可以做以下变形:
g ⊕ ( P − 1 ) ≡ 1 ( m o d P ) g ⊕ ( P − 1 ) = k P + 1 g = ( k P + 1 ) ⊕ ( P − 1 ) ≤ m ( k P + 1 ) + ( P − 1 ) ≤ m ( k + 1 ) p ≤ m g\oplus(P-1)\equiv1(mod~P)\\ g\oplus(P-1)=kP+1\\ g=(kP+1)\oplus (P-1)~~\le m\\ (kP+1)+(P-1)\le m\\ (k+1)p\le m\\ g⊕(P−1)≡1(mod P)g⊕(P−1)=kP+1g=(kP+1)⊕(P−1) ≤m(kP+1)+(P−1)≤m(k+1)p≤m
那么对于区间 0 ≤ k ≤ ⌊ m p ⌋ − 1 0\le k \le \left \lfloor \frac{m}{p} \right \rfloor - 1 0≤k≤⌊pm⌋−1,均有$ (kP+1)+(P-1)\le (k+1)P \le m$,满足要求。
对于所有 k ≤ ⌈ m P ⌉ + 1 k \le \left \lceil \frac{m}{P} \right \rceil + 1 k≤⌈Pm⌉+1,均有$ (kP+1)+(P-1)\ge (k-1)P + 2 \le m$,不满足要求。
因此我们只需要检查区间 ⌊ m p ⌋ ≤ k ≤ ⌈ m p ⌉ \left \lfloor \frac{m}{p} \right \rfloor \le k \le \left \lceil \frac{m}{p} \right \rceil ⌊pm⌋≤k≤⌈pm⌉的值即可。
标程
cpp
#define int long long
using namespace std;
void solve() {
int p,m;
cin >> p >> m;
int ans = (m/p);
// cout << ans << endl;
for(int i = m/p; i <= m / p +1; i ++)
if(((1 + i * p) ^ (p-1)) <= m)
ans++;
cout << ans << endl;
}
F. Equivalent Rewriting
题意
题目首先给出两个数字n,m,分别表示操作次数和数组长度。然后下列操作是对数组进行操作。
- p a 1 a 2 a 3 . . . a p p~a_1~a_2~a_3~...~a_p p a1 a2 a3 ... ap
若当前为第 i i i次操作,那么该操作为将数组中 a 1 , a 2 , a 3 , . . . , a p a_1,a_2,a_3,...,a_p a1,a2,a3,...,ap全部变为 i i i。
经过若干次这样的操作之后,得到数组的最后状态。题目要求能够改变操作的顺序,使得最终数组的结果不变。
思路
本题需要考虑是否存在某一个操作可以改变顺序。我们可以发现,当前操作结果可能会被后续操作覆盖,并且分为三种情况:
- 当前的操作结果会部分被后续操作影响。
- 当前的操作结果会全部被后续操作。
- 当前的操作结果不会被后续操作影响。
特别地对于后两种情况,可以分开讨论:
- 某一次操作不会收到后续操作的影响,可将其放置在其后任意一个位置(最后一次操作除外)。
- 某一次操作会被后续操作全部覆盖,那么可将其放置在其前面任意一个位置(第一次操作除外)。
事实上由于是可以选任意位置,我们只需要将其与相邻位置交换即可。
标程
cpp
#define int long long
void solve() {
int n, m; cin >> n >> m;
vector<vector<int>> op(n + 1);
for(int i = 1; i <= n; i ++ ) {
int x; cin >> x;
while(x -- ) {
int pos; cin >> pos;
op[i].push_back(pos);
}
}
vector<bool> st(m + 1);
vector<int> cnt(m + 1);
for(auto t : op[n]) cnt[t] ++ ;
bool flag = false;
for(int i = n - 1; i >= 1; i -- ) {
bool flag = true;
for(auto t : op[i]) {
if(st[t]) continue;
cnt[t] ++ ;
if(cnt[t] > 1) flag = false;
}
for(auto t : op[i + 1]) {
if(st[t]) continue;
cnt[t] -- ; st[t] = true;
}
if(flag) {
cout << "Yes\n";
for (int j = 1; j <= i - 1; j ++ ) cout << j << ' ';
cout << i + 1 << ' ' << i;
for (int j = i + 2; j <= n; j ++ ) cout << ' ' << j;
cout << '\n';
return;
}
}
cout << "No\n";
}
G. Knapsack
题意
给定 n n n件物品的体积 w i w_i wi与价值 v i v_i vi,以及背包的总容量 W W W。
你可以优先选择 k k k件物品获得他们的价值,随后再选择一些物品,要求随后选择的物品的体积之和不超过` W W W。
最大化选择物品的价值之和。
思路
注意到一定存在一个最优选法同时满足以下两个条件:
- 如果存在物品 a , b a,b a,b满足 𝑎 被免费选走, b b b被付费选走,那么 w a ≥ w b w_a \ge w_b wa≥wb。否则我们可以改成免费选 b b b,付费选 a a a,方案不会变劣。
- 如果存在物品 a , b a,b a,b满足 a a a被免费选走, b b b没有被选走,那么 v a ≥ v b v_a \ge v_b va≥vb。否则我们可以改成免费选 b b b,不选择 a a a,方案不会变劣。
因此,如果我们将所有物品按照 w i w_i wi从小到大排序,那么对于最优策略而言,一定存在一个分界点 M M M,满足 i > M i>M i>M的物品中,价值前 k k k大的物品被免费选走。
对于每个 i i i,可以通过维护一个堆来预处理出从第 i i i个物品开始的后缀免费选出 k k k个物品的最大价值之和。
因此我们只需要对每个前缀维护出 0/1 背包的结果即可。复杂度 O ( n w + n k + n l o g n ) O(nw+nk+nlogn) O(nw+nk+nlogn)。
标程
cpp
#define int long long
#define PII pair<int, int>
#define fi first
#define se second
const int INF = 0x7fffffff;
void Solved() {
int n, w, k; cin >> n >> w >> k;
vector<PII> a(n);
vector<int> f(w + 1), g(n + 1);
// f[i]:计算背包问题的滚动数组
// g[i]:从第 i 个物品开始的后缀免费选出 K 个物品的最大价值之和
priority_queue<int, vector<int>, greater<int>> q;
for(auto &[i, j] : a) cin >> i >> j;
sort(ALL(a));
int sum = 0;
g[n] = 0;
// 计算出后缀选出的免费物品的最大价值之和
for(int i = n - 1; i >= 0; i -- ) {
q.push(a[i].se);
sum += a[i].se;
if(q.size() > k) {
sum -= q.top();
q.pop();
}
g[i] = sum;
}
for(int i = 0; i <= w; i ++ ) f[i] = -INF;
f[0] = 0;
int res = g[0]; // 结果初值,只买免费物品
// 01背包模板
for(int i = 0; i < n; i ++ ) {
for(int j = w; j >= a[i].fi; j -- ) {
f[j] = max(f[j], f[j - a[i].fi] + a[i].se);
res = max(res, f[j] + g[i + 1]); // 计算在分界点i时的情况
}
}
cout << res << endl;
}
I. Counter
题意
有一个计数器,一开始是0,有两种操作,每次操作可以把值+1
或清零。
给若干已知条件表示第 x i x_i xi次操作后计数器的值为 v i v_i vi,问是否有一种操作序列满足所有的已知条件。
思路
根据题目的性质,对于已知的操作,分为两种情况:计数器的值是否为0。
同时可以发现,对于某一次操作,若 v i = 5 v_i=5 vi=5,那么 x i x_i xi前(包括 x i x_i xi)会有连续5次的+1
操作。
那么,若已知的操作中有不符合上述情况的,这个操作序列肯定就不合法。因此可以按照 x i x_i xi排序,然后从后向前遍历。
遍历过程中标记当前位置的+1
操作是从哪个位置开始的,然后遍历过程中对于不合法的情况就直接结束即可,对于不合法的情况:
- v i = 0 v_i=0 vi=0
- x i > x x_i>x xi>x
- v i ! = 0 v_i!=0 vi!=0
- x i = t x_i=t xi=t
- x i > t & & v i ! = x i − t x_i>t ~~~\&\&~~~ v_i!=x_i-t xi>t && vi!=xi−t
- 否则为合法,更新标记位置 t t t。
标程
cpp
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se secondvoid solve() {
int n, m; cin >> n >> m;
vector<PII> v(m);
for(auto &[i, j] : v) cin >> i >> j;
sort(v.begin(), v.end());
int x = n + 1;
for(int i = m - 1; i >= 0; i -- ) {
if(v[i].se == 0) {
if(v[i].fi > x) {cout << "No\n"; return;}
} else {
if(v[i].fi == x) {cout << "No\n"; return;}
else if(v[i].fi > x) {
if(v[i].se != v[i].fi - x) {cout << "No\n"; return;}
} else {
x = v[i].fi - v[i].se;
if(x < 0) {cout << "No\n"; return;}
}
}
}
cout << "Yes\n";
}