首页 > 其他分享 >[题解]P4381 [IOI2008] Island

[题解]P4381 [IOI2008] Island

时间:2024-06-02 12:21:54浏览次数:24  
标签:IOI2008 cnt int 题解 de pos ff P4381 dis

P4381 [IOI2008] Island

题意:给定一个基环树森林,求每个基环树的直径之和。

我们发现,一棵基环树的直径只有下面两种情况:

  • 环上的某一点作为根的子树的直径。
  • 环上有两点,每个点引出一条链,然后再将这两点相连。

对于第一种情况,我们仅需用树形dp的方法求出每个子树的直径(见OI Wiki - 方法2,以后会写直径的笔记),然后比较出最大值即可。这里使用拓扑排序解决。

而第二种情况,我们首先需要记录环上每个节点\(i\)为根的子树中,从\(i\)出发的最长路径。我们将其记为\(f[i]\)(这个\(f\)正好可以用于计算情况\(1\)的子树直径)。

那么接下来我们只需要解决:在环上找到\(i,j\)两点(\(i<j,j-i<n\)),使得\(f[i]+f[j]+dist(i,j)\)最大(\(dist(i,j)\)表示\(i\)到\(j\)的最大距离)。

由于我们断环为链,所以对这条链的节点求一个距离的前缀和,即用\(dis[i]\)表示从链的左端到链的节点\(i\)的距离。如此,我们需要使\(f[i]+f[j]+dis[j]-dis[i]\)最大。

整理得\(f[j]+dis[j]+(f[i]-dis[i])\)。我们发现可以用单调队列优化。枚举右边界\(j\),然后对于每一个\(j\),找满足\(j-n+1\le i<j\)的最大\(f[i]-dis[i]\)即可,此时的答案是\(f[j]+dis[j]+f[i]-dis[i]\)。

实现细节:

  • long long
  • 可能需要用较快的读入方式。
  • 由于是无向图,所以拓扑排序需要稍加修改,见代码。
  • 处理环的过程中可能会遇到大小为\(2\)的环。此时拆成链的操作不是很好写,可以特判以下。
  • 单调队列优化时,因为\(j\ne i\),所以遍历前应当先加入决策\(1\),再从\(2\)开始遍历。遍历过程中,需要先更新最大值,再把当前元素入队列,具体见代码。
点击查看代码
//P4381
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct edge{int to,w;};
int n,deg[1000010],f[1000010],ans,cnt;
//deg[i]:i的度,用于拓扑排序
//f[i]:以i为根的子树中,从i出发的最长路径
int ff[1000010];
//ff[i]:断成链后每个点对应的的f
bool vis[1000010];
int d[500010],num[1000010],fir;
//d[i]:编号为i的连通块的答案
//num[i]:节点i所属连通块编号
int dis[1000010];
vector<edge> G[1000010];
queue<int> q;
deque<int> de;
void add(int u,int v,int w){
	G[u].push_back({v,w});
	deg[v]++;
}
void dfs(int pos){//为每个连通块编号
	if(vis[pos]) return;
	vis[pos]=1,num[pos]=cnt;
	for(auto i:G[pos]) dfs(i.to);
}
void topo(){//寻找环,并且计算子树直径
	for(int i=1;i<=n;i++) if(deg[i]==1) q.push(i);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(auto i:G[u]){
			int v=i.to,w=i.w;
			if(deg[v]>1){//u是v的子节点
				d[num[v]]=max(d[num[v]],f[v]+f[u]+w);
				f[v]=max(f[v],f[u]+w);
				deg[v]--;
				if(deg[v]==1) q.push(v);
			}
		}
	}
}
void cycle(int pos,int cnt){//处理环
	vis[pos]=1;
	ff[cnt]=f[pos];
	bool end=1;
	int tempw=-1;
	for(auto i:G[pos]){
		int v=i.to,w=i.w;
		if(v==fir) tempw=w;
		if(vis[v]||deg[v]==1) continue;
		dis[cnt+1]=dis[cnt]+w,end=0;
		cycle(v,cnt+1);
		break;
	}
	if(end){
		int tnum=num[fir];
		if(cnt==2){//特判2个节点的环
			for(auto i:G[pos]){
				int v=i.to,w=i.w;
				if(v==fir) d[tnum]=max(d[tnum],w+f[pos]+f[v]);
			}
			return;
		}
		for(int i=cnt+1;i<=2*cnt-1;i++){
			if(i==cnt+1) dis[i]=dis[i-1]+tempw;
			else dis[i]=dis[i-1]+dis[i-cnt]-dis[i-cnt-1];
			ff[i]=ff[i-cnt];
		}
		de.clear();
		de.push_back(1);
		for(int i=2;i<=2*cnt-1;i++){
			if(!de.empty()&&i-de.front()>=cnt) de.pop_front();
			d[tnum]=max(d[tnum],ff[i]+dis[i]+ff[de.front()]-dis[de.front()]);
			while(!de.empty()&&ff[i]-dis[i]>=ff[de.back()]-dis[de.back()])
				de.pop_back();
			de.push_back(i);
		}
	}
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin>>n;
	for(int i=1;i<=n;i++){
		int u,w;
		cin>>u>>w;
		add(i,u,w),add(u,i,w);
	}
	for(int i=1;i<=n;i++)//为每个连通块编号
		if(!vis[i]) ++cnt,dfs(i);
	topo();//计算情况1
	memset(vis,0,sizeof vis);
	for(int i=1;i<=n;i++)//计算情况2
		if(!vis[i]&&deg[i]!=1) fir=i,cycle(i,1);
	for(int i=1;i<=cnt;i++) ans+=d[i];
	cout<<ans;
	return 0;
}

