Qoj 9111 Zayin ans String / ABC 356 E

谨以此帖记录一个有意思的 Trick

题意

给了一个长度为 n 的目标串 sm 个模式串

每个模式串有一个价值 v

要求从 s 中选出一个子序列 t, 定义 t 的价值为他的所有子串的价值和(若该子串没出现在模式串中, 那么他的价值为 0)

最大化 t 的价值和长度的比值

思路

看到多模匹配问题, 想到 AC 自动机

考虑把所有模式串加到 AC 自动机里面, 然后在自动机上 dp

自然地想到 dp(i,j,k) 表示现在在 s 的第 i 位, 选了 j 位, 这一位在自动机的 k 节点上

转移就枚举选的下一位是谁

时间复杂度 O(n4), 不可接受

现在该 Trick 救场了

难点在于我们无法直接比较一个选了 i 位, 一个选了 j 位的两个子序列的价值, 所以必须记录他们的长度

现在介绍一种方法

二分答案为 lim

lim=0ij<|T|val(i,j)|T|

把长度移项

|T|lim=0ij<|T|val(i,j)

这样, 等式右侧的是 dp 值, 左侧的 |T| 开一个数组来记录

每次转移的时候 dp=lim

因为每转移一次, 位数增加一, 所以只需看最后的 dp 是否为非负数即可判断该答案是否合法

该 Trick 的关键在于实现 在不记录长度的情况下, 比较两个子序列的价值大小

代码

#include <bits/stdc++.h>
#define int long long
#define double long double
#pragma GCC optimize(2)
using namespace std;
const int N = 1e5 + 10, MOD = 998244353, INF = 1e9;
const double eps = 1e-17;
int Q_pow(int a, int b){
int ans = 1, p = a;
while(b){
if(b & 1) ans = (ans * p) % MOD;
b >>= 1;
p = (p * p) % MOD;
}
return ans;
}
int t;
int n, m, val;
string s, ss;
struct Node{
int son[26];
int fail, ed;
}tr[N];
int cnt, sum[N], v[N][26];
queue <int> q;
void Insert(string s, int v){
int p = 0;
for(auto i : s){
int ch = i - 'a';
if(!tr[p].son[ch]) tr[p].son[ch] = ++cnt;
p = tr[p].son[ch];
}
tr[p].ed += v;
}
void Get_fail(){
for(int i = 0; i < 26; i++){
if(tr[0].son[i]){
q.push(tr[0].son[i]);
}
}
while(!q.empty()){
int tp = q.front();
q.pop();
sum[tp] = sum[tr[tp].fail] + tr[tp].ed;
for(int i = 0; i < 26; i++){
if(tr[tp].son[i]){
tr[tr[tp].son[i]].fail = tr[tr[tp].fail].son[i];
q.push(tr[tp].son[i]);
}else{
tr[tp].son[i] = tr[tr[tp].fail].son[i];
}
}
}
}
double f[1001][4001];
int dp[1001][4001], len[1001][4001];
bool Check(double lim){
for(int i = 0; i <= n; i++){
for(int j = 0; j <= cnt; j++){
f[i][j] = -INF;
dp[i][j] = 0;
len[i][j] = 0;
}
}
f[0][0] = 0;
dp[0][0] = 0;
len[0][0] = 0;
for(int i = 0; i < n; i++){
for(int j = 0; j <= cnt; j++){
f[i + 1][j] = f[i][j];
dp[i + 1][j] = dp[i][j];
len[i + 1][j] = len[i][j];
}
for(int j = 0; j <= cnt; j++){
int ch = s[i] - 'a';
int to = tr[j].son[ch];
if(!to) continue;
if(f[i + 1][to] + eps < f[i][j] + sum[to] - lim){
f[i + 1][to] = f[i][j] + sum[to] - lim;
dp[i + 1][to] = dp[i][j] + sum[to];
len[i + 1][to] = len[i][j] + 1;
}
}
}
for(int i = 1; i <= cnt; i++){
if(f[n][i] >= 0){
return 1;
}
}
return 0;
}
signed main(){
// freopen("1.in", "r", stdin);
cin >> t;
while(t--){
cin >> n >> m >> s;
for(int i = 1; i <= m; i++){
cin >> ss >> val;
Insert(ss, val);
}
Get_fail();
double l = 0, r = 2e8, ans = 0;
for(int i = 1; i <= 50; i++){
double mid = (l + r) / 2;
if(Check(mid)){
ans = mid;
l = mid + 1;
}else{
r = mid - 1;
}
}
Check(ans);
for(int i = 1; i <= cnt; i++){
if(f[n][i] >= 0){
int ret = dp[n][i] * Q_pow(len[n][i], MOD - 2) % MOD;
cout << ret << "\n";
break;
}
}
for(int i = 0; i <= cnt; i++){
memset(tr[i].son, 0, sizeof(tr[i].son));
tr[i].ed = tr[i].fail = 0;
sum[i] = 0;
}
cnt = 0;
}
}

类似的题 - ABC 356 E

题意

(ai,aj)aiaj (i !=j aiaj)

思路

转化一下, 其实就是在求 [kaj,(k+1)aj) 的数量再乘上 k

类似埃氏筛地做即可

和上面那题类似, 都是把除数均摊到了每一步上

代码

# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N = 1e7 + 10;
int n;
int a[N], sum[N];
map <int, int> cnt;
signed main(){
// freopen("1.in", "r", stdin);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
cnt[a[i]]++;
sum[a[i]]++;
}
for(int i = 1; i <= (int)1e7; i++){
sum[i] += sum[i - 1];
}
int ans = 0;
for(auto i : cnt){
int ti = 1;
ans += (sum [i.first + i.first - 1] - sum[i.first]) * i.second;
for(int j = i.first + i.first; j <= 2e6; j += i.first){
++ti;
ans += (sum[j + i.first - 1] - sum[j - 1]) * ti * i.second;
}
ans += (i.second - 1) * i.second / 2;
}
cout << ans << "\n";
}
posted on   Bubble_e  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话



点击右上角即可分享
微信分享提示