[BJOI 2019] 奥术神杖

( ext{Description})

传送门

( ext{Solution})

发现有 (ans=sqrt[c]{prod_{i=1}^c v_i})。不过显然这个东西直接算出来会爆。

有一个操作就是同时取对数。就有:

[ln ans=lnleft(sqrt[c]{prod_{i=1}^c v_i} ight ) ]

[ln ans=frac{1}{c} lnleft(prod_{i=1}^c v_i ight ) ]

[ln ans=frac{1}{c} sum_{i=1}^c ln v_i ]

因为 (e>1)(ln(x)) 是单增的,所以就是求 (frac{1}{c} sum_{i=1}^c ln v_i) 的最大值。观察柿子,有一个未知的求和柿子和与之相配的系数。想到了什么?(0/1) 分数规划!

所以二分答案 (mid),若有:

[mid<frac{1}{c} sum_{i=1}^c ln v_i ]

[ccdot mid<sum_{i=1}^c ln v_i ]

[sum_{i=1}^c left(ln v_i-mid ight)>0 ]

我们可以对于每个 (mid),求出 (sum_{i=1}^c left(ln v_i-mid ight)) 的最大值(这里的所有 (v) 显然是自己选定)。如果满足最大值 (>0),说明 (mid) 还可以取到更大(带回原来的柿子看)。

对于求出 (sum_{i=1}^c left(ln v_i-mid ight)) 的最大值,显然就是将单个咒语权值减 (mid),用 ( ext{AC}) 自动姬统计某个后缀出现的权值(这个后缀可能包含很多咒语),然后大力 ( ext{DP})

(f[i][j]) 为到神杖的第 (i) 位,在 ( ext{AC}) 自动姬上的节点 (j) 的最大值。(如果不是很懂为什么要取节点 (0),可以看看我的这篇博客

就有:

[f_{i+1,son_{j,k}}=max{f_{i,j}+val_{son_{j,k}},f_{i+1,son_{j,k}}} ]

(son_{j,k}) 就是本轮添加字符 (k) 对应的节点。

( ext{Code})

#include <cstdio>

#define rep(i,_l,_r) for(register signed i=(_l),_end=(_r);i<=_end;++i)
#define fep(i,_l,_r) for(register signed i=(_l),_end=(_r);i>=_end;--i)
#define erep(i,u) for(signed i=head[u],v=to[i];i;i=nxt[i],v=to[i])
#define efep(i,u) for(signed i=Head[u],v=to[i];i;i=nxt[i],v=to[i])
#define print(x,y) write(x),putchar(y)

template <class T> inline T read(const T sample) {
    T x=0; int f=1; char s;
    while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
    while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
    return x*f;
}
template <class T> inline void write(const T x) {
    if(x<0) return (void) (putchar('-'),write(-x));
    if(x>9) write(x/10);
    putchar(x%10^48);
}
template <class T> inline T Max(const T x,const T y) {if(x>y) return x; return y;}
template <class T> inline T Min(const T x,const T y) {if(x<y) return x; return y;}
template <class T> inline T fab(const T x) {return x>0?x:-x;}
template <class T> inline T gcd(const T x,const T y) {return y?gcd(y,x%y):x;}
template <class T> inline T lcm(const T x,const T y) {return x/gcd(x,y)*y;}
template <class T> inline T Swap(T &x,T &y) {x^=y^=x^=y;}

#include <cmath>
#include <queue>
#include <cstring>
using namespace std;

const int maxn=1505;
const double eps=1e-6,inf=1e11;

int n,m,Value,t[maxn][12],tot,num[maxn],f[maxn],memo[maxn][maxn][2];
char s[maxn],ori[maxn],ans[maxn];
double val[maxn],dp[maxn][maxn];
queue <int> q;

void Insert() {
	int p=0,len=strlen(s+1);
	rep(i,1,len) {
		int d=s[i]-'0';
		if(!t[p][d]) t[p][d]=++tot;
		p=t[p][d]; 
	}
	++num[p],val[p]+=log2(Value);
}

void GetFail() {
	rep(i,0,9) if(t[0][i]) q.push(t[0][i]);
	while(!q.empty()) {
		int u=q.front(); q.pop();
		val[u]+=val[f[u]],num[u]+=num[f[u]];
		rep(i,0,9)
			if(t[u][i]) f[t[u][i]]=t[f[u]][i],q.push(t[u][i]);
			else t[u][i]=t[f[u]][i];
	}
}

bool ok(double x) {
	rep(i,0,tot) val[i]-=x*num[i];
	rep(i,0,n) rep(j,0,tot) dp[i][j]=-inf;
	dp[0][0]=0;
	rep(i,0,n-1) rep(j,0,tot) rep(k,0,9)
		if(ori[i+1]=='.'||ori[i+1]==k+'0') {
			if(dp[i+1][t[j][k]]<dp[i][j]+val[t[j][k]]) {
				dp[i+1][t[j][k]]=dp[i][j]+val[t[j][k]];
				memo[i+1][t[j][k]][0]=j,memo[i+1][t[j][k]][1]=k;
			}
		}
	rep(i,0,tot) val[i]+=x*num[i];
	int pos=0;
	rep(i,1,tot) if(dp[n][i]>dp[n][pos]) pos=i; // 找匹配 n 位后值最大
	if(dp[n][pos]>0) {
		for(int i=n,now=pos;i>=1;now=memo[i][now][0],--i) ans[i]=memo[i][now][1]+'0';
		return 1;
	}
	return 0;
} 

int main() {
	n=read(9),m=read(9),scanf("%s",ori+1);
	rep(i,1,m) scanf("%s",s+1),Value=read(9),Insert();
	GetFail();
	double l=0,r=log2(1e9+7),mid; // 因为 V_i<=1e9,所以其集合平均数也在这个范围
	while(r-l>eps) {
		mid=(l+r)/2;
		if(ok(mid)) l=mid;
		else r=mid;
	}
	ok(l); puts(ans+1);
	return 0;
}
原文地址:https://www.cnblogs.com/AWhiteWall/p/14182001.html