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

链接: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.mfbz.cn/a/124157.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在虚拟机中新安装的Linux无法联网解决办法

1、我们在虚拟机中新安装了linux&#xff0c;默认是无法连接网络的&#xff0c;这个时候&#xff0c;需要配置自动获取ip的网设置。 2、我们在VMware Workstatio需要配置net网络&#xff0c;如下图 3、进入linux系统&#xff0c;找到 /etc/sysconfig/network-scripts/ [rootn…

软件测试|MySQL WHERE条件查询详解:筛选出需要的数据

简介 在数据库中&#xff0c;我们常常需要从表中筛选出符合特定条件的数据&#xff0c;以便满足业务需求或获取有用的信息。MySQL提供了WHERE条件查询&#xff0c;使我们能够轻松地筛选数据。本文将详细介绍MySQL WHERE条件查询的用法和示例&#xff0c;帮助大家更好地理解和应…

Go uuid库介绍

简介&#xff1a; 在现代软件开发中&#xff0c;全球唯一标识符&#xff08;UUID&#xff09;在许多场景中发挥着重要的作用。UUID是一种128位的唯一标识符&#xff0c;它能够保证在全球范围内不重复。在Go语言中&#xff0c;我们可以使用第三方库github.com/google/uuid来方便…

2023-11-09 node.js-有意思的项目-记录

摘要: 2023-11-09 node.js-有意思的项目-记录 记录: 1、 NodeBB Star: 13.3k 一个基于Node.js的现代化社区论坛软件&#xff0c;具有快速、可扩展、易于使用和灵活的特点。它支持多种数据库&#xff0c;包括MongoDB、Redis和PostgreSQL&#xff0c;并且可以轻松地进行自定义…

Java修仙传之神奇的ES2(巧妙的查询及结果处理篇)

SDL语句查询 查询的基本语法 GET /indexName/_search {"query": {"查询类型": {"查询条件": "条件值"}} } 根据文档id查询 #查询文档 GET hotel/_doc/36934 查询所有 会弹出该索引库下所有文档// 查询所有 GET /indexName/_searc…

Flutter StreamBuilder 实现局部刷新 Widget

Stream 就是事件流或者管道&#xff0c;是基于事件流驱动设计代码&#xff0c;然后监听订阅事件&#xff0c;并针对事件变换处理响应。 Stream 分单订阅流和广播流,单订阅流在发送完成事件之前只允许设置一个监听器&#xff0c;并且只有在流上设置监听器后才开始产生事件&…

基于SSM的图书管理借阅系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

2023亚太杯数学建模A题思路分析

文章目录 0 赛题思路1 竞赛信息2 竞赛时间3 建模常见问题类型3.1 分类问题3.2 优化问题3.3 预测问题3.4 评价问题 4 建模资料5 最后 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 竞赛信息 2023年第十三…

字节流操作

for i in range(100):ai.to_bytes(2,byteorderbig)print(i,a,end )if i%40:print() 字节流 a5678 先把5678转换为二进制就变成 0001_0110_0010_1110拆分两个字节&#xff0c;高字节在前&#xff0c;低字节在后 hig_byte 0001_0110 对应的16进制 0x16 little_byte 0010_11…

3 任务3 使用趋动云部署自己的stable-diffusion

使用趋动云部署自己的stable-diffusion 1 创建项目&#xff1a;2 初始化开发环境实例3 部署模型4 模型测试 1 创建项目&#xff1a; 1.进入趋动云用户工作台&#xff0c;选择&#xff1a;当前空间&#xff0c;请确保当前所在空间是注册时系统自动生成的空间。 a.非系统自动生成…

MATLAB|风玫瑰图

目录 扫一扫关注公众号 效果图 粉丝给的图&#xff1a; 复刻的图&#xff1a; 其他样式效果&#xff1a; 数据 绘图教程 绘制左边Y轴 绘制主、次网格和主、次刻度的极坐标区域。 添加刮风数据&#xff0c;添加数据和颜色、图列大小映射关系。 颜色条绘制​​​​​​…

图的算法

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

4K壁纸下载器,多种风格壁纸,一键批量下载到本地,桌面壁纸,高清壁纸,壁纸下载

一个桌面壁纸爬虫工具&#xff0c;该工具可以从内置的多个壁纸网站爬取高清壁纸&#xff0c;并支持将壁纸一键下载到本地&#xff0c;真正实现了所见即所得&#xff0c;不必再费心费力的翻看多个网站。 文末附工具下载链接~ 一、软件简介 本次带来的工具由吾爱的一位大佬开发…

小白学爬虫:通过商品ID或商品链接封装接口获取淘宝商品销量数据接口|淘宝商品销量接口|淘宝月销量接口|淘宝总销量接口

淘宝商品销量接口是淘宝开放平台提供的一种API接口&#xff0c;通过该接口&#xff0c;商家可以获取到淘宝平台上的商品销量数据。使用淘宝商品销量接口的步骤如下&#xff1a; 1、在淘宝开放平台注册并创建应用&#xff0c;获取API Key和Secret Key等必要的信息。 2、根据淘宝…

终于有人说清楚了Cookie、Session、Token的区别。详解,面试题

前言&#xff1a; 众所周知&#xff0c;我们访问网页一般都是使用http协议&#xff0c;而http协议的每一次访问都是无状态的。 何为无状态&#xff1f;就是这一次请求和上一次请求是没有任何关系的、互不认识的、没有关联的。这种无状态的好处就是快速&#xff0c;坏处就是无法…

Unity热更新那些事

目录 热更新方案Unity程序的两种编译方式编译阶段执行阶段Mono方式IL2CPP方式两种方式打包以后的项目目录结构 其他 ILRuntime热更新ILRuntime使用注意ILRuntime的实现原理ILRuntime的性能优化建议ILRuntime的性能优化建议 HybridCLR热更新 参考链接 Unity热更新那些事 一小时极…

【算法与数据结构】216、LeetCode组合总和 III

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;本题可以直接利用77题的代码【算法与数据结构】77、LeetCode组合&#xff0c;稍作修改即可使用。   …

实验5-2——网络yum源的配置

网络yum源的配置 实验步骤&#xff1a; 1.在/etc/yum.repos.d中新建一个文件夹bak备份原来的东西,查看/etc/yum.repos.d/内容 cd /etc/yum.repos.d mkdir bak ls 2.把/etc/yum.repos.d中已有的repo文件都移入bak文件夹中并查看 mv *.repo bak ls 3. 下载安装weget以防万一本…

C语言 每日一题 11.9 day15

数组元素循环右移问题 一个数组A中存有N&#xff08; > 0&#xff09;个整数&#xff0c;在不允许使用另外数组的前提下&#xff0c;将每个整数循环向右移M&#xff08;≥0&#xff09;个位置&#xff0c;即将A中的数据由&#xff08;A0​A1⋯AN−1&#xff09;变换为&…

leetCode 206.反转链表 图解

206. 反转链表 - 力扣&#xff08;LeetCode&#xff09; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表 class Solution { public:ListNode* reverseList(ListNode* head) {ListNode* s NULL;ListNode* phead;while(p) {headhead->nex…