【CodeForces】Mail.Ru Cup 2018 Round 1 (Div. 1 + Div. 2) 题解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39972971/article/details/83241357

【比赛链接】

【题解链接】

**【A】**Elevator or Stairs?

【思路要点】

  • 按照题意计算两种方式的用时,取较优的方案采纳。
  • 时间复杂度 O ( 1 ) O(1)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int main() {
	int x, y, z, t1, t2, t3;
	read(x), read(y), read(z), read(t1), read(t2), read(t3);
	int s = abs(x - y) * t1;
	int e = abs(x - z) * t2 + abs(x - y) * t2 + t3 * 3;
	if (e <= s) printf("YES\n");
	else printf("NO\n");
	return 0;
}

**【B】**Appending Mex

【思路要点】

  • 显然在操作合法的情况下,序列中的数形成的集合是一个仅包含最小的 x x 个自然数的集合,因此,可能加入的数应当是 0 0 x x 中的一个整数,按照此判断标准检验操作序列的合法性即可。
  • 时间复杂度 O ( N ) O(N)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int main() {
	int n, Max = -1; read(n);
	for (int i = 1; i <= n; i++) {
		int x; read(x);
		if (x > Max + 1) {
			printf("%d\n", i);
			return 0;
		}
		chkmax(Max, x);
	}
	printf("-1\n");
	return 0;
}

**【C】**Candies Distribution

【思路要点】

  • 计算 a l l i = l i + r i all_i=l_i+r_i a l l i all_i 表示大于 a i a_i 的数的个数。
  • 因此,实际上所有 a i a_i 的大小关系已经确定了,我们根据 a l l i all_i 构造出一组符合条件的 a i a_i ,再检验其是否满足 l i l_i r i r_i 的限制即可。
  • 时间复杂度 O ( N 2 ) O(N^2)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, a[MAXN], l[MAXN], r[MAXN];
int rnk[MAXN], pos[MAXN];
bool cmp(int x, int y) {
	return l[x] + r[x] < l[y] + r[y];
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++)
		read(l[i]);
	for (int i = 1; i <= n; i++)
		read(r[i]);
	for (int i = 1; i <= n; i++)
		pos[i] = i;
	sort(pos + 1, pos + n + 1, cmp);
	for (int i = 1; i <= n; i++) {
		int now = pos[i], last = pos[i - 1];
		if (i == 1 || l[now] + r[now] != l[last] + r[last]) rnk[i] = i;
		else rnk[i] = rnk[i - 1];
		a[now] = n - rnk[i] + 1;
	}
	for (int i = 1; i <= n; i++) {
		int cnt = 0;
		for (int j = 1; j <= i - 1; j++)
			if (a[j] > a[i]) cnt++;
		if (cnt != l[i]) {
			printf("NO\n");
			return 0;
		}
		cnt = 0;
		for (int j = i + 1; j <= n; j++)
			if (a[j] > a[i]) cnt++;
		if (cnt != r[i]) {
			printf("NO\n");
			return 0;
		}
	}
	printf("YES\n");
	for (int i = 1; i <= n; i++)
		printf("%d ", a[i]);
	return 0;
}

**【D】**Changing Array

