2024icpc南京站

2024 i c p c 南京站 \Huge{2024icpc南京站} 2024icpc南京站

文章目录

比赛地址: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。

最大化选择物品的价值之和。

思路

注意到一定存在一个最优选法同时满足以下两个条件:

  1. 如果存在物品 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,方案不会变劣。
  2. 如果存在物品 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";
}
相关推荐
cwj&xyp7 分钟前
Python(二)str、list、tuple、dict、set
前端·python·算法
xiaoshiguang34 小时前
LeetCode:222.完全二叉树节点的数量
算法·leetcode
爱吃西瓜的小菜鸡4 小时前
【C语言】判断回文
c语言·学习·算法
别NULL4 小时前
机试题——疯长的草
数据结构·c++·算法
TT哇4 小时前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
yuanbenshidiaos6 小时前
C++----------函数的调用机制
java·c++·算法
唐叔在学习6 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA6 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
chengooooooo6 小时前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展
jackiendsc6 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法