洛谷 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;
}
posted @ 2021-09-15 23:31  xixike  阅读(61)  评论(0编辑  收藏  举报