2020 ICPC 澳门(G,J,I)详解

news2024/11/24 9:48:43

链接:The 2020 ICPC Asia Macau Regional Contest

G Game on Sequence

题意

给定长度为 n n n 数组 a i a_i ai,A与G博弈,G先手,给定初始位置 k k k,若当前在 i i i 点转移到 j j j,满足 i < j i < j i<j,并且 a i , a j a_i, a_j ai,aj 二进制数位最多只有一位不同,谁不能移动了就输了。现在有两个操作,操作1:在数组末尾增加一个数 k k k. 操作2:询问从 k k k 出发二者谁赢。

思路

乍一看每次新增一个数似乎都要将前面的都更新一遍时间复杂度爆炸,但是对于博弈的这种题我们需要发现一些性质来入手。
我们知道平等组合游戏中,若当前点能转移到必败点,则当前点是必胜点。
在这里插入图片描述

s g i = 0 / 1 sg_i = 0/1 sgi=0/1 分别代表从该点出发先手必败/必胜。

我们考虑以下情况若存在两个位置 a i = a j ( i < j ) a_i = a_j (i < j) ai=aj(i<j) a j a_j aj s g j sg_j sgj 值分类讨论:
s j = 1 s_j = 1 sj=1 则说明 a j a_j aj 后存在一个可转移的点 k k k s g k = 0 sg_k = 0 sgk=0,那么 a i a_i ai 同样可以转移到 k k k,所以 s g i = 1 sg_i = 1 sgi=1.
s j = 0 s_j = 0 sj=0 a i a_i ai 可以直接转移 j j j 点, s g i = 1 sg_i = 1 sgi=1.
所以若 a i a_i ai 后存在相同的值,则 a i a_i ai 必胜。

所以每次增加一个数从后往前更新,只需要更新能转移的数的最后一个位置就可以了,总共只有 0 ∼ 255 0\sim255 0255 种数。

具体见代码有详细注释。

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10, M = 300;

int sg[N], a[N];

int last[M];

