Loading

数据结构做题记录

本人由于数据结构太菜,所以有了这个东西。


T1 互斥的数

link

分析题目,求两两不互斥的最大子集。换句话说,就是删除互斥的元素。

考虑贪心,将数组从小到大排序。

如果 \(a_i\)\(a_j\) 是互斥的,这两个数当中必须要删掉一个。显然删掉 \(a_j\) 是更划算的,因为 \(a_i\) 只是影响到了 \(a_j\) ,而 \(a_j\) 还会影响 \(p × a_j\)

由于 \(a_i\) 的值很大,所以我们用 \(\texttt{hash}\) 维护即可。

#include <bits/stdc++.h>
using namespace std;
const int P = 1e8 + 7, p = 31;
int n, k;
int a[100005], ans;
bool b[P + 5];
int get_hash(int x) {
	return (p + x) % P;
}
signed main() {
	scanf("%d %d", &n, &k);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	sort(a + 1, a + n + 1);
	for (int i = 1; i <= n; i++) {
		if (!b[get_hash(a[i])]) {
			ans++;
			b[get_hash(a[i] * k)] = true;
		}
	}
	printf("%d", ans);
	return 0;
} 	

T2 Power Strings

link

再来一道模板题吧。

一道字符串 \(\texttt{hash}\)

我们可以利用 \(\texttt{hash}\) 的一个特点:当知道整个字符串的 \(\texttt{hash}\) 值时,就可以在 \(\mathcal{O} (1)\) 的时间负责度内算出其字串的 \(\texttt{hash}\) 值。

然后将原字符串进行拆分,使拆分出来的子字符串 \(\texttt{hash}\) 值相等即可。

双倍经验:link

#include <cstdio>
#include <iostream>
#include <cstring>
#define int unsigned long long
using namespace std;
const int base = 17;
int n;
char op[1000005];
int Hash[1000005], Pow[1000005];
void get_hash(char c[]) {
	int len = strlen(c + 1);
	for (int i = 1; i <= len; i++) {
		Hash[i] = Hash[i - 1] * base + c[i];
	}
}
int ans;
int get(int l, int r) {
	return Hash[r] - Hash[l - 1] * Pow[r - l + 1];
}
signed main() {
	Pow[1] = base;
	for (int i = 2; i <= 1000000; i++) Pow[i] = Pow[i - 1] * base;
	ios::sync_with_stdio(false);
	while (cin >> op + 1) {
		if (op[1] == '.') break;
		get_hash(op);
		int len = strlen(op + 1);
		for (int i = 1; i <= len; i++) {
			if (len % i != 0) continue;
			int p1 = get(1, i);
			bool f = true;
			for (int j = i + 1; j <= len; j += i) {
				int t = get(j, j + i - 1);
				if (t != p1) {
					f = false;
					break;
				}
			}
			if (f) {
				printf("%d\n", len / i);
				break;
			}
		}
	}
	return 0;
}

T3 [CEOI2017]Palindromic Partitions

用两个指针 \(L\)\(R\) ,分别指向字符串首尾。从两端同时枚举找到相同的字符串, \(s[1 - L]\)\(s[R - n]\), 然后下次再从 \(L\)\(R\) 出发,继续找到相同的字符串,如果最后找到字符串的长度不足 \(n\) 就将答案加 \(1\)

例如:\(\texttt{bonobo}\)

先找到 \(s[1 - 2]=s[5-6]=\texttt{"bo"}\),已匹配字符串长度 \(:4\)

然后我们就会发现剩下的字符无法匹配,而且我们发现已匹配字符串长度不足 \(n\)。所以剩下的 \(\texttt{"no"}\) 只能单独为一部分。

故最后答案为 \(3\)

对于匹配字符串,我们可以用 \(hash\) 来维护,两个字符串 \(hash\) 值相等,则两个字符串可以匹配。

code

#include <bits/stdc++.h>
#define int unsigned long long
using namespace std;
const int maxn = 1e6 + 5; 
const int base = 31;
int Hash[maxn], Pow[maxn];
char s[maxn];
int gethash() {
	int len = strlen(s + 1);
	for (int i = 1; i <= len; i++) {
		Hash[i] = Hash[i - 1] * base + s[i];
	}
}
int get(int L, int R) {
	return Hash[R] - Hash[L - 1] * Pow[R - L + 1];
}
int ans;
signed main() {
	Pow[1] = base;
	for (int i = 2; i <= maxn; i++) Pow[i] = Pow[i - 1] * base;
	int t;
	ios::sync_with_stdio(false);
	cin >> t;
	while (t--) {
		cin >> s + 1;
		gethash();
		ans = 0;
		int len = strlen(s + 1), st = 0;
		int LL = 1, L = 1, RR = len, R = len;
		while (L < R) {
			if (get(LL, L) == get(R, RR)) {
				ans += 2;
				st += 2 * (L - LL + 1);
				L++;
				R--;
				LL = L;
				RR = R;
			} else {
				L++;
				R--;
			}
		}
		if (st < len) ans++;
		cout << ans << endl;
	}
	return 0;
}

