[DTOJ3996]:Lesson5!(DP+拓扑+线段树)

题目描述

  “最短的捷径就是绕远路,绕远路就是我最短的捷径”
  转眼就$Stage\ X$了,$Stage\ X$的比赛路线可以看做一个$n$个点$m$条边的有向无环图,每条边长度都是$1$。杰洛$\cdot$齐贝林会选择走最长的那一条路径。
  迪亚哥$\cdot$布兰度决定摧毁一个城市以及所有关于该城市的边,由于变成恐龙后脑子有点问题,他想要让摧毁后的$Stage$最长路径最短,他想知道要摧毁哪个城市,及摧毁后最长路径的长度,如果有多个城市答案相同,则输出编号最小的那一个。


输入格式

  本题包含多组数据,输入第一行一个整数$T$代表数据组数
  每组数据第一行两个整数$n,m$表示点数,边数。
  每组数据第$2\sim m+1$行每行两个整数$x_i,y_i$表示有一条连接$x_i,y_i$的边。


输出格式

  对于每组数据,输出一行两个整数,表示删除的城市编号及删除该城市后最长路径的长度。


样例

样例输入:

1
6 5
1 3
1 4
3 6
3 4
4 5

样例输出:

1 2


数据范围与提示

对于所有数据,满足$T\leqslant 10,1\leqslant n\leqslant 100,000,0\leqslant m\leqslant 500,000$。


题解

$Dijkstra$不能跑最长路!!!

解释一下。

众所周知$Dijkstra$不能跑带负边权的最短路,而跑最长路也就相当于是跑带负边权的最短路,所以它死了……

那么回来考虑这道题。

对于$DAG$,首先想到拓扑。

不妨先跑正反拓扑计算出$dis_s$和$dis_t$分别表示正反拓扑的最长路,思想类似$DP$。

那么边$i$对答案的贡献就是$dis_{s_{i_u}}+dis{t_{i_v}}+1$,删除一个点就相当与删掉了与它相连的边。

快速修改用线段树就好啦。

时间复杂度:$\Theta(m\log n)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
#define L(x) x<<1
#define R(x) x<<1|1
using namespace std;
struct rec{int nxt,to;}e[1000001];
int head[2][100001],cnt;
int n,m;
int a[100001];
int dis[2][100001],sum[100001],tr[400001],in[100001],ou[100001];
pair<int,int> ans;
queue<int> q;
void pre_work()
{
	memset(head,0,sizeof(head));
	memset(dis,0,sizeof(dis));
	memset(sum,0,sizeof(sum));
	memset(tr,0,sizeof(tr));
	memset(in,0,sizeof(in));
	memset(ou,0,sizeof(ou));
	memset(a,0,sizeof(a));
	cnt=0;ans=make_pair(1,n);
}
void add(bool id,int x,int y)
{
	e[++cnt].nxt=head[id][x];
	e[cnt].to=y;
	head[id][x]=cnt;
}
void pushup(int x){tr[x]=max(tr[L(x)],tr[R(x)]);}
void add(int x,int l,int r,int k)
{
	if(l==r)
	{
		sum[l]++;
		if(sum[l]>0)tr[x]=l;
		else{sum[l]=0;tr[x]=-1;}
		return;
	}
	int mid=(l+r)>>1;
	if(k<=mid)add(L(x),l,mid,k);
	else add(R(x),mid+1,r,k);
	pushup(x);
}
void del(int x,int l,int r,int k)
{
	if(l==r)
	{
		sum[l]--;
		if(sum[l]>0)tr[x]=l;
		else{sum[l]=0;tr[x]=-1;}
		return;
	}
	int mid=(l+r)>>1;
	if(k<=mid)del(L(x),l,mid,k);
	else del(R(x),mid+1,r,k);
	pushup(x);
}
int main()
{
	freopen("johnny.in","r",stdin);
	freopen("johnny.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		pre_work();
		for(int i=1;i<=m;i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			add(0,x,y);in[y]++;
			add(1,y,x);ou[x]++;
		}
		for(int i=1;i<=n;i++)if(!in[i]){a[++a[0]]=i;q.push(i);}
		while(q.size())
		{
			int x=q.front();q.pop();
			for(int i=head[0][x];i;i=e[i].nxt)
			{
				if(dis[0][e[i].to]<dis[0][x]+1)dis[0][e[i].to]=dis[0][x]+1;
				in[e[i].to]--;
				if(!in[e[i].to]){a[++a[0]]=e[i].to;q.push(e[i].to);}
			}
		}
		for(int i=1;i<=n;i++)if(!ou[i])q.push(i);
		while(q.size())
		{
			int x=q.front();q.pop();
			for(int i=head[1][x];i;i=e[i].nxt)
			{
				if(dis[1][e[i].to]<dis[1][x]+1)dis[1][e[i].to]=dis[1][x]+1;
				ou[e[i].to]--;
				if(!ou[e[i].to])q.push(e[i].to);
			}
		}
		for(int i=1;i<=n;i++)add(1,0,n,dis[1][i]);
		for(int x=1;x<=n;x++)
		{
			for(int i=head[1][a[x]];i;i=e[i].nxt)del(1,0,n,dis[0][e[i].to]+dis[1][a[x]]+1);
			del(1,0,n,dis[1][a[x]]);
			if(tr[1]<ans.second||(ans.second==tr[1]&&a[x]<ans.first))ans=make_pair(a[x],tr[1]);
			for(int i=head[0][a[x]];i;i=e[i].nxt)add(1,0,n,dis[0][a[x]]+dis[1][e[i].to]+1);
			add(1,0,n,dis[0][a[x]]);
		}
		printf("%d %d\n",ans.first,ans.second);
	}
	return 0;
}


