BF的数据结构题单-提高组 题解(洛谷)

3 篇文章 0 订阅
3 篇文章 0 订阅

按难度顺序来写

扫描线不学(感觉这玩意没啥用)

2 xor的艺术

这道题我们可以利用线段树来处理 此时:

维护一个s[1],s[0]代表区间内1/0的个数 假如区间翻转的话 我们就 swap(s[1],s[0])同时标记他的子树 其他类似带懒标记的区间操作

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 2e5 + 10;
struct node
{
	int l, r;
	int s[2];//s[0]代表0的个数 s[1]代表1的个数
	int tag;
}tree[maxn<<2];
string ss;
void pushup(int rt)
{
	for (int i = 0; i <= 1; i++)
		tree[rt].s[i] = (tree[rt << 1].s[i] + tree[rt << 1 | 1].s[i]);
}
void build(int rt, int l, int r)
{
	if (l == r)
	{
		tree[rt].s[ss[l] - '0'] = 1;
		return;
	}
	int mid = l + r >> 1;
	build(rt << 1, l, mid);
	build(rt << 1 | 1, mid + 1, r);
	pushup(rt);
}
void pushdown(int rt)
{
	if (tree[rt].tag)
	{
		tree[rt << 1].tag ^= 1;
		tree[rt << 1 | 1].tag ^= 1;
		swap(tree[rt<<1].s[0], tree[rt<<1].s[1]);
		swap(tree[rt << 1 | 1].s[0], tree[rt << 1 | 1].s[1]);
		tree[rt].tag ^= 1;
	}
}
void modify(int rt, int l, int r, int ql, int qr)//区间翻转
{
	if (l == ql && r == qr)
	{
		swap(tree[rt].s[0], tree[rt].s[1]);
		tree[rt].tag^=1;
		return;
	}
	int mid = l + r >> 1;
	pushdown(rt);
	if (qr <= mid)
		modify(rt << 1, l, mid, ql, qr);
	else if (ql > mid)
		modify(rt << 1 | 1, mid + 1, r, ql, qr);
	else
	{
		modify(rt << 1, l, mid, ql,mid);
		modify(rt << 1 | 1, mid + 1, r, mid + 1, qr);
	}
	pushup(rt);
}
int query(int rt, int l, int r, int ql, int qr)
{
	if (l == ql && r == qr)
	{
		return tree[rt].s[1];
	}
	pushdown(rt);
	int mid = l + r >> 1;
	if (qr <= mid)
		return query(rt << 1, l, mid, ql, qr);
	else if (ql > mid)
		return query(rt << 1 | 1, mid + 1, r, ql, qr);
	else
	{
		int ret = query(rt << 1, l, mid, ql, mid);
		ret += query(rt << 1 | 1, mid + 1, r, mid + 1, qr);
		return ret;
	}
}
signed main()
{
	int len, m;
	cin >> len >> m;
	cin >> ss;
	ss = '~' + ss;
	build(1, 1, len);
	for (int i = 1; i <= m; i++)
	{
		int op;
		cin >> op;
		if (op == 0)
		{
			int x, y; cin >> x >> y;
			modify(1, 1, len, x, y);
		}
		else
		{
			int x, y; cin >> x >> y;
			cout << query(1, 1, len, x, y) << endl;
		}
	}
}

模板题不写

3.P1197 P1525 在其他的题单写过了

4.P2894

我们可以考虑利用线段树 至于维护什么东西的话比较好想

就是前缀最长连续 后缀最长连续 和中间最长连续 

值得注意的是中间最长连续是区合并后的,左区间的 和右区间的最长

比较麻烦的是如何询问

我们考虑贪心

假如左区间可以存在合法 我们就往左区间询问 假如没有我们就询问中间的 最后考虑右边的

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 1e5 + 10;
struct node
{
	int l, r;
	int flag;
	int premax,sufmax;//前缀最长0
	int midmax;//后缀最长0
}tree[maxn<<2];
int a[maxn];
void pushup(int rt)
{
	tree[rt].premax = tree[rt << 1].premax;
	if (tree[rt << 1].midmax == tree[rt << 1].r - tree[rt << 1].l + 1)
		tree[rt].premax = tree[rt << 1].midmax + tree[rt << 1 | 1].premax;
	tree[rt].sufmax = tree[rt << 1 | 1].sufmax;
	if (tree[rt << 1 | 1].midmax == tree[rt << 1 | 1].r - tree[rt << 1 | 1].l + 1)
		tree[rt].sufmax = tree[rt << 1].sufmax + tree[rt << 1 | 1].midmax;
	tree[rt].midmax = tree[rt << 1].sufmax + tree[rt << 1 | 1].premax;
	tree[rt].midmax = max(tree[rt].midmax, max(tree[rt<<1].midmax, tree[rt<<1|1].midmax));
}
void pushdown(int rt)//代表
{
	if (tree[rt].flag==1)//>0代表区间变为1
	{
		tree[rt << 1].flag=tree[rt].flag;
		tree[rt << 1 | 1].flag=tree[rt].flag;
		tree[rt << 1].sufmax = tree[rt<<1].midmax = tree[rt << 1].premax = 0;
		
		tree[rt << 1|1].sufmax = tree[rt<<1|1].midmax = tree[rt << 1|1].premax = 0;
	
		tree[rt].flag= 0;
	}
	else if (tree[rt].flag==-1)//代表区间变为0
	{
		tree[rt << 1].flag=tree[rt].flag;
		tree[rt << 1 | 1].flag=tree[rt].flag;
		tree[rt << 1].sufmax = tree[rt << 1].midmax = tree[rt << 1].premax =tree[rt<<1].r-tree[rt<<1].l+1;
		
		tree[rt << 1 | 1].sufmax = tree[rt << 1 | 1].midmax = tree[rt << 1 | 1].premax = tree[rt << 1 | 1].r - tree[rt << 1 | 1].l + 1;
		
		tree[rt].flag = 0;
	}
}
void build(int rt, int l, int r)
{
	tree[rt].l = l, tree[rt].r = r;
	tree[rt].flag = 0;
	if (l == r)
	{
		tree[rt].premax = tree[rt].sufmax = tree[rt].midmax = 1;

		return;
	}
	int mid = l + r >> 1;
	build(rt << 1, l, mid);
	build(rt << 1 | 1, mid + 1, r);
	pushup(rt);
}
void modify(int rt, int l, int r,int ql,int qr,int now)
{
	if (ql == l && qr == r)
	{
		if (now > 0)//代表区间变为1
		{
			tree[rt].midmax = tree[rt].sufmax = tree[rt].premax = 0;	
			tree[rt].flag=now;
		}
		else
		{
			tree[rt].midmax = tree[rt].sufmax = tree[rt].premax =r-l+1;
			tree[rt].flag=now;
		}
		return;
	}
	pushdown(rt);
	int mid = l + r >> 1;
	if (qr <= mid)
		modify(rt<<1, l, mid, ql, qr, now);
	else if (ql > mid)
		modify(rt<<1|1, mid + 1, r, ql, qr, now);
	else
	{
		modify(rt<<1, l, mid, ql, mid, now);
		modify(rt<<1|1, mid + 1, r, mid + 1, qr, now);
	}
	pushup(rt);
}
int query(int rt, int l, int r, int k)//代表询问区间连续k个的最左边
{

	if (l==r)
	{
		return l;
	}
	pushdown(rt);
	int mid = l + r >> 1;
	if (tree[rt<<1].midmax >= k)
		return query(rt << 1, l, mid, k);
	if (tree[rt << 1].sufmax + tree[rt << 1 | 1].premax >= k)
		return mid - tree[rt << 1].sufmax + 1;
	return query(rt << 1 | 1, mid + 1, r, k);
}
signed main()
{
	int n, m;
	cin >> n >> m;
	build(1, 1, n);
	
	for (int i = 1; i <= m; i++)
	{
		int op, x, y;
		cin >> op;
		if (op == 1)
		{
			cin >> x;
			if (tree[1].midmax >= x)
			{
			//	cout << tree[1].flag << endl;
				int l = query(1, 1, n, x);
				cout << l << endl;
				modify(1, 1, n, l, l + x - 1, 1);
			}
			else
				cout << 0 << endl;
		}
		else
		{
			cin >> x >> y;
			modify(1, 1, n, x, x + y - 1, -1);
		}
	
	}
}

