bzoj4567 [Scoi2016]背单词

22 篇文章 0 订阅

这题的题意真是难于理解

意思就是挨个放单词,放每个单词之前一定把这个单词的后缀都先放上去,每个单词的代价等于这个单词的位置减去上一个出现的这个单词的后缀的位置

第一个条件是没用的,因为如果触发这个条件一定不是最优的

然后嘛,我们把串反一下,建一颗trie,把除了根意外不是作为串的结尾的没用的点去掉,这样就变成了一颗树,问题转化为给树上每个点标号,每个点的标号大于其父亲的编号,每个点代价为他的标号减去其父亲标号,最小化代价

贪心,dfs并先走siz小的儿子进行标号即可,证明如下

感性证明:手玩一下觉得是dfs序,dfs序的话按siz贪心显然

接下来理性证明

首先我们证明二叉菊花情况

根节点代价为0,对于非根节点x,代价为1+选fa[x]到选x之间选的点的数量,对所有点的代价作sigma,变为n-1+sigma选fa[x]到选x之间选的点的数量

n-1常量去掉,因为是二叉菊花,所以等价为选第一个叉的叶子之前选的第二个叉上点的数量+选第二个叉的叶子之前选的第一个叉上点的数量因为其中一个一定是最后一个选的,所以一定是先把小的那一叉选完为最优,这样是一个

二叉菊花证完后x叉菊花可视为x-1叉菊花接一个叉,x-1叉叉内的相对顺序已知,作类似证明可知一定先把x-1叉和新叉里小的那一个走完

把菊花上边接一条链,相当于菊花,然后再把这样的东西拼一起,作类似证明,就推广到了树

证毕

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
using namespace std;
#define MAXN 100010
#define MAXM 510010
#define ll long long
#define INF 1000000000
#define MOD 1000000007
#define eps 1e-8
int son[MAXM][26];
char s[MAXM];
bool v[MAXM];
int tms[MAXM];
int tim;
int rt,tot;
int n;
ll ans;
vector<int>mp[MAXN];
int siz[MAXN];
bool cmp(int x,int y){
	return siz[x]<siz[y];
}
void ins(){
	int l=strlen(s+1);
	int p=rt;
	int i;
	for(i=l;i;i--){
		if(!son[p][s[i]-'a']){
			son[p][s[i]-'a']=++tot;
		}
		p=son[p][s[i]-'a'];
	}
	v[p]=1;
}
void dfs(int x){
	int i;
	if(v[x]){
		mp[tms[x]].push_back(++tim);
		tms[x]=tim;
	}
	for(i=0;i<26;i++){
		if(son[x][i]){
			tms[son[x][i]]=tms[x];
			dfs(son[x][i]);
		}
	}
}
void dp(int x){
	int i,y;
	siz[x]=1;
	for(i=0;i<mp[x].size();i++){
		y=mp[x][i];
		dp(y);
		siz[x]+=siz[y];
	}
}
void cal(int x){
	int i;
	tms[x]=++tim;
	sort(mp[x].begin(),mp[x].end(),cmp);
	for(i=0;i<mp[x].size();i++){
		ans+=tim+1-tms[x];
		cal(mp[x][i]);
	}
}
int main(){
	int i;
	rt=tot=1;
	scanf("%d",&n);
	for(i=1;i<=n;i++){
		scanf("%s",s+1);
		ins();
	}
	tms[rt]=tim=1;
	dfs(rt);
	dp(1);
	tim=0;
	cal(1);
	printf("%lld\n",ans);
	return 0;
}

/*
2
a
ba
*/



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值