void update(){
    vector<int> A;
    for(int i = 0; i <= 255; i ++){
        if(last[i]) A.push_back(last[i]);
    }
    sort(A.begin(), A.end()); // 排序,因为一定要从后向前更新
    for(int i = A.size() - 1; i >= 0; i --){
        sg[A[i]] = 0; // 先赋值为 0
        for(int j = 0; j < 8 && !sg[A[i]]; j ++){ // 重新将其更新
            int bi = (a[A[i]] ^ (1 << j));
            if(last[bi] > A[i] && !sg[last[bi]]) sg[A[i]] = 1;
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++){
        cin >> a[i];
        if(last[a[i]]) sg[last[a[i]]] = 1; // 后面有相同值,该点必胜
        last[a[i]] = i;
    }

    for(int i = n; i >= 1; i --){ // 从后向前转移
        if(sg[i]) continue ; // 只从必败点转移
        for(int j = 0; j < 8; j ++){ // 枚举可以转移到此的数,且只更新最后一个该数
            int bi = (a[i] ^ (1 << j)); // 改变j数位上的数
            if(last[bi]) sg[last[bi]] = 1; // 有必败点则自己必胜
        }
    }

    for(int i = 1; i <= m; i ++){
        int op, k;
        cin >> op >> k;
        if(op == 1){
            if(last[k]) sg[last[k]] = 1; // 与自己相同的必胜
            last[k] = ++ n;
            a[n] = k;
            update(); // 更新所有值最后点的sg值
        }
        else{
            // printf("sg[%d] = %d\n",k, sg[k]);
            cout << (sg[k] == 1?"Grammy":"Alice") << "\n";
        }
    }
    return 0;
}

J Jewel Grab

题意

给定 n n n 个物品有颜色 c i c_i ci 和 价值 v i v_i vi,两个操作。操作1:将 x x x 号物品颜色价值改为 c j , v j c_j,v_j cj,vj. 操作2:从 s s s 位置开始,不允许拿走同色的物品,但可以跳过 k k k 次选择不拿,问最多能拿价值多少的物品。(拿走物品后,下一次操作前就会原样回复,但修改不会)。

思路

我们可以随便用线段树/树状数组维护价值。考虑主要的颜色问题。

对于和区间数字种类有关的问题,我们考虑维护一个 l a s t i last_i lasti,代表该位置前一个同色的物品的位置。用线段树维护 l a s t i last_i lasti 的区间最大值,因为 k k k 很小,第一次我们都在线段树区间 [ s , n ] [s,n] [s,n] 中二分找 l a s t i ≥ s last_i \geq s lastis 的最小的位置,每次询问过后得到 i d x idx idx(这就是我们第一个要跳过的颜色),下次询问的时候就扩展至在区间 [ i d x + 1 , n ] [idx + 1, n] [idx+1,n] 继续询问。 这样就能找到所有重复的颜色,减去同色的较小值,保留最大值,最后再用树状数组求一个区间求和即可。

具体见代码。

代码

/* 
对于找相同元素的问题,可以考虑维护last:上一个相同元素的位置
线段树维护最大的last,线段树上二分逐一找到10个重复元素
 */
#include <bits/stdc++.h>
using namespace std;

#define ls p << 1
#define rs p << 1 | 1
#define ll long long

const int N = 2e5 + 10, inf = 1e9;

int n, m;
ll rt[N]; // 树状数组维护区间求和单点修改
int lowbit(int x){ return x & -x; }
void update(int r, int k){
    for(int i = r; i <= n; i += lowbit(i)) rt[i] += k;
}
ll get_sum(int l, int r){
    ll ans = 0;
    for(int i = r; i; i -= lowbit(i)) ans += rt[i];
    for(int i = l - 1; i; i -= lowbit(i)) ans -= rt[i];
    return ans;
}

set<int> s[N]; // si:维护颜色i的下标
struct seg_tree{
    int l, r, max_last;
}tr[N * 4];

void build(int p, int l, int r){
    tr[p] = {l, r, 0};
    if(l == r) return ;
    int mid = (l + r) >> 1;
    build(ls, l, mid); build(rs, mid + 1, r);
}

void pushup(int p){
    tr[p].max_last = max(tr[ls].max_last, tr[rs].max_last);
}
void update(int p, int loc, int k){
    if(tr[p].l == tr[p].r){
        tr[p].max_last = k;
        return ;
    }
    int mid = tr[ls].r;
    if(loc <= mid) update(ls, loc, k);
    else update(rs, loc, k);
    pushup(p);
}

int query(int p, int k, int l, int r){ // 线段树上二分
    if(tr[p].max_last < k) return inf;
    if(tr[p].l == tr[p].r) return tr[p].l;
    
    if(l <= tr[p].l && tr[p].r <= r){
        if(tr[ls].max_last >= k) return query(ls, k, l, r);
        else return query(rs, k, l, r);
    }
    int mid = tr[ls].r, ans = inf;
    if(l <= mid) ans = query(ls, k, l, r);
    if(r > mid && ans == inf) ans = query(rs, k, l, r);
    return ans;
}

int c[N], v[N], last[N], nex[N], last_c[N]; // lasti:前一个同色的位置 nexi:后一个同色的位置

void solve(){
    int si, k;
    cin >> si >> k;
    vector<int> ci;

    ll sub = 0;
    int idx = si;
    // 此处last_c[i]: 中存的是需要跳过的颜色的最大价值
    for(int i = 1; i <= k && idx < n; i ++){
        idx = query(1, si, idx + 1, n);
        if(idx == inf) break;
		
		if(!last_c[c[idx]]) last_c[c[idx]] = v[last[idx]]; // 之前没有跳过该颜色,记录
        if(last_c[c[idx]] < v[idx]){ // 减去除了最大价值的同色的价值
            sub -= last_c[c[idx]];
            last_c[c[idx]] = v[idx];
        }
        else sub -= v[idx];
        
        ci.push_back(c[idx]);
    }

    for(auto x : ci) last_c[x] = 0;
    idx = (idx + 1 <= n) ? query(1, si, idx + 1, n) : inf; // 找到最近的不能跳过的点
    idx = min(idx - 1, n); 
    cout << get_sum(si, idx) + sub << "\n";
}

void update(){
    int x, ci, vi;
    cin >> x >> ci >> vi;

    update(x, vi - v[x]); v[x] = vi;
    if(ci == c[x]) return  ;
    
    if(last[x] && nex[x]){
        update(1, nex[x], last[x]); 
        last[nex[x]] = last[x];
        nex[last[x]] = nex[x];
    }
    else if(last[x]) nex[last[x]] = 0;
    else if(nex[x]){
        update(1, nex[x], 0);
        last[nex[x]] = 0;
    }
	
    s[c[x]].erase(x);
    auto it = s[ci].lower_bound(x);
    last[x] = nex[x] = 0;
    if(it != s[ci].end()){
        int nx = *it;
        update(1, nx, x);
        last[nx] = x;
        nex[x] = nx;
    }
    
    if(it != s[ci].begin()){
        int la = *prev(it);
        update(1, x, la);
        last[x] = la;
        nex[la] = x;
    }
    else update(1, x, 0);
    s[ci].insert(x);
    c[x] = ci;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    cin >> n >> m;
	
	build(1, 1, n);
	
    for(int i = 1; i <= n; i ++){ // 此处last_ci:颜色i的最后一个位置
        cin >> c[i] >> v[i];
        update(i, v[i]);

        int idx = last_c[c[i]];
        if(idx){
            last[i] = idx;
            nex[idx] = i;
        }
        last_c[c[i]] = i;
        if(last[i]) update(1, i, last[i]);
        s[c[i]].insert(i);
    }

    for(int i = 1; i <= n; i ++) last_c[i] = 0;

    for(int i = 1; i <= m; i ++){
        int op;
        cin >> op;
        if(op == 1) update();
        else solve();
    }
    return 0;
}

C Club Assignment

用的很麻烦的思路,以及臭长的代码,明天再补思路和注释,写吐我了。

题意

思路

代码

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> pii;
const int N = 1e5 + 10, inf = (1 << 30);

struct DSU {
    std::vector<int> f;
    
    DSU() {}
    DSU(int maxn) {
        init(maxn);
    }
    void init(int maxn) {
        f.resize(++ maxn); // 重构容器大小到 n
        std::iota(f.begin(), f.end(), 0); // 批量递增赋值
    }
    int find(int x) {
        while (x != f[x]) {
            x = f[x] = f[f[x]];
        }
        return x;
    }
    bool same(int x, int y) {
        return find(x) == find(y);
    }
    bool merge(int x, int y) {
        x = find(x);
        y = find(y);
        if (x == y) {
            return false;
        }
        f[y] = x;
        return true;
    }
};

int son[N * 30][2], siz[N * 30], id[N * 30], tot;
void insert(int x, int ID){
    int p = 0;
    for(int i = 29; i >= 0; i --){
        int u = x >> i & 1;
        if(!son[p][u]) son[p][u] = ++ tot;
        p = son[p][u];
        siz[p] ++;
    }
    id[p] = ID;
}

int check(int x){
    int p = 0;
    for(int i = 29; i >= 0; i --){
        int u = x >> i & 1;
        if(!son[p][u]) return 0;
        p = son[p][u];
    }
    return siz[p];
}

pii get_min(int x, int s, int p){
    int ans = 0;
    for(int i = s; i >= 0; i --){
        int u = x >> i & 1;
        if(son[p][u]) p = son[p][u];
        else{
            p = son[p][!u];
            ans |= (1 << i);
        }
    }
    return {ans, id[p]};
}

struct edge{
    int u, v, w;
    bool operator < (const edge& A)const{
        return w < A.w;
    }
};

edge query(int p1, int x, int i, int p2, int s){ // 遍历子树小的一边
    edge res = {0, 0, inf};
    if(son[p1][0]) res = min(res, query(son[p1][0], x, i - 1, p2, s));
    if(son[p1][1]) res = min(res, query(son[p1][1], x + (1 << i - 1), i - 1, p2, s));
    if(res.w == inf){
        pii tmp = get_min(x, s, p2);
        res = {id[p1], tmp.second, tmp.first};
    }
    return res;
}

edge e[N];
int cnt;
void dfs(int p, int i){ // 最小异或生成树
    if(son[p][0]) dfs(son[p][0], i - 1);
    if(son[p][1]) dfs(son[p][1], i - 1);
    if(son[p][0] && son[p][1]) {
        if(siz[son[p][0]] < siz[son[p][1]]){
            e[++ cnt] = query(son[p][0], 0, i - 1, son[p][1], i - 2);
        }
        else{
           e[++ cnt] = query(son[p][1], 1 << (i - 1), i - 1, son[p][0], i - 2);
        }
        e[cnt].w |= (1 << i - 1);
    }
    return ;
}


DSU dsu;
int n, Enemy[N], c[N];
vector<int> g[N];

void init(int op = 0){ // 清空
    for(int i = 0; i <= tot; i ++){
        son[i][0] = son[i][1] = id[i] = siz[i] = 0;
    }
    tot = 0;
    if(op) return ;
    dsu.init(n);
    for(int i = 1; i <= n; i ++){
        Enemy[i] = c[i] = 0;
        g[i].clear();
    }
    cnt = 0;
}

void dfs(int u){ // 二分图染色
    for(auto v : g[u]){
    	if(c[v]) continue ;
        c[v] = c[u] % 2 + 1;
        dfs(v);
    }
}
int a[N];
void solve(){
    cin >> n;

    init();
    for(int i = 1; i <= n; i ++){
        cin >> a[i]; 
        insert(a[i], i);
    }

    dfs(0, 30);

    sort(e + 1, e + 1 + cnt);

    dsu.init(n);

    int ans = 0;
    for(int i = 1; i <= cnt; i ++){
        // auto [u, v, w] = e[i];
        int u = e[i].u, v = e[i].v, w = e[i].w;
        if(dsu.same(u, v)){
            // cout << "w = " << w << "\n";
            ans = w;
            break ;
        }
        g[u].push_back(v);
        g[v].push_back(u); // 建二分图

        if(!Enemy[u]) Enemy[u] = v;
        else dsu.merge(Enemy[u], v);

        if(!Enemy[v]) Enemy[v] = u;
        else dsu.merge(Enemy[v], u);
    }

    for(int i = 1; i <= n; i ++){
        if(Enemy[i]){
        	if(!c[i]) c[i] = 1, dfs(i);
        }
        else c[i] = 1;
    }
    
    for(int i = 1; i <= n; i ++){
        if(!c[i]) c[i] = 1;
    }
    
    if(!ans){
    	init(1);
    	ans = inf;
    	for(int i = 1; i <= n && ans; i ++){
    		if(c[i] == 1){
    			int sum = check(a[i]);
    			if(sum >= 1){
    				 c[i] = 2;
    				 continue ;
    			}
    		    if(!sum && tot){
    		    	ans = min(ans, get_min(a[i], 29, 0).first);
    		    }
    		    insert(a[i], i);
            }
    	}
        init(1);
    	for(int i = 1; i <= n && ans; i ++){
            if(c[i] == 2){
            	int sum = check(a[i]);
            	if(sum >= 1) ans = 0;
                if(!sum && tot){
                	ans = min(ans, get_min(a[i], 29, 0).first);
                }
                insert(a[i], i); 
            }
        }
    }

    cout << ans << "\n";
    for(int i = 1; i <= n; i ++) cout << c[i];
    cout << "\n";
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int t;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1190097.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Google play的企业开发者账号比个人号上包成功率更高?

众所周知&#xff0c;Google play作为全球最大的Android应用市场&#xff0c;是开发者们推广应用的首选平台。Google play平台提供了两种账号类型&#xff1a;个人开发者和企业开发者&#xff0c;开发者们可以选择创建个人开发者账号或者企业开发者账号进行应用上架。 不过&am…

NR DCI size alignment

DCI对齐在38.212 7.3.1.0 DCI size alignment 中讲述。 Step 0 CSS 下&#xff0c;DCI 0_0根据初始UL BWP 确定大小&#xff0c;DCI 1_0 根据CORESET0 或初始DL BWP&#xff08;没有CORESET 0时&#xff09; 确定大小 根据激活的UL/DL BWP 确定DCI 0_0和DCI 1_0 的size&…

图的算法

拓扑排序算法 解析 要求&#xff1a;无环有向图 编译过程使用的是拓扑排序。A依赖BCD&#xff0c;在BCD三个文件编译完成才能引入A&#xff1b;B依赖ECD&#xff0c;在ECD三个文件编译完成才能引入B。拓扑排序排出整体的编译顺序E→CD→B→A 算法实现 找到整个图入度为0的点&…

《第三期(先导课)》之《Python 开发环境搭建》

文章目录 《第 1 节 初始Python》《第 6 节 pip包管理工具》 《第 1 节 初始Python》 。。。 《第 6 节 pip包管理工具》 pip是Python的包管理工具,用于安装、升级和管理Python包。 pip是Python标准库之外的一个第三方工具,可以从Python Package Index(PyPI)下载和安装各种P…

频次最高的38道selenium面试题及答案

1、selenium的原理是什么&#xff1f; selenium的原理涉及到3个部分&#xff0c;分别是&#xff1a; 浏览器driver&#xff1a;一般我们都会下载driverclient&#xff1a;也就是我们写的代码 client其实并不知道浏览器是怎么工作的&#xff0c;但是driver知道&#xff0c;在…

机器学习中的决策阈值

什么是决策阈值&#xff1f; sklearn不允许我们直接设置决策阈值&#xff0c;但它允许我们访问用于进行预测的决策分数&#xff08;决策函数o/p&#xff09;。我们可以从决策函数输出中选择最佳得分并将其设置为决策阈值&#xff0c;并且将小于该决策阈值的所有那些决策得分值…

点信息标注_BillboardTextActor3D

开发环境&#xff1a; Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example参考代码 demo解决问题&#xff1a;点附近创建左边或其他信息&#xff0c;且信息面板显示状态不受相机缩放、旋转影响 prj name: BillboardTextActor3D #include…

【Qt之QMetaType】使用

介绍 QMetaType类管理元对象系统中的命名类型。 该类用作QVariant和排队的信号与槽连接中类型的编组辅助器。它将类型名称与类型关联起来&#xff0c;以便可以在运行时动态创建和销毁它。使用Q_DECLARE_METATYPE()声明新类型&#xff0c;以使它们可供QVariant和其他基于模板的…

民生画派创始人张龙(天驰)作品

简介 张龙&#xff08;天驰&#xff09; 中国民生画派创始人 首届“陆俨少奖”金奖得主 人民大学巨幅主题创作高级研修班导师 中央美院客座教授 神舟十二号载人飞船遨游太空搭载作品创作者 被评为2021、2022年年度最具收藏价值艺术家 中国美术家协会会员 中国美术家协…

【CesiumJS入门】(11)加载LAS点云数据

前言 最近有两次投递简历以及面试都被问到了是否有三维点云数据处理相关的经验。然而我的岗位都没有和点云相关的工作任务&#xff0c;所以还是得自己加把劲呀。 本篇将从数据获取到加载来简易地介绍一个LAS点云数据的加载。 加载数据 首先&#xff0c;你得有一份LAS格式的…

Python实验项目6 :文件操作与模块化

1、使用random库&#xff0c;产生10个100到200之间的随机数&#xff0c;并求其最大值、平均值、标准差和中位数。 # 1、使用random库&#xff0c;产生10个100到200之间的随机数&#xff0c;并求其最大值、平均值、标准差和中位数。 import random # 定义一个列表 list[] for i …

MySQL–第4关:查询用户日活数及支付金额

MySQL–第4关&#xff1a;查询用户日活数及支付金额 – WhiteNights Site 标签&#xff1a;MySQL 非常好的题&#xff0c;爱来自中国。 题目 没啥用 任务描述 现有3张业务表&#xff0c;详见如下: 需要输出结果如下&#xff0c;没有支付的日期不需要显示&#xff0c;请写出对…

Leetcode---370周赛

题目列表 2923. 找到冠军 I 2924. 找到冠军 II 2925. 在树上执行操作以后得到的最大分数 2926. 平衡子序列的最大和 一、找到冠军I 第一题模拟题&#xff0c;简单来说是看每一行(列)是否全是1&#xff0c;当然不包括自己比自己强的情况&#xff0c;需要特判 代码如下 …

python回文日期 并输出下一个ABABBABA型回文日期

题目&#xff1a; 输入&#xff1a; 输入包含一个八位整数N&#xff0c;表示日期 对于所有的测评用例&#xff0c;10000101 ≤N≤89991231&#xff0c;保证N是一个合法日期的8位数表示 输出&#xff1a; 输出两行&#xff0c;每行一个八位数。第一行表示下一个回文日期第二…

任务管理器的正确使用教程

快捷键 Ctrlshiftesc&#xff1a;进入任务管理器 我以Win11举例 如何给XX排序 给XX排序&#xff0c;点击空白处可以选择某项降序排列&#xff08;可以找到最占用某项资料的程序&#xff09;&#xff0c;再点击空白处可以选择某项升序排列 文件正在使用&#xff0c;如何解决 …

windows系统下查看安卓apk的sha1

1.在apk所在文件夹打开cmd或者powershell 2.输入 certutil -hashfile xxx.apk SHA1 这样就可以了 3.指令格式 certutil -hashfile FileName [HashAlgorithm] certutil -hashfile&#xff1a;原样输入 FileName&#xff1a;文件名 HashAlgorithm&#xff1a;可选项包括&…

简述扫码登录原理及测试要点

扫码登录本质是解决将APP端的用户登录信息&#xff08;通常是Token&#xff09;通过扫码的形式安全稳定地同步给Web端。 操作流程&#xff1a; 打开登录页面&#xff0c;展示一个二维码(web)&#xff1b;打开APP扫描该二维码后&#xff0c;APP显示确认、取消按钮(app)&#xf…

A Survey on Neural Network Interpretability

A Survey on Neural Network Interpretability----《神经网络可解释性调查》 摘要 随着深度神经网络的巨大成功&#xff0c;人们也越来越担心它们的黑盒性质。可解释性问题影响了人们对深度学习系统的信任。它还与许多伦理问题有关&#xff0c;例如算法歧视。此外&#xff0c;…

野火霸天虎 STM32F407 学习笔记_5 按键输入;位带操作介绍

输入——按键点灯 开发板按键电路如下&#xff1a; 按键未按下接地&#xff0c;按下后为高电平。电容起到消抖作用&#xff0c;软件处理就不需要手动延时消抖了。 编程没啥难度&#xff0c;就是改了一下输入模式。使用 ReadInputDataBits 读取。 //bsp_button.c #include &q…

golang 2018,go 1.19安装Gin

GOPROXYhttps://mirrors.aliyun.com/goproxy/ 一致提示URL不能有点&#xff0c;给我整郁闷了&#xff0c;换了这个地址好了 但是一致提示zip的包问题&#xff0c;最后还是不行又换回七牛 NEWBEE&#xff01; [GIN-debug] Environment variable PORT is undefined. Using por…