「BJOI 2019」奥术神杖

传送门


problem

神杖上从左到右镶嵌了 n n n 颗奥术宝石,奥术宝石一共有 10 10 10 种,用数字 “ 0123456789 ” “0123456789” 0123456789 表示。有些位置的宝石已经残缺,用 “ . ” “.” . 表示,你需要用完好的奥术宝石填补每一处残缺的部分(每种奥术宝石个数不限,且不能够更换未残缺的宝石)。古老的魔法书上记载了 m m m 种咒语 ( S i , V i ) (S_i,V_i) (Si,Vi),其中 S i S_i Si 是一个非空数字串, V i V_i Vi 是这种组合能够激发的神力。

神杖的初始神力值 Magic=1 \texttt{Magic=1} Magic=1,每当神杖中出现了连续一段宝石与 S i S_i Si 相等时,神力值 Magic \texttt{Magic} Magic 就会乘以 V i V_i Vi。但神杖如果包含了太多咒语就不再纯净导致神力降低:设 c c c 为神杖包含的咒语个数(若咒语类别相同但出现位置不同视为多次),神杖最终的神力值为 Magic c \sqrt[c]{\texttt{Magic}} cMagic 。(若 c = 0 c=0 c=0 则神杖最终神力值为 1 1 1。)

例如有两种咒语 ( 01 , 3 ) (01,3) (01,3) ( 10 , 4 ) (10,4) (10,4),那么神杖 “ 0101 ” “0101” 0101 的神力值为 3 × 4 × 3 3 \sqrt[3]{3×4×3} 33×4×3

你需要使修复好的神杖的最终的神力值最大,输出任何一个解即可。

数据范围: 1 ≤ m ≤ 1501 1≤m≤1501 1m1501 1 ≤ V i ≤ 1 0 9 1≤V_i≤10^9 1Vi109


solution

由于这是多个串的匹配问题AC 自动机是少不了的,先把它建出来再说。

现在分析题目,假设串中有 c c c 个咒语,每个咒语的权值分别为 w i w_i wi,那么魔法值就是 Magic = ∏ i = 1 c w i c \texttt{Magic}=\sqrt[c]{\prod_{i=1}^cw_i} Magic=ci=1cwi

我们两边取对数,把根号消掉,得到 ln ⁡ Magic = 1 c ∑ i = 1 c ln ⁡ w i \ln\texttt{Magic}=\frac 1 c\sum_{i=1}^c\ln w_i lnMagic=c1i=1clnwi

由于我们只用比较 Magic \texttt{Magic} Magic 的相对大小,并不需要知道 Magic \texttt{Magic} Magic 具体的值,可以在一开始就对 w i w_i wi 取对数,最大化 1 c ∑ i = 1 c ln ⁡ w i \frac 1 c\sum_{i=1}^c \ln w_i c1i=1clnwi

比较显然的是可以 0 / 1 0/1 0/1 分数规划,设当前二分到的答案是 λ \lambda λ,则:

1 c ∑ i = 1 c ln ⁡ w i > λ ∑ i = 1 c ( ln ⁡ w i − λ ) > 0 \begin{aligned}\frac 1 c\sum_{i=1}^c\ln w_i&>\lambda\\\sum_{i=1}^c(\ln w_i-\lambda)&>0\end{aligned} c1i=1clnwii=1c(lnwiλ)>λ>0

也就是说,我们把小串的权值改成 ln ⁡ w i − λ \ln w_i-\lambda lnwiλ,然后在自动机上 d p dp dp,看是否存在一种权值 > 0 >0 >0 的走法即可。

具体来说就是设 f [ i ] [ j ] f[i][j] f[i][j] 表示已经遍历了神杖上前 i i i 个字符,且当前在自动机的节点 j j j 的最大权值。那么只需枚举下一步怎么走然后转移即可(如果 i + 1 i+1 i+1 已经固定了那就只有 1 1 1 种走法)。

PS:代码实现稍微有点不一样,但是大体思路就是这样。


code

