数据结构刷题

数据结构刷题记录

AT1219 歴史の研究 - 莫队

  • 简单的回滚莫队,考虑到维护最大值,加操作好做而减操作难
  • 还可以将每个数可能的贡献算出,即对于一个数x,贡献为x1,x2,...,x*y(y为在整个序列中x出现个数),离散化后,用值域分块维护,只用普通莫队即可
  • 下面是回滚莫队做法的代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100010;
int n, m, len;
long long a[MAXN], b[MAXN], dr[MAXN];
int block, bn, B[MAXN];
long long ans, cm[MAXN], Ans[MAXN], ct[MAXN];
int s[MAXN], st;
struct question
{
	int l, r, id;
}ask[MAXN];
inline bool cmp(const question &a, const question &b)
{
	if(B[a.l] ^ B[b.l]) return B[a.l] < B[b.l];
	return a.r < b.r;
}
inline long long max(const long long &x, const long long &y)
{
	return x > y ? x : y;
}
inline long long min(const long long &x, const long long &y)
{
	return x < y ? x : y;
}
inline void discrete()
{
	len = 0;
	sort(b + 1, b + n + 1);
	for (int i = 1; i <= n; i++)
	{
		if(i == 1 || b[i] != b[i - 1]) 
			dr[++len] = b[i]; 
	}
}
inline long long calc(int l, int r)
{
	long long res = 0;
	for (int i = l; i <= r; i++) cm[a[i]] = 0;
	for (int i = l; i <= r; i++)
	{
		cm[a[i]]++;
		res = max(cm[a[i]] * dr[a[i]], res);
	}
	return res;
}
int main()
{
	scanf("%d %d", &n, &m); block = pow(n, 1.0 / 2.0);
	for (int i = 1; i <= n; i++) scanf("%lld", &a[i]), b[i] = a[i];
	discrete();
	for (int i = 1; i <= n; i++) a[i] = lower_bound(dr + 1, dr + len + 1, a[i]) - dr;
	for (int i = 1; i <= m; i++)
	{
		scanf("%d %d", &ask[i].l, &ask[i].r);
		ask[i].id = i;
	}
	for (int i = 1; i <= n; i++) B[i] = (i - 1) / block + 1;
	sort(ask + 1, ask + m + 1, cmp);
	bn = B[n];
	for (int i = 1, j = 1; i <= bn; i++)
	{
		int br = min(n, i * block), l = br + 1, r = br;
		st = 0;//s数组维护出现了那些数
		ans = 0;
		for (; B[ask[j].l] == i; j++)
		{
			if(B[ask[j].r] == i)
			{
				Ans[ask[j].id] = calc(ask[j].l, ask[j].r);
				continue;
			}
			while(r < ask[j].r)
			{
				r++;
				if(!ct[a[r]]) s[++st] = a[r];
				ct[a[r]]++; ans = max(ans, ct[a[r]] * dr[a[r]]);
			}
			long long tmp = ans;
			while(l > ask[j].l)
			{
				l--;
				ct[a[l]]++; tmp = max(tmp, ct[a[l]] * dr[a[l]]);
			}
			Ans[ask[j].id] = tmp;
			while(l <= br)
			{
				ct[a[l]]--;
				l++;
			}
		}
		for (int i = 1; i <= st; i++) ct[s[i]] = 0;
	}
	for (int i = 1; i <= m; i++) printf("%lld\n", Ans[i]);
	return 0;
}