【思路要点】

  • s i s_i a i a_i 的前缀异或和,一个区间 [ l , r ] [l,r] 的区间异或和即为 s r s l 1 s_r\oplus s_{l-1} ,区间 [ l , r ] [l,r] 的区间异或和为 0 0 当且仅当 s r = s l 1 s_r=s_{l-1}
  • 题目中对一个数 a i a_i 进行取反操作相当于执行 a i = a i ( 2 k 1 ) a_i=a_i\oplus (2^k-1) ,这样的操作对 s s 的影响是对 s s 数组的某一段后缀执行 s i = s i ( 2 k 1 ) s_i=s_i\oplus (2^k-1) 。因此,我们使用该操作可以任意地对 s i s_i 执行 s i = s i ( 2 k 1 ) s_i=s_i\oplus (2^k-1) ,最后我们希望最小化 s i = s j s_i=s_j ( i , j ) (i,j) 对数,那么对于 x y = 2 k 1 x\oplus y=2^k-1 ,显然将等于 x x s i s_i 和等于 y y s i s_i 的个数修改得尽量平均是最优的。
  • 时间复杂度 O ( N L o g N ) O(NLogN)
  • 由该做法,我们也可以证明下面一个贪心算法的正确性,即:从左向右考虑每一个区间的右端点 i i ,若执行 a i = a i ( 2 k 1 ) a_i=a_i\oplus (2^k-1) 会使得 [ 1 , i ] , [ 2 , i ] , . . . , [ i , i ] [1,i],[2,i],...,[i,i] 中区间异或和为 0 0 的变少,则执行之。
  • 两个算法在本质上是一样的,以下代码实现了上述贪心算法。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
map <int, int> mp;
int n, k, a[MAXN];
int main() {
	read(n), read(k);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	int goal = (1 << k) - 1;
	ll ans = n * (n + 1ll) / 2;
	for (int i = 1; i <= n; i++) {
		a[i] ^= a[i - 1], mp[a[i - 1]]++;
		int tmp = min(mp[a[i]], mp[goal ^ a[i]]);
		ans -= tmp; if (tmp == mp[goal ^ a[i]]) a[i] ^= goal;
	}
	writeln(ans);
	return 0;
}

**【E】**Chips Puzzle

【思路要点】

  • 题目给定了 4 s 4*s 次操作,不妨考虑用 2 s 2*s 次操作将起始状态化为一个对于所有起始状态统一的中间状态,再用 2 s 2*s 次操作由中间状态得到目标状态。
  • 我们选取将所有白子移动至左上角,所有黑子移动至右下角的状态为中间状态。
  • 将起始状态化为中间状态的方式:将棋盘边缘上的所有棋子花费 1 1 步移动至右上角或左下角,再将这两个角落中的棋子花费 1 1 步归类至左上角或右下角。现在,棋盘边缘上只有左上角和右下角有棋子,对于中间的每一枚棋子,花费 2 2 步将其经棋盘边缘归类至左上角或右下角即可。
  • 将中间状态化为目标状态的方式:借助棋盘边缘花费 2 2 步完成对棋盘中部的构造,然后借助右上角或左下角花费 2 2 步完成对棋盘边缘除了四角处的构造,进而借助右上角或左下角花费 2 2 步完成对左上角和右下角的构造,最后花费 1 1 步完成对右上角和左下角的构造。
  • 时间复杂度 O ( s ) O(s) ,使用操作次数不超过 4 s 4*s

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 305;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m;
string a[MAXN][MAXN], b[MAXN][MAXN];
vector <pair <int, int> > from, to;
void moveto(int sx, int sy, int tx, int ty) {
	if (sx == tx && sy == ty) return;
	if (sx != tx) {
		from.emplace_back(sx, sy);
		to.emplace_back(tx, sy);
		moveto(tx, sy, tx, ty);
	} else {
		from.emplace_back(sx, sy);
		to.emplace_back(sx, ty);
		moveto(sx, ty, tx, ty);
	}
}
bool valid(int x, int y) {
	if (x == 1 && y == m) return true;
	if (x == n && y == 1) return true;
	return false;
}
void Moveto(int sx, int sy, int tx, int ty) {
	if (sx == tx && sy == ty) return;
	if (sx != tx && valid(tx, sy)) {
		from.emplace_back(sx, sy);
		to.emplace_back(tx, sy);
		Moveto(tx, sy, tx, ty);
	} else if (sy != ty && valid(sx, ty)) {
		from.emplace_back(sx, sy);
		to.emplace_back(sx, ty);
		Moveto(sx, ty, tx, ty);
	} else moveto(sx, sy, tx, ty);
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++) {
		cin >> a[i][j];
		reverse(a[i][j].begin(), a[i][j].end());
	}
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++) {
		cin >> b[i][j];
		reverse(b[i][j].begin(), b[i][j].end());
	}
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++) {
		if (i == 1 && j == m) continue;
		if (i == n && j == 1) continue;
		if (i == 1 || j == m) {
			a[1][m] += a[i][j];
			for (auto x : a[i][j]) moveto(i, j, 1, m);
			a[i][j].clear();
		} else if (i == n || j == 1) {
			a[n][1] += a[i][j];
			for (auto x : a[i][j]) moveto(i, j, n, 1);
			a[i][j].clear();
		}
	}
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++) {
		for (auto x : a[i][j])
			if (x == '0') moveto(i, j, 1, 1);
			else moveto(i, j, n, m);
	}
	for (int i = 2; i <= n - 1; i++)
	for (int j = 2; j <= m - 1; j++) {
		for (auto x : b[i][j])
			if (x == '0') moveto(1, 1, i, j);
			else moveto(n, m, i, j);
		b[i][j].clear();
	}
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++) {
		if (i == 1 && j == 1) continue;
		if (i == n && j == 1) continue;
		if (i == 1 && j == m) continue;
		if (i == n && j == m) continue;
		for (auto x : b[i][j])
			if (x == '0') Moveto(1, 1, i, j);
			else Moveto(n, m, i, j);
		b[i][j].clear();
	}
	for (auto x : b[1][1])
		if (x == '0') moveto(1, 1, 1, m), moveto(1, m, 1, 1);
		else moveto(n, m, 1, 1);
	for (auto x : b[n][m])
		if (x == '0') moveto(1, 1, n, m);
		else moveto(n, m, 1, m), moveto(1, m, n, m);
	for (auto x : b[1][m])
		if (x == '0') moveto(1, 1, 1, m);
		else moveto(n, m, 1, m);
	for (auto x : b[n][1])
		if (x == '0') moveto(1, 1, n, 1);
		else moveto(n, m, n, 1);
	writeln(from.size());
	for (unsigned i = 0; i < from.size(); i++)
		printf("%d %d %d %d\n", from[i].first, from[i].second, to[i].first, to[i].second);
	return 0;
}