P4145

这道题其实有一种比较套路的写法:类似的有区间取模 区间+lowbit之类的 都是在操作一定次数后会有一些比较固定的事情发生:

就比如这道题 我们对于1个数来说 一旦开根号过多次 那么代表他会越来越接近1 到1 的时候我们就可以不用继续对其操作了 这时我们维护就要利用区间最大值

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
#define int long long
const int maxn = 1e5 + 10;
struct node
{
	int l, r, maxx;
	int sum;
}tree[maxn<<2];
int a[maxn];
void pushup(int rt)
{
	tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum;
	tree[rt].maxx = max(tree[rt << 1].maxx, tree[rt << 1 | 1].maxx);
}
void build(int rt,int l,int r)
{
	tree[rt].l = l, tree[rt].r = r;
	if (l == r)
	{
		tree[rt].sum = a[l];
		tree[rt].maxx = a[l];
		return;
	}
	int mid = l + r >> 1;
	build(rt << 1, l, mid);
	build(rt << 1 | 1, mid + 1, r);
	pushup(rt);
}
void modify(int rt, int l, int r, int ql, int qr)
{
	if (tree[rt].maxx == 1 && l == ql && qr == r)
		return;
	if (l == r)
	{
		tree[rt].maxx = sqrt(a[l]);
		a[l] = sqrt(a[l]);
		tree[rt].sum = a[l];
		return;
	}
	int mid = l + r >> 1;
	if (qr <= mid)
		modify(rt << 1, l, mid, ql, qr);
	else if (ql > mid)
		modify(rt << 1 | 1, mid + 1,r, ql, qr);
	else
	{

		modify(rt << 1, l, mid, ql, mid);
		modify(rt << 1 | 1, mid + 1, r, mid + 1, qr);
	}
	pushup(rt);
}
int query(int rt, int l, int r, int ql, int qr)
{
	if (l == ql && r == qr)
	{
		return tree[rt].sum;
	}
	int mid = l + r >> 1;
	if (qr <= mid)
		return query(rt << 1, l, mid, ql, qr);
	else if (ql > mid)
		return query(rt << 1|1, mid + 1, r, ql, qr);
	else
	{
		int res = query(rt << 1, l, mid, ql, mid);
		res += query(rt << 1 | 1, mid + 1, r, mid + 1, qr);
		return res;
	}

}
signed main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> a[i];
	int m;
	build(1, 1, n);
	cin >> m;
	for (int i = 1; i <= m; i++)
	{
		int op;
		cin >> op;
		if (op == 1)
		{
			int x, y; cin >> x >> y;if(x>y)swap(x,y);
			cout << query(1, 1, n, x, y)<<endl;
		}
		else
		{
			int x, y; cin >> x >> y;if(x>y)swap(x,y);
			modify(1,1, n, x, y);
		}
	}
}

 p4513:

这道题就是利用线段树维护区间最大连续和

我利用的方法:以前记载的的 不知道是哪位大佬创造的方法:

可以直接当成模板来用:

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 5e5 + 10;
struct node
{
	int l, r, midmax;
	int lmax, rmax;
	int sum;
	node operator+(const node&b)const
	{
		node c;
		c.l = l, c.r = b.r;
		c.sum = sum + b.sum;
		c.midmax = max(midmax, b.midmax);
		c.midmax = max(c.midmax, rmax + b.lmax);
		c.lmax = max(lmax, sum + b.lmax);
		c.rmax = max(b.rmax, b.sum + rmax);
		return c;
	}
}tree[maxn << 2];
int a[maxn];
void pushup(int rt)
{
	tree[rt].lmax = max(tree[rt << 1].lmax, tree[rt << 1].sum + tree[rt << 1 | 1].lmax);
	tree[rt].rmax = max(tree[rt << 1 | 1].rmax, tree[rt << 1 | 1].sum + tree[rt << 1].rmax);
	tree[rt].midmax = max(tree[rt << 1].rmax + tree[rt << 1 | 1].lmax, max(tree[rt << 1].midmax, tree[rt << 1 | 1].midmax));
	tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum;
}
void build(int rt, int l, int r)
{
	tree[rt].l = l, tree[rt].r = r;
	if (l == r)
	{
		tree[rt].lmax = tree[rt].rmax = tree[rt].midmax = a[l];
		tree[rt].sum = a[l];
		return;
	}
	int mid = l + r >> 1;
	build(rt << 1, l, mid);
	build(rt << 1 | 1, mid + 1, r);
	pushup(rt);
}
void modify(int rt, int l, int r, int q, int v)
{
	if (l == r && l == q)
	{
		tree[rt].lmax = tree[rt].rmax = tree[rt].midmax = v;
		tree[rt].sum = v;
		a[l] = v;
		return;
	}
	int mid = l + r >> 1;
	if (q <= mid)
		modify(rt << 1, l, mid, q, v);
	else
		modify(rt << 1 | 1, mid + 1, r, q, v);
	pushup(rt);
}
node query(int rt, int l, int r, int ql, int qr)
{
	if (ql == l && r == qr)
	{
		return tree[rt];
	}
	int mid = l + r >> 1;
	if (qr <= mid)
		return query(rt << 1, l, mid, ql, qr);
	else if (ql > mid)
		return query(rt << 1 | 1, mid + 1, r, ql, qr);
	else
	{
		return query(rt << 1, l, mid, ql, mid) + query(rt << 1 | 1, mid + 1, r, mid + 1, qr);
	}
}
signed main()
{
	int n, m; cin >> n >> m;
	for (int i = 1; i <= n; i++)cin >> a[i];
	build(1, 1, n);
	for (int i = 1; i <= m; i++)
	{
		int op; cin >> op;
		if (op == 1)
		{
			int x, y; cin >> x >> y;
			if (x > y)
				swap(x, y);
			node cc = query(1, 1, n, x, y);
			cout << cc.midmax << endl;
		}
		else
		{
			int x, y; cin >> x >> y;
			modify(1, 1, n, x, y);
		}
	}
}