[国家集训队]旅游 - 树剖

  • 简单的树剖题(码量有点大)
  • 考虑点权变为边权后计算的位置即可(记录每条边的位置)
  • 利用线段树懒标记解决区间问题
  • 区间取相反数有点像区间翻转,打个标记就行了
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5000010;
int n, m;
int head[MAXN], nxt[MAXN << 1], v[MAXN << 1], w[MAXN << 1], b[MAXN << 1], cnt;
int son[MAXN], size[MAXN], d[MAXN], wo[MAXN], fa[MAXN], top[MAXN], sv[MAXN], ff[MAXN], totw;
int val[MAXN];
struct SegmentTree
{
	int l, r, sum, res, ma, mi;
	#define l(x) t[x].l
	#define r(x) t[x].r
	#define sum(x) t[x].sum
	#define res(x) t[x].res
	#define ma(x) t[x].ma
	#define mi(x) t[x].mi
}t[MAXN << 2];
void add(int x, int y, int z, int i)
{
	nxt[++cnt] = head[x]; head[x] = cnt; v[cnt] = y; w[cnt] = z; b[cnt] = i;
}
void dfs1(int now, int f, int de)
{
	size[now] = 1; fa[now] = f; d[now] = de;
	int maxx = -1;
	for (int i = head[now]; i; i = nxt[i])
	{
		if(v[i] == f) continue;
		dfs1(v[i], now, de + 1);
		if(size[v[i]] > maxx) maxx = size[v[i]], son[now] = v[i], sv[now] = i;
		size[now] += size[v[i]];
	}
}
void dfs2(int now, int topf, int p)
{
	top[now] = topf; ff[now] = p;
	if(now ^ 1) wo[b[p]] = ++totw, val[totw] = w[p];
	if(!son[now]) return;
	dfs2(son[now], topf, sv[now]);
	for (int i = head[now]; i; i = nxt[i])
	{
		if(v[i] == fa[now] || v[i] == son[now]) continue;
		dfs2(v[i], v[i], i);
	}
}
void maintain(int p)
{
	sum(p) = sum(p << 1) + sum(p << 1 | 1);
	ma(p) = max(ma(p << 1), ma(p << 1 | 1));
	mi(p) = min(mi(p << 1), mi(p << 1 | 1));
}
void build(int p, int l, int r)
{
	l(p) = l; r(p) = r;
	if(l == r) 
	{
	 	sum(p) = mi(p) = ma(p) = val[r];
	 	res(p) = 0;
		return;
	}
	int mid = (l + r) >> 1;
	build(p << 1, l, mid);
	build(p << 1 | 1, mid + 1, r);
	maintain(p);
}
void pushdown(int p)
{
	if(res(p))
	{
		sum(p << 1) = -sum(p << 1);
		sum(p << 1 | 1) = -sum(p << 1 | 1);
		swap(mi(p << 1), ma(p << 1));
		mi(p << 1) = -mi(p << 1);
		ma(p << 1) = -ma(p << 1);
		swap(mi(p << 1 | 1), ma(p << 1 | 1));
		mi(p << 1 | 1) = -mi(p << 1 | 1);
		ma(p << 1 | 1) = -ma(p << 1 | 1);
		res(p) ^= 1;
		res(p << 1) ^= 1;
		res(p << 1 | 1) ^= 1;
	}
}
int askma(int p, int l, int r)
{
	if(l(p) >= l && r(p) <= r) return ma(p);
	pushdown(p);
	int ans = -100000000;
	int mid = (l(p) + r(p)) >> 1;
	if(l <= mid) ans = max(ans, askma(p << 1, l, r));
	if(r > mid) ans = max(ans, askma(p << 1 | 1, l, r));
	return ans;
}
int asksu(int p, int l, int r)
{
	if(l(p) >= l && r(p) <= r) return sum(p);
	pushdown(p);
	int ans = 0;
	int mid = (l(p) + r(p)) >> 1;
	if(l <= mid) ans += asksu(p << 1, l, r);
	if(r > mid) ans += asksu(p << 1 | 1, l ,r);
	return ans;
}
int askmi(int p, int l, int r)
{
	if(l(p) >= l && r(p) <= r)
	{
		return mi(p);
	}
	pushdown(p);
	int ans = 100000000;
	int mid = (l(p) + r(p)) >> 1;
	if(l <= mid) ans = min(ans, askmi(p << 1, l, r));
	if(r > mid) ans = min(ans, askmi(p << 1 | 1, l, r));
	return ans;
}
void modify(int p, int l, int r)
{
	if(l(p) >= l && r(p) <= r)
	{
		res(p) ^= 1;
		sum(p) = -sum(p);
		int tmp = ma(p);
		ma(p) = -mi(p);
		mi(p) = -tmp;
		return;
	}
	pushdown(p);
	int mid = (l(p) + r(p)) >> 1;
	if(l <= mid) modify(p << 1, l, r);
	if(r > mid) modify(p << 1 | 1, l, r);
	maintain(p);
}
void change(int p, int x, int dt)
{
	if(l(p) == r(p))
	{
		sum(p) = ma(p) = mi(p) = dt;
		return;
	}
	pushdown(p);
	int mid = (l(p) + r(p)) >> 1;
	if(x <= mid) change(p << 1, x, dt);
	else change(p << 1 | 1, x, dt);
	maintain(p);
}
void mo(int x, int y)
{
	while(top[x] != top[y])
	{
		if(d[top[x]] < d[top[y]]) swap(x, y);
		modify(1, wo[b[ff[top[x]]]], wo[b[ff[x]]]);
		x = fa[top[x]];
	}
	if(x == y) return;
	if(d[x] > d[y]) swap(x, y);
	modify(1, wo[b[sv[x]]], wo[b[ff[y]]]);
	return;
}
int mam(int x, int y)
{
	int ans = -100000000;
	while(top[x] != top[y])
	{
		if(d[top[x]] < d[top[y]]) swap(x, y);
		ans = max(ans, askma(1, wo[b[ff[top[x]]]], wo[b[ff[x]]]));
		x = fa[top[x]];
	}
	if(x == y) return ans;
	if(d[x] > d[y]) swap(x, y);
	ans = max(ans, askma(1, wo[b[sv[x]]], wo[b[ff[y]]]));
	return ans;
}
int mim(int x, int y)
{
	int ans = 100000000;
	while(top[x] != top[y])
	{
		if(d[top[x]] < d[top[y]]) swap(x, y);
		ans = min(ans, askmi(1, wo[b[ff[top[x]]]], wo[b[ff[x]]]));
		x = fa[top[x]];
	}
	if(x == y) return ans;
	if(d[x] > d[y]) swap(x, y);
	ans = min(ans, askmi(1, wo[b[sv[x]]], wo[b[ff[y]]]));
	return ans;
}
int sus(int x, int y)
{
	int ans = 0;
	while(top[x] != top[y])
	{
		if(d[top[x]] < d[top[y]]) swap(x, y);
		ans += asksu(1, wo[b[ff[top[x]]]], wo[b[ff[x]]]);
		x = fa[top[x]];
	}
	if(x == y) return ans;
	if(d[x] > d[y]) swap(x, y);
	ans += asksu(1, wo[b[sv[x]]], wo[b[ff[y]]]);
	return ans;	
}
int main()
{
//	freopen("inp", "r", stdin);
//	freopen("out", "w", stdout);
	scanf("%d", &n);
	for (int i = 1; i < n; i++)
	{
		int x, y, z;
		scanf("%d %d %d", &x, &y, &z); x++, y++;
		add(x, y, z, i);
		add(y, x, z, i);
	}
	dfs1(1, 0, 1);
	dfs2(1, 1, 1);
	build(1, 1, totw);
	scanf("%d", &m);
	for (int i = 1; i <= m; i++)
	{
		char opt[10];
		scanf("%s", opt + 1);
		if(opt[1] == 'C')
		{
			int x, y;
			scanf("%d %d", &x, &y);
			change(1, wo[x], y);
		}
		else if(opt[1] == 'N')
		{
			int x, y;
			scanf("%d %d", &x, &y);x++, y++;
			mo(x, y);
		}
		else if(opt[1] == 'S')
		{
			int x, y;
			scanf("%d %d", &x, &y);x++, y++;
			printf("%d\n", sus(x, y));
		}
		else if(opt[2] == 'A')
		{
			int x, y;
			scanf("%d %d", &x, &y);x++, y++;
			printf("%d\n", mam(x, y));
		}	
		else 
		{
			int x, y;
			scanf("%d %d", &x, &y);x++, y++;
			printf("%d\n", mim(x, y));
		}
	}
	return 0;
}

