【记录】「SCOI2016」三道模拟赛/26.7.12

pMath女士,中考后玩固然开心,但你的训练怎么办呢?


P3294 SCOI2016 背单词 - 洛谷 (luogu.com.cn)

看错题,但我觉得可能不是我的问题。

教练使用了原版题意,意义不明,导致我卡了一个小时。

字典树 + 一点点小贪心。

赛后 1h 重新读题+重构 ac 了:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 5e5 + 10;
int ch[N][30];
int id;
string s[N];
int ed[N], ped[N], led[N];
int rd[N];

void ins(string ss, int x) {
	int p = 0;
	for (int i = 0; ss[i]; i ++) {
		int j = ss[i] - 'a';
		if (ch[p][j] == 0) {
			id ++;
			ch[p][j] = id;
		}
		p = ch[p][j];
	}
	ed[p] = x;
}

vector<int> G[N];

void dfs(int p, int fa) {
    if(ed[p]) {
        G[fa].push_back(p);  // 只连接到最近的后缀
        fa = p;
    }
    for(int i = 0; i < 26; i++) {
        if(ch[p][i]) dfs(ch[p][i], fa);
    }
}

int dfn[N];
int siz[N];
int tsp;
LL ans;

void dfsa(int x) {
	siz[x] = 1;
	for (int y : G[x]) {
		dfsa(y);
		siz[x] += siz[y];
	}
}

bool cmp(int na, int nb) {
	return siz[na] < siz[nb];
}

void dfsb(int x, int fa) {
	dfn[x] = ++ tsp;
	ans += dfn[x] - dfn[fa];
	sort(G[x].begin(), G[x].end(), cmp);
	for (int y : G[x]) {
		dfsb(y, x);
	}
}

signed main () {
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int n;
	cin >> n;
	
	id = 0;
	memset(ch, 0, sizeof(ch));
	memset(ed, 0, sizeof(ed));
	for (int i = 1; i <= n; i ++) {
		cin >> s[i];
		reverse(s[i].begin(), s[i].end());
		ins(s[i], i);
	}
	
	dfs(0, 0);
	memset(siz, 0, sizeof(siz));
	dfsa(0);
	memset(dfn, 0, sizeof(dfn));
	tsp = 0;
	ans = 0;
	dfsb(0, 0);
	
	cout << ans << "\n";
	
	return 0;
} 

P3292 SCOI2016 幸运数字 - 洛谷 (luogu.com.cn)

忘了线性基,赛时打了个差不多的没调出来(最好是差不多)。

【模版】线性基相关(贪心模板和高斯消元第 K 小)-CSDN博客

知道线性基后就很简单了。

1.题目里图是棵树,树上两点间路径唯一,且由两点的 LCA 到两点的直接距离构成。

2.要求路径上所有点构成的线性基,从而求出最大异或和。

3.将一个集合分开成两部分单独求线性基再合并,和整个集合求线性基,

两者求出的结果相同(不影响异或和运算)。

4.将从根节点到节点 x 路径上所有点的线性基存在 px\[\] 里,将线性基对应的点编号存在 posx\[\]。

5.当有两个点都能构成节点 x 二进制第 i 位的线性基时,优先选 dep 大的(深层的点)。

(为什么这样贪心?因为合并线性基时,只有 dep >= LCA dep 的才可以被纳入,即在路径上)

(所以我们选择尽量深的基,dep 更大更容易被选)

6.合并根节点到 x 和根节点到 y 的基时,只能选择 dep >= LCA dep 的,不然不在路径上。

详见注释代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 2e4 + 10;
const int D = 63;
LL a[N];
vector<int> G[N];
int dep[N];
int pos[N][D];
LL p[N][D];
int f[N][D];

void insert(int x, int fa) {
    for (int i = 0; i <= 60; i ++) {
        p[x][i] = p[fa][i];
        pos[x][i] = pos[fa][i];
    }

    LL X = a[x]; int P = x;

    for (int i = 60; i >= 0; i --) if (X & (1ll << i)) {
        if (!p[x][i]) {
            p[x][i] = X;
            pos[x][i] = P;
            return ;
        }
        if (dep[pos[x][i]] < dep[P]) {
            swap(pos[x][i], P);
            swap(p[x][i], X);
        }
        X ^= p[x][i];
    }
}

void dfs(int x, int fa) {
    dep[x] = dep[fa] + 1;
    f[x][0] = fa;
    for (int i = 1; i <= 15; i ++) {
        f[x][i] = f[f[x][i - 1]][i - 1];
    }
    insert(x, fa);
    for (int y : G[x]) if (y != fa) {
        dfs(y, x);
    }
}

int LCA(int x, int y) {
    if (dep[x] < dep[y]) {
        swap(x, y);
    }
    for (int i = 15; i >= 0; i --) {
        if (dep[f[x][i]] >= dep[y]) {
            x = f[x][i];
        }
    }
    if (x == y) {
        return x;
    }
    for (int i = 15; i >= 0; i --) {
        if (f[x][i] != f[y][i]) {
            x = f[x][i];
            y = f[y][i];
        }
    }
    return f[x][0];
}

LL b[D];