P1783

这道题还是比较好想到二分答案的 我们每次判断一个r是否可以满足条件该怎么办呢

这个问题类似园相交 我们可以利用并查集来处理 (也是一种套路吧)

对此我们通过并查集将两个相交的圆连接 最后和端点判断是否可以覆盖区域来判断答案是否合法

 具体看代码

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
#define int long long
const double esp = 1e-5;
const int maxn = 1010;
struct node
{
	double x, y;
	inline bool operator<(const node&t)const
	{
		return x < t.x;
	}
}a[maxn];
int n, m;
int fa[maxn];
int find(int x)
{
	if (x == fa[x])return x;
	else
		return fa[x] = find(fa[x]);
}
bool check(double r)
{
	for (int i = 1; i <= m; i++)fa[i] = i;
	for (int i = 1; i <= m; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			if (sqrt((a[i].x - a[j].x)*(a[i].x - a[j].x) + (a[i].y - a[j].y)*(a[i].y - a[j].y)) - 2 * r <= esp)
			{
				int fx = find(i), fy = find(j);
				if (fx != fy)
				{
					fa[fx] = fy;
				}
			}
		}
	}
	int id = 0;
	id = find(1);
	int now = 0, now2 = 0;
	int a1[810], b1[810];
	for (int i = 1; i <= m; i++)
	{
		if (a[i].x <= r)
			a1[++now] = i;
	}
	for (int i = 1; i <= m; i++)
	{
		if (a[i].x >= n - r)
		{
			b1[++now2] = i;
		}
	}
	for (int i = 1; i <= now; i++)
	{
		for (int j = 1; j <= now2; j++)
		{
			if (find(a1[i]) == find(b1[j]))
				return 1;
		}
	}
	return 0;
}
signed main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		cin >> a[i].x >> a[i].y;
	}
	sort(a + 1, a + 1 + m);
	double l = max(a[1].x, (double)n - a[m].x), r = 1e10;
	while (r - l > esp)
	{
		double mid = (l + r) / 2;
		if (check(mid))
		{
			r = mid;
		}
		else
			l = mid;
	}
	printf("%.2lf", l);
}

P5092

这道题看了半天只知道和并查集有关至于维护根本没想到

其实就是个带权并查集的板子题 可以看题解来学习带权并查集

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 3e4 + 10;
int fa[maxn], lenght[maxn], dis[maxn];
int find(int x)
{
	if (x == fa[x])
		return x;
	int faa = find(fa[x]);
	dis[x] += dis[fa[x]];
	fa[x] = faa;
	return faa;
}
void move(int x, int y)
{
	int fx = find(x);
	int fy = find(y);
	fa[fx] = fy;
	dis[fx] += lenght[fy];
	lenght[fy] += lenght[fx];
}
int query(int x)
{
	int fx=find(x);
	return dis[x];
}
signed main()
{
	int n;
	cin >> n;
	for (int i = 1; i <=30001; i++)
	{
		fa[i] = i, lenght[i] = 1;
	}
	for (int i = 1; i <= n; i++)
	{
		char op;
		cin >> op;
		if (op == 'M')
		{
			int x, y; cin >> x >> y;
			move(x, y);
		}
		else
		{
			int x; cin >> x;
			cout << query(x)<<endl;
		}
	}
}

p5889

写了半天发现我的写法是错误的 我之前写的是维护系数和常数来转移 但之后学完连样例都没有通过 

我是看题解才写出来的 该题的解决方法是由于我们有回退的操作 在这个回退的操作过程中我们的常数会被消除 既然这样我们就维护一个最高回退高度 然后再维护一个向下跳的系数和常数 这样的话就可以避免上述问题了 

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 5e5 + 10;
struct node{
	int l, r;
	int q;
	int re;
	int sum;
	//q代表最高回退的高度,re代表回退后在到达的深度 sum代表此时的系数
	const inline node operator+(const node&f)
	{
		node c;
		if (f.q == 0 && f.re == 0)
		{
			return *this;
		}
		else if (q == 0 && re == 0)
		{
			return f;
		}
		else if(re>f.q)
		{
			c.q = q;
			c.l = l, c.r = f.r;
			c.re = re + f.re - f.q;
			c.sum = ((sum >> f.q) << f.re)+f.sum;

		}
		else
		{
			c.q = q + f.q - re;
			c.l = l, c.r = f.r;
			c.re = f.re;
			c.sum = f.sum;
		}
		return c;
	}
}tree[maxn<<2];
int q[maxn];
void pushdown(int rt)
{
	tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];
}
void build(int rt, int l, int r)
{
	tree[rt].l = l, tree[rt].r = r;
	if (l == r)
	{
		if (q[l] == 1) {
			tree[rt].re=1;
		}
		else if (q[l] == 2)
		{
			tree[rt].re = 1;
			tree[rt].sum = 1;
		}
		else
		{
			tree[rt].q = 1;
		}
		return;
	}
	int mid = l + r >> 1;
	build(rt << 1, l, mid);
	build(rt << 1 | 1, mid + 1, r);
	pushdown(rt);
}
node query(int rt, int l, int r, int ql, int qr)
{
	if (l == ql && r == qr)
		return tree[rt];
	int mid = l + r >> 1;
	if (qr <= mid)
		return query(rt << 1, l, mid, ql, qr);
	else if (ql > mid)
		return query(rt << 1 | 1, mid + 1, r, ql, qr);
	else
	{
		node v = query(rt << 1, l, mid, ql, mid);
		return (v + query(rt << 1 | 1, mid + 1, r, mid + 1, qr));
	}
}
void modify(int rt, int l, int r, int pos, int v)
{
	if (l == pos && l == r)
	{
		tree[rt].q = tree[rt].re = tree[rt].sum = 0;
		if (v== 1) {
			tree[rt].re = 1;
		}
		else if (v == 2)
		{
			tree[rt].re = 1;
			tree[rt].sum = 1;
		}
		else
		{
			tree[rt].q = 1;
		}
		return;
	}
	int mid = l + r >> 1;
	if (pos <= mid)
		modify(rt << 1, l, mid, pos, v);
	else
		modify(rt << 1 | 1, mid + 1, r, pos, v);
	pushdown(rt);
}
signed main()
{
	int n, m, Q;
	cin >> n >> m >> Q;
	for (int i = 1; i <= m; i++)
	{
		cin >> q[i];

	}
	build(1, 1, m);
	for (int i = 1; i <= Q; i++)
	{
		int op;
		cin >> op;
		if (op == 1)
		{
			int x, y, z; cin >> x >> y >> z;
			node ff = query(1, 1, m, y, z);
			int ans = ((max(1ll*1, x >> ff.q)) << ff.re) + ff.sum;
			cout << ans << endl;
		}
		else
		{
			int x, y; cin >> x >> y;
			modify(1, 1, m, x, y);
		}
	}
}