#include<bits/stdc++.h>
#define N 2005
#define eps 1e-6
using namespace std;
typedef pair<int,int> pii;
int n,m,tot,c[N][N],edge[N][N];
double f[N][N],v;
struct Trie{
	double val;
	int num,fail,son[26];
}T[N];
char S[N],a[N];
void Insert(){
	int p=0,l=strlen(a+1);
	for(int i=1;i<=l;++i){
		int x=a[i]-'0';
		if(!T[p].son[x])  T[p].son[x]=++tot;
		p=T[p].son[x];
	}
	++T[p].num,T[p].val=v;
}
queue<int>Q;
void Get_fail(){
	for(int i=0;i<10;++i)
		if(T[0].son[i])  Q.push(T[0].son[i]);
	while(!Q.empty()){
		int x=Q.front();Q.pop();
		T[x].num+=T[T[x].fail].num;
		T[x].val+=T[T[x].fail].val;
		for(int i=0;i<10;++i){
			if(T[x].son[i]){
				Q.push(T[x].son[i]);
				T[T[x].son[i]].fail=T[T[x].fail].son[i];
			}
			else  T[x].son[i]=T[T[x].fail].son[i];
		}
	}
}
bool check(double mid){
	memset(f,-0x3f,sizeof(f));
	f[0][0]=0;
	for(int i=1;i<=n;++i){
		for(int j=0;j<=tot;++j){
			if(S[i]=='.'){
				for(int k=0;k<10;++k){
					int to=T[j].son[k];
					double tmp=f[i-1][j]+T[to].val-mid*T[to].num;
					if(tmp>f[i][to])  f[i][to]=tmp,c[i][to]=k,edge[i][to]=j;
				}
			}
			else{
				int to=T[j].son[S[i]-'0'];
				double tmp=f[i-1][j]+T[to].val-mid*T[to].num;
				if(tmp>f[i][to])  f[i][to]=tmp,edge[i][to]=j;
			}
		}
	}
	for(int i=0;i<=tot;++i)  if(f[n][i]>eps)  return true;
	return false;
}
void Get(int x,int to){
	if(!x)  return;
	if(S[x]=='.')  S[x]=c[x][to]+'0';
	Get(x-1,edge[x][to]);
}
int main(){
	scanf("%d%d%s",&n,&m,S+1);
	for(int i=1;i<=m;++i){
		scanf("%s%lf",a+1,&v);
		v=log(v),Insert();
	}
	Get_fail();
	double l=0,r=100;
	while(r-l>eps){
		double mid=(l+r)/2;
		if(check(mid))  l=mid;
		else  r=mid;
	}
	check(l);
	for(int i=0;i<=tot;++i)  if(f[n][i]>eps)  {Get(n,i);break;}
	printf("%s\n",S+1);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
校园失物招领系统管理系统按照操作主体分为管理员和用户。管理员的功能包括字典管理、论坛管理、公告信息管理、失物招领管理、失物认领管理、寻物启示管理、寻物认领管理、用户管理、管理员管理。用户的功能等。该系统采用了Mysql数据库,Java语言,Spring Boot框架等技术进行编程实现。 校园失物招领系统管理系统可以提高校园失物招领系统信息管理问题的解决效率,优化校园失物招领系统信息处理流程,保证校园失物招领系统信息数据的安全,它是一个非常可靠,非常安全的应用程序。 ,管理员权限操作的功能包括管理公告,管理校园失物招领系统信息,包括失物招领管理,培训管理,寻物启事管理,薪资管理等,可以管理公告。 失物招领管理界面,管理员在失物招领管理界面中可以对界面中显示,可以对失物招领信息的失物招领状态进行查看,可以添加新的失物招领信息等。寻物启事管理界面,管理员在寻物启事管理界面中查看寻物启事种类信息,寻物启事描述信息,新增寻物启事信息等。公告管理界面,管理员在公告管理界面中新增公告,可以删除公告。公告类型管理界面,管理员在公告类型管理界面查看公告的工作状态,可以对公告的数据进行导出,可以添加新公告的信息,可以编辑公告信息,删除公告信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值