[APIO2014]回文串 题解
题目地址:洛谷:【P3649】[APIO2014]回文串 – 洛谷、BZOJ: …
May all the beauty be blessed.
题目地址:洛谷:【P2601】[ZJOI2009]对称的正方形 – 洛谷、BZOJ:Problem 1414. — [ZJOI2009]对称的正方形
Orez很喜欢搜集一些神秘的数据,并经常把它们排成一个矩阵进行研究。最近,Orez又得到了一些数据,并已经把它们排成了一个n行m列的矩阵。通过观察,Orez发现这些数据蕴涵了一个奇特的数,就是矩阵中上下对称且左右对称的正方形子矩阵的个数。 Orez自然很想知道这个数是多少,可是矩阵太大,无法去数。只能请你编个程序来计算出这个数。
求上下左右对称的正方形子矩阵个数。
输入格式:
文件的第一行为两个整数n和m。接下来n行每行包含m个正整数,表示Orez得到的矩阵。
输出格式:
文件中仅包含一个整数answer,表示矩阵中有answer个上下左右对称的正方形子矩阵。
输入样例#1:
5 5 4 2 4 4 4 3 1 4 4 3 3 5 3 3 3 3 1 5 3 3 4 2 1 2 4
输出样例#1:
27
数据范围
对于30%的数据 n,m≤100
对于100%的数据 n,m≤1000 ,矩阵中的数的大小≤10^9
参考资料:题解 P2601 【[ZJOI2009]对称的正方形】 – Miaomiao’s Home – 洛谷博客
本题可以先对矩阵元素间插入数字0以便处理奇偶回文的情况,对每行每列跑一遍Manacher,统计出回文半径。
对于每个格子,上下左右找一遍以该格子为顶点的区间回文半径不大于区间长度的最大区间,这些区间长度的最小值就是这个格子为中心的最大对称正方形,而消除插入0的影响后的值就是这个格子对答案的贡献。
思路很简单也很暴力,算法$O(n^2 \log n)$,但是写出来常数挺大的,而且其实不太好写。
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
inline int min(int a, int b) {
return a < b ? a : b;
}
const int MAXN = 2005;
int n, m, a[MAXN][MAXN], lh[MAXN][MAXN], lv[MAXN][MAXN], t[MAXN];
inline void manacher(int n, int *arr, int *len) {
int id = 0, mx = 0;
for(register int i = 1; i <= n; i++) {
if(i < mx) len[i] = min(len[(id << 1) - i], mx - i);
else len[i] = 1;
while(arr[i - len[i]] == arr[i + len[i]]
&& i - len[i] >= 1 && i + len[i] <= n) len[i]++;
if(mx < i + len[i]) {
id = i; mx = i + len[i];
}
}
}
int Log[MAXN], mn[MAXN][12], ans[MAXN][MAXN];
inline void initst(int n, int x, int arr[][MAXN]) {
for(register int i = 1; i <= n; i++) {
mn[i][0] = arr[i][x] - 1;
}
for(register int j = 1; j < 12; j++) {
for(register int i = 1; i <= n; i++) {
if(i + (1 << j) - 1 > n) break;
mn[i][j] = min(mn[i][j - 1], mn[i + (1 << (j - 1))][j - 1]);
}
}
}
inline int query(int x, int y) {
int lg = Log[y - x + 1];
return min(mn[x][lg], mn[y - (1 << lg) + 1][lg]);
}
int main() {
for(register int i = 2; i < MAXN; i++) {
Log[i] = Log[i >> 1] + 1;
}
n = readint(); m = readint();
for(register int i = 1; i <= n; i++) {
for(register int j = 1; j <= m; j++) {
a[(i << 1) - 1][(j << 1) - 1] = readint();
}
}
n = (n << 1) - 1; m = (m << 1) - 1;
for(register int i = 1; i <= n; i++) {
for(register int j = 1; j <= m; j++) {
t[j] = a[i][j];
}
manacher(m, t, lh[i]);
}
for(register int i = 1; i <= m; i++) {
for(register int j = 1; j <= n; j++) {
t[j] = a[j][i];
}
manacher(n, t, lv[i]);
}
memset(ans, 0x3f, sizeof(ans));
for(register int i = 1; i <= n; i++) {
initst(m, i, lv);
int t = 1;
for(register int j = 1; j <= m; j++) {
while(t < j && query(t, j) < j - t) t++;
ans[i][j] = min(ans[i][j], j - t);
}
t = m;
for(register int j = m; j >= 1; j--) {
while(t > j && query(j, t) < t - j) t--;
ans[i][j] = min(ans[i][j], t - j);
}
}
for(register int i = 1; i <= m; i++) {
initst(n, i, lh);
int t = 1;
for(register int j = 1; j <= n; j++) {
while(t < j && query(t, j) < j - t) t++;
ans[j][i] = min(ans[j][i], j - t);
}
t = n;
for(register int j = n; j >= 1; j--) {
while(t > j && query(j, t) < t - j) t--;
ans[j][i] = min(ans[j][i], t - j);
}
}
int res = 0;
for(register int i = 1; i <= n; i++) {
for(register int j = 1; j <= m; j++) {
if((i & 1) && (j & 1)) res += (ans[i][j] >> 1) + 1;
else if(!(i & 1) && !(j & 1)) res += (ans[i][j] + 1) >> 1;
}
}
printf("%d", res);
return 0;
}
题目地址:BZOJ:Problem 3561. — DZY Loves Math VI
给定正整数n,m,求 \displaystyle \sum_{i=1}^n \sum_{i=1}^m \mathrm{lcm}(i, j)^{\mathrm{gcd}(i, j)} 。
输入格式:
一行两个整数n,m。
输出格式:
一个整数,为答案模1000000007后的值。
输入样例#1:
5 4
输出样例#1:
424
数据规模:
1<=n,m<=500000,共有3组数据。
参考资料:BZOJ3561 DZY Loves Math VI 数论 快速幂 莫比乌斯反演 – -zhouzhendong- – 博客园
我们知道有\mathrm{lcm}(i, j)\mathrm{gcd}(i, j) = ij,因此题目给的式子可以搞成
\sum_{i=1}^n \sum_{i=1}^m \left( \frac{ij}{\mathrm{gcd}(i, j)} \right)^{\mathrm{gcd}(i, j)}
类似反演经典题目那样,把gcd=k的问题转化成gcd=1的问题,这样就可以直接判定是否互质了
\sum_{d=1}^n \sum_{i=1}^{\left\lfloor \frac{n}{d} \right\rfloor} \sum_{j=1}^{\left\lfloor \frac{m}{d} \right\rfloor} \left( \frac{ijd^2}{d} \right)^d [\mathrm{gcd}(i, j) == 1]
然后你发现这个gcd判定可以用\mu(n)来换掉,有利于之后改变求和顺序
\sum_{d=1}^n \sum_{i=1}^{\left\lfloor \frac{n}{d} \right\rfloor} \sum_{j=1}^{\left\lfloor \frac{m}{d} \right\rfloor} \left( \frac{ijd^2}{d} \right)^d \sum_{p|i, p|j} \mu(p)
接着我们枚举gcd,改变求和顺序,在这个过程中还可以把不变的常数项提到前面来
\sum_{d=1}^n d^d \sum_{p=1}^{\left\lfloor \frac{n}{d} \right\rfloor} \mu(p) \sum_{i=1}^{\left\lfloor \frac{n}{pd} \right\rfloor} \sum_{j=1}^{\left\lfloor \frac{m}{pd} \right\rfloor} (ijp^2)^d
最后的整理
\sum_{d=1}^n d^d \sum_{p=1}^{\left\lfloor \frac{n}{d} \right\rfloor} \mu(p)p^{2d} \sum_{i=1}^{\left\lfloor \frac{n}{pd} \right\rfloor} i^d \sum_{j=1}^{\left\lfloor \frac{m}{pd} \right\rfloor} j^d
乘方求前缀和可以预处理,\mu(n)可以筛出来,d^d快速幂搞一下就好。总复杂度O(n \log n)。
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for (; !isdigit(c); c = fgc()) if (c == '-') neg = -1;
for (; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
const int MAXN = 500005, MO = 1e9 + 7;
bool notprime[MAXN];
int mu[MAXN], prime[MAXN], ptot;
inline void sieve() {
notprime[1] = true; mu[1] = 1;
for(int i = 2; i < MAXN; i++) {
if(!notprime[i]) {
prime[++ptot] = i; mu[i] = -1;
}
for(int j = 1; j <= ptot && prime[j] * i < MAXN; j++) {
int k = prime[j] * i;
notprime[k] = true;
if(i % prime[j]) {
mu[k] = -mu[i];
} else {
mu[k] = 0; break;
}
}
}
}
inline LL fpow(LL x, LL k) {
LL res = 1;
for(; k; k >>= 1) {
if(k & 1) res = res * x % MO;
x = x * x % MO;
}
return res;
}
int n, m;
LL powd[MAXN], psum[MAXN];
int main() {
sieve();
n = readint(); m = readint();
if(n > m) std::swap(n, m);
for(int i = 1; i <= m; i++) {
powd[i] = 1;
}
LL ans = 0;
for(int d = 1; d <= n; d++) {
LL res = 0;
for(int i = 1; i <= m / d; i++) {
powd[i] = powd[i] * i % MO; psum[i] = (psum[i - 1] + powd[i]) % MO;
}
for(int p = 1; p <= n / d; p++) {
res = (res + mu[p] * powd[p] % MO * powd[p] % MO
* psum[n / p / d] % MO * psum[m / p / d] % MO) % MO;
}
res = (res % MO + MO) % MO;
ans = (ans + res * fpow(d, d) % MO) % MO;
}
printf("%lld", ans);
return 0;
}