BZOJ 4567: [Scoi2016]背单词

该博客探讨了如何解决BZOJ 4567题目中的背单词问题。通过构建一棵特殊树,其中节点表示单词并根据后缀连接,然后对树进行DFS序标号,以最小化节点标号之差的总和。博主指出,按子树大小对兄弟节点排序后再标号是关键策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

显然第一种情况可以避免

将每个串都看成树上的一个节点,父亲为它的后缀串中最长的那个

这棵树可以通过每个串reverse后加入Trie树中,最后去掉Trie树的虚节点来获得

于是问题变成了给树上每个点标号,使得每个点的标号减去它父亲的标号的和最小

显然要按DFS序标号

考虑相邻的兄弟节点u,v

先u再v比先v再u的答案大siz[u]-siz[v]

所以子节点按子树大小排序后依次标号即可

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<algorithm>
#include<map>
#include<set>
#include<stack>
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define per(i,r,l) for(int i=r;i>=l;i--)
#define mmt(a,v) memset(a,v,sizeof(a))
#define tra(i,u) for(int i=head[u];i;i=e[i].next)
using namespace std;
typedef long long ll;
const int N=510000+5;
int ch[N][26],val[N],sz;
int siz[N];
vector<int>son[N];
void insert(char *s,int k){
	int u=0,n=strlen(s+1);
	reverse(s+1,s+1+n);
	rep(i,1,n){
		int c=s[i]-'a';
		if(!ch[u][c])ch[u][c]=++sz;
		u=ch[u][c];
	}
	val[u]=k;
}
void dfs(int u,int fa){
	if(val[u])son[fa].push_back(val[u]),fa=val[u];
	rep(i,0,25)if(ch[u][i])dfs(ch[u][i],fa);
}
bool cmp(int i,int j){return siz[i]>siz[j];}
int dfs(int u){
	siz[u]=1;
	per(i,(int)son[u].size()-1,0)
	siz[u]+=dfs(son[u][i]);
	sort(son[u].begin(),son[u].end(),cmp);
	return siz[u];
}
int id;
ll solve(int u,int fa){
	ll ans=0;
	ans+=id-fa;fa=id++;
	per(i,(int)son[u].size()-1,0)
	ans+=solve(son[u][i],fa);
	return ans;
}
char s[N];
int main(){
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	int n;scanf("%d",&n);
	rep(i,1,n)scanf("%s",s+1),insert(s,i);
	dfs(0,0);
	dfs(0);
	printf("%lld\n",solve(0,0));
	return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值