crashed
2019-11-23 17:32:02
点这里看题目。
这道题真的不简单,细节巨多,恶心死你。但是思路并没有太难。
拿到平均分了!喜闻乐见
写一个暴力全排把它骗过去就可以了。
两个部分分摆在眼前,先做哪一个呢?
经验也许告诉你,链的情况比较好做。但是实际上,我强烈推荐写菊花图。
为什么?没有为什么因为菊花图需要挖掘的性质并不深。
不妨设菊花图的花心为
于是很容易地想到一个贪心的构造环的方法。按照
在环还没有封闭的时候,请注意它们都还是一些链,所以不要出现两个点的后一个点相同的情况。
保证最后会出现一个大环而不是若干个小环,这个可以用并查集维护。
然后恭喜你,你有 35pts 了。比平均分高!喜闻乐见
下面我们将要攻克单链。
(敲黑板)这个时候就需要挖掘题目的一个非常重要的性质了。假如我们想要把
对于
对于
对于
考虑链的情况。由于链上的的点的度最多为 2,所以我们可以直接用一个变量表示两条边的先后关系——左先右后、左后右先或者还未确定。
同样是从小到大枚举数。假如当前枚举到了
最暴力的方法,假如当前
另外,注意链的两个端点需要特殊处理,因为它们的度都是 1,所以就不存在先后关系的说法了。
这样做是
然后发现这个扩展的方式存在单调性——例如在往左边扩展的时候,我们不能经过
然后就可以由近及远地枚举点,走不动了就退出,中间选取那些可行的终止点并取出最小的,时间就是
终于,我们要直面正解了!
根据我们从链的情况中得到的结论,我们会发现,对于删边的顺序,不同的点之间是独立的。也就是说对于边
并且,不会存在这种情况:
上面的结论说明,一个点的邻接边抽象成点后,用有向边表示删除先后关系的话,形成的总是一堆链。
当然,根据上面的性质,我们可以知道,在一个点的邻接边中,有一条会强制被第一个删除,有一条会强制被最后一条删除。它们必须是链的起点(第一个)或终点(最后一个)。
于是,假如我们当前枚举一个数
不能有边比当前的第一条边先删除;不能有边比当前的最后一条边后删除
一条边不能在存在另一条边紧跟着它被删除的情况下,再出现一条边,也要紧跟着它删除。
如果当前的操作会导致当前的第一条边和最后一条边合并在一条链里面,并且除开它们所在的链以外还有一些链没有合并起来,那么这就是不合法的(参考菊花图,这样会导致边删不完)
用 DFS 扩展的时候判一下这些条件。用并查集和一个表示当前有没有入边/出边的数组来维护这些条件。找到了目标点之后就再用一个 DFS 来更新条件。
时间是
这道题其实赛场满分只有 10 分,那么你其实已经完成了 10 道题了。
#include <cstdio>
const int INF = 0x3f3f3f3f;
const int MAXN = 2005;
template<typename _T>
void read( _T &x )
{
x = 0; char s = getchar();int f = 1;
while( s < '0' || '9' < s ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + s - '0', s = getchar(); }
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ) { putchar( '-' ), x = -x; }
if( 9 < x ) { write( x / 10 ); }
putchar( x % 10 + '0' );
}
template<typename _T>
_T MIN( const _T a, const _T b )
{
return a < b ? a : b;
}
struct edge
{
int to, nxt;
}Graph[MAXN << 1];
struct UFS
{
int fa[MAXN];
bool beg[MAXN], fin[MAXN];
//记录有没有出边或者入边,true 代表没有
void operator () ( const int siz ) { for( int i = 1 ; i <= siz ; i ++ ) fa[i] = i, beg[i] = fin[i] = true; }
int& operator [] ( const int u ) { return fa[u] = ( fa[u] == u ? fa[u] : ( *this )[fa[u]] ); }
bool operator () ( const int a, const int b ) { return ( *this )[a] == ( *this )[b]; }
//并查集维护所属链
}info[MAXN];
int stt[MAXN], fir[MAXN], las[MAXN], deg[MAXN];
int head[MAXN];
int N, cnt;
void addEdge( const int from, const int to )
{
cnt ++;
Graph[cnt].to = to, Graph[cnt].nxt = head[from];
head[from] = cnt, deg[from] ++;
}
void clear()
{
cnt = 1;
for( int i = 1 ; i <= N ; i ++ )
head[i] = fir[i] = las[i] = 0, info[i]( N - 1 ), deg[i] = 0;
}
int expand( const int u, const int faE )
{
int res = INF;
if( faE && ( ! las[u] || las[u] == faE ) )
if( info[u].fin[faE] && ! ( fir[u] && deg[u] > 1 && info[u][faE] == info[u][fir[u]] ) )
res = u;
//判断是否可以作为终点
int id, v;
for( int i = head[u] ; i ; i = Graph[i].nxt )
{
id = i >> 1, v = Graph[i].to;
if( id == faE ) continue;
if( ! faE )
{
if( ! fir[u] || fir[u] == id )
{
if( ! info[u].beg[id] ) continue;
if( las[u] && deg[u] > 1 && info[u]( id, las[u] ) ) continue;
res = MIN( res, expand( v, id ) );
}
//起点的移动
}
else
{
if( faE == las[u] || id == fir[u] || info[u]( faE, id ) ) continue;
if( ! info[u].beg[id] || ! info[u].fin[faE] ) continue;
if( fir[u] && las[u] && deg[u] > 2 && info[u]( fir[u], faE ) && info[u]( id, las[u] ) ) continue;
res = MIN( res, expand( v, id ) );
//中转点的移动
}
}
return res;
}
bool cover( const int u, const int faE, const int tar )
{
if( u == tar ) { las[u] = faE; return true; }
int v, id;
for( int i = head[u] ; i ; i = Graph[i].nxt )
{
v = Graph[i].to, id = i >> 1;
if( id ^ faE && cover( v, id, tar ) )
{
if( ! faE ) fir[u] = id;
else
{
info[u].fin[faE] = false, info[u].beg[id] = false;
info[u][id] = info[u][faE];
deg[u] --;
}
return true;
}
}
return false;
}
//更新信息
int main()
{
int T;
read( T );
while( T -- )
{
int fr, to;
read( N );
clear();
for( int i = 1 ; i <= N ; i ++ ) read( stt[i] );
for( int i = 1 ; i < N ; i ++ )
read( fr ), read( to ), addEdge( fr, to ), addEdge( to, fr );
if( N == 1 )
{
puts( "1" );
continue;
}
for( int i = 1 ; i <= N ; i ++ )
{
int mn = expand( stt[i], 0 );
cover( stt[i], 0, mn );
write( mn ), putchar( i == N ? '\n' : ' ' );
}
}
return 0;
}```