**【F】**Electric Scheme

【思路要点】

  • 显然的一组合法解是在每一个火花处连接两根不同的电线。
  • 我们可以连接相邻的两根在同一直线上的同向电线,每连接一处,就可以使答案变优 1 1 ,但同时我们需要保证连接的电线不会相交。可能连接方式总数是 O ( N ) O(N) 的。
  • 若两种连接方式产生相交,我们称这两种方式不兼容。显然同向的连接方式不会不兼容,因此“兼容”关系构成了一张二分图,求解其最大独立集即可。
  • 时间复杂度 O ( D i n i c ( N , N 2 ) ) O(Dinic(N,N^2)) ,实测跑得很快。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e3 + 5;
const int MAXP = 2e3 + 5;
const int INF = 1e9;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct edge {int dest, flow; unsigned pos; };
vector <edge> a[MAXP]; unsigned curr[MAXP];
int tot, s, t, dist[MAXP];
void addedge(int x, int y, int z) {
	a[x].push_back((edge) {y, z, a[y].size()});
	a[y].push_back((edge) {x, 0, a[x].size() - 1});
}
int dinic(int pos, int limit) {
	if (pos == t) return limit;
	int used = 0, tmp;
	for (unsigned &i = curr[pos]; i < a[pos].size(); i++)
		if (a[pos][i].flow != 0 && dist[pos] + 1 == dist[a[pos][i].dest] && (tmp = dinic(a[pos][i].dest, min(limit - used, a[pos][i].flow)))) {
			used += tmp;
			a[pos][i].flow -= tmp;
			a[a[pos][i].dest][a[pos][i].pos].flow += tmp;
			if (used == limit) return used;
		}
	return used;
}
bool bfs() {
	static int q[MAXP];
	int l = 0, r = 0;
	memset(dist, 0, sizeof(dist));
	dist[s] = 1, q[0] = s;
	while (l <= r) {
		int tmp = q[l];
		for (unsigned i = 0; i < a[tmp].size(); i++)
			if (dist[a[tmp][i].dest] == 0 && a[tmp][i].flow != 0) {
				q[++r] = a[tmp][i].dest;
				dist[q[r]] = dist[tmp] + 1;
			}
		l++;
	}
	return dist[t] != 0;
}
int n, x[MAXN], y[MAXN], r[MAXN], d[MAXN];
int pd[MAXN], pr[MAXN];
int main() {
	read(n);
	for (int i = 1; i <= n; i++)
		read(x[i]), read(y[i]);
	s = 0, t = tot = 1;
	int ans = n * 2;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			if (x[i] == x[j] && y[j] < y[i] && (d[i] == 0 || y[j] > y[d[i]])) d[i] = j;
			if (y[i] == y[j] && x[j] > x[i] && (r[i] == 0 || x[j] < x[r[i]])) r[i] = j;
		}
		if (d[i]) pd[i] = ++tot, ans--, addedge(s, tot, 1);
		if (r[i]) pr[i] = ++tot, ans--, addedge(tot, t, 1);
	}
	for (int i = 1; i <= n; i++) {
		if (!d[i]) continue;
		for (int j = 1; j <= n; j++) {
			if (!r[j]) continue;
			int px = x[i], py = y[j];
			if (px > x[j] && px < x[r[j]] && py > y[d[i]] && py < y[i]) addedge(pd[i], pr[j], INF);
		}
	}
	while (bfs()) {
		memset(curr, 0, sizeof(curr));
		ans += dinic(s, INF);
	}
	cerr << ans << endl;
	static vector <pair <int, int> > from, to;
	static bool head[MAXN];
	memset(head, true, sizeof(head));
	from.clear(), to.clear();
	for (int i = 1; i <= n; i++) {
		if (r[i] && dist[pr[i]] != 0) r[i] = 0;
		if (r[i]) head[r[i]] = false;
	}
	for (int i = 1; i <= n; i++) {
		if (!head[i]) continue;
		int pos = i;
		while (r[pos]) pos = r[pos];
		from.emplace_back(x[i], y[i]);
		to.emplace_back(x[pos], y[pos]);
	}
	writeln(from.size());
	for (unsigned i = 0; i < from.size(); i++)
		printf("%d %d %d %d\n", from[i].first, from[i].second, to[i].first, to[i].second);
	memset(head, true, sizeof(head));
	from.clear(), to.clear();
	for (int i = 1; i <= n; i++) {
		if (d[i] && dist[pd[i]] == 0) d[i] = 0;
		if (d[i]) head[d[i]] = false;
	}
	for (int i = 1; i <= n; i++) {
		if (!head[i]) continue;
		int pos = i;
		while (d[pos]) pos = d[pos];
		from.emplace_back(x[i], y[i]);
		to.emplace_back(x[pos], y[pos]);
	}
	writeln(from.size());
	for (unsigned i = 0; i < from.size(); i++)
		printf("%d %d %d %d\n", from[i].first, from[i].second, to[i].first, to[i].second);
	return 0;
}