p2590 p3178 是比较板的树剖 要是想练习树剖的话可以考虑p4116这道题 挺不错的

p1972 说实话没想到这个写法 只是觉得这个和他的位置有关:

本题解法: 1.对于一段区间我们要询问区间内不重复的点的个数 那么很显然 对于一个区间内存在两个及以上的相同的点 他的贡献仅为1 此时我们会发现和两个相同的点的距离有关 假如1个点他下一个相同的点在该区间内的话 他们的贡献会被合并 当下一个点在区间外的话 他们的贡献就是1(合并在一起) 所以我们考虑 维护每个点的下一个相同点的位置 对于每一次询问我们询问在[l,r]

下一个点在r+1及以后的点时的点 不过区间范围应该在【l,r】之中 所以要利用到主席树

/*
1.我们维护每个下标的对应的数的下一次出现的位置
对于每一次询问[l,r]只要(l<=i<=r&&next[i]>r)就是答案
*/
#include<iostream>
#include<algorithm>
using namespace std;
#define endl '\n'
const int maxn = 1e6 + 10;
struct node
{
	int l, r;
	int val;
}tree[maxn<<6];
int root[maxn],a[maxn],idx,n,m,head[maxn],next1[maxn];
void modify(int&root, int pre, int l, int r, int x)
{
	root = ++idx;
	tree[root] = tree[pre];
	tree[root].val++;
	if (l == r)
	{
		return;
	}
	int mid = (l + r) >> 1;
	if (x <= mid)
		modify(tree[root].l, tree[pre].l, l, mid, x);
	else
		modify(tree[root].r, tree[pre].r, mid + 1, r, x);
}
int query(int rootx, int rooty, int l, int r, int num)
{
	if (l == r)
	{
		return tree[rooty].val - tree[rootx].val;
	}
	int mid = (l + r) >> 1;
	if (num <= mid)
		return query(tree[rootx].l, tree[rooty].l, l, mid, num) + tree[tree[rooty].r].val - tree[tree[rootx].r].val;
	else
		return query(tree[rootx].r, tree[rooty].r, mid + 1, r, num);
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	for (int i = 1; i <= n; i++)
	{
		if (head[a[i]])
			next1[head[a[i]]] = i;
		head[a[i]] = i;
	}
	for (int i = 1; i <= n; i++)
		if (!next1[i])
			next1[i]=n+1;
	for (int i = 1; i <= n; i++)
	{
		modify(root[i], root[i - 1], 1, n+1, next1[i]);
	}
	int m;
	cin >> m;
	for (int i = 1; i <= m; i++)
	{
		int x, y;
		cin >> x >> y;
		cout << query(root[x - 1], root[y], 1, n + 1, y + 1)<<endl;
	}

}

p6186 这道题自己想的时候根本没有思路

学习大佬思路 对于逆序对问题一般情况都是利用树状数组来处理 考虑我们以前学逆序对时回维护什么东西?每个数前面有多少个数比其大 (before[i])

思考这玩意对冒泡排序有什么影响 我们模拟一下冒泡排序就会发现一点:对于第i位来考虑 第一次

冒泡排序 我们会将其前面大于他最大的数放到其后面 其他数的位置可能改变但不对第i位产生影响 接下来考虑 接下来我们会按照比他大的数的大小顺序来贡献 所以可以发现第i位会在第1-第(before[i])次产生贡献 

所以我们要再维护一个record[i]代表有多少个数前面有i个数比他大的

那接下来考虑冒泡排序 我们跑到第i次的话 此时有贡献的所有数为:前面有大于等于i个数比他大的会产生贡献 因为我们每次都是按顺序跑的 所以我们可以比较容易维护一下小于i个数比他大的数的个数 所以此时答案的贡献为-(n-tot)//tot 为小于i个数比他大的数的个数

此时我们维护好了冒泡排序的作用了

现在考虑如何处理交换:我们交换的话有两种可能:

1.a[x]<a[x+1]此时逆序对整体加1 交换后befor[x+1]++;考虑对冒泡排序的影响:

对于未冒泡的贡献加1 对于冒泡的会在第 before[x+2]以后减少1 所以我们只要单点修改就好了

对于另一种状态同理

(之后肯定要再写一遍 那样收获更大)

p3431:

这道题还是比较明显的dp 由于n,m过大 我们可以确定无法像正常二维dp一样写 所以我们考虑k的范围 由于k是1e5 我们考虑对每个点来进行考虑 

此时比较容易想到dp[i]=max(dp[j]+p[i]):i.x<=j.x&&i.y<=j.y 此时为两维的大小关系:

其实我最初是想到kdt的 可是那玩意写起来太麻烦了 所以我就正常写了

首先我们可以sort一下直接优化掉一维的大小关系 (假设我按y大小排列)接下来就是要找x比他小的最大了 此时我们可以发现只要维护一个线段树保存x位置上的最大dp 在查找最大值就好了

#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 1e5 + 10;
struct node
{
	int x, y, p;
	inline bool operator<(const node&j)const
	{
		if (y != j.y)
			return y < j.y;
		else
			return x < j.x;
	}
}a[maxn];
int dp[maxn];
struct node1
{
	int l, r;
	int maxx;
}tree[maxn*40];
int idx,root;
void modify(int&rt, int l, int r, int q, int v)
{
	if (!rt)
		rt = ++idx;
	if (l == r)
	{
		tree[rt].maxx = max(tree[rt].maxx, v);
		return;
	}
	int mid = l + r >> 1;
	if (q <= mid)
		modify(tree[rt].l, l, mid, q, v);
	else
		modify(tree[rt].r, mid + 1, r, q, v);
	tree[rt].maxx = max(tree[tree[rt].l].maxx, tree[tree[rt].r].maxx);
}
int query(int rt, int l, int r, int ql, int qr)
{
	if (!rt)
		return 0;
	if (l == ql && r == qr)
	{
		return tree[rt].maxx;
    }
	int mid = l + r >> 1;
	if (qr <= mid)
		return query(tree[rt].l, l, mid, ql, qr);
	else if (ql > mid)
		return query(tree[rt].r, mid + 1, r, ql, qr);
	else
	{
		int res = query(tree[rt].l, l, mid, ql, mid);
		res = max(res, query(tree[rt].r, mid + 1, r, mid + 1, qr));
		return res;
	}
}
signed main()
{
	int n, m, k;
	cin >> n >> m >> k;
	for (int i = 1; i <= k; i++)
	{
		cin >> a[i].x >> a[i].y >> a[i].p;
	}
	sort(a + 1, a + 1 + k);
	for (int i = 1; i <= k; i++)//找到
	{
		dp[i] = a[i].p;
		if (i == 1)
		{
			modify(root, 1, n, a[i].x, dp[i]);
		}
		else
		{
			int ma = query(root, 1, n, 1, a[i].x);
			dp[i]+=ma;
			modify(root, 1, n, a[i].x, dp[i]);
		}
	}
	int ans = 0;
	for (int i = 1; i <= k; i++)
		ans = max(ans, dp[i]);
	cout << ans;
}