T4 花神游历各国

[luogu] P4145

由于其特殊的操作 \(\sqrt{\delta_i}\), 我们可以发现从 \(\delta_i\) 的最大值开方到 \(1\) 最多仅需 \(6\) 次。于是我们就可以考虑线段树维护区间最大值,若该区间的最大值已经小于 \(1\),就可以直接跳过本次操作。否则直接枚举 \(l \sim r\) 单点修改即可。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int SIZE = 1e6;
int n, q;
int a[SIZE + 5];
struct tree {
	int l, r;
	int val;
} p[SIZE * 4], ma[SIZE * 4];
void build(int x, int l, int r) {
	if (l == r) {
		ma[x].l = l, ma[x].r = r;
		ma[x].val = a[l];
		p[x].l = l, p[x].r = r;
		p[x].val = a[l];
		return;
	}
	p[x].l = l, p[x].r = r;
	ma[x].l = l, ma[x].r = r;
	int mid = (l + r) >> 1;
	build(x * 2, l, mid);
	build(x * 2 + 1, mid + 1, r);
	p[x].val = p[x * 2].val + p[x * 2 + 1].val;
	ma[x].val = max(ma[x * 2].val, ma[x * 2 + 1].val);
}

void add(int t, int x) {
	if (p[x].l == p[x].r) {
		p[x].val = sqrt(p[x].val);
		ma[x].val = sqrt(ma[x].val);
		return;
	} 
	int mid = (p[x].l + p[x].r) >> 1;
	if (t <= mid) add(t, x * 2);
	else add(t, x * 2 + 1);
	p[x].val = p[x * 2].val + p[x * 2 + 1].val;
	ma[x].val = max(ma[x * 2].val, ma[x * 2 + 1].val);
}
int ask1(int x, int l, int r) {
	if (l <= p[x].l && p[x].r <= r) return p[x].val;
	int mid = (p[x].l + p[x].r) >> 1;
	int sum = 0;
	if (l <= mid) sum += ask1(x * 2, l, r);
	if (r > mid) sum += ask1(x * 2 + 1, l, r);
	return sum;
}
int ask2(int x, int l, int r) {
	if (l <= ma[x].l && ma[x].r <= r) return ma[x].val;
	int mid = (ma[x].l + ma[x].r) >> 1;
	int sum = INT_MIN;
	if (l <= mid) sum = max(sum, ask2(x * 2, l, r));
	if (r > mid) sum = max(sum, ask2(x * 2 + 1, l, r));
	return sum;
}
int op, l, r, x;
signed main() {
	scanf("%lld", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
	}
	build(1, 1, n);
	scanf("%lld", &q);
	while (q--) {
		scanf("%lld", &op);
		if (op == 1) {
			scanf("%lld %lld", &l, &r);
			if (l > r) swap(l, r);
			printf("%lld\n", ask1(1, l, r));
		} else {
			scanf("%lld %lld", &l, &r);
			if (l > r) swap(l, r);
			if (ask2(1, l, r) <= 1) continue;
			for (int i = l; i <= r; i++) {
//				cout << i << endl;
				if (a[i] == 1) continue;
				a[i] = sqrt(a[i]);
				add(i, 1);
			} 
		}
	}
	return 0;
}

T5 立方体大作战

link

很妙的一道题目。
对于 \(a\) 值相等的两个数 \(l\)\(r\) ,我们将左端点加 \(1\) ,右端点加 \(-1\),要使他们中间的数全部消除,需要的操作次数即为区间 \([l,r]\) 的和。
用树状数组维护就行了。
时间复杂度 \(O(n \log n)\)

#include <bits/stdc++.h>
//#define int long long
using namespace std;
template <typename T>
void read (T &x) {
    x = 0; T f = 1;
    char ch = getchar ();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar ();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + ch - '0';
        ch = getchar ();
    }
    x *= f;
}
char For_Print[25];
template <typename T>
void write (T x) {
    if (x == 0) { putchar ('0'); return; }
    if (x < 0) { putchar ('-'); x = -x; }
    int poi = 0;
    while (x) {
        For_Print[++poi] = x % 10 + '0';
        x /= 10;
    }
    while (poi) putchar (For_Print[poi--]);
}
template <typename T>
void print (T x, char ch) {
    write (x); putchar (ch);
}