[NOI Online #1 提高组]冒泡排序 - 树状数组、逆序对

  • 比较有思维的一道题
  • 首先考虑冒泡排序的本质是通过n轮检查邻项是否逆序,并通过邻项的交换减少逆序对个数从而使序列有序
  • 我们用\(fm[i]\)表示第i个数前面有几个数比他大,总逆序对个数为\(\sum_{i=1}^{n}{fm[i]}\)
  • 考虑一轮冒泡排序,每一位置的\(c[i]\)必定会减一,且仅会减一
  • 故k轮后\(Ans = \sum_{i=1}^{n}{[fm[i]\geq{k + 1}]\times(fm[i] - k)}\)
  • 我们求出了fm数组后,用两个树状数组在fm[i]值域上分别维护前缀个数和前缀\(\sum{fm[i]}\)
  • 注意当k >= n 时特判逆序对个数必定为0(n-1轮就有序了)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5000010;
int n, m, a[MAXN];
long long c[MAXN], f[MAXN];
int fm[MAXN];
inline int lowbit(int x) { return x & (-x); }
inline long long ask(int x)
{
	long long ans = 0;
	while(x)
	{
		ans += c[x];
		x -= lowbit(x);
	}
	return ans;
}
inline void add(int x, int dt)
{
	if(x == 0) return;
	while(x <= n)
	{
		c[x] += dt;
		x += lowbit(x);
	}
}
inline long long ask2(int x)
{
	long long ans = 0;
	while(x)
	{
		ans += f[x];
		x -= lowbit(x);
	}
	return ans;
}
inline void add2(int x, int dt)
{
	if(x == 0) return;
	while(x <= n)
	{
		f[x] += dt;
		x += lowbit(x);
	}
}
int main()
{	
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1; i <= n; i++)
	{
		fm[i] = i - 1 - ask(a[i]);
		add(a[i], 1);
	}
	for (int i = 1; i <= n; i++) c[i] = 0;
	for (int i = 1; i <= n; i++)
	{
		add(fm[i], 1);
		add2(fm[i], fm[i]);
	}
	for (int i = 1; i <= m; i++)
	{
		int opt, x;
		scanf("%d %d", &opt, &x);
		if(opt == 1)
		{
			add(fm[x], -1); add2(fm[x], -fm[x]);
			add(fm[x + 1], -1); add2(fm[x + 1], -fm[x + 1]);
			if(a[x] > a[x + 1]) fm[x + 1]--;
			else fm[x]++;
			swap(fm[x], fm[x + 1]);
			swap(a[x], a[x + 1]);
			add(fm[x], 1); add2(fm[x], fm[x]);
			add(fm[x + 1], 1); add2(fm[x + 1], fm[x + 1]);
		}
		else 
		{
			if(x >= n) printf("0\n");
			else
			{
				long long si = ask2(n) - ask2(x), ti = ask(n) - ask(x);
				printf("%lld\n", si - ti * x);
			}
		}
	}
	return 0;
}