p2564 其实这道题我的想法是双指针 还是比较好写的:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int maxn=1e6+10;
struct node
{
	int val,id;
	inline bool operator<(const node&jk)const
	{
		return val<jk.val;
	}
}a[maxn];
int b[70];//用来维护此时的种类数
int n,m;
signed main()
{
   cin>>n>>m;
 int now=0;
   for(int i=1;i<=m;i++)
   {
       int t;
       cin>>t;
       for(int j=1;j<=t;j++)
       {
           cin>>a[++now].val;
           a[now].id=i;//val代表位置
       }
   }
   sort(a+1,a+1+n);
   int l=1,r=1;
       b[a[1].id]=1;
   int cnt=1;//代表此时我们的种类数
   for(int i=2;i<=now;i++)
   {
   	if(a[i].val==a[i-1].val)
   	{
   		r++;
   		b[a[i].id]++;
   		if(b[a[i].id]==1)
   		{
   			cnt++;//代表此时我们有了新的种类
   		}
   	}
   	else
   		break;
   }
   int ans=1e9;
   while(l<=n&&r<=n)//开始双指针
   {
       if(cnt==m)
       {
           ans=min(ans,a[r].val-a[l].val);
              l++;
              b[a[l-1].id]--;
              if(b[a[l-1].id]==0)
              	cnt--;
              while(a[l+1].val==a[l].val)
              {
              	l++;
              	if(l>n)
              		break;
              	b[a[l-1].id]--;
              	if(b[a[l-1].id]==0)
              	{
              		cnt--;
              	}
              }
       }
       else
       {
       	r++;
       	b[a[r].id]++;
       	if(b[a[r].id]==1)
       		cnt++;
       	while(a[r+1].val==a[r].val)
       	{
       		r++;
       		if(r>n)
       			break;
       		b[a[r].id]++;
       		if(b[a[r].id]==1)
       			cnt++;
       	}
       }
   }
   cout<<ans;
}

P2216:

这道题第一眼看到的时候我以为是写树套树 结果好像并不需要:

那现在我们考虑比较暴力的做法:因为此时我们的nm仅为1000 我们求最大和最小那么比较确定的就是st表

先考虑求最大值

然后如何求一段区间的最大值中的最大值 这里类似求一段(固定)区间的最大值  这时我们就可以想到使用滑动窗口来解决了

我真服了 学了个错误的滑动窗口板子 一直wa#3#4#5

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+10;
int mp[maxn][maxn];
int Fmax[maxn][maxn][11];
int Fmin[maxn][maxn][11];//第1维是行数
int a,b,n;
void st(int l)//代表此时我们建造哪一行
{
    for(int i=1;i<=b;i++)
    {
    	Fmax[l][i][0]=mp[l][i];
    	Fmin[l][i][0]=mp[l][i];
    }
    int k=log2(b);
    for(int j=1;j<=k;j++){
    	for(int i=1;i<=b-(1<<j)+1;i++)
        {
    		Fmax[l][i][j]=max(Fmax[l][i][j-1],Fmax[l][i+(1<<(j-1))][j-1]);
    	   Fmin[l][i][j]=min(Fmin[l][i][j-1],Fmin[l][i+(1<<(j-1))][j-1]);
    	}
    }
}
int mx[1100];
int mi[1110];
int find_max(int i,int l,int r){
	int k=log2(r-l+1);
	return max(Fmax[i][l][k],Fmax[i][r-(1<<k)+1][k]);
}
int find_min(int i,int l,int r)
{
	int k=log2(r-l+1);
	return min(Fmin[i][l][k],Fmin[i][r-(1<<k)+1][k]);
}
int q[1100];
int hh=1,tt=0;
int p[1100];
signed main()
{
    cin>>a>>b>>n;
    for(int i=1;i<=a;i++)
    	for(int j=1;j<=b;j++)
    		cin>>mp[i][j];
    for(int i=1;i<=a;i++)
    {
       st(i);
    }
    int ans=1e9;
    for(int i=1;i<=b-n+1;i++){
       std::vector<int> v,v1;
       cout<<i<<endl;
       for(int j=1;j<=a;j++)
       {
       	mx[j]=find_max(j,i,i+n-1);
       	mi[j]=find_min(j,i,i+n-1);
   //    	cout<<mx[j]<<' '<<mi[j]<<endl;
       }
       hh=1,tt=0;//最大的最大值
       for(int j=1;j<=a;j++)
       {
       while(hh<=tt&&mx[j]>=q[tt])
       	     --tt;
       q[++tt]=mx[j];
       p[tt]=j;
       while(p[hh]<=j-n)
       hh++; 
        if(j>=n)
       	v.push_back(q[hh]);
       }
       hh=1,tt=0;//最小的最小值
       for(int j=1;j<=a;j++)
       {
       while(hh<=tt&&mi[j]<=q[tt])
       	tt--;       
       q[++tt]=mi[j];
       p[tt]=j;
       while(p[hh]<=j-n)
       	hh++;
        if(j>=n)
        	v1.push_back(q[hh]);
       }
      for(int j=0;j<(int)v1.size();j++)
      {
      	ans=min(ans,v[j]-v1[j]);
      } 
   cout<<endl;
    }
    cout<<ans;
}

细节在里面写好了

p1169:

这道题我自己想了挺久的 我只写过最大正方形的dp 矩形的话想来想去只能0(n*m^2+n^2*m)的写法 后来看了答案才知道如何处理 这也应该是一个套路吧 叫悬线法dp

就是我们维护一个left,right 代表一个点可以向左和向右延申的最大到哪里 

up是向上的最大长度 此时答案就差不多出来了

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+10;
int left1[maxn][maxn],right1[maxn][maxn];
int up[maxn][maxn];//left代表此时我们可以向左延申的最大位置 right是可以向右延申
//最大位置 up是代表该点可以向上的最大长度
int n,m;
int mp[maxn][maxn];
signed main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>mp[i][j];
        left1[i][j]=j;
        right1[i][j]=j;
        up[i][j]=1;
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=2;j<=m;j++)
        {
            if(mp[i][j-1]!=mp[i][j]){
                left1[i][j]=left1[i][j-1];
            }
        }
    }
     for(int i=1;i<=n;i++)
    {
        for(int j=m-1;j>=1;j--)
        {
            if(mp[i][j+1]!=mp[i][j]){
                right1[i][j]=right1[i][j+1];
            }
        }
    }
    /*for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cout<<right1[i][j]<<' '<<left1[i][j]<<endl;
        }
    }*/
    int ans1=0,ans2=0;
    for(int i=2;i<=n;i++){
        for(int j=1;j<=m;j++){
         if(mp[i][j]!=mp[i-1][j]){
            left1[i][j]=max(left1[i][j],left1[i-1][j]);
            right1[i][j]=min(right1[i][j],right1[i-1][j]);
            up[i][j]=up[i-1][j]+1;
         
         }
         int len=right1[i][j]-left1[i][j]+1;
         int jk=min(len,up[i][j]);
         ans1=max(ans1,jk*jk);
         ans2=max(ans2,len*up[i][j]);
        }
    }
      cout<<ans1<<endl;
      cout<<ans2<<endl;
}

