BZOJ4727: [POI2017]Turysta

Description

给出一个n个点的有向图,任意两个点之间有且仅一条有向边。对于每个点v,求出从v出发的一条经过点数最多,
且没有重复经过同一个点两次以上的简单路径。

Input

第一行包含一个正整数n(2<=n<=2000),表示点数。接下来n-1行,其中的第i行有i-1个数,如果第j个数是1,那么
表示有向边j->i+1,如果是0,那么表示有向边j<-i+1。

Output

输出n行,第i行首先包含一个正整数k,表示从i点出发的最优路径所经过的点数,接下来k个正整数,依次表示路
径上的每个点。若有多组最优解,输出任意一组。

Sample Input

4
1
1 1
1 0 1

Sample Output

4 1 2 3 4
3 2 3 4
3 3 4 2
3 4 2 3

HINT

Source

图论
首先竞赛图一定存在哈密顿路径
然后强连通的竞赛图一定存在哈密顿回路
并且竞赛图缩点之后会变成一条链(如果一个点有两条出边,那么那两个对应的点一定有边相连)
然后就是一个dp问题了,具体看代码的注释
对于哈密顿回路的找法,可以看这个博客的图
http://blog.csdn.net/lych_cys/article/details/54633296
另外图下面的第一句话写错了,应该改成如果存在边6->1
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 2005;

int n, dfn[MAXN], low[MAXN], scc[MAXN], size[MAXN], num, tim, st[MAXN], top;//tarjan记录的东西
int nxt[MAXN], h[MAXN], m;//nxt表示在当前的scc中的下一个点,h[1-m]是把当前scc中的点提取出来
int nxtscc[MAXN];//nxtscc表示下一个的scc的其中一个点
int dp[MAXN], nxtdp[MAXN];
bool a[MAXN][MAXN], vis[MAXN], can[MAXN], b[MAXN][MAXN];//a是map,b是scc中的map,vis表示是否在之前的环中出现过,can表示是否能到达之前的环上的点

inline void pushin(int x)
{
	vis[ x ] = 1;
	for( int i = 1 ; i <= m ; i++ ) if( a[ h[ i ] ][ x ] ) can[ h[ i ] ] = 1;
}

inline void getnxt()
{
	int head = h[ 1 ], tail = h[ 1 ];//表示路径的首尾
	for( int i = 2 ; i <= m ; i++ ) //找一条哈密顿路径
	{
		int x = h[ i ];
		if( a[ x ][ head ] ) { nxt[ x ] = head; head = x; continue; } //如果x->head,则它作为队首
		for( int now = head, last = 0 ; ; last = now, now = nxt[ now ] )
		{
			if( last == tail )//如果之前路径上没有x直接连边到达的点,则x作为路径的末尾
			{
				nxt[ tail ] = x; tail = x;
				break;
			}
			if( a[ x ][ now ] )//如果x到之前路径上存在last->x->now,把x插在中间
			{
				nxt[ x ] = now; nxt[ last ] = x;
				break;
			}
		}
	}
	int end = head; pushin( head );//end表示当前找到的环的末尾,end->head存在一条虚边
	while( end ^ tail )
	{
		int x = nxt[ end ];
		if( a[ x ][ head ] ) { end = x; pushin( x ); continue; } //如果x->head,则end->x,x作为end
		for( int now = head, last = 0 ; ; last = now, now = nxt[ now ] )
		{
			if( a[ x ][ now ] ) { nxt[ last ] = x; nxt[ end ] = head; end = x; head = now; pushin( x ); break; } //last->x->now,把x插入
			//注意插入的时候要把end给修改掉
			if( last == end )//如果环上的点到x的边都指向x,那么找到x后面的一个点使得它向之前环上的点连边,记为newend, 如果没有就不强连通了
			//newend指向的点记为y,则y->环上->pre[y]->x->链上->newend->(虚边)y构成新的环
			{
				int newend, y;
				for( int i = 1 ; i <= m ; i++ ) if( !vis[ h[ i ] ] && can[ h[ i ] ] ) { newend = h[ i ]; break; }
				for( int i = head ; ; i = nxt[ i ] ) if( a[ newend ][ i ] || i == end ) { y = i; break; }
				for( int i = nxt[ end ] ; ; i = nxt[ i ] ) { pushin( i ); if( i == newend ) break; }
				nxt[ end ] = head; head = y; end = newend;
				for( int now = head ; ; now = nxt[ now ] )
					if( nxt[ now ] == y ) 
					{
						nxt[ now ] = x;
						break;
					}
				break;
			}
		}
	}
	nxt[ tail ] = head;
	for( int i = 1 ; i <= m ; i++ ) vis[ h[ i ] ] = can[ h[ i ] ] = 0;
}

inline void dfs(int x)
{
	int tmp = 0;
	dfn[ x ] = low[ x ] = ++tim; 
	st[ ++top ] = x;
	for( int y = 1 ; y <= n ; y++ ) if( a[ x ][ y ] )
		if( !dfn[ y ] ) dfs( y ), low[ x ] = min( low[ x ], low[ y ] );
		else if( !scc[ y ] ) low[ x ] = min( low[ x ], dfn[ y ] );
	if( dfn[ x ] == low[ x ] )
	{
		nxtscc[ ++num ] = x;
		m = 0;
		while( tmp ^ x )
		{
			tmp = st[ top-- ];
			size[ scc[ tmp ] = num ]++;
			h[ ++m ] = tmp;
		}
		getnxt();
	}
}

inline int getdp(int x)
{
	if( dp[ x ] ) return dp[ x ];
	//printf( "%d\n", x );
	for( int i = 1 ; i <= num ; i++ ) if( b[ x ][ i ] && x != i )
	{
		int g = getdp( i );
		if( dp[ x ] < g ) { dp[ x ] = g; nxtdp[ x ] = i; }
	}
	dp[ x ] += size[ x ];
	//printf( "dp[%d]=%d\n", x, dp[ x ] );
	return dp[ x ];
}

inline void solve(int x)
{
	if( !x ) return ;
	printf(	" %d", x );
	for( int i = nxt[ x ] ; i ^ x ; i = nxt[ i ] ) printf( " %d", i );
	solve( nxtscc[ nxtdp[ scc[ x ] ] ] );
}

int main()
{
	scanf( "%d", &n );
	for( int j = 2 ; j <= n ; j++ )
		for( int i = 1 ; i < j ; i++ )
			scanf( "%d", &a[ i ][ j ] ), a[ j ][ i ] = a[ i ][ j ] ^ 1;
	for( int i = 1 ; i <= n ; i++ ) if( !dfn[ i ] ) dfs( i );
	//for( int i = 1 ; i <= n ; i++ ) printf( "%d ", nxt[ i ] );
	for( int i = 1 ; i <= n ; i++ )
		for( int j = 1 ; j <= n ; j++ )
			if( a[ i ][ j ] ) b[ scc[ i ] ][ scc[ j ] ] |= 1;
	//for( int i = 1 ; i <= num ; i++, putchar( 10 ) )
		//for( int j = 1 ; j <= num ; j++ )
			//printf( "%d ", b[ i ][ j ] );
	for( int i = 1 ; i <= num ; i++) getdp( i );
	//for( int i = 1 ; i <= n ; i++ ) printf( "scc[%d]=%d size=%d dp=%d\n", i, scc[ i ], size[ scc[ i ] ], dp[ scc[ i ] ] ); 
	for( int i = 1 ; i <= n ; i++ )
		printf( "%d", dp[ scc[ i ] ] ), solve( i ), putchar( 10 );
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值