[HAOI2007]理想的正方形 - 单调队列

  • 考虑我们如果可以求出每一个位置\((i, j)\)为右下角的\(n\times{n}\)的矩形中的最大值和最小值,即可求出答案
  • 两次单调队列先做一行,再在每一列上根据第一次做的答案,求出矩形上的最值
  • 即一次做到点到线上的维护,第二次做到线到面上的维护
#include <bits/stdc++.h>
using namespace std;
const int SIZE = 1010;
int a, b, n;
int g[SIZE][SIZE];
int ma[SIZE][SIZE], mi[SIZE][SIZE], maa[SIZE][SIZE], mii[SIZE][SIZE];
int q[SIZE], l = 1, r = 0;
int main()
{
	scanf("%d %d %d", &a, &b, &n);
	for (int i = 1; i <= a; i++)
		for (int j = 1; j <= b; j++)
			scanf("%d", &g[i][j]);
	for (int i = 1; i <= a; i++)
	{
		l = 1; r = 0;
		for (int j = 1; j <= b; j++)
		{
			while(l <= r && j - q[l] + 1 > n) l++;
			while(l <= r && g[i][j] >= g[i][q[r]]) r--;
			q[++r] = j;
			ma[i][j] = g[i][q[l]];
		}
	}
	/*
	for (int i = 1; i <= a; i++)
	{
		for (int j = 1; j <= b; j++)
			cout << ma[i][j] << " ";
		cout << endl;
	}
	*/
	for (int i = 1; i <= a; i++)
	{
		l = 1; r = 0;
		for (int j = 1; j <= b; j++)
		{
			while(l <= r && j - q[l] + 1 > n) l++;
			while(l <= r && g[i][j] <= g[i][q[r]]) r--;
			q[++r] = j;
			mi[i][j] = g[i][q[l]];
		}
	}
	for (int i = 1; i <= b; i++)
	{
		l = 1; r = 0; 
		for (int j = 1; j <= a; j++)
		{
			while(l <= r && j - q[l] + 1 > n) l++;
			while(l <= r && ma[j][i] >= ma[q[r]][i]) r--;
			q[++r] = j;
			maa[j][i] = ma[q[l]][i];
		}
	}
	for (int i = 1; i <= b; i++)
	{
		l = 1; r = 0; 
		for (int j = 1; j <= a; j++)
		{
			while(l <= r && j - q[l] + 1 > n) l++;
			while(l <= r && mi[j][i] <= mi[q[r]][i]) r--;
			q[++r] = j;
			mii[j][i] = mi[q[l]][i];
		}
	}
	int ans = 1000000000;
	for (int i = n; i <= a; i++)
		for (int j = n; j <= b; j++)
		{
			ans = min(ans, maa[i][j] - mii[i][j]);
		}
	printf("%d", ans);
	return 0;
}