LL query(int x, int y) {
    memset(b, 0, sizeof(b));
    
    int lca = LCA(x, y);
    for (int i = 60; i >= 0; i --) {  // 先将 p[x] 里符合条件插到 b 数组里 
    	if (dep[pos[x][i]] >= dep[lca]) {
    		b[i] = p[x][i];
		}
	}
	
	for (int i = 60; i >= 0; i --) {    // 合并 p[y] 里的线性基 
		if (dep[pos[y][i]] >= dep[lca]) {
			LL X = p[y][i];
			for (int j = i; j >= 0; j --) if (X & (1ll << j)) {
				if (!b[j]) {
					b[j] = X;
					break;
				}
				X ^= b[j];
			}
		}
	}
	
	LL ans = 0;
	for (int i = 60; i >= 0; i --) if ((ans ^ b[i]) > ans) {
		ans ^= b[i];
	}
	return ans;
}

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

    int n, q;
    cin >> n >> q;
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
    }
    for (int i = 1; i < n; i ++ ) {
        int x, y;
        cin >> x >> y;
        G[x].push_back(y);
        G[y].push_back(x);
    }

    dep[0] = 0;
    memset(p, 0, sizeof(p));
    memset(f, 0, sizeof(f));
    dfs(1, 0);

    for (int i = 1; i <= q; i ++) {
        int x, y;
        cin >> x >> y;
        cout << query(x, y) << "\n";
    }

    return 0;
}

P3295 SCOI2016 萌萌哒 - 洛谷 (luogu.com.cn)

cpp 复制代码
#include<bits/stdc++.h> 
using namespace std;

typedef long long LL;
const int N = 100005;
const LL P = 1e9 + 7;

int fa[N][20];
// fa[i][k] 表示从位置 i 开始,长度为 2^k 的区间块的"代表元"
// 18 是因为 2^17 = 131072 > 1e5,所以够用

int findfa(int x, int k) {
	if (fa[x][k] == x) {
		return fa[x][k];
	}
	return fa[x][k] = findfa(fa[x][k], k);
}

// 并查集合并,将两个长度为 2^k 的块合并到同一个集合
void merge(int x, int y, int k) {
    x = findfa(x, k);
	y = findfa(y, k);
    if(x != y) {
		fa[x][k] = y;  // 将 x 的根指向 y 的根
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	
    int n, m;
	cin >> n >> m;
    int D = floor(log2(n));  // 最大的块大小 
    
    // 初始化:每个块单独成一个集合
    for (int i = 1; i <= n; i ++) {
        for (int k = 0; k <= D; k ++) {
            fa[i][k] = i;    
		}
	}
    
    /*
    对于每个条件:[l1, r1] == [l2, r2]
    用二进制拆分的方法,将区间拆成若干个长度为 2 的幂次的块
    并合并对应的块
     */
    for (int i = 1; i <= m; i ++) {
        int la, ra, lb, rb;
    	cin >> la >> ra >> lb >> rb;
        // 从大到小枚举 k,贪心地拆分区间
        for (int k = D; k >= 0; k --) {
            // 如果当前区间还能分出一个长度为 2^k 的块
            if (la + (1 << k) - 1 <= ra) {
                merge(la, lb, k);     // 合并这两个长度为 2^k 的块
                la += 1 << k;         // 移动指针到下一个未处理的位置
                lb += 1 << k;
            }
        }
    }    
    
    /*
    将高层的相等关系传递到低层
    如果两个长度为 2^k 的块相等,那么它们的前半段和后半段也分别相等
    即:(x, k) == (y, k) => (x, k-1) == (y, k-1) 且 (x+2^(k-1), k-1) == (y+2^(k-1), k-1)
     */
    for (int k = D; k >= 1; k --) {  // 从最高层向低层递推
        for (int i = 1; i + (1 << k) - 1 <= n; ++i) {
            int pos = findfa(i, k);  // 找到 i 所在长度为 2^k 块的根
            // 实际上是将根所在块的信息下推给所有成员
            // 注意:这里是把 i 和 pos 的前半段合并,后半段也合并
            merge(i, pos, k - 1);
            merge(i + (1 << (k - 1)), pos + (1 << (k - 1)), k - 1);
        }
    }
    
    /*
    统计答案
    现在所有的相等关系已经传递到第 0 层(即单个字符层)
    fa[i][0] 就表示位置 i 最终属于哪个等价类
     */
    LL ans = 0;
    for (int i = 1; i <= n; i ++) {
        if (fa[i][0] == i) {  // 找到一个新的等价类(根节点)
            // 第一个位置(最高位)不能为 0,所以第一个等价类有 9 种选择
            // 其他等价类有 10 种选择(0-9)
            if (ans == 0) {
                ans = 9;
            }
            else {
                ans = ans * 10ll % P;
            }
        }
    }
    
    cout << ans << "\n";
    return 0;
}
相关推荐
退休倒计时3 小时前
【每日一题】LeetCode 39. 组合总和 TypeScript
算法·leetcode·typescript
不是az3 小时前
力控相关知识点
算法·机器学习·最小二乘法
战血石LoveYY3 小时前
Integer类型超限,导致数据异常
java·算法
会周易的程序员3 小时前
从零构建多核CPU负载自适应控制系统
linux·c++·笔记·物联网·测试工具
紫金修道3 小时前
PnP算法介绍(Perspective-n-Point)
算法
程序猿编码4 小时前
用C++从零开始造一个微型GPT,不借助任何第三方库
开发语言·c++·gpt·模型推理
qz5zwangzihan14 小时前
题解:Atcoder Beginner Contest abc466 F - Many Mod Calculation
c++·题解·优先队列·atcoder·大根堆·abc466·abc466f
froyoisle5 小时前
CSP 真题解析:[CSP-J 2019-T4] 加工零件
c++·算法·bfs·csp-j·算法竞赛·信息学·信奥赛
2401_869769595 小时前
内容7 内存管理 1
c++