**【G】**New Road Network

【思路要点】

  • S i S_i 表示包含节点 i i 的社区集合。
  • 若一个社区仅包含 1 1 个节点,那么删去它不会产生影响。
  • 否则,一个社区将包含至少 2 2 个节点,这意味着若树上的一个叶子结点 x x 的父亲为 y y ,应当有 S x S y S_x\subseteq S_y 。并且,我们能够说明,当 S x S y S_x\subseteq S_y ,并且存在可行解,我们必然能够将该解其调整至 x x 为一个叶子结点,且其父亲为 y y
  • 上述观察给出了一个 O ( N 3 M w ) O(N^3*\frac{M}{w}) 的做法。
  • 接下来便是本题厉害的地方:若我们构建一张无向图, i i j j 之间的边边权为 S i S j |S_i \cap S_j| ,上述算法本质上是在寻找这张图的一棵最大生成树,因此,我们求解一棵最大生成树,再检验其是否合法即可。
  • 时间复杂度 O ( N 2 ( L o g N + M w ) ) O(N^2*(LogN+\frac{M}{w}))

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e3 + 5;
const int MAXM = MAXN * MAXN;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, f[MAXN], ansx[MAXN], ansy[MAXN];
int x[MAXM], y[MAXM], len[MAXM], pos[MAXM];
bitset <MAXN> a[MAXN], b[MAXN], now;
vector <int> e[MAXN];
int F(int x) {
	if (f[x] == x) return x;
	else return f[x] = F(f[x]);
}
void work(int pos) {
	if (now[pos]) now[pos] = 0;
	else return;
	for (auto x : e[pos]) work(x);
}
bool check() {
	for (int i = 1; i <= n; i++)
		e[i].clear();
	for (int i = 1; i <= n - 1; i++) {
		e[ansx[i]].push_back(ansy[i]);
		e[ansy[i]].push_back(ansx[i]);
	}
	for (int i = 1; i <= m; i++) {
		now = a[i];
		work(now._Find_first());
		if (now.count()) return false;
	}
	return true;
}
int main() {
	int T; read(T);
	while (T--) {
		read(n), read(m);
		for (int i = 1; i <= n || i <= m; i++)
			a[i].reset(), b[i].reset();
		for (int i = 1; i <= m; i++) {
			static char s[MAXN];
			scanf("\n%s", s + 1);
			for (int j = 1; j <= n; j++)
				a[i][j] = b[j][i] = s[j] - '0';
		}
		int tot = 0;
		for (int i = 1; i <= n; i++)
		for (int j = i + 1; j <= n; j++) {
			tot++, pos[tot] = tot;
			x[tot] = i, y[tot] = j, len[tot] = (b[i] & b[j]).count();
		}
		sort(pos + 1, pos + tot + 1, [&] (int x, int y) {return len[x] > len[y];});
		for (int i = 1; i <= n; i++)
			f[i] = i;
		int cnt = 0;
		for (int i = 1; i <= tot; i++) {
			int tmp = pos[i];
			if (F(x[tmp]) != F(y[tmp])) {
				f[F(x[tmp])] = F(y[tmp]), cnt++;
				ansx[cnt] = x[tmp];
				ansy[cnt] = y[tmp];
			}
		}
		if (check()) {
			printf("YES\n");
			for (int i = 1; i <= cnt; i++)
				printf("%d %d\n", ansx[i], ansy[i]);
		} else printf("NO\n");
	}
	return 0;
}