[HEOI2012]采花

  • 首先\(10^5\)的数据可以用莫队做
  • 更高效的做法是用树状数组做,与HH的项链相似
  • 考虑先预处理出每个位置的下一个相同颜色的位置,同时在每个颜色第二次出现的位置先加上一
  • 将询问离线做,按左端点排序,则每次递增删除当前未在左区间的点的贡献,即将此位置的下个同颜色的位置贡献减去,下下个位置贡献加上(此中颜色当前最左边位置少了一个)
  • 再用树状数组计算前缀和,作差即可
  • \(Ans = sum(r) - sum(l - 1)\)
/*
#include <bits/stdc++.h>
using namespace std;
int read() 
{
	int a = 0, f = 1; char c = getchar();
	while(c > '9' || c < '0') {if(c == '-') f = -1; c = getchar();}
	while(c >= '0' && c <= '9') {a = a * 10 + c - '0'; c = getchar();}
	return a * f;
}
const int MAXN = 2e6 + 14;
int n, m, t, xl, xr, block;
int ans;
int a[MAXN];
int cnt[MAXN];
int Ans[MAXN];
struct question 
{
	int l, r, bo, id;
	#define l(x) ask[x].l
	#define r(x) ask[x].r
	#define bo(x) ask[x].bo
	#define id(x) ask[x].id
} ask[MAXN];
bool cmp(question a, question b) 
{
	return a.bo ^ b.bo ? a.bo < b.bo : a.bo % 2 == 1 ? a.r < b.r : a.r > b.r;
}
void add(int x) 
{
	cnt[a[x]]++;
	if(cnt[a[x]] == 2) ans++;
}
void del(int x) 
{
	cnt[a[x]]--;
	if(cnt[a[x]] == 1) ans--;
}
int main() {
	n = read(); t = read(); m = read();  block = sqrt(n);
	for (int i = 1; i <= n; i++) a[i] = read();
	for (int i = 1; i <= m; i++) l(i) = read(), r(i) = read(), bo(i) = l(i) / block, id(i) = i;
	sort(ask + 1, ask + m + 1, cmp);
	for (int i = 1; i <= m; i++) 
	{
		while(xr > r(i)) del(xr--);
		while(xr < r(i)) add(++xr);
		while(xl < l(i)) del(xl++);
		while(xl > l(i)) add(--xl);
		Ans[id(i)] = ans;
	}
	for (int i = 1; i <= m; i++) 
	{  
		printf("%d\n", Ans[i]);
	}
	return 0;
}
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2000010;
inline int read()
{
	int a = 0, f = 1; char c = getchar();
	while(c > '9' || c < '0') { if(c == '-') f = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { a = a * 10 + c - '0'; c = getchar(); }
	return a * f;
}
int n, m, t;
int a[MAXN], pre[MAXN], Ans[MAXN], la[MAXN], cnt[MAXN];
struct node
{
	int l, r, id;
}ask[MAXN];
inline bool cmp(const node &a, const node &b)
{
	return a.l < b.l;
}
int c[MAXN];
inline int lowbit(int x)
{
	return x & (-x);
}
inline void add(int x, int dt)
{
	while(x <= n)
	{
		c[x] += dt;
		x += lowbit(x);
	}
}
inline int sum(int x)
{
	int ans = 0;
	while(x)
	{
		ans += c[x];
		x -= lowbit(x);
	}
	return ans;
}
int main()
{
	n = read(); t = read(); m = read(); 
	for (int i = 1; i <= n; i++) a[i] = read();
	for (int i = 1; i <= m; i++) ask[i].l = read(), ask[i].r = read(), ask[i].id = i;
	for (int i = n; i >= 1; i--)
	{
		if(pre[a[i]]) la[i] = pre[a[i]];
		pre[a[i]] = i;
	}
	for (int i = 1; i <= t; i++)
		if(la[pre[i]]) 
			add(la[pre[i]], 1);
	sort(ask + 1, ask + m + 1, cmp);	
	int nl = 1;
	for (int i = 1; i <= m; i++)
	{
		while(nl < ask[i].l)
		{
			if(la[nl]) add(la[nl], -1);
			if(la[la[nl]]) add(la[la[nl]], 1);
			nl++;
		}
		Ans[ask[i].id] = sum(ask[i].r) - sum(ask[i].l - 1);
	}
	for (int i = 1; i <= m; i++)
		printf("%d\n", Ans[i]);
	return 0;
}

posted @ 2020-07-07 08:43  行zzz  阅读(186)  评论(0编辑  收藏  举报