rp++

原文地址:https://www.cnblogs.com/wzc521/p/11804336.html

时间: 2024-11-02 21:28:36

[DTOJ3996]:Lesson5!(DP+拓扑+线段树)的相关文章

【CF675E】Trains and Statistic(贪心,DP,线段树优化)

题意:a[i]表示从第i个车站可以一张票到第[i+1,a[i]]这些车站;p[i][j]表示从第i个车站到第j个车站的最少的票数,现在要求∑dp[i][j](1<=i<=n,i<j<=n); 思路:从I开始走,在i+1到a[i]之间一定会到使a[j]最大的j,因为要使步数最小,接下来能走得更快 区间询问最值用RMQ与线段树都可以 dp[i]表示dp[i,i+1],dp[i,i+2]...dp[i,n]这些值的和 dp[i]=dp[k]+(n-i)-(a[i]-k),k为[i+1,a

POJ 1769 Minimizing maximizer(DP+zkw线段树)

[题目链接] http://poj.org/problem?id=1769 [题目大意] 给出一些排序器,能够将区间li到ri进行排序,排序器按一定顺序摆放 问在排序器顺序不变的情况下,一定能够将最大值交换到最后一位至少需要保留几个排序器 [题解] 我们发现,对于每个排序器,dp[ri]=min(dp[ri],min(dp[li]~dp[ri-1])+1) 我们用线段树对dp值进行最小值维护,顺序更新即可. [代码] #include <cstdio> #include <algorit

LightOJ 1085(树状数组+离散化+DP,线段树)

All Possible Increasing Subsequences Time Limit:3000MS     Memory Limit:65536KB     64bit IO Format:%lld & %llu Appoint description: Description An increasing subsequence from a sequence A1, A2 ... An is defined by Ai1, Ai2 ... Aik, where the followi

Codecraft-18 and Codeforces Round #458 C dp D 线段树

Codecraft-18 and Codeforces Round #458 C. Travelling Salesman and Special Numbers 题意: 一个由0.1 组成的数 n,操作:n 有 m 个 1,就把 n 变为 m. 问 <=n 的数中有多少个恰好经过 k 次操作能变为 1. tags:  dp[i][j] 表示长度为 i 且有 j 个 1  的串,比对应的 n 要小的方案数. #include<bits/stdc++.h> using namespace

【HDU】5195-DZY Loves Topological Sorting(拓扑 + 线段树 + 贪心)

每次找出入度小于K的编号最大点. 找的时候用线段树找,找完之后将这个点出度链接的点的入度全部减一 简直爆炸... #include<cstdio> #include<vector> #include<cstring> #include<algorithm> using namespace std; #define lson (pos<<1) #define rson (pos<<1|1) const int maxn = 100005

一个神秘的oj2587 你猜是不是dp(线段树优化建图)

哇 这难道不是happiness的翻版题嘛? 从\(S\)向一个点连染成白色的收益 从这个点向\(T\)连染成黑色的收益 对于额外的收益,建一个辅助点,跟区间内的每个点连\(inf\),然后向S/T,连流量为收益 这不就结束了吗? 自信写完,提交 woc!!只有40分? #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath>

bzoj2325 [ZJOI2011]道馆之战 树链剖分+DP+类线段树最大字段和

题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=2325 题解 可以参考线段树动态维护最大子段和的做法. 对于线段树上每个节点 \(o\),维护 \(ls_{0/1}, rs_{0/1}, s_{0/1, 0/1}\) 分别表示从最左边的上面/下面的格子进入最多走的方块数量,从最右边的上面/下面的格子进入最多走的方块数量,从最左边的上面/下面到最右边的上面/下面的做多走的方块数量. 然后合并的时候也类似与线段树最大字段和.\(ls\) 的话保

hdu 1087 最大上升子序列的和(dp或线段树)

Super Jumping! Jumping! Jumping! Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 23328    Accepted Submission(s): 10266 Problem Description Nowadays, a kind of chess game called “Super Jumping!

HDU 3607 线段树+DP+离散化

题意:从左往右跳箱子,每个箱子有金币数量,只能从矮处向高处跳,求最大可获得金币数,数据规模1<=n<=1e5. 显然是一个dp的问题,不难得出dp[ i ] = max(dp[j] )+val [ i ] ,j < i ; 第一眼会想到o(n^2)的算法,显然会超时,这个时候就需要用线段树维护最大值,将复杂度降低到o(nlogn) 首先离散化处理,将高度从小到大排序,并使用unique函数去重,之后每个高度就可以映射为它的下标pos,然后用线段树维护每个下标对应的最优解bestans [