p4147 其实和上面一样就稍微改改条件就好了

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+10;
int left1[maxn][maxn],right1[maxn][maxn];
int up[maxn][maxn];//left代表此时我们可以向左延申的最大位置 right是可以向右延申
//最大位置 up是代表该点可以向上的最大长度
int n,m;
char mp[maxn][maxn];
signed main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>mp[i][j];
        left1[i][j]=j;
        right1[i][j]=j;
        up[i][j]=1;
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=2;j<=m;j++)
        {
            if(mp[i][j-1]==mp[i][j]&&mp[i][j]=='F'){
                left1[i][j]=left1[i][j-1];
            }
        }
    }
     for(int i=1;i<=n;i++)
    {
        for(int j=m-1;j>=1;j--)
        {
            if(mp[i][j+1]==mp[i][j]&&mp[i][j]=='F'){
                right1[i][j]=right1[i][j+1];
            }
        }
    }
    /*for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cout<<right1[i][j]<<' '<<left1[i][j]<<endl;
        }
    }*/
    int ans2=0;
    for(int i=2;i<=n;i++){
        for(int j=1;j<=m;j++){
         if(mp[i][j]==mp[i-1][j]&&mp[i][j]=='F'){
            left1[i][j]=max(left1[i][j],left1[i-1][j]);
            right1[i][j]=min(right1[i][j],right1[i-1][j]);
            up[i][j]=up[i-1][j]+1;
        }
        if(mp[i][j]!='R'){
         int len=right1[i][j]-left1[i][j]+1;
         ans2=max(ans2,len*up[i][j]);    
        }
    }
    }
      
      cout<<3*ans2;
}

p2572 这题我debug半天(真正意义上的半天)

我的询问区间最大连续1 写错了 可是看来评论区的好几组hack都过了 给我整不会了

最后好在对拍出来了

首先这道题的重点在于区间翻转和区间覆盖的优先级那个高 :一般来说是区间覆盖:当我们有区间覆盖和区间翻转时 我们先将其覆盖在把他的翻转给消除:因为此时翻转已经没有了意义(这个是对于先有区间翻转后有区间覆盖的情况)对于先有区间覆盖后有区间翻转的情况:

我们可以在标记翻转的时候判断其是否被覆盖 假如有的话 我们就将其的覆盖标记变为另一个就好

