首页 > 其他分享 >【Coel.学习笔记】基环树动态规划

【Coel.学习笔记】基环树动态规划

时间:2022-09-29 21:12:16浏览次数:79  
标签:head int ll Coel 笔记 基环树 maxn d1

引入

基环树(又称环套树)是一种特殊的图,在原有的树形结构上添加一条边,就会形成一个环,看起来就像从环延伸出树。特别地,对于有向图而言,环上点所连接的边指向环外为外向树,反之为内向树。

基环树可以看成是只有一个回路的仙人掌,但也因为这个特性,某些基环树的题目具有更灵活的性质,以至于某些基环树题无法用圆方树求解。

例题讲解

[IOI2008] Island

洛谷传送门
给定一个 \(n\) 个点 \(n\) 条边的基环树森林,求每个基环树的最长距离之和。(原题题面太长就不写完了)

解析:类似仙人掌的做法,求解两点最大距离也要进行分类讨论。

  1. 两点在同一棵树上。和仙人掌一样,求出该点向下的最大值和次大值,相加即为答案。
  2. 两个点在不同的树上。不难发现,由于两点属于不同的树,所以从一点到另一点必然要走环,那么就要找环上的较长段。也和仙人掌一样,把距离分成三段:环上最大距离和点到环的最大距离。

当然求解的先决条件是找到环在哪。可以上 tarjan 算法,但是这道题比较特殊,所以只要用一个普通的 dfs 就够了。

#include <iostream>
#include <cstring>

using namespace std;

typedef long long ll;

const int maxn = 2e6 + 10;

int n;
int head[maxn], nxt[maxn], to[maxn], val[maxn], cnt;
int fa[maxn], len[maxn], Q[maxn];
int cir[maxn], ed[maxn], idx;
ll s[maxn], d[maxn], sum[maxn];
bool vis[maxn], ins[maxn];
ll ans, res;

void add(int u, int v, int w) {
	nxt[cnt] = head[u], to[cnt] = v, val[cnt] = w, head[u] = cnt++;
}

void gma(ll &x, ll y) {
	if (x < y) x = y;
}

void dfs(int u, int f) {
	vis[u] = ins[u] = true;
	for (int i = head[u]; ~i; i = nxt[i]) {
		if (i == (f ^ 1)) continue;
		int v = to[i], w = val[i];
		fa[v] = u, len[v] = w;
		if (!vis[v]) dfs(v, i);
		else if (ins[v]) {
			idx++;
			ed[idx] = ed[idx - 1];
			ll sum = val[i];
			for (int k = u; k != v; k = fa[k]) {
				s[k] = sum, sum += len[k];
				cir[++ed[idx]] = k;
			}
			s[v] = sum, cir[++ed[idx]] = v;
		}
	}
	ins[u] = false;
}

ll dfs_d(int u) {
	vis[u] = true;
	ll d1 = 0, d2 = 0;
	for (int i = head[u]; ~i; i = nxt[i]) {
		int v = to[i];
		if (vis[v]) continue;
		ll dis = dfs_d(v) + val[i];
		if (dis >= d1) d2 = d1, d1 = dis;
		else gma(d2, dis);
	}
	gma(res, d1 + d2);
	return d1;
}

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n;
	memset(head, -1, sizeof(head));
	for (int u = 1; u <= n; u++) {
		int v, w;
		cin >> v >> w;
		add(u, v, w), add(v, u, w);
	}
	for (int i = 1; i <= n; i++)
		if (!vis[i]) dfs(i, -1); //找环
	memset(vis, false, sizeof(vis));
	for (int i = 1; i <= ed[idx]; i++)
		vis[cir[i]] = true; //标记一下每个环,防止搜索时搜到环上的点
	for (int i = 1; i <= idx; i++) {
		res = 0;
		int sz = 0, l = 0, r = -1;
		for (int j = ed[i - 1] + 1; j <= ed[i]; j++) {
			int k = cir[j];
			d[sz] = dfs_d(k);
			sum[sz] = s[k];
			sz++;
		}
		for (int j = 0; j < sz; j++) d[sz + j] = d[j], sum[sz + j] = sum[j] + sum[sz - 1];
		for (int j = 0; j < sz * 2; j++) { //单调队列优化
			if (l <= r && j - Q[l] >= sz) l++;
			if (l <= r) gma(res, d[j] + sum[j] + d[Q[l]] - sum[Q[l]]);
			while (l <= r && d[Q[r]] - sum[Q[r]] <= d[j] - sum[j]) r--;
			Q[++r] = j;
		}
		ans += res;
	}
	cout << ans;
	return 0;
}

标签:head,int,ll,Coel,笔记,基环树,maxn,d1
From: https://www.cnblogs.com/Coel-Flannette/p/16743074.html

相关文章

  • 算法数学笔记-二、线性代数
    目录二、线性代数矩阵模板高斯消元二、线性代数矩阵模板namespaceMatrix{ structmatrix{ inthang,lie; vector<vector<int>>num; matrix(intx=0,......
  • 自学笔记
    1,方法使用static修饰时,调用该方法时就不用通过对象.方法。而是直接写方法名字就可以完成调用2,一个java文件可以有多个类,但是只能有一个用public修斯的类。3,短路与或非&&......
  • java-抽象类笔记
    抽象方法和抽象类抽象方法使用abstract修饰的方法,没有方法体,只有声明。定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。抽象类包含抽象方......
  • 读书笔记1
    每一程序员都有属于自己的编程风格,每个人都有自己擅长的和不擅长的,随着时间的推移会逐渐形成属于自己的编程环境。一个成功的程序员成功的一个关键是他们会对自己所做的事......
  • springaop笔记
    springaop解决的问题什么是增强增强代码,比如买装备在不惊动源代码的基础上对代码进行更改,增强,什么是aop第一步导入坐标第二步创建aop文件夹@Aspect作用标识此......
  • drf学习笔记
    今日内容概要drf之请求与响应drf之视图组件两个视图基类今日内容详细补充知识反射:通过字符串动态的获取,设置,判断对象中得属性或方法-getattr:res=getattr(se......
  • 《程序员修炼之道:从小工到专家》阅读笔记2
    在某些方面,编程就像是绘画。你从空白的画布和某些基本原材料开始,通过知识、艺术和技艺的结合去确定用前者做些什么。你勾画出全景,绘制背景,然后填入各种细节。你不时后退一......
  • 《程序员修炼之道:从小工到专家》阅读笔记
    当我读了《程序员修炼之道:从小工到专家》之后,我最感同身受的是在所有弱点中,最大的弱点就是害怕暴露弱点。我本人就是一个害怕暴露弱点的人。喜欢把强势的一面表现出来而把......
  • 脚手架笔记
    笔记脚手架文件结构├──node_modules├──public│├──favicon.ico:页签图标│└──index.html:主页面├──src│├──assets:存放静态......
  • UE4学习笔记2
    P8.创建项目全流程和模板试玩P9.2-1超详细编辑界面介绍P10.2-2视口导航(在视口界面移动视角)(P8)创建项目,没什么好说的,要注意选的是空白的还是带模板的,注意下面的存......