[贪心] [trie] P10283 Identity Theft P
posted on 2024-04-02 05:46:31 | under | source
看到 \(01\) 串无脑建 \(\rm trie\),尝试放在树上讨论下。
如果在每个串结尾的对应位置上放个点,那么问题等价于:一次操作可以让一个点向下走一步,求使得每个点的子树内不存在其它点的最小操作数。
(后半句又等价于让每个点呆在不同叶子上。)
显然有个贪心:从底向上调整每个点,并把用花费最小的方案将其移到目标位置。
于是维护 \(f_u\) 表示当前在 \(u\) 上放一个点,需要调整多少次。
边界条件:如果 \(u\) 是叶子节点,那么 \(f_u=2\);如果 \(u\) 上没有点且恰好有一个儿子为空,则 \(f_u=1\);
对于一般情况:\(f_u=\min(f_{lson},f_{rson})+1\)。
然后每次将 \(f_u\) 算入答案,并更新 \(f\) 的值。更新的话沿着转移路径,找到目标位置,然后向上更新即可。
最后,它为什么是对的?注意到一个点的次优方案始终劣于最优方案,因为向上走一步会使代价都 \(+1\)。换句话说,如果现在不选最优方案也不会产生更好的贡献,那不如每次选最优。
代码
#include<bits/stdc++.h>
using namespace std;
#define lt t[u][0]
#define rt t[u][1]
const int N = 2e6 + 5, inf = 1e9;
int n, len, tot = 1, res, t[N][2], f[N], ed[N];
char ch[N];
inline void upd(int u){
f[u] = min(f[lt], f[rt]) + 1;
if((!lt && rt) || (!rt && lt)) f[u] = min(f[u], 1);
if(!lt && !rt) f[u] = min(f[u], 2);
}
inline void ins(int u){
if(!lt && !rt) {lt = ++tot, rt = ++tot, f[lt] = f[rt] = 2, upd(u); return ;}
if(!lt) {lt = ++tot, f[lt] = 2, upd(u); return ;}
if(!rt) {rt = ++tot, f[rt] = 2, upd(u); return ;}
if(f[lt] <= f[rt]) ins(lt);
else ins(rt);
upd(u);
}
inline void dfs(int u){
if(lt) dfs(lt); if(rt) dfs(rt);
if(ed[u] && !lt && !rt) --ed[u], f[u] = 2;
else upd(u);
while(ed[u]--) res += f[u], ins(u);
}
int main(){
f[0] = inf;
cin >> n;
for(int i = 1; i <= n; ++i){
scanf("%s", ch + 1), len = strlen(ch + 1);
int u = 1;
for(int j = 1; j <= len; ++j){
if(!t[u][ch[j] - '0']) t[u][ch[j] - '0'] = ++tot;
u = t[u][ch[j] - '0'];
}
++ed[u];
}
dfs(1);
cout << res;
return 0;
}

浙公网安备 33010602011771号