BZOJ 2286 SDOI 2011 消耗战 LCA单调性

5 篇文章 0 订阅
4 篇文章 0 订阅

题目大意:给出一棵有根树,n组询问,每一组询问给出树上的一些关键点,问割掉一些边使得根与这些点不联通的最小花费是多少。总询问的点不超过O(n)。


思路:基础思路是每一次询问做一次O(n)的DP,这本来已经够快了,但是有很多询问,这样做就n^2了。注意到所有询问的点加起来不超过O(n),也就是说每次询问的点可能很少。那么我们为何要将所有点扫一次?只需要将询问的点重新建树,然后跑树形DP,这样DP的总时间就是O(n)了。当然瓶颈在求两点之间的最短边上,O(nlogn)的倍增。

具体做法是维护一个单调栈,所有时刻这个栈中的所有点是从根开始的深度递增的一条链。把所有点按照DFS序排序,依次加入栈中,同时维护这个栈,使它是一条链。假如新加进来的点与栈顶的LCA高于栈顶,那么就说明新加进来的点不能继续与栈顶形成链了。就将栈顶和次栈顶连边,然后弹出栈顶。还有一些小细节什么的。。


CODE:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAX 510010
#define INF 0x3f3f3f3f
using namespace std;

struct Complex{
	int x,pos;
	
	Complex(int _,int __):x(_),pos(__) {}
	Complex() {}
	bool operator <(const Complex &a)const {
		return pos < a.pos;
	}
}src[MAX];

int points,asks;
int head[MAX],total;
int next[MAX],aim[MAX],length[MAX];
int pos[MAX],cnt;

inline void Add(int x,int y,int len)
{
	next[++total] = head[x];
	aim[total] = y;
	length[total] = len;
	head[x] = total;
}

int father[MAX][20],_min[MAX][20];
int deep[MAX];

void DFS(int x,int last)
{
	deep[x] = deep[last] + 1;
	pos[x] = ++cnt;
	for(int i = head[x]; i; i = next[i]) {
		if(aim[i] == last)	continue;
		father[aim[i]][0] = x;
		_min[aim[i]][0] = length[i];
		DFS(aim[i],x);
	}
}

void MakeTable()
{
	for(int j = 1; j < 19; ++j)
		for(int i = 1; i <= points; ++i) {
			father[i][j] = father[father[i][j - 1]][j - 1];
			_min[i][j] = min(_min[i][j - 1],_min[father[i][j - 1]][j - 1]);
		}
}

inline int GetLCA(int x,int y)
{
	if(deep[x] < deep[y])	swap(x,y);
	for(int i = 19; ~i; --i)
		if(deep[father[x][i]] >= deep[y])
			x = father[x][i];
	if(x == y)	return x;	
	for(int i = 19; ~i; --i)
		if(father[x][i] != father[y][i])
			x = father[x][i],y = father[y][i];
	return father[x][0];
}

inline int GetMin(int x,int y)
{
	if(deep[x] < deep[y])	swap(x,y);
	int re = INF;
	for(int i = 19; ~i; --i)
		if(deep[father[x][i]] >= deep[y]) {
			re = min(re,_min[x][i]);
			x = father[x][i];	
		}
	for(int i = 19; ~i; --i)
		if(father[x][i] != father[y][i]) {
			re = min(re,_min[x][i]);
			re = min(re,_min[y][i]);
			x = father[x][i];
			y = father[y][i];
		}
	if(x != y)	re = min(re,min(_min[x][0],_min[y][0]));
	return re;
}

struct Graph{
	int head[MAX],v[MAX],T,total;
	int next[MAX],aim[MAX];
	int super[MAX];
	long long f[MAX];
	
	void Add(int x,int y) {
		//cout << x << ' ' << y <<  endl;
		if(v[x] != T) {
			v[x] = T;
			head[x] = 0;
		}
		next[++total] = head[x];
		aim[total] = y;
		head[x] = total;
	}
	void Set(int x) {
		super[x] = T;
	}
	void TreeDP(int x) {
		f[x] = 0;
		if(v[x] != T) {
			v[x] = T;
			head[x] = 0;
		}
		for(int i = head[x]; i; i = next[i]) {
			TreeDP(aim[i]);
			f[x] += min(super[aim[i]] == T ? INF:f[aim[i]],(long long)GetMin(x,aim[i]));
		}
	}
}graph;

int main()
{
	cin >> points;
	for(int x,y,z,i = 1; i < points; ++i) {
		scanf("%d%d%d",&x,&y,&z);
		Add(x,y,z),Add(y,x,z);	
	}
	DFS(1,0);
	MakeTable();
	cin >> asks;
	for(int cnt,i = 1; i <= asks; ++i) {
		scanf("%d",&cnt);
		for(int j = 1; j <= cnt; ++j)
			scanf("%d",&src[j].x),src[j].pos = pos[src[j].x];
		sort(src + 1,src + cnt + 1);
		++graph.T;
		graph.total = 0;
		static int stack[MAX];
		int top = 0;
		stack[++top] = 1;
		for(int j = 1; j <= cnt; ++j) {
			int lca = GetLCA(stack[top],src[j].x);
			while(deep[lca] < deep[stack[top]]) {
				if(deep[stack[top - 1]] <= deep[lca]) {
					int away = stack[top--];
					if(stack[top] != lca)
						stack[++top] = lca;
					graph.Add(lca,away);
					break;
				}
				graph.Add(stack[top - 1],stack[top]),--top;
			}
			if(stack[top] != src[j].x)
				stack[++top] = src[j].x;
			graph.Set(src[j].x);
		}
		while(top)
			graph.Add(stack[top - 1],stack[top]),--top;
		graph.TreeDP(1);
		printf("%lld\n",graph.f[1]);
	}
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一道经典的单调栈问题。题目描述如下: 有 $n$ 个湖,第 $i$ 个湖有一个高度 $h_i$。现在要在这些湖之间挖一些沟渠,使得相邻的湖之间的高度差不超过 $d$。请问最少需要挖多少个沟渠。 这是一道单调栈的典型应用题。我们可以从左到右遍历湖的高度,同时使用一个单调栈来维护之前所有湖的高度。具体来说,我们维护一个单调递增的栈,栈中存储的是湖的下标。假设当前遍历到第 $i$ 个湖,我们需要在之前的湖中找到一个高度最接近 $h_i$ 且高度不超过 $h_i-d$ 的湖,然后从这个湖到第 $i$ 个湖之间挖一条沟渠。具体的实现可以参考下面的代码: ```c++ #include <cstdio> #include <stack> using namespace std; const int N = 100010; int n, d; int h[N]; stack<int> stk; int main() { scanf("%d%d", &n, &d); for (int i = 1; i <= n; i++) scanf("%d", &h[i]); int ans = 0; for (int i = 1; i <= n; i++) { while (!stk.empty() && h[stk.top()] <= h[i] - d) stk.pop(); if (!stk.empty()) ans++; stk.push(i); } printf("%d\n", ans); return 0; } ``` 这里的关键在于,当我们遍历到第 $i$ 个湖时,所有比 $h_i-d$ 小的湖都可以被舍弃,因为它们不可能成为第 $i$ 个湖的前驱。因此,我们可以不断地从栈顶弹出比 $h_i-d$ 小的湖,直到栈顶的湖高度大于 $h_i-d$,然后将 $i$ 入栈。这样,栈中存储的就是当前 $h_i$ 左边所有高度不超过 $h_i-d$ 的湖,栈顶元素就是最靠近 $h_i$ 且高度不超过 $h_i-d$ 的湖。如果栈不为空,说明找到了一个前驱湖,答案加一。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值