**【H】**Epic Convolution

【思路要点】

  • 首先,模数 P = 490019 P=490019 ,为一个质数, φ ( P ) = 490018 = 2 491 499 \varphi(P)=490018=2*491*499
  • c k = i 2 j 3 % 490018 = k a i b j c_k=\sum_{i^2*j^3\%490018=k}a_i*b_j ,若能求出 c k c_k ,那么答案即为 k = 0 490017 c k x k \sum_{k=0}^{490017} c_k*x^k
  • 由中国剩余定理,考虑 i 2 j 3 i^2*j^3 490018 490018 的余数,即考虑 i 2 j 3 i^2*j^3 2 , 491 , 499 2,491,499 分别的余数。
  • 由于 491 , 499 491,499 均为奇质数,它们分别存在原根。 491 491 存在原根 2 2 499 499 存在原根 7 7 。因此,我们分别取 i 2 i^2 j 3 j^3 的离散对数能够将下标上的乘法转化为加法,而模 2 2 意义下的乘法相当于 &amp; \&amp; 运算。
  • 于是,问题转化为了求 c i , j , k = i 1 &amp; i 2 = i , j 1 + j 2 = j , k 1 + k 2 = k a i 1 , j 1 , k 1 b i 2 , j 2 , k 2 c_{i,j,k}=\sum_{i_1\&amp;i_2=i,j_1+j_2=j,k_1+k_2=k}a_{i_1,j_1,k_1}*b_{i_2,j_2,k_2} 。在第一维上 F W T FWT ,第二、第三维上 F F T FFT 即可。
  • 上述算法还有个小问题:在 i 2 i^2 j 3 j^3 491 491 499 499 的倍数时,不存在符合要求离散对数,但这样的位置至多只有 O ( N + M P ) O(\frac{N+M}{\sqrt{P}}) 个,因此在这些位置上暴力即可。
  • 时间复杂度 O ( P L o g P + N M P ) O(PLogP+\frac{N*M}{\sqrt{P}}) ,其中 P = 490019 P=490019

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 490019;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int power(int x, int y, int p) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2, p);
	if (y % 2 == 0) return 1ll * tmp * tmp % p;
	else return 1ll * tmp * tmp % p * x % p;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
struct point {long double x, y; };
point operator + (point a, point b) {return (point) {a.x + b.x, a.y + b.y}; }
point operator - (point a, point b) {return (point) {a.x - b.x, a.y - b.y}; }
point operator * (point a, point b) {return (point) {a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x}; }
point operator / (point a, long double x) {return (point) {a.x / x, a.y / x}; }
namespace FFT {
	const int MAXN = 1024;
	const long double pi = acos(-1);
	int N, Log, home[MAXN];
	void FFTinit() {
		N = 1024, Log = 10;
		for (int i = 0; i < N; i++) {
			int tmp = i, ans = 0;
			for (int j = 1; j <= Log; j++) {
				ans <<= 1;
				ans += tmp & 1;
				tmp >>= 1;
			}
			home[i] = ans;
		}
	}
	void FFT(point *a, int mode) {
		for (int i = 0; i < N; i++)
			if (home[i] < i) swap(a[i], a[home[i]]);
		for (int len = 2; len <= N; len <<= 1) {
			point delta = (point) {cos(2 * pi / len * mode), sin(2 * pi / len * mode)};
			for (int i = 0; i < N; i += len) {
				point now = (point) {1, 0};
				for (int j = i, k = i + len / 2; k < i + len; j++, k++) {
					point tmp = a[j];
					point tnp = a[k] * now;
					a[j] = tmp + tnp;
					a[k] = tmp - tnp;
					now = now * delta;
				}
			}
		}
		if (mode == -1) {
			for (int i = 0; i < N; i++)
				a[i] = a[i] / N;
		}
	}
}
int n, m, v, val[MAXN], vbl[MAXN];
bool flga[MAXN], flgb[MAXN]; int merg[2][1024][1024];
int a[2][1024][1024], b[2][1024][1024]; int c[P];
int p491[1024], p499[1024], log491[1024], log499[1024];
void multiply() {
	FFT :: FFTinit();
	static point res[2][1024][1024], tmp[1024], tnp[1024];
	static point ta[2][1024][1024], tb[2][1024][1024];
	for (int i = 0; i <= 1; i++)
	for (int j = 0; j <= 1023; j++) {
		for (int k = 0; k <= 1023; k++) {
			tmp[k] = (point) {(double) a[i][j][k], 0};
			tnp[k] = (point) {(double) b[i][j][k], 0};
		}
		FFT :: FFT(tmp, 1);
		FFT :: FFT(tnp, 1);
		for (int k = 0; k <= 1023; k++) {
			ta[i][j][k] = tmp[k];
			tb[i][j][k] = tnp[k];
		}
	}
	for (int i = 0; i <= 1; i++)
	for (int k = 0; k <= 1023; k++) {
		for (int j = 0; j <= 1023; j++) {
			tmp[j] = ta[i][j][k];
			tnp[j] = tb[i][j][k];
		}
		FFT :: FFT(tmp, 1);
		FFT :: FFT(tnp, 1);
		for (int j = 0; j <= 1023; j++) {
			ta[i][j][k] = tmp[j];
			tb[i][j][k] = tnp[j];
		}
	}
	for (int i = 0; i <= 1023; i++)
	for (int j = 0; j <= 1023; j++) {
		ta[0][i][j] = ta[0][i][j] + ta[1][i][j];
		tb[0][i][j] = tb[0][i][j] + tb[1][i][j];
		res[0][i][j] = ta[0][i][j] * tb[0][i][j];
		res[1][i][j] = ta[1][i][j] * tb[1][i][j];
	}
	for (int i = 0; i <= 1; i++)
	for (int j = 0; j <= 1023; j++) {
		for (int k = 0; k <= 1023; k++)
			tmp[k] = res[i][j][k];
		FFT :: FFT(tmp, -1);
		for (int k = 0; k <= 1023; k++)
			res[i][j][k] = tmp[k];
	}
	for (int i = 0; i <= 1; i++)
	for (int k = 0; k <= 1023; k++) {
		for (int j = 0; j <= 1023; j++)
			tmp[j] = res[i][j][k];
		FFT :: FFT(tmp, -1);
		for (int j = 0; j <= 1023; j++)
			res[i][j][k] = tmp[j];
	}
	for (int i = 0; i <= 1023; i++)
	for (int j = 0; j <= 1023; j++) {
		res[0][i][j] = res[0][i][j] - res[1][i][j];
		update(c[merg[0][p491[i]][p499[j]]], (ll) (res[0][i][j].x + 0.5) % P);
		update(c[merg[1][p491[i]][p499[j]]], (ll) (res[1][i][j].x + 0.5) % P);
	}
}
int main() {
	read(n), read(m), read(v);
	for (int i = 0, ta = 1, tb = 1; i <= 1023; i++, ta = ta * 2 % 491, tb = tb * 7 % 499) {
		p491[i] = ta, p499[i] = tb;
		if (i <= 491 - 2) log491[ta] = i;
		if (i <= 499 - 2) log499[tb] = i;
	}
	for (int i = 0; i <= P - 1; i++) {
		assert(merg[i % 2][i % 491][i % 499] == 0);
		merg[i % 2][i % 491][i % 499] = i;
	}
	for (int i = 0; i <= n - 1; i++) {
		read(val[i]);
		int tmp = 1ll * i * i % (P - 1);
		if (tmp % 491 && tmp % 499) update(a[tmp & 1][log491[tmp % 491]][log499[tmp % 499]], val[i]);
		else flga[i] = true;
	}
	for (int i = 0; i <= m - 1; i++) {
		read(vbl[i]);
		int tmp = 1ll * i * i * i % (P - 1);
		if (tmp % 491 && tmp % 499) update(b[tmp & 1][log491[tmp % 491]][log499[tmp % 499]], vbl[i]);
		else flgb[i] = true;
	}
	multiply();
	for (int i = 0; i <= n - 1; i++) {
		if (!flga[i]) continue;
		for (int j = 0; j <= m - 1; j++) {
			int tmp = 1ll * i * i % (P - 1) * j % (P - 1) * j % (P - 1) * j % (P - 1);
			update(c[tmp], 1ll * val[i] * vbl[j] % P);
		}
	}
	for (int j = 0; j <= m - 1; j++) {
		if (!flgb[j]) continue;
		for (int i = 0; i <= n - 1; i++) {
			if (flga[i]) continue;
			int tmp = 1ll * i * i % (P - 1) * j % (P - 1) * j % (P - 1) * j % (P - 1);
			update(c[tmp], 1ll * val[i] * vbl[j] % P);
		}
	}
	int ans = 0;
	for (int i = 0; i <= P - 1; i++)
		update(ans, 1ll * c[i] * power(v, i, P) % P);
	writeln(ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39972971/article/details/83241357