标签:IOI2008,cnt,int,题解,de,pos,ff,P4381,dis
From: https://www.cnblogs.com/Sinktank/p/18225189

相关文章

  • 《扶苏的问题》题解
    《扶苏的问题》题解原题传送门:P1253扶苏的问题PS:请先阅读完题面,在继续观看题意概述:​ 对于给定的数列\({a_1,a_2,a_3……a_n}\),进行以下三个操作:​ 1.change 将区间\([L,R]\)的值修改为\(X\);​ 2.add 将区间\([L,R]\)的值加上为\(D\);​ 3.query 查询区间\([......
  • WSL2--DNS解析问题解决
    1.问题xurong@DESKTOP-SOE9MG1:~/.ssh$sudoaptupdateIgn:1http://security.ubuntu.com/ubuntunoble-securityInReleaseIgn:2http://archive.ubuntu.com/ubuntunobleInReleaseIgn:3http://archive.ubuntu.com/ubuntunoble-updatesInReleaseIgn:4http://archive......
  • CF1961E Turtle and Intersected Segments 题解
    题目链接点击打开链接题目解法不是,我这咋不会做/fn边数很多的最小生成树有一个方法是\(boruvka\),但这个处理完全图的比较方便另一个方法是用到一个\(trick\):连出的图中的环,可以删去最长边扫描线是容易想到的,主要是如何把连的边数缩减到合理的范围内考虑扫描线到当前时刻......
  • 第二十一届宁波大学程序设计竞赛(同步赛) A,B,D,F,H题解
    链接:第二十一届宁波大学程序设计竞赛(同步赛)_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ(nowcoder.com)A:直接输出不多解释B:B-LoveYouGuys_第二十一届宁波大学程序设计竞赛(同步赛)(nowcoder.com)#include<bits/stdc++.h>usingnamespacestd;intx,y......
  • atcoder350,351,352,353,354,355期部分题解
    声明:有些题感觉已经说到很明白了,就先不写代码了,有空会补上目录350D: newfriend350E:toward0351D:GridandMagnet352D:permutation subsequence353C:sigmaproblem353D:anothersigmaproblem354C:atcodermagics355C:bingo2355D:intersectingintervals......
  • 【问题解决】MySQL恢复数据库报错Unknown command '\''.
    问题使用以下命令备份恢复数据库,恢复失败提示ERRORatline39595:Unknowncommand'\''.#备份数据库mysqldump-uusername-p--no-create-db-Rdatabasename>dump.sql#恢复数据库mysql-uusername-pdatabasename2<dump.sql问题原因及解法原因:中文字符的问题......
  • P6156 简单题 题解
    P6156简单题题解题目大意题目传送门给定\(n,k\),求\(\sum_{i=1}^n\sum_{j=1}^n(i+j)^k\gcd(i,j)\mu^2(\gcd(i,j))\)。\(1\leqn\leq5\times10^6\)题目分析先推导一波式子:\[\begin{aligned}ans&=\sum_{i=1}^n\sum_{j=1}^n(i+j)^k\gcd(i,j)\mu^2(\gcd(i,j))\\&=......
  • 磁盘管理后续——盘符漂移问题解决
    之前格式化磁盘安装了文件系统,且对磁盘做了相应的挂载,但是服务器重启后挂载信息可能有问题,或者出现盘符漂移、盘符变化、盘符错乱等故障,具体是dev/sda,sdb,sdc等等在某些情况下会混乱掉比如sda变成了sdb或者sdc变成了sdb等等,这样无形中会导致磁盘设备管理的混乱,最常见......
  • 2024ICPC武汉邀请赛E. Boomerang 题解
    E-Boomerang(动态维护树的直径+二分)分析代码实现#include<bits/stdc++.h>#ifdefLOCAL#include"algo/debug.h"#else#definedebug(...)42#endif#defineintlonglongusingEdge=int;structHLD{ intn,times=0; std::vector<int>siz,top,......
  • ARC119F 题解
    blog。被自动机做法恶心到了,现在也来恶心一下大家。\(\color{red}\textbf{以下内容强烈建议自己推一遍,几乎一半是重复的,推完会很爽,并且理解会很深。}\)\(\color{red}\textbf{以下内容不建议用}\LaTeX\textbf{书写,因为写起来像在吃大便。}\)暴力\(dp_{i,a,b}\)表示当前在\(......