[BZOJ3439]Kpm的MC密码 题解
题目地址:BZOJ& …
May all the beauty be blessed.
题目地址:Codeforces:Problem – 842D – Codeforces、vjudge:Vitya and Strange Lesson – CodeForces 842D – Virtual Judge
Today at the lesson Vitya learned a very interesting function — mex. Mex of a sequence of numbers is the minimum non-negative number that is not present in the sequence as element. For example, mex([4, 33, 0, 1, 1, 5]) = 2 and mex([1, 2, 3]) = 0.
Vitya quickly understood all tasks of the teacher, but can you do the same?
You are given an array consisting of n non-negative integers, and m queries. Each query is characterized by one number x and consists of the following consecutive steps:
Note that after each query the array changes.
给一个数列,每次查询给出一个值x,先对数列中每个元素异或上x,然后查询数列的mex值(即数列中不包含的最小非负整数)。
输入格式:
First line contains two integer numbers n and m (1 ≤ n, m ≤ 3 \cdot 10^5) — number of elements in array and number of queries.
Next line contains n integer numbers ai (0 ≤ ai ≤ 3 \cdot 10^5) — elements of then array.
Each of next m lines contains query — one integer number x (0 ≤ x ≤ 3 \cdot 10^5).
输出格式:
For each query print the answer on a separate line.
输入样例#1:
2 2 1 3 1 3
输出样例#1:
1 0
输入样例#2:
4 3 0 1 5 6 1 2 4
输出样例#2:
2 0 0
输入样例#3:
5 4 0 1 5 6 7 1 1 4 5
输出样例#3:
2 2 0 2
冷静分析一下这题,发现异或操作是对整个数列的,这个直接在外面维护标记就行了,因为异或满足结合律。找mex是一个难点,需要用01Trie处理。
先把数列元素去个重插入01Trie中,预先算好发现数据最大是大约19位二进制,那就存20位算了,插入的同时统计一下每个子树中有多少数。每次查询的时候先把查询的这一位异或上,即如果要异或的数为1,则1当0处理,0当1处理。要异或的数为0时,选择0子树肯定是比较小的,但是要看一下这个子树有没有满,满了只能向1走,否则向0走,当然如果子树为空直接返回即可。每次走的时候维护一个结果,如果向1走要在结果的这一位上设成1。这个结果就是询问的答案。
// Code by KSkun, 2018/2
#include <cstdio>
#include <algorithm>
int trie[6000005][2], tot = 1, has[6000005];
inline void insert(int n) {
int t = 1;
for(int i = 20; i > 0; i--) {
has[t]++;
if(n & (1 << (i - 1))) {
if(!trie[t][1]) trie[t][1] = ++tot;
t = trie[t][1];
} else {
if(!trie[t][0]) trie[t][0] = ++tot;
t = trie[t][0];
}
}
has[t]++;
}
int dlt = 0;
inline int query() {
int t = 1, res = 0;
for(int i = 20; i > 0; i--) {
if(dlt & (1 << (i - 1))) {
if(!trie[t][1]) {
return res;
} else if(has[trie[t][1]] < (1 << (i - 1))) {
t = trie[t][1];
} else {
t = trie[t][0];
res += (1 << (i - 1));
}
} else {
if(!trie[t][0]) {
return res;
} else if(has[trie[t][0]] < (1 << (i - 1))) {
t = trie[t][0];
} else {
t = trie[t][1];
res += (1 << (i - 1));
}
}
}
return res;
}
int n, m, a[300005], t;
int main() {
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
std::sort(a, a + n);
n = std::unique(a, a + n) - a;
for(int i = 0; i < n; i++) {
insert(a[i]);
}
for(int i = 0; i < m; i++) {
scanf("%d", &t);
dlt ^= t;
printf("%d\n", query());
}
return 0;
}
题目地址:洛谷:【P3294】[SCOI2016]背单词 – 洛谷、BZOJ:Problem 4567. — [Scoi2016]背单词
Lweb 面对如山的英语单词,陷入了深深的沉思,”我怎么样才能快点学完,然后去玩三国杀呢?“。这时候睿智的凤老师从远处飘来,他送给了 Lweb 一本计划册和一大缸泡椒,他的计划册是长这样的:
—————序号 单词—————
1 XXX
2 XXX
……
n-2 XXX
n-1 XXX
n XXX
———————————————
然后凤老师告诉 Lweb ,我知道你要学习的单词总共有 n 个,现在我们从上往下完成计划表,对于一个序号为 x 的单词(序号 1…x-1 都已经被填入):
Lweb 是一个吃到辣辣的东西会暴走的奇怪小朋友,所以请你帮助 Lweb ,寻找一种最优的填写单词方案,使得他记住这 n 个单词的情况下,吃最少的泡椒。
输入格式:
输入一个整数 n ,表示 Lweb 要学习的单词数。接下来 n 行,每行有一个单词(由小写字母构成,且保证任意单词两两互不相同)1<=n<=100000, 所有字符的长度总和 1<=|len|<=510000
输出格式:
Lweb 吃的最少泡椒数
输入样例#1:
2 a ba
输出样例#1:
2
把字符串翻转一下后缀问题就变成前缀问题了。前缀问题可以用Trie树处理。直接把这些字符串扔进Trie里就好。
第一种情况明显是最差的。我们可以让这个单词的所有后缀放在它前面来避免这种情况的发生,所以不需要管第一种情况。
第二种情况只会在它没有后缀的情况发生。
第三种情况考虑一种贪心想法,在Trie树上,一个结点的子树为以这个结点为前缀的字符串,统计子树中字符串的数量size,并求出一种DFS序,使得子树小的儿子优先被访问。这样,子树大的儿子中的字符串对答案的贡献将会整体减小。而DFS序保证了前缀都在这个字符串以前。
考虑对Trie树简化建树,将不是字符串结尾的结点全部删去,并对新树DFS。每一个儿子对答案的贡献为儿子的DFS序号-父亲的DFS序号。这个可以用当前DFS序号维护一下。写树形DP大概也是可以的?
// Code by KSkun, 2018/2
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
typedef long long LL;
std::vector<int> gra[510005];
int tot1 = 1, siz[510005], dp[510005];
int ch[510005][26], tot = 1;
bool wrd[510005];
int n;
LL pts = 0, ans = 0;
char str[510005];
inline bool cmp(int a, int b) {
return siz[a] < siz[b];
}
inline void insert(char* str) {
int t = 1;
int len = strlen(str);
for(int i = len - 1; i >= 0; i--) {
if(!ch[t][str[i] - 'a']) {
t = ch[t][str[i] - 'a'] = ++tot;
} else {
t = ch[t][str[i] - 'a'];
}
}
wrd[t] = true;
}
inline void rebuild(int fa, int u) {
if(wrd[u]) {
gra[fa].push_back(++tot1);
siz[fa = tot1] = 1;
}
for(int i = 0; i < 26; i++) {
if(ch[u][i]) {
rebuild(fa, ch[u][i]);
}
}
}
inline void calsize(int u) {
for(int i = 0; i < gra[u].size(); i++) {
calsize(gra[u][i]);
siz[u] += siz[gra[u][i]];
}
std::sort(gra[u].begin(), gra[u].end(), cmp);
}
inline void dfs(int u, int w) {
pts++;
ans += pts - w;
w = pts;
if(wrd[u]) dp[u] = 1;
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
dfs(v, w);
}
}
int main() {
scanf("%d", &n);
for(int i = 0; i < n; i++) {
scanf("%s", str);
insert(str);
}
rebuild(1, 1);
calsize(1);
dfs(1, 1);
printf("%lld", ans);
return 0;
}