题目大意:给定一个字符串,将其划分成三个部分,使其满足一个加法等式。
设计思路:在这里首先我们考虑假定和为 n \;n\; n位数,那么两个加数的位数肯定都不会超过 n \;n\; n,并且其中一个加数一定是 n \;n\; n位或者 n − 1 \;n-1\; n−1位。这样我们先求出整个字符串的 H a s h \;Hash\; Hash函数,这里 b a s e \;base\; base取 10 \;10\; 10便于加法计算,然后枚举等号的位置,判断三个字符串的关系。
Solution:
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
const ll base = 10;
const ll mod1 = 1e9 + 7, mod2 = 1e9 + 9;
int len;
char s[maxn];
ll res1, res2;
ll Hash1[maxn], Hash2[maxn], p1[maxn], p2[maxn];
inline ll getHash1(int l, int r)
{
return ((Hash1[r] - Hash1[l - 1] * p1[r - l + 1]) % mod1 + mod1) % mod1;
}
inline ll getHash2(int l, int r)
{
return ((Hash2[r] - Hash2[l - 1] * p2[r - l + 1]) % mod2 + mod2) % mod2;
}
inline void write(int pos1,int pos2)
{
for (int i = 1; i <= pos1; i++)
{
putchar(s[i]);
}
putchar('+');
for (int i = pos1 + 1; i <= pos2; i++)
{
putchar(s[i]);
}
putchar('=');
for (int i = pos2 + 1; i <= len; i++)
{
putchar(s[i]);
}
putchar(10);
}
inline bool check(int l, int r)
{
if (r - l > 1) //b不是1位数,要判断其是否含前导0,如果有则不符合要求
{
if (s[l + 1] == '0')
return false;
}
if (len - r > 1) //x不是1位数,要判断其是否含前导0,如果有则不符合要求
{
if (s[r + 1] == '0')
return false;
}
ll a1 = getHash1(1, l), a2 = getHash2(1, l);
ll b1 = getHash1(l + 1, r), b2 = getHash2(l + 1, r);
ll c1 = getHash1(r + 1, len), c2 = getHash2(r + 1, len);
if ((a1 + b1) % mod1 == c1 && (a2 + b2) % mod2 == c2)
{
write(l, r);
return true;
}
return false;
}
int main()
{
cin >> s + 1;
len = (int)strlen(s + 1);
if (s[1] == '0') //如果第一位是0,那么只能是0+b=c的情况
{
write(1, (len + 1) / 2);
return 0;
}
p1[0] = p2[0] = 1;
for (int i = 1; i <= len; i++)
{
Hash1[i] = (Hash1[i - 1] * base + s[i] - '0') % mod1;
Hash2[i] = (Hash2[i - 1] * base + s[i] - '0') % mod2;
p1[i] = p1[i - 1] * base % mod1;
p2[i] = p2[i - 1] * base % mod2;
}
for (int i = 1, j = len / 2; i <= j; i++)
{
if (len - 2 * i <= i)
{
if (check(i, len - i)) //a与c等长的情况
{
break;
}
}
if (len - 2 * i - 1 <= i + 1) //a为n-1,c为n的情况
{
if (check(i, len - i - 1))
{
break;
}
}
if (check(i, (len + i) / 2)) //b与c等差或相差1的情况
{
break;
}
}
return 0;
}
题目大意:给定一个字符串,对于它的所有子串 k − s u b s t r i n g s \;k-substrings\; k−substrings,问其前缀和后缀相同的最大长度,且长度必须为奇数,如果不存在则为 − 1 \;-1\; −1。 k − s u b s t r i n g s \;k-substrings\; k−substrings是这样定义的:对于每个字符串,每次在开头和结尾删去 i \;i\; i个字符后得到的字符串。
设计思路:对于这道题目,我们考虑用字符串 H a s h \;Hash\; Hash来解决。先考虑朴素的枚举思想,即对于一个子串,从大到小枚举所有奇数前缀和后缀,然后根据Hash值来判断二者是否相等,复杂度在最坏情况下会达到 O ( n 2 ) \;O(n^2)\; O(n2),即使 4 s \;4s\; 4s也不够解决。观察字符串的特点,当在计算 i \;i\; i的最大公共前缀和后缀时,由于每次子串是两头向中间收缩,所以有这样一个结果,即 r e s [ i ] − 2 ≤ r e s [ i + 1 ] \;res[i]-2≤res[i+1]\; res[i]−2≤res[i+1],观察下图所示:
在计算出第 i \;i\; i位开始的子串的最大长度,紧接着计算 i + 1 \;i+1\; i+1位的长度,首先去掉首字符和尾字符,由于之前我们已经得到两部分子串长度相同,所以对于 i + 1 \;i+1\; i+1开始的子串的结果至少为上一次结果-2。也就是说对于上一次的前缀和后缀,去掉它们首字符和尾字符,中间部分一定是相同的,所以有 r e s [ i ] − 2 ≤ r e s [ i + 1 ] \;res[i]-2≤res[i+1]\; res[i]−2≤res[i+1],简单移项后就有 r e s [ i ] ≤ r e s [ i + 1 ] + 2 \;res[i]≤res[i+1]+2\; res[i]≤res[i+1]+2,问题恰好为求最大值,根据这个等式可以得到较长子串一定不会较短子串的结果+2,所以从中间开始枚举,然后对每个子串从上一次的结果开始,逐次-2,判断 H a s h \;Hash\; Hash值是否相等。
Solution:
#include
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
const ll base = 131;
const ll mod = 1e9 + 7;
ll Hash[maxn], b[maxn];
char ch[maxn];
int n, t, res[maxn / 2];
inline int read()
{
int t = 0;
char ch = getchar();
while (ch < '0' || ch > '9')
{
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
t = (t << 3) + (t << 1) + (ch ^ 48);
ch = getchar();
}
return t;
}
inline void write(int t)
{
if (t < 0)
{
putchar('-');
t = -t;
}
if (t > 9)
{
write(t / 10);
}
putchar(t % 10 + '0');
}
inline void init()
{
b[0] = 1;
for (int i = 1; i <= n; i++)
{
b[i] = (b[i - 1] * base) % mod;
}
for (int i = 1; i <= n; i++)
{
Hash[i] = (Hash[i - 1] * base + ch[i] - 'a' + 1) % mod;
}
}
inline ll get_hash(int l, int r)
{
return ((Hash[r] - Hash[l - 1] * b[r - l + 1]) % mod + mod) % mod;
}
inline void solve(int x)
{
int y = t + t - x;
if (!(n & 1)) //还是奇偶问题,偶数的时候右边界+1,简单模拟一下就可以发现
{
y++;
}
for (int i = res[x + 1] + 2; i >= 1; i -= 2) //从上一次结果出发开始枚举
{
if (get_hash(x, x + i - 1) == get_hash(y - i + 1, y))
{
res[x] = i;
return;
}
}
res[x] = -1;
return;
}
int main()
{
n = read();
scanf("%s", ch + 1);
init();
t = n / 2; //分别处理字符串长度为奇数和偶数的情况
if (n & 1)
{
t++;
res[t] = -1; //对于奇数长度的字符串,两端长度是关于一个字符对称的,正中间的字符一定没有前后缀
}
else
{
if (ch[t] == ch[t + 1]) //偶数长度则比较最中间两个字符即可
{
res[t] = 1;
}
else
{
res[t] = -1;
}
}
for (int i = t - 1; i > 0; i--)
{
solve(i); //从中间开始枚举,依此解决较长子串
}
for (int i = 1; i <= t; i++)
{
write(res[i]);
if (i == t)
{
putchar(10);
}
else
{
putchar(' ');
}
}
return 0;
}
题目大意:给定一个字符串,然后每次截取两端相等长度的子串,问这两个子串是否同构。(同构就是两个字符对应位置只有一一映射的关系,即不能多对一也不能一对多)
设计思路:如果简单采取一一映射的方法,对每次询问子串遍历,那么复杂度肯定也是无法满足需求。由于这里的问题是对于两个字符串而言,它们能否构成同构,而字符串 H a s h \;Hash\; Hash的思想是将字符串映射为一个尽可能唯一的整数,通过比较两个整数来判断两个字符串是否相等,而这里两个字符串相等的关系不是仅仅的对应字符相等,而是要满足同构这种特殊相等关系,所以这时候不再对字符串进行 H a s h \;Hash\; Hash,而对每个字符的位置 H a s h \;Hash\; Hash,然后通过 H a s h \;Hash\; Hash值判断是否同构。
如何理解对位置 H a s h \;Hash\; Hash是这个问题的难点,首先要知道 H a s h \;Hash\; Hash函数有一种类似前缀和的性质,每一位字符 H a s h \;Hash\; Hash值是在上一位的结果基础上乘以进制再加上本位字符的数值大小。现在考虑两个字符串是否同构,那么来看如下一个例子;
比如对于字符串 a b c a b b a c d b c c \;abcabbacdbcc\; abcabbacdbcc,选取它的两个子串如 b c a b b \;bcabb\; bcabb和 c d b c c \;cdbcc\; cdbcc,肉眼可以直观看出这两个字符串是同构的,那么如何用计算机来判断呢?在这个例子中 f ( b ) = c , f ( c ) = d , f ( a ) = b \;f(b)=c\;,\;f(c)=d,f(a)=b\; f(b)=c,f(c)=d,f(a)=b,也就是说,每个字母和另一个有对应关系,有了这个想法,我们考虑将原字符串映射为26个01串,每个01串表示一个字母,该位置为这个字符为1其余为0,那么上述例子中就可以转化为100100100000, 010011000100, 001000010011, 000000001000…这时候问题就转化了,对于 f ( b ) = c \;f(b)=c\; f(b)=c,在 b \;b\; b转化的01串中,对应位置的子串为10011,在 c \;c\; c转化的01串中,对应位置的子串为10011,这两个转化的子串是完全一致的,也就是说,对于 b \;b\; b和 c \;c\; c,满足一一映射的关系,因为如果子串比如是…a…a…和…c…c…c…,那么对应的01串是…010…010…和…010…010…010…,这两个字符串一定是不相等的。所以判断两个字符串是否同构,只需要得到这两个字符串每个每个字母的 H a s h \;Hash\; Hash,然后判断两个字符串的26个字母是否都存在一一对应的关系。
Solution:
#include
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 6;
const ll base = 131;
const ll mod = 1e9 + 7;
int n, m;
char ch[maxn];
int x, y, len;
ll Hash[maxn][30], b[maxn];
inline int read()
{
int t = 0;
char ch = getchar();
while (ch < '0' || ch>'9')
{
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
t = (t << 3) + (t << 1) + (ch ^ 48);
ch = getchar();
}
return t;
}
inline void init()
{
b[0] = 1;
for (int i = 1; i <= n; i++)
{
b[i] = (b[i - 1] * base) % mod;
}
}
inline ll get_hash(int l, int r, int t)
{
return (((Hash[r][t] - Hash[l - 1][t] * b[r - l + 1])) % mod + mod) % mod;
}
inline bool check()
{
ll res_a[30], res_b[30];
for (int i = 1; i <= 26; i++) //得到两个字符串对于的26个Hash值
{
res_a[i] = get_hash(x, x + len - 1, i);
res_b[i] = get_hash(y, y + len - 1, i);
}
//排序是为了便于比较
sort(res_a + 1, res_a + 27);
sort(res_b + 1, res_b + 27);
for (int i = 1; i <= 26; i++)
{
if (res_a[i] != res_b[i])
{
return false;
}
}
return true;
}
int main()
{
n = read();
m = read();
scanf("%s", ch + 1);
init();
//将原字符串转换为26个01串
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= 26; j++)
{
Hash[i][j] = (Hash[i - 1][j] * base % mod + (ll)(ch[i] == (j - 1 + 'a'))) % mod;
}
}
while (m--)
{
x = read();
y = read();
len = read();
if (check())
{
puts("YES");
}
else
{
puts("NO");
}
}
return 0;
}
题目大意:给定一个字符串,从中删除任一字符后得到的字符串能够分为两个相同的子串,如果不可能输出"NOT POSSIBLE",存在唯一解输出这个字符串,存在多解输出“NOT UNIQUE”。
设计思路:首先对于长度为偶数的字符串一定是不存在解的,删除一个字符后剩下的字符串不能分为两个等长的字符串。然后对于奇数长度的字符串,只需要枚举删除每一位字符后剩下的字符串分为两部分是否相等,如果相等则保存它的 H a s h \;Hash\; Hash值,用于下次再存在解时判重。同时可以进一步优化,当枚举的字符位于前 n 2 \;\frac{n}{2}\; 2n时,拆分的其中一个字符串一定是原字符串的后 n 2 \;\frac{n}{2}\; 2n位,同样枚举到后一半的字符时其中一个字符串一定是原字符串的前 n 2 \;\frac{n}{2}\; 2n位,预处理这两个值避免每次枚举时重复计算。( p s : \;ps:\; ps:这道题卡了 1 e 9 + 7 1e9+7 1e9+7和 1 e 9 + 9 1e9+9 1e9+9,所以模数要放大一些)
Solution:
#include
using namespace std;
#include
typedef unsigned long long ull;
const ull base = 131;
const int maxn = 2e6 + 5;
int n, x, pos;
char ch[maxn];
ull Hash[maxn], b[maxn];
ull ans, res, res1, res2;
inline void init()
{
b[0] = 1;
for (int i = 1; i <= n; i++)
{
b[i] = b[i - 1] * base;
Hash[i] = Hash[i - 1] * base + (ull)(ch[i] - 'A' + 1);
}
}
inline ull get_hash(int l, int r)
{
return Hash[r] - Hash[l - 1] * b[r - l + 1];
}
inline bool check(int t)
{
if (t >= x)
{
ans = get_hash(x, t - 1) * b[n - t] + get_hash(t + 1, n);
return ans == res1;
}
else
{
ans = get_hash(1, t - 1) * b[x - t] + get_hash(t + 1, x);
return ans == res2;
}
}
int main()
{
scanf("%d", &n);
if (n == 1 || !(n & 1))
{
puts("NOT POSSIBLE");
return 0;
}
scanf("%s", ch + 1);
init();
x = n / 2 + 1;
res1 = get_hash(1, x - 1);
res2 = get_hash(x + 1, n);
for (int i = 1; i <= n; i++)
{
if (check(i))
{
if (!res)
{
res = ans;
}
else if (res != ans)
{
puts("NOT UNIQUE");
return 0;
}
pos = i;
}
}
if (!pos)
{
puts("NOT POSSIBLE");
return 0;
}
if (pos < x)
{
for (int i = x + 1; i <= n; i++)
{
putchar(ch[i]);
}
}
else
{
for (int i = 1; i < x; i++)
{
putchar(ch[i]);
}
}
putchar(10);
return 0;
}
有时间再更新啦 完结撒花!