[UOJ176]新年的繁荣
\[\newcommand\siz[1]{{{\Large|}#1{\Large|}}}
\]
壹、题目描述 ¶
贰、题解 ¶
称之为 \(\rm and\) 版 \(\rm MST\)?显然我们可以使用 \(\rm Kruskal\) 或者 \(\rm Prim\) 骗得价值不菲的分数,但是,同样是 \(\rm MST\) 算法,\(\rm Boruvka\) 在这方面,居然可以直接获得 \(100pts\)?不知道 \(\rm Boruvka\) 就自行 \(\text{OI Wiki}\) 了。
此处主要说明如何找一个点到其连通块之外的点的最大 \(\rm and\) 值了。
显然需要维护一棵 \(\rm trie\) 树,对于树上每个节点,维护加入到其子树中的点所属的连通块的最大值、最小值,如果我们想找一个属于 \(i\) 集合的点到其他集合点的 \(\rm and\) 最大值,我们走的时候只需要看看我们的目标点是否 \(\max_{id}=\min_{id}=i\),如果是,那么就走不成了。
但是还有一个问题 —— 如果 \(a_i\) 该位为 \(1\) 还好,我们只需要往 \(1\) 方向走,但是 \(a_i\) 该位为 \(0\) 时,往左往右怎么知道哪边更优?该处使用一个很强的操作,因为我们并不涉及类似计数问题的东西,即最后答案与树的形态无关,那么我们可以考虑将每个点的右子树复制一份合并到左子树上面去,这样往左子树走时就相当于左右同时走了。
时间复杂度 \(\mathcal O(nm\log n)\).
叁、参考代码 ¶
理论可过,但是会 \(\color{yellow}{\text{MLE}}\),这我就没办法了......
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<iostream>
#include<cstdlib>
using namespace std;
// #define NDEBUG
#include<cassert>
namespace Elaina{
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define mmset(a, b) memset(a, b, sizeof a)
// #define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
}
using namespace Elaina;
const int maxn=1e5;
const int inf=0x3f3f3f3f;
vector<int>a;
vector< vector<int> >compo;
int n, m;
inline void input(){
n=readin(1), m=readin(1);
a.resize(n);
for(int i=0; i<n; ++i)
a[i]=readin(1);
}
struct edge{
int u, v, w;
inline bool operator <(const edge rhs) const{
return w<rhs.w;
}
};
namespace ufs{
vector<int>fa;
int find(int u){ return fa[u]==u? u: fa[u]=find(fa[u]); }
inline bool merge(int u, int v){
u=find(u), v=find(v);
if(u==v) return false;
if(compo[u]<compo[v]) swap(u, v);
for(int x: compo[v]){
compo[u].push_back(x);
fa[x]=u;
}
compo[v].clear();
return true;
}
inline void build(){
fa.resize(n);
rep(i, 0, n-1) fa[i]=i;
}
}
namespace saya{
int trie[maxn*10+5][2], ncnt;
int mx[maxn*10+5], mn[maxn*10+5];
inline void clear(){ ncnt=0; }
inline int newnode(){
++ncnt;
mx[ncnt]=-1, mn[ncnt]=n;
trie[ncnt][0]=trie[ncnt][1]=0;
return ncnt;
}
inline void update(int i, int c){
getmax(mx[i], c), getmin(mn[i], c);
}
inline void insert(int x, int c){
int cur=1, to; update(1, c);
for(int j=m-1; j>=0; --j){
to=(x>>j)&1;
if(!trie[cur][to])
trie[cur][to]=newnode();
cur=trie[cur][to];
update(cur, c);
}
}
/** @warning must ensure the shape of the subtre @p v */
int combine(int u, int v){
if(!v) return u;
if(!u) u=newnode();
getmin(mn[u], mn[v]), getmax(mx[u], mx[v]);
trie[u][0]=combine(trie[u][0], trie[v][0]);
trie[u][1]=combine(trie[u][1], trie[v][1]);
return u;
}
void dfs(int u, int dep){
if(dep<0) return;
if(trie[u][0]) dfs(trie[u][0], dep-1);
if(trie[u][1]) dfs(trie[u][1], dep-1);
trie[u][0]=combine(trie[u][0], trie[u][1]);
}
inline void build(){
clear(); newnode(); // to clear the root which id is 1
for(int i=0; i<n; ++i)
insert(a[i], ufs::find(i));
dfs(1, m-1);
}
inline pii query(int x, int c){
int cur=1, to, ret=0;
for(int j=m-1; j>=0; --j){
to=(x>>j)&1;
if(to){
if(trie[cur][1] && (mn[trie[cur][1]]!=c || mx[trie[cur][1]]!=c))
ret^=(1<<j), cur=trie[cur][1];
else cur=trie[cur][0];
}
else cur=trie[cur][0];
}
return {c==mn[cur]? mx[cur]: mn[cur], ret};
}
}
signed main(){
input();
compo.resize(n);
rep(i, 0, n-1) compo[i]=vector<int>(1, i);
ufs::build();
int cnt=n; ll ans=0;
while(cnt>1){
saya::build();
vector<edge>Es; edge mx;
for(auto cur: compo) if(!cur.empty()){
mx={-1, -1, -1};
for(int x: cur){
pii ret=saya::query(a[x], ufs::find(x));
mx=max(mx, {x, ret.fi, ret.se});
}
Es.push_back(mx);
}
for(auto e: Es) if(ufs::merge(e.u, e.v))
--cnt, ans+=e.w;
}
writc(ans);
return 0;
}
肆、关键之处 ¶
此题将右子树合并到左子树上的基础条件是,所求与 \(\rm trie\) 树的形态正确与否无关。