对于只有区间翻转的话:这个比较好搞就不解释了:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e5+10;
int a[maxn];
struct node
{
    int l,r;
    int s[2];
    int premax0,sufmax0,midmax0;
    int flag,rev;//flag(1,2)代表此时要全部变为0/1 rev代表区间翻转
    int premax1,sufmax1,midmax1;
    inline node operator+(const node&m)const
    {
   node c;
         c.s[0]=s[0]+m.s[0];
        c.s[1]=s[1]+m.s[1];
    c.l=l,c.r=m.r;
        c.premax1=premax1;
     if(s[1]==r-l+1)//代表此时左边全部为1
         c.premax1=s[1]+m.premax1;   
      c.sufmax1=m.sufmax1;
     if(m.s[1]==m.r-m.l+1)//代表此时右边全部为1
     {
        c.sufmax1=m.s[1]+sufmax1;  
     }
    c.midmax1=sufmax1+m.premax1;
    c.midmax1=max({midmax1,m.midmax1,c.midmax1}); 
     return c;
      }
}tree[maxn<<2];
void pushdown(int rt){
     for(int i=0;i<=1;i++)
    tree[rt].s[i]=tree[rt<<1].s[i]+tree[rt<<1|1].s[i];
     tree[rt].premax0=tree[rt<<1].premax0;
     if(tree[rt<<1].s[0]==tree[rt<<1].r-tree[rt<<1].l+1)//代表此时左边全部为0
     {
        tree[rt].premax0=tree[rt<<1].s[0]+tree[rt<<1|1].premax0;
     }
      tree[rt].premax1=tree[rt<<1].premax1;
     if(tree[rt<<1].s[1]==tree[rt<<1].r-tree[rt<<1].l+1)//代表此时左边全部为0
     {
        tree[rt].premax1=tree[rt<<1].s[1]+tree[rt<<1|1].premax1;
     }
 //维护前缀    
      tree[rt].sufmax0=tree[rt<<1|1].sufmax0;
     if(tree[rt<<1|1].s[0]==tree[rt<<1|1].r-tree[rt<<1|1].l+1)//代表此时左边全部为0
     {
        tree[rt].sufmax0=tree[rt<<1|1].s[0]+tree[rt<<1].sufmax0;  
     }
      tree[rt].sufmax1=tree[rt<<1|1].sufmax1;
     if(tree[rt<<1|1].s[1]==tree[rt<<1|1].r-tree[rt<<1|1].l+1)//代表此时左边全部为0
     {
        tree[rt].sufmax1=tree[rt<<1|1].s[1]+tree[rt<<1].sufmax1;  
     }
  //维护后缀
    tree[rt].midmax0=tree[rt<<1].sufmax0+tree[rt<<1|1].premax0;
    tree[rt].midmax0=max({tree[rt].premax0,tree[rt].sufmax0,tree[rt].midmax0});
         tree[rt].midmax1=tree[rt<<1].sufmax1+tree[rt<<1|1].premax1;
    tree[rt].midmax1=max({tree[rt].premax1,tree[rt].sufmax1,tree[rt].midmax1}); 
}
void pushup(int rt)
{
    if(tree[rt].flag==1)
    {//变0
        tree[rt<<1].s[0]=tree[rt<<1].r-tree[rt<<1].l+1;
        tree[rt<<1].premax0=tree[rt<<1].sufmax0=tree[rt<<1].midmax0=tree[rt<<1].r-tree[rt<<1].l+1;
        tree[rt<<1].s[1]=0;
        tree[rt<<1].flag=tree[rt<<1|1].flag=1;
        tree[rt<<1].premax1=tree[rt<<1].sufmax1=tree[rt<<1].midmax1=0;
        tree[rt<<1|1].s[0]=tree[rt<<1|1].r-tree[rt<<1|1].l+1;
        tree[rt<<1|1].premax0=tree[rt<<1|1].sufmax0=tree[rt<<1|1].midmax0=tree[rt<<1|1].r-tree[rt<<1|1].l+1;
        tree[rt<<1|1].s[1]=0;
        tree[rt<<1|1].premax1=tree[rt<<1|1].sufmax1=tree[rt<<1|1].midmax1=0;
     tree[rt].flag=0;
    }
    else if(tree[rt].flag==2)
    {
        tree[rt<<1].s[1]=tree[rt<<1].r-tree[rt<<1].l+1;
        tree[rt<<1].premax1=tree[rt<<1].sufmax1=tree[rt<<1].midmax1=tree[rt<<1].r-tree[rt<<1].l+1;
        tree[rt<<1].flag=tree[rt<<1|1].flag=2;
        tree[rt<<1].s[0]=0;
        tree[rt<<1].premax0=tree[rt<<1].sufmax0=tree[rt<<1].midmax0=0;
        tree[rt<<1|1].s[1]=tree[rt<<1|1].r-tree[rt<<1|1].l+1;
        tree[rt<<1|1].premax1=tree[rt<<1|1].sufmax1=tree[rt<<1|1].midmax1=tree[rt<<1|1].r-tree[rt<<1|1].l+1;
        tree[rt<<1|1].s[0]=0;
        tree[rt<<1|1].premax0=tree[rt<<1|1].sufmax0=tree[rt<<1|1].midmax0=0;
       tree[rt].flag=0;

    }
    if(tree[rt].rev)
    {
     swap(tree[rt<<1].s[0],tree[rt<<1].s[1]);
     swap(tree[rt<<1].midmax0,tree[rt<<1].midmax1);
     swap(tree[rt<<1].sufmax0,tree[rt<<1].sufmax1);
     swap(tree[rt<<1].premax1,tree[rt<<1].premax0);
      swap(tree[rt<<1|1].s[0],tree[rt<<1|1].s[1]);
     swap(tree[rt<<1|1].midmax0,tree[rt<<1|1].midmax1);
     swap(tree[rt<<1|1].sufmax0,tree[rt<<1|1].sufmax1);
     swap(tree[rt<<1|1].premax1,tree[rt<<1|1].premax0);
     tree[rt<<1].rev^=tree[rt].rev;
     tree[rt<<1|1].rev^=tree[rt].rev;
     tree[rt].rev^=1;
    }
}
void build(int rt,int l,int r)
{
    tree[rt].l=l,tree[rt].r=r;
    if(l==r)
     {
        if(a[l]==1)
        tree[rt].sufmax1=tree[rt].midmax1=tree[rt].premax1=tree[rt].s[1]=1;   
       else  
        tree[rt].sufmax0=tree[rt].midmax0=tree[rt].premax0=tree[rt].s[0]=1; 
       return;
    }
    int mid=(l+r)>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    pushdown(rt);
}
void modify(int rt,int l,int r,int ql,int qr,int val)//val:1变为0 2:变为1 3 rev
{
    if(ql==l&&qr==r)
    {
        if(val==1)
        {
            tree[rt].flag=1;
            tree[rt].rev=0;//代表此时翻转被覆盖
            tree[rt].s[0]=tree[rt].r-tree[rt].l+1;
        tree[rt].premax0=tree[rt].sufmax0=tree[rt].midmax0=tree[rt].r-tree[rt].l+1;
             tree[rt].s[1]=0;
        tree[rt].premax1=tree[rt].sufmax1=tree[rt].midmax1=0;
        }
        else if(val==2)
        {
            tree[rt].flag=2;
            tree[rt].rev=0;//代表此时翻转被覆盖
            tree[rt].s[1]=tree[rt].r-tree[rt].l+1;
        tree[rt].premax1=tree[rt].sufmax1=tree[rt].midmax1=tree[rt].r-tree[rt].l+1;
             tree[rt].s[0]=0;
        tree[rt].premax0=tree[rt].sufmax0=tree[rt].midmax0=0;
        }
        else
        {
            if(tree[rt].flag==1)
            {
               tree[rt].flag=2;
               tree[rt].s[1]=tree[rt].r-tree[rt].l+1;
               tree[rt].premax1=tree[rt].sufmax1=tree[rt].midmax1=tree[rt].r-tree[rt].l+1;
               tree[rt].s[0]=0;
               tree[rt].premax0=tree[rt].sufmax0=tree[rt].midmax0=0;      
            }
            else if(tree[rt].flag==2)
            {
                 tree[rt].flag=1;
                 tree[rt].s[0]=tree[rt].r-tree[rt].l+1;
                 tree[rt].premax0=tree[rt].sufmax0=tree[rt].midmax0=tree[rt].r-tree[rt].l+1;
                 tree[rt].s[1]=0;
                 tree[rt].premax1=tree[rt].sufmax1=tree[rt].midmax1=0;
            }
            else
            {
                 tree[rt].rev^=1;
               swap(tree[rt].s[0],tree[rt].s[1]);
               swap(tree[rt].midmax0,tree[rt].midmax1);
               swap(tree[rt].sufmax0,tree[rt].sufmax1);
               swap(tree[rt].premax1,tree[rt].premax0);
            }
        }
        return;
    }
    pushup(rt);
    int mid=(l+r)>>1;
    if(qr<=mid)
        modify(rt<<1,l,mid,ql,qr,val);
    else if(ql>mid)
       modify(rt<<1|1,mid+1,r,ql,qr,val);
   else
   {
    modify(rt<<1,l,mid,ql,mid,val);
    modify(rt<<1|1,mid+1,r,mid+1,qr,val);
   }
    pushdown(rt);
}
int querysum(int rt,int l,int r,int ql,int qr)//询问该区间有多少个1
{
    if(l==ql&&r== qr)
    {
        return tree[rt].s[1];
    }
    int mid=(l+r)>>1;
    pushup(rt);
    if(qr<=mid)
        return querysum(rt<<1,l,mid,ql,qr);
    else if(ql>mid)
        return querysum(rt<<1|1,mid+1,r,ql,qr);
    else
    {
        int res=querysum(rt<<1,l,mid,ql,mid);
        res+=querysum(rt<<1|1,mid+1,r,mid+1,qr);
        return res;
    }
} 
node query1(int rt,int l,int r,int ql,int qr)
{
    if(l==ql&&r==qr)
    {
        return tree[rt];
    }
    int mid=(l+r)>>1;
    pushup(rt);
    if(qr<=mid)
        return query1(rt<<1,l,mid,ql,qr);
    else if(ql>mid)
        return query1(rt<<1|1,mid+1,r,ql,qr);
    else
    {
        node c=query1(rt<<1,l,mid,ql,mid);
        c=c+query1(rt<<1|1,mid+1,r,mid+1,qr);
        return c;
    }
}
signed main()
{
     int n,m;
     cin>>n>>m;
     for(int i=1;i<=n;i++)cin>>a[i];
        build(1,1,n);
    while(m--)
    {
        int op,l,r;
        cin>>op>>l>>r;
        l++,r++;
        if(op==0)
        {
            modify(1,1,n,l,r,1);
        }
        else if(op==1)
        {
            modify(1,1,n,l,r,2);
        }
        else if(op==2)
        {
            modify(1,1,n,l,r,3);
        }
        else if(op==3)
        {
            int ans=querysum(1,1,n,l,r);
            cout<<ans<<endl;
        }
        else
        {
            node c=query1(1,1,n,l,r);
            int ans=c.midmax1;
            cout<<ans<<endl;
        }
    }
}

p4113 :

这道题其实我是想写莫队的 再加上类似于双指针的操作 应该是正确的 不过这个2*10^6 应该会超时:

至于主席树的写法:我感觉可以参考HH的项链 维护一个next[i]和一个pre[i]一个点有贡献当且仅当其 next[i]>r 且pre[i]>=l i:[l,r] 像这样的写法肯定是不行的 因为我们每次查找只能维护一种条件 像这样的两个条件的并 我们可以考虑利用 交集的想法 根据简单的容斥原理 我们可以发现上述式子等价于 siz(next[i]>r) +siz(pre[i]>=l) -siz(nex[i]>r||pre[i]>=l)

所以现在问题转移到了求siz(next[i]>r||pre[i]>=l)上面 我们可以发现 只要两条件符合一个就好 那么我们就将 满足上述条件的i下表再开一个主席树标记 然后询问区间内有被标记的下标数量就好:

现在我们开了3个主席树 按理来说没有那个2e6 这样写应该是可以的 但他卡主席树和莫队:qwq想到啥算法他卡啥 吐了(我看评论区说卡的)上面的写法是我口胡的;

我看了题解 主席树的思想是没有错的(扶苏大佬写的想法和我差不多) 但是我的实现可能有些麻烦了 (主要是我基本没怎么写过树状数组)

考虑利用树状数组维护:1。last1[i]代表第i种颜色第上上次的位置 last2[i]为上次的位置 我们考虑和hh的项链一样的前缀和思想 当一个点出现两次的时候我们应该将上一次的位置清空 再在这一次的位置加1 这样就好了

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+10;
struct node
{
    int l,r,id;
}q[maxn];
int last1[maxn],last2[maxn],c[maxn],a[maxn],ans[maxn];
int n,q1,m;
inline bool cmp(node&a1,node&b)
{
    return a1.r<b.r;
}
inline void add(int x,int val)
{
    for(int i=x;i<=n;i+=(i&(-i)))
        c[i]+=val;
}
inline int query(int x)
{
    int ans=0;
    for(int i=x;i;i-=(i&(-i)))
        ans+=c[i];
    return ans;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin>>n>>q1>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
        for(int i=1;i<=m;i++)
        {
            cin>>q[i].l>>q[i].r;
            q[i].id=i;
        }

    sort(q+1,q+1+m,cmp);
    int j=1;
    for(int i=1;i<=m;i++)
    {
        for(;j<=q[i].r;j++)
        {
            if(!last1[a[j]])last1[a[j]]=j;
            else
            {
                if(!last2[a[j]])
                {
                    add(last1[a[j]],1);
                    last2[a[j]]=j;
                }
                else
                {
                    add(last2[a[j]],1);
                    add(last1[a[j]],-1);
                    last1[a[j]]=last2[a[j]];
                    last2[a[j]]=j;
                }


            }
        }
        ans[q[i].id]=query(q[i].r)-query(q[i].l-1);
    } 
    for(int i=1;i<=m;i++)
    {
        cout<<ans[i]<<endl;
    }
}

p2680:

这道题我自己想的时候没有想出来 主要是我觉得二分不靠谱 就没有继续思考 结果想了个错误的想法。。。

正解:此时我们要求的是最大距离的最小值 符合二分 单调性明显 我们继续考虑如何实现:

对于这道题 首先要发现一点:即我们可以明确 在树形图中任意两点的距离为固定值

考虑我们如何确定这个答案为正确的:那么我们应该要有一条边:他的边被所有路径长大于此时的答案经过才行:

先思考如何确定一条边被经过的次数:利用差分:将C[x]++,C[y]++,C[lca(x,y)]-=2;这里利用的是将边下放到点的操作:

此时我们在dfs一次将所有的差分维护起来 在跑的过程中查询是否有这样一条边:

其实这道题的关键是往二分上面去思考但我没具备这个意识 所以还是要多加练习

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=3e5+10;
int u, v, m, x, y, z;//r根节点
int cnt, tot, n;
struct node{
	int to, next,dis;
}e[maxn << 1];
int head[maxn];
int deep[maxn], siz[maxn], son[maxn], fa[maxn];
int top[maxn], id[maxn], w[maxn];
int dis[maxn];
struct node2
{
	int u,v,confa;
	int dis;
}a[maxn];
int C[maxn];
void add(int u, int v,int d) {
	e[++cnt].to = v;
	e[cnt].dis=d;
	e[cnt].next = head[u];
	head[u] = cnt;
}
void dfs1(int u, int f){
	deep[u] = deep[f] + 1;
	siz[u] = 1; fa[u] = f;
	for (int i = head[u]; i; i = e[i].next){
		int y = e[i].to;
		if (y == f)
			continue;
		w[y]=e[i].dis;
		dis[y]=dis[u]+e[i].dis;//dis[y]为根节点到目前节点的距离	
		dfs1(y, u);
		siz[u] += siz[y];
		if (!son[u] || siz[y] > siz[son[u]])
			son[u] = y;
	}
}
void dfs2(int u, int t){
	top[u] = t;
	if (!son[u])
		return;
	dfs2(son[u], t);
	for (int i = head[u]; i; i = e[i].next){
		int y = e[i].to;
		if (y == fa[u] || y == son[u])
			continue;
		dfs2(y, y);
	}
}
int lca(int u, int v){
	while(top[u]!=top[v])
	{
		if(deep[top[u]]<deep[top[v]])swap(u,v);
		u=fa[top[u]];
	}
	return deep[u]<=deep[v]?u:v;	
}
bool flag=0;
int num=0,need=0;
int now;
int res=0;
void dfs(int u,int fa)
{
	for(int i=head[u];i;i=e[i].next)
    {	
	 int y=e[i].to;
     if(y==fa)continue;
     dfs(y,u);
     C[u]+=C[y];
	}
}
bool check(int x)
{
  memset(C,0,sizeof C);
  //开始记录那些被标记的边
  flag=0,num=0,need=now-x;
  res=0;
  for(int i=1;i<=m;i++)
  {
  	if(a[i].dis>x)
  	{
  		num++;
  		C[a[i].u]++;
  		C[a[i].v]++;
  		C[a[i].confa]-=2;
  	}
  }
  dfs(1,0);
  for(int i=1;i<=n;i++)
  {
  	if(C[i]==num)
  		res=max(res,w[i]);
  }
 if(now-res>x)
 	return 0;
 else
 	return 1;
}
int ans=0;
int l=0,r=0;
void erfen()
{
	now=r;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid))//最大值最小
		{
			 r=mid-1;
             ans=mid;    
		}
		else
			l=mid+1;
	}
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> m;
     int d;
	for (int i = 1; i < n; i++){
		cin >> u >> v>>d; add(u, v,d), add(v, u,d);
	}
	dfs1(1, 0); dfs2(1, 1);
  for(int i=1;i<=m;i++)
  {
  	cin>>a[i].u>>a[i].v;
   a[i].confa=lca(a[i].u,a[i].v); 
   a[i].dis=dis[a[i].u]+dis[a[i].v]-2*dis[a[i].confa];
     //   cout<<a[i].dis<<endl;
    r=max(a[i].dis,r);
  }
  erfen(); 
  cout<<ans;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值