JZOJ 6997. 2021.03.06【2021省赛模拟】排列
题目大意
p
p
p 为一个
1
1
1 到
n
n
n 的排列,令
F
(
p
)
=
∑
i
=
1
n
min
j
=
1
i
a
p
i
⨁
b
p
j
F(p)=sum_{i=1}^n min_{j=1}^i a_{p_i}igoplus b_{p_j}
F ( p ) = ∑ i = 1 n min j = 1 i a p i ⨁ b p j ,求使
F
(
p
)
F(p)
F ( p ) 最小且字典序最小的
p
p
p 。
n
≤
50
nle50
n ≤ 5 0
题解
若选择
b
y
b_y
b y 和
a
x
a_x
a x 构成一组贡献,则相当于从
y
y
y 往
x
x
x 连了一条边,即要求
y
y
y 在排列中需要在
x
x
x 的前面,把所有的
(
x
,
y
)
(x,y)
( x , y ) 对应的边都连出来,边权为
b
y
⨁
a
x
b_yigoplus a_x
b y ⨁ a x ,则题目可以转化为在这些边中选出一些边构成树形结构,其中边的方向都由父亲指向儿子,并最小化边权和。 基本上就是求最小树形图,但同时要考虑排列字典序最小。可以依次枚举每个位置填什么数,并固定下来,用剩下的点求最小树形图。每次选择权值和最小的且最小的数填入当前位置。 在实现上有细节需要注意:最小树形图中,可能会出现自己连向自己的情况,并且也是合法的;用剩下的点求最小树形图时,根节点不仅是当前固定下来的点,而是前面已经固定好的所有点合并作为根。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 110
int a[ N] , b[ N] , dis[ N] [ N] , ar[ N] , tot, n;
int ch[ N] , st[ N] , fa[ N] , vi[ N] , e[ N] [ N] , sn[ N] [ N] ;
int solve ( int x) {
int s = dis[ fa[ x] ] [ x] ;
int ci[ N] ;
ci[ 0 ] = 0 ;
for ( int i = 1 ; i <= tot; i++ ) if ( sn[ x] [ i] ) ci[ sn[ x] [ i] ] = i, ci[ 0 ] ++ ;
for ( int i = 1 ; i <= ci[ 0 ] ; i++ ) fa[ ci[ i] ] = ci[ i % ci[ 0 ] + 1 ] , s + = solve ( ci[ i] ) ;
return s;
}
int count ( int v0) {
int i, j, k, ok = 0 , l = - 1 ;
memset ( sn, 0 , sizeof ( sn) ) ;
while ( ! ok) {
l++ ;
memset ( fa, 0 , sizeof ( fa) ) ;
memset ( vi, 0 , sizeof ( vi) ) ;
for ( i = 1 ; i <= e[ l] [ 0 ] ; i++ ) if ( e[ l] [ i] != v0) {
int mi = 1e9 , p, x = e[ l] [ i] ;
for ( j = 1 ; j <= e[ l] [ 0 ] ; j++ ) if ( e[ l] [ j] != x && dis[ e[ l] [ j] ] [ x] < mi) mi = dis[ e[ l] [ j] ] [ x] , p = e[ l] [ j] ;
fa[ x] = p;
int mii = ( x <= n ? dis[ x] [ x] : 1e9 ) ; p = x;
for ( j = 1 ; j <= ar[ 0 ] ; j++ ) if ( dis[ ar[ j] ] [ x] < mii) mii = dis[ ar[ j] ] [ x] , p = ar[ j] ;
if ( mii <= mi) {
vi[ x] = 1 ;
fa[ x] = p;
}
}
vi[ v0] = 1 ; int id = 1 ;
ok = 1 ;
for ( i = 1 ; i <= e[ l] [ 0 ] ; i++ ) if ( vi[ e[ l] [ i] ] ) e[ l + 1 ] [ ++ e[ l + 1 ] [ 0 ] ] = e[ l] [ i] ;
for ( i = 1 ; i <= e[ l] [ 0 ] ; i++ ) if ( ! vi[ e[ l] [ i] ] ) {
id++ ;
st[ 0 ] = 0 ;
int x = e[ l] [ i] ;
while ( ! vi[ x] ) {
vi[ x] = id;
st[ ++ st[ 0 ] ] = x;
x = fa[ x] ;
}
if ( vi[ x] == id) {
ok = 0 ;
tot++ ;
for ( j = 1 ; j <= tot; j++ ) dis[ tot] [ j] = dis[ j] [ tot] = 1e9 ;
for ( j = 1 ; st[ j] != x && j <= st[ 0 ] ; j++ ) ;
int j0 = j;
for ( j; j <= st[ 0 ] ; j++ ) {
int x = st[ j] ;
sn[ tot] [ x] = j - j0 + 1 ;
for ( k = 1 ; k < tot; k++ ) {
dis[ tot] [ k] = min ( dis[ tot] [ k] , dis[ x] [ k] ) ;
dis[ k] [ tot] = min ( dis[ k] [ tot] , dis[ k] [ x] - dis[ fa[ x] ] [ x] ) ;
}
}
e[ l + 1 ] [ ++ e[ l + 1 ] [ 0 ] ] = tot;
for ( j = 1 ; st[ j] != x && j <= st[ 0 ] ; j++ ) {
e[ l + 1 ] [ ++ e[ l + 1 ] [ 0 ] ] = st[ j] ;
}
}
else {
for ( j = 1 ; j <= st[ 0 ] ; j++ ) e[ l + 1 ] [ ++ e[ l + 1 ] [ 0 ] ] = st[ j] ;
}
}
}
int s = 0 ;
for ( i = 1 ; i <= e[ l] [ 0 ] ; i++ ) {
int x = e[ l] [ i] ;
s + = solve ( x) ;
}
return s;
}
int main ( ) {
int i, j, k;
scanf ( "%d" , & n) ;
for ( i = 1 ; i <= n; i++ ) scanf ( "%d" , & a[ i] ) ;
for ( i = 1 ; i <= n; i++ ) scanf ( "%d" , & b[ i] ) ;
for ( i = 1 ; i <= n; i++ )
for ( j = 1 ; j <= n; j++ ) dis[ i] [ j] = b[ i] ^ a[ j] ;
for ( i = 1 ; i <= n; i++ ) {
int mi = 1e9 , x;
ar[ 0 ] ++ ;
for ( j = 1 ; j <= n; j++ ) if ( ! ch[ j] ) {
memset ( e, 0 , sizeof ( e) ) ;
for ( k = 1 ; k <= n; k++ ) if ( ! ch[ k] ) e[ 0 ] [ ++ e[ 0 ] [ 0 ] ] = k;
tot = n;
ar[ i] = j;
int t = count ( j) ;
int mii = 1e9 ;
for ( k = 1 ; k <= i; k++ ) mii = min ( mii, dis[ ar[ k] ] [ j] ) ;
if ( t + mii < mi) mi = t + mii, x = j;
}
ch[ x] = 1 ;
ar[ i] = x;
if ( i == 1 ) printf ( "%d
" , mi) ;
}
for ( i = 1 ; i <= n; i++ ) printf ( "%d " , ar[ i] ) ;
return 0 ;
}
自我小结
这题看似是最小树形图的模板题,其实却有很多与之不同且极其需要注意的地方,同时是第一次写最小树形图,思路和细节上都出现了许多问题。