const int maxn = 50000;
int n;
int a[maxn * 2 + 5];
int L[maxn * 2 + 5], R[maxn * 2 + 5];
int p[maxn * 2 + 5];
int lowbit(int x) { return x & -x; }
void add(int x, int k) {
	for (; x <= 2 * n; x += lowbit(x)) {
		p[x] += k;
	}
}
int ask(int x) {
	int ans = 0;
	for (; x; x -= lowbit(x)) {
		ans += p[x];
	}
	return ans;
}
int ans;
signed main() {
	read(n);
	for (int i = 1; i <= 2 * n; i++) {
		read(a[i]);
		if (L[a[i]] == 0) L[a[i]] = i;
		else R[a[i]] = i;
	}
	for (int i = 1; i <= 2 * n; i++) {			
//		cout << ask(i) << endl;
		if (L[a[i]] == i) {
//			cout << i << endl;
			add(i, 1);
		} else if (R[a[i]] == i) {
			add(i, -1);
			ans += ask(i) - ask(L[a[i]] - 1);
//			cout << ask(i) << ' ' << ask(L[a[i]] - 1) << endl; 
//			cout << ask(i) - ask(L[a[i]] - 1) << endl;
			add(i, 1);
			add(L[a[i]], -1);
		}
	}
	write(ans);
	return 0;
} 

T6 [ABC157E] Simple String Queries

link
分块好像不会被卡。

Solution.

我们可以用一个 \(b\) 数组来记录该块的每种字母的数量。
在每次查询的时候,我们可以新建一个桶,根据分块的基本思想,如果 \(l\)\(r\) 在同一块,就直接将 \(l \sim r\) 之间的字母加入桶。否则就把 \(l\)\(r\) 之间的块中的字母加入桶。最后枚举一下桶中有多少个不同的字母即可。

时间复杂度 \(O(26 × n \sqrt{n})\)

Code.

#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int maxn = 1e6 + 5;
int n, q;
char s[maxn];
int op, l, r;
int pos[maxn], L[maxn], R[maxn];
int b[maxn][301];
int len, tot;
void init() {//分块
	len = sqrt(n);
	for (int i = 1; i + len <= n; i += len) {
		L[++tot] = i;
		R[tot] = i + len - 1;
	}
	if (R[tot] < n) {
		tot++;
		L[tot] = R[tot - 1] + 1;
		R[tot] = n;
	}
	for (int i = 1; i <= tot; i++) {
		for (int j = L[i]; j <= R[i]; j++) {
			pos[j] = i;
			b[i][s[j]]++;
		}
	}
}
void updata(int x, char c) {//单点修改
	b[pos[x]][s[x]]--;	
	s[x] = c;
	b[pos[x]][c]++; 
} 
bool p[301];
int ask(int l, int r) {
	int t1 = pos[l], t2 = pos[r], ans = 0;
	if (t1 == t2) {//如果在同一块
		memset(p, 0, sizeof(p));
		for (int i = l; i <= r; i++) {//直接将 l-r 加入
			if (!p[s[i]]) {
				p[s[i]] = true;
				ans++;
			}
		}
	} else {
		memset(p, 0, sizeof(p));
		for (int i = l; i <= R[t1]; i++) {
			if (!p[s[i]]) {
				p[s[i]] = true;
				ans++;
			}
		}
		for (int i = t1 + 1; i <= t2 - 1; i++) {//将块中的字母加入桶
			for (int j = 'a'; j <= 'z'; j++) {
				if (b[i][j] != 0) {
					if (!p[j]) {
						ans++;
						p[j] = true;
					}
				}
			}
		}
		for (int i = L[t2]; i <= r; i++) {
			if (!p[s[i]]) {
				p[s[i]] = true;
				ans++;
			}
		}
	}
	return ans;
}
signed main() {
	ios::sync_with_stdio(false);
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> s[i];
	init();
	cin >> q;
	while (q--) {
		cin >> op;
		if (op == 1) {
			char c;
			cin >> l >> c;
			updata(l, c);
		} else if (op == 2) {
			cin >> l >> r;
			printf("%d\n", ask(l, r));
		}
	}
	return 0;
} 

posted @ 2021-11-13 21:08  cqbzjyh  阅读(51)  评论(0)    收藏  举报