CF1082F - Speed Dial

CF1082F - Speed Dial

题目大意

给定\(n\)个电话号码,你可以随意生成\(k\)个快捷键,每个快捷键是一个数字串

最终拨号方式:

选择 至多一个 快捷键按下,对于剩余部分手动补全,且不允许退格

每个电话号码有拨打次数,最小化手动补全部分的长度总和


分析

如果每次选定一个集合使其公用一个快捷键,那么其长度必然是集合中所有串的\(\text{LCP}\)

假设确定了一个\(\text{LCP}\),那么对应的串集合容易发现就是\(\text{trie}\)树上的一个子树

于是先将所有串加入\(\text{trie}\)树,此时问题转化为

选择至多\(k\)\(\text{LCP}\)(默认根节点选了且没有代价),使得每个电话号码到其祖先中最深\(\text{LCP}\)的距离之和最小

由于有拨打次数的限制,且其数值相对较大,难以存入dp状态

于是想到在祖先钦定\(\text{LCP}\),然后从子树取值

\(dp_{u,i,j}\)表示计算\(u\)子树内的答案,已经钦定祖先中最深的\(\text{LCP}\)长度为\(i\),且在子树内又钦定了\(j\)\(\text{LCP}\)

合并子树时对于每个\(i\),背包合并\(j\),决策\(u\)自己是否选为\(\text{LCP}\)即可

const int N=510,INF=1e9+10;

int n,m;
int trie[N][10],cnt,c[N];
char s[N];
int dp[N][N][12];
int F[N][12],G[12],dep[N];

void dfs(int u) {
	for(int v:trie[u]) if(v) dep[v]=dep[u]+1,dfs(v);
	memset(F,63,sizeof F);
	rep(i,0,dep[u]) F[i][0]=c[u]*(dep[u]-i);
	for(int v:trie[u]) if(v) {
		rep(j,0,dep[u]) {
			rep(k,0,m) G[k]=F[j][k],F[j][k]=INF;
			rep(k,0,m) rep(d,0,m-k) cmin(F[j][k+d],G[k]+dp[v][j][d]);
		}
	}
	rep(d,0,dep[u]) {
		rep(i,0,m) dp[u][d][i]=INF;
		rep(i,0,m) {
			cmin(dp[u][d][i+1],F[dep[u]][i]);
			cmin(dp[u][d][i],F[d][i]);
		}
	}
}

int main(){
	n=rd(),m=rd();
	rep(i,1,n) {
		scanf("%s",s+1);
		int u=0;
		for(int j=1;s[j];++j) {
			int &v=trie[u][s[j]-'0'];
			if(!v) v=++cnt;
			u=v;
		}
		c[u]+=rd();
	}
	dfs(0);
	int ans=INF;
	rep(i,0,m) cmin(ans,dp[0][0][i]);
	printf("%d\n",ans);
}
posted @ 2021-05-08 18:59  chasedeath  阅读(59)  评论(0编辑  收藏  举报