洛谷 CF898F Restoring the Expression
Description
CF898F Restoring the Expression
Solution
很明显的一道哈希题,那么我们考虑哈希能否支持加法。
我们通过列举大量式子发现,普通的哈希是不行的,但是我们换一个乘数,也就是令 \(Base = 10\),那么是不是就可以支持加法了呢。
但是这样一来,冲突的概率就大了很多,所以要双哈希,而且由于是 CF 的题这道题非常毒瘤的把 \(998244353\),\(1000000007\) 等常见模数全部卡掉了 \(QwQ\),恶心至极。所以要换一些模数。
然后就是具体过程。
我们可以枚举等号的位置,假设等号后面的数长为 \(len\),由于加法最多进一位,所以加号前的数长度为 \(len\) 或 \(len - 1\),加号后面的数同理。
另外注意这道题还要判前导 0。
这道题最麻烦的就是边界的处理,我整整做了一个晚上啊啊啊啊啊啊啊。
Code
(有注释)
#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
const ll N = 1e6 + 10;
const ll mod1 = 1e9 + 21;//双哈希模数
const ll mod2 = 1e9 + 33;
ll n, pos1 = N, pos2 = N;//pos1: 加号位置 pos2: 等号位置
ll fx[N], f[N];//双哈希两个数组
ll gx[N], g[N];
char s[N];
inline ll Hash1(ll l, ll r){//去第一个哈希值
return (f[r] - f[l - 1] * fx[r - l + 1] % mod1 + mod1) % mod1;
}
inline ll Hash2(ll l, ll r){//取第二个哈希值
return (g[r] - g[l - 1] * gx[r - l + 1] % mod2 + mod2) % mod2;
}
inline bool check(int l, int r){//判断是否有前导0,1为没有,0为有
if(s[l] != '0') return 1;//第一位不为0,直接返回1
for(int i = l + 1; i <= r; i++)
if(s[i] == '0') return 0;
return 1;
}
inline bool checkr1(int len, int n, int flag){//判断第二个加数长度为 len 或 len - 1 时,等式是否成立。flag = 0时,长度为 len - 1,flag = 1时,长度为 len
int l = n - (len << 1) + flag;//l 为第一个加数右边界
if(l < 1) return 0;
//第一个加数 + 第二个加数 == 和
return ((Hash1(1, l) + Hash1(l + 1, n - len)) % mod1) == Hash1(n - len + 1, n) && check(l + 1, n - len);
}
inline bool checkr2(int len, int n, int flag){//第二个哈希
int l = n - (len << 1) + flag;
if(l < 1) return 0;
return ((Hash2(1, l) + Hash2(l + 1, n - len)) % mod2)== Hash2(n - len + 1, n) && check(l + 1, n - len);
}
inline bool checkl1(int len, int n, int flag){//判断第一个加数的情况,其它同上
int l = len + flag - 1;
if(l < 1) return 0;
return ((Hash1(1, l) + Hash1(l + 1, n - len)) % mod1) == Hash1(n - len + 1, n) && check(l + 1, n - len);
}
inline bool checkl2(int len, int n, int flag){
int l = len + flag - 1;
if(l < 1) return 0;
return ((Hash2(1, l) + Hash2(l + 1, n - len)) % mod2) == Hash2(n - len + 1, n) && check(l + 1, n - len);
}
inline void updater(int len, int flag){//当第二个加数符合条件时,更新加号和等号的位置
if(n - (len << 1) + flag < pos1) pos1 = n - (len << 1) + flag, pos2 = n - len;
}
inline void updatel(int len, int flag){//同理
if(len + flag - 1 < pos1) pos1 = len + flag - 1, pos2 = n - len;
}
signed main(){
// freopen("a.in", "r", stdin);
// freopen("a.out", "w", stdout);
scanf("%s", s + 1);
n = strlen(s + 1);
fx[0] = 1, gx[0] = 1;
for(ll i = 1; i <= n; i++){
fx[i] = fx[i - 1] * 10 % mod1;
f[i] = (f[i - 1] * 10 + s[i] - '0') % mod1;
gx[i] = gx[i - 1] * 10 % mod2;
g[i] = (g[i - 1] * 10 + s[i] - '0') % mod2;
}
for(int len = n / 3; len <= (n >> 1); len++){
if(!check(n - len + 1, n)) continue;//和 有前导0,直接跳过
if(checkr1(len, n, 0) && checkr2(len, n, 0)) updater(len, 0);//第二个加数长度为 len - 1
if(checkr1(len, n, 1) && checkr2(len, n, 1)) updater(len, 1);//第二个加数长度为 len
if(checkl1(len, n, 0) && checkl2(len, n, 0)) updatel(len, 0);//第一个加数长度为 len - 1
if(checkl1(len, n, 1) && checkl2(len, n, 1)) updatel(len, 1);//第二个加数长度为 len
}
for(ll i = 1; i <= pos1; i++)//输出
putchar(s[i]);
putchar('+');
for(ll i = pos1 + 1; i <= pos2; i++)
putchar(s[i]);
putchar('=');
for(ll i = pos2 + 1; i <= n; i++)
putchar(s[i]);
puts("");
return 0;
}