给定一颗 n n 个点的有根树,和 n n 权值 d1...dn d 1 . . . d n 。将每个权值分配个每个节点,要求使父亲的权值小于等于儿子的权值。依次最大化 1,2,3...n 1 , 2 , 3... n 号点的权值,输出方案。
n≤600000 n ≤ 600000
从 1 1 号点开始枚举,由于我们要使一个点最大,所以我们每次都要求出当前符合要求的最大的权值。
di d i 的顺序与答案无关,所以我们可以先将 di d i 按从大到小的顺序排序
如何判断一个权值是符合要求的?
⇒ ⇒ 当且仅当有 sizei−1 s i z e i − 1 个大与等于它的权值还没有被选中
我们设 fi f i 代表有多少个满足 i≤j i ≤ j 的 dj d j 的权值还没有被选中,这样一个权值是符合要求的,当且仅当 sizei≤fi s i z e i ≤ f i 。
接下来我们只需要考虑如何维护 fi f i 就可以解决问题。
我们发现如果选择了一个点 di d i ,那么他子树中的点一定都会选择大于等于 di d i 的点。这时对于其他子树上的点,就相当于有 sizei−1 s i z e i − 1 个大于等于 di d i 的点和一个 di d i 被取走了,即
当然,我们在枚举它的子树时要把它的贡献加回来,不过注意不要把它自己也加回来了。
每次查找时,先查找符合条件的最大值,然后再找到当前值最右侧的部分进行修改。
#include
using namespace std;
const int N = 600000;
int n, d[N], a[N], ed[N]; double k;
struct segment_tree {
int _min[N<<2], opt[N<<2];
#define lson (o<<1)
#define rson (o<<1|1)
inline void pushup( int o ) { _min[o] = min( _min[lson], _min[rson] ); }
inline void pushdown( int o )
{
if( !opt[o] ) return;
_min[lson] += opt[o]; opt[lson] += opt[o];
_min[rson] += opt[o]; opt[rson] += opt[o];
opt[o] = 0;
}
inline void build( int o, int l, int r )
{
if( l == r ) {
_min[o] = l; return;
}
int mid = (l+r) >>1;
build( lson, l, mid );
build( rson,mid+1,r );
pushup( o );
}
inline void modify( int o, int l, int r, int ql, int qr, int val )
{
if( ql <= l && r <= qr ) {
_min[o] += val; opt[o] += val; return;
}
pushdown( o ); int mid = (l+r) >>1;
if( ql <= mid ) modify( lson, l, mid, ql, qr, val );
if( mid+1<=qr ) modify( rson,mid+1,r, ql, qr, val );
pushup( o );
}
inline int find( int o, int l, int r, int size )
{
if( l == r ) return size <= _min[o] ? l : l +1;
pushdown( o ); int mid = (l+r) >>1;
if( size <= _min[rson] ) return find( lson, l, mid, size );
return find( rson, mid+1, r, size );
}
#undef lson
#undef rson
} seg;
bool cmp( int a, int b ) { return a > b; }
int fa[N], size[N]; vector <int> son[N];
void calc_size( int u )
{
size[u] = 1;
for( int i = 0; i< son[u].size(); i ++ )
{
int v = son[u][i];
calc_size( v );
size[u] += size[v];
}
}
int main()
{
scanf( "%d%lf", &n, &k );
for( int i = 1; i <= n; i ++ )
scanf( "%d", &d[i] );
sort( d+1, d+1+n, cmp );
for( int i = 1; i <= n; i ++ )
{
fa[i] = i / k;
son[fa[i]].push_back( i );
}
for( int i = n; i >= 1; i -- )
ed[i] = ( d[i] == d[i+1] ) ? ed[i+1] +1 : 0;
calc_size( 0 );
seg.build( 1, 1, n );
for( int i = 1; i <= n; i ++ )
{
if( fa[i] && fa[i] != fa[i-1] )
seg.modify( 1, 1, n, a[fa[i]], n, size[fa[i]]-1 );
int pos = seg.find( 1, 1, n, size[i] );
pos += ed[pos]; // 找到当前权值最后的位置
ed[pos] ++; // 选择该权值的数量 +1
pos -= ed[pos] - 1; // 找到这次选择的位置
a[i] = pos; // 记录答案
seg.modify( 1, 1, n, pos, n, -size[i] );
}
for( int i = 1; i <= n; i ++ )
printf( "%d ", d[a[i]] ); printf( "\n" );
return 0;
}