UNR#4 Day2
A. 同构判定鸭
既然要输出字典序最小的坏串,直觉是它肯定不长(证明不会,看 官解),考虑确定坏串的长度。
设 \(S_{u, k}\) 表示从 \(u\) 出发长度为 \(k\) 的路径的配对串的集合,我们要做的就是找到最小的 \(k\),使得 \(\bigcup {S_1}_{u, k} \ne \bigcup {S_2}_{u, k}\)。
Hash 即可。
确定长度之后贪心确定坏串,需要记录长度为 \(k\) 的路径中以某个字母开头的哈希值,可以 DP 实现。
时间复杂度 \(\mathcal O(nm)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr int N = 510, M = 3010, MOD = 1e9 + 7;
mt19937 eng(random_device{}());
int w[N << 1][128];
struct Graph {
int n, m;
int hs[N << 1], val[N], con[N], f[N << 1][N];
struct Edge {int u, v; char c;} e[M];
inline void init() {fill(val + 1, val + n + 1, 1);}
inline void lock() {memcpy(val, con, sizeof(val));}
void input() {
cin >> n >> m;
for (int i = 1; i <= m; i++) cin >> e[i].u >> e[i].v >> e[i].c;
init();
}
int calc(int x) {
memset(con, 0, sizeof(con));
for (int i = 1; i <= m; i++) con[e[i].v] = (con[e[i].v] + 1ll * val[e[i].u] * w[x][e[i].c]) % MOD;
int res = 0;
for (int i = 1; i <= n; i++) res = (res + con[i]) % MOD;
return lock(), res;
}
void dp(int T) {
for (int i = 1; i <= n; i++) f[T + 1][i] = 1;
for (int i = T; i; i--) for (int j = 1; j <= m; j++) {
f[i][e[j].u] = (f[i][e[j].u] + 1ll * w[i][e[j].c] * f[i + 1][e[j].v]) % MOD;
}
}
int spec(int x, char c) {
memset(con, 0, sizeof(con));
for (int i = 1; i <= m; i++) if (e[i].c == c) con[e[i].v] = (con[e[i].v] + 1ll * val[e[i].u] * w[x][e[i].c]) % MOD;
int res = 0;
for (int i = 1; i <= n; i++) res = (res + 1ll * con[i] * f[x + 1][i]) % MOD;
return res;
}
} G1, G2;
int main() {
ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
G1.input(), G2.input();
int n = 1e5;
for (int i = 1; i <= n; i++) for (int j = 'a'; j <= 'z'; j++) w[i][j] = eng() % MOD;
int len = 0;
for (int i = 1; i <= n; i++) if (G1.calc(i) != G2.calc(i)) {len = i; break;}
if (!len) {cout << "Same"; return 0;}
G1.dp(len), G2.dp(len);
G1.init(), G2.init();
for (int i = 1; i <= len; i++) for (char c = 'a'; c <= 'z'; c++) {
if (G1.spec(i, c) != G2.spec(i, c)) {
cout << c;
G1.lock(), G2.lock();
break;
}
}
return 0;
}
B. 己酸集合
对每个 \(j\) 求满足 \(x_i^2 + (y_i - z_j)^2 \le R_j^2\) 的 \(i\) 的个数。
展开,写成 \(x_i^2 + y_i^2 \le 2z_jy_i + R_j^2 - z_j^2\)。
记 \(X_i = y_i, Y_i = x_i^2 + y_i^2, k_j = 2z_j, b_j = R_j^2 - z_j^2\),则原式又可以写作:\(k_jX_i + b_j \ge Y_i\),即 \((X_i, Y_i)\) 在直线 \(l_j: y = k_jx + b_j\) 的下方。
对每个询问暴力判断,时间复杂度 \(\mathcal O(qn)\)。
把询问按 \(k_j\) 离线,维护点的相对顺序,然后二分。
具体地,在一个确定的 \((k, b)\) 下,\((X_i, Y_i)\) 优于 \((X_j, Y_j)\) 当且仅当 \(Y_i - kX_i < Y_j - kX_j\),又因为最开始 \(k \to -\infin\),所以按 \(X\) 升序(\(X\) 相同时按 \(Y\) 升序)排序是对的,然后需要每个满足 \(i < j\) 的点对 \((i, j)\) 求出什么时候交换位置即可。
-
如果 \(X_i = X_j\),那不可能有位置的交换。
-
否则则由最初的排序有 \(X_j > X_i\),要满足的新式子是 \(Y_j - kX_j < Y_i - kX_i\),即 \(k(X_i - X_j) < Y_i - Y_j\),同除 \(X_i - X_j\) 不等号方向要改变,即 \(k > \dfrac{Y_i - Y_j}{X_i - X_j}\),然后我们就知道何时 \((i, j)\) 应交换位置了。
最后对每个询问在维护相对顺序的同时二分查找就能数出个数了,时间复杂度 \(\mathcal O((n^2 + q)\log n)\)。
分块平衡一下预处理和查询的时间复杂度,设块长为 \(B\),则时间复杂度为 \(\mathcal O\left(\left(nB + \dfrac{qn}B\right)\log n\right)\)。
取 \(B = \sqrt q\) 最优,时间复杂度 \(\mathcal O\left(n\sqrt q\log n\right)\)。
实际实现时,因为 \(\mathcal O(nB\log n)\) 的预处理部分的常数比较大,所以可以给 \(B\) 带一个 \(\left(\dfrac12, 1\right)\) 中的系数再次平衡,我带的是 \(\dfrac23\)。
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N = 12010, Q = 1e6 + 10;
int n, qn, ans[Q], pos[N];
struct Point {
ll x, y;
Point() {}
Point(ll x, ll y) : x(y), y(x * x + y * y) {}
bool operator<(const Point &rhs) const {return x != rhs.x ? x < rhs.x : y < rhs.y;}
} p[N];
struct Query {
int k; ll b; int id;
Query() {}
Query(ll z, ll r, int id) : k(z << 1), b(r * r - z * z), id(id) {}
bool operator<(const Query &rhs) const {return k != rhs.k ? k < rhs.k : b < rhs.b;}
} q[Q];
struct Change {
int x, y; double k;
bool operator<(const Change &rhs) const {return k < rhs.k;}
void work() {if (pos[x] < pos[y]) swap(p[pos[x]], p[pos[y]]), swap(pos[x], pos[y]);}
} c[Q];
int main() {
ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> qn;
for (int i = 1, x, y; i <= n; i++) cin >> x >> y, p[i] = Point(x, y);
for (int i = 1, z, r; i <= qn; i++) cin >> z >> r, q[i] = Query(z, r, i);
sort(q + 1, q + qn + 1);
auto getans = [&](int i, int L, int R) -> void {
int l = L, r = R, mid, res = L - 1;
while (l <= r) {
mid = (l + r) >> 1;
if (q[i].k * p[mid].x + q[i].b >= p[mid].y) res = mid, l = mid + 1;
else r = mid - 1;
}
ans[q[i].id] += res - L + 1;
};
auto solve = [&](int l, int r) -> void {
int m = 0;
for (int i = l; i <= r; i++) for (int j = i + 1; j <= r; j++) if (p[i].x != p[j].x) {
double k = 1.0 * (p[i].y - p[j].y) / (p[i].x - p[j].x);
if (k <= q[qn].k) c[++m] = Change{i, j, k};
}
stable_sort(c + 1, c + m + 1);
for (int i = 1, j = 1; i <= qn; i++) {
while (j <= m && c[j].k < q[i].k) c[j++].work();
getans(i, l, r);
}
};
sort(p + 1, p + n + 1), iota(pos + 1, pos + n + 1, 1);
int len = sqrt(qn) / 1.5, m = (n + len - 1) / len;
for (int i = 1; i <= m; i++) solve((i - 1) * len + 1, min(i * len, n));
for (int i = 1; i <= qn; i++) cout << ans[i] << '\n';
return 0;
}
C. 挑战哈密顿
咕咕咕咕咕咕………………