Codeforces 566C Logistical Questions
考虑如下做法:选择一条边 ,计算 ,若 较小,则只考虑将中心设置在 所在的子树,否则,只考虑将中心设置在 所在的子树。
考虑其正确性,首先,移除必须在节点处设置中心的限制,我们认为任何地方都可以设置中心。
对于树上的任意一条路径,各个点关于中心坐标的代价是一个凸函数,这意味着其导数是单调的,若干个单调的函数相加时,得到的依然是单调函数。
因此,对于树上的任意一条路径,存在唯一的最优解,从而上述算法是正确的。
那么,对原树进行边分治即可,边分治新增的边长可以看做一个趋于零的数 ,两边 相同时,可以比较两侧的导数。
时间复杂度为 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 4e5 + 5;
const double eps = 1e-11;
typedef long long ll;
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;
}
int n, tot, w[MAXN], size[MAXN], dist[MAXN];
int belong[MAXN], x[MAXN], y[MAXN], l[MAXN];
vector <pair <int, int>> a[MAXN], b[MAXN];
bool side[MAXN], val[MAXN];
void dfs(int pos, int fa, bool col) {
size[pos] = val[pos];
side[pos] = col;
for (auto x : a[pos])
if (x.first != fa) {
dist[x.first] = dist[pos] + x.second;
dfs(x.first, pos, col);
size[pos] += size[x.first];
}
}
bool judge(int x, int y) {
dist[x] = 0, dfs(x, 0, true);
double costx = 0, costy = 0;
for (int i = 1; i <= tot; i++)
costx += w[i] * sqrt(dist[i]) * dist[i];
dist[y] = 0, dfs(y, 0, false);
for (int i = 1; i <= tot; i++)
costy += w[i] * sqrt(dist[i]) * dist[i];
if (fabs(costx - costy) / fabs(costx) >= eps) {
if (costx > costy) return false;
else return true;
}
dist[x] = 0, dfs(x, y, true);
dist[y] = 0, dfs(y, x, false);
double derx = 0, dery = 0;
for (int i = 1; i <= tot; i++)
if (side[i]) dery += w[i] * sqrt(dist[i]);
else derx += w[i] * sqrt(dist[i]);
return derx < dery;
}
void col(int pos, int fa) {
val[pos] = false;
for (auto x : a[pos])
if (x.first != fa) col(x.first, pos);
}
int findans(int cnt) {
while (cnt != 1) {
pair <int, int> e; int div = cnt;
dfs(1, 0, true);
for (int i = 1; i <= tot; i++)
for (auto x : a[i]) {
int tmp = min(size[i], size[x.first]);
chkmax(tmp, cnt - tmp);
if (tmp < div) {
div = tmp;
e = make_pair(i, x.first);
}
}
if (judge(e.first, e.second)) col(e.second, e.first);
else col(e.first, e.second);
cnt = 0;
for (int i = 1; i <= tot; i++)
cnt += val[i];
}
for (int i = 1; i <= tot; i++)
if (val[i]) return i;
assert(false);
return -1;
}
void addedge(int x, int y, int z) {
a[x].emplace_back(y, z);
a[y].emplace_back(x, z);
}
void rebuild(int pos, int fa) {
int last = 0, len = 0;
for (auto x : b[pos])
if (x.first != fa) {
rebuild(x.first, pos);
if (last == 0) last = x.first, len = x.second;
else {
addedge(++tot, last, len);
addedge(tot, x.first, x.second);
belong[tot] = pos;
last = tot, len = 0;
}
}
if (last) addedge(pos, last, len);
}
int main() {
read(n), tot = n;
for (int i = 1; i <= n; i++)
read(w[i]), belong[i] = i;
for (int i = 1; i <= n - 1; i++) {
read(x[i]), read(y[i]), read(l[i]);
b[x[i]].push_back(make_pair(y[i], l[i]));
b[y[i]].push_back(make_pair(x[i], l[i]));
}
rebuild(1, 0);
for (int i = 1; i <= tot; i++)
val[i] = true;
int ans = findans(tot); double bns = 0;
dist[ans] = 0, dfs(ans, 0, true);
for (int i = 1; i <= tot; i++)
bns += w[i] * sqrt(dist[i]) * dist[i];
printf("%d %.10lf\n", belong[ans], bns);
return 0;
}
Codeforces 566E Restoring Map
当树是菊花时,所有点的集合均为全集,可以特判。
考虑找到树上的一个叶子结点以及与其相连的一条边,把它们删去,迭代 次。
叶子节点对应的集合一定被与其相邻的一个点的集合包含,并且,满足该条件的大小最小的集合也一定是叶子节点对应的集合,考虑首先找到大小最小的,存在另一个集合包含它的集合 。
中包含了到该叶子节点距离在 中的数,取其中在所有集合中出现次数最少的点作为所找到的叶子 ,本部分的复杂度可以通过只枚举树上相邻点的集合,即交集大小超过 的集合来保证。
考虑找到与 相连的点 的候选集合 ,令 为所有与 交集在 以上的集合的交集。
可以证明, 的大小不超过 ,分类讨论几类情况可以发现,当且仅当树的形态是中心相邻的两个菊花, ,且无法区分 与 中的哪一个元素相邻。
那么,找到菊花的中心,将剩余的点接在中心上即可。注意将 接在某个点 上时,须增加 “ 不能与 中不存在的点相邻” 的限制,用于判断在菊花中心处的拼接。
时间复杂度 。
题解中的做法提到了一种更简单的思路:若两个集合的交集为 ,则我们可以确定树上的一条边,这样我们可以找到所有非叶节点之间的边。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
typedef long long ll;
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;
}
int n, cnt[MAXN];
vector <int> adj[MAXN];
bitset <MAXN> a[MAXN], d[MAXN];
bool visa[MAXN], mark[MAXN];
void solve(int lft) {
bitset <MAXN> lst; lst.set();
for (int i = 1; i <= n; i++)
if (!visa[i]) {
bool flg = false;
for (auto x : adj[i])
if ((a[x] & a[i]) == a[i]) {
flg = true;
break;
}
if (flg && a[i].count() < lst.count()) lst = a[i];
}
int Min = n + 1, pos = 0;
for (int i = 1; i <= n; i++)
if (!mark[i] && lst[i] && cnt[i] < Min) {
Min = cnt[i];
pos = i;
}
if (Min == lft) {
for (int i = 1; i <= n; i++)
if (!mark[i] && i != pos) printf("%d %d\n", i, pos);
return;
}
bitset <MAXN> tmp; tmp.set();
for (int i = 1; i <= n; i++)
if (!visa[i] && a[i][pos]) tmp &= a[i];
tmp.reset(pos);
bitset <MAXN> tnp = tmp;
for (int i = 1; i <= n; i++)
if (!visa[i] && (tnp & a[i]).count() >= 2) tmp &= a[i];
assert(tmp.count() == 1 || tmp.count() == 2);
int res = tmp._Find_first();
if (tmp.count() == 2) {
int x = res, y = tmp._Find_next(x); bool flg = false;
for (int i = 1; i <= n; i++)
if (!visa[i] && !a[i][pos]) {
if (a[i][y] ^ a[i][x]) {
flg = true;
if (a[i][y]) res = x;
else res = y;
}
}
if (!flg) {
printf("%d %d\n", x, y);
mark[x] = mark[y] = true;
int val = 0;
for (int i = 1; i <= n; i++)
if (!mark[i]) val = i;
vector <int> s, t;
for (int i = 1; i <= n; i++)
if (!visa[i] && a[i].count() < lft && a[i][val]) {
for (int j = a[i]._Find_first(); j <= n; j = a[i]._Find_next(j)) {
if (!mark[j]) {
mark[j] = true;
s.push_back(j);
}
}
break;
}
for (int i = 1; i <= n; i++)
if (!mark[i]) t.push_back(i);
for (auto v : s) if (d[v][x]) swap(x, y);
for (auto v : t) if (d[v][y]) swap(x, y);
for (auto v : s) printf("%d %d\n", x, v);
for (auto v : t) printf("%d %d\n", y, v);
return;
}
}
printf("%d %d\n", pos, res);
int home = 0;
mark[pos] = true, cnt[pos] = 0;
for (int i = 1; i <= n; i++)
if (!visa[i] && a[i][pos]) {
a[i].reset(pos);
if (home == 0 || a[i].count() <= a[home].count()) home = i;
}
visa[home] = true;
for (int i = 1; i <= n; i++)
if (a[home][i]) cnt[i]--;
d[res] |= ~a[home];
solve(lft - 1);
}
int main() {
read(n);
for (int i = 1; i <= n; i++) {
int x; read(x);
while (x--) {
int y; read(y), cnt[y]++;
a[i].set(y);
}
}
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++) {
if ((a[i] & a[j]).count() >= 4) {
adj[i].push_back(j);
adj[j].push_back(i);
}
if ((a[i] & a[j]) == a[i]) adj[i].push_back(i);
if ((a[i] & a[j]) == a[j]) adj[j].push_back(j);
}
solve(n);
return 0;
}
Codeforces 568C New Language
逐位确定答案,并用 2-SAT 判断可行性即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 405;
typedef long long ll;
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;
}
char s[MAXN], lim[MAXN], ans[MAXN];
int n, m, l, edge[MAXN][MAXN], tot, point[MAXN][2];
int timer, dfn[MAXN], low[MAXN], cnt, belong[MAXN];
int top, Stack[MAXN]; bool instack[MAXN];
void work(int pos) {
dfn[pos] = low[pos] = ++timer;
Stack[++top] = pos, instack[pos] = true;
for (int i = 1; i <= tot; i++)
if (edge[pos][i]) {
if (dfn[i] == 0) {
work(i);
chkmin(low[pos], low[i]);
} else if (instack[i]) chkmin(low[pos], dfn[i]);
}
if (low[pos] == dfn[pos]) {
int tmp = Stack[top--];
belong[tmp] = ++cnt;
instack[tmp] = false;
while (tmp != pos) {
tmp = Stack[top--];
belong[tmp] = cnt;
instack[tmp] = false;
}
}
}
bool check() {
timer = cnt = 0, memset(dfn, 0, sizeof(dfn));
for (int i = 1; i <= tot; i++)
if (dfn[i] == 0) work(i);
for (int i = 1; i <= n; i++)
if (belong[point[i][0]] == belong[point[i][1]]) return false;
return true;
}
void limit(int pos, bool type, int d) {
edge[point[pos][type ^ 1]][point[pos][type]] += d;
}
int main() {
scanf("\n%s", s + 1);
l = strlen(s + 1), read(n), read(m);
int cnt[2] = {0, 0};
for (int i = 1; i <= l; i++)
cnt[s[i] == 'V']++;
for (int i = 1; i <= n; i++) {
point[i][0] = ++tot;
point[i][1] = ++tot;
}
if (cnt[0] == 0) {
for (int i = 1; i <= n; i++)
limit(i, 1, 1);
}
if (cnt[1] == 0) {
for (int i = 1; i <= n; i++)
limit(i, 0, 1);
}
for (int i = 1; i <= m; i++) {
int x, y; char tx, ty;
scanf("%d %c %d %c", &x, &tx, &y, &ty);
edge[point[x][tx == 'V']][point[y][ty == 'V']]++;
edge[point[y][ty != 'V']][point[x][tx != 'V']]++;
}
scanf("\n%s", lim + 1);
for (int i = 1; i <= n; i++)
limit(i, s[lim[i] - 'a' + 1] == 'V', 1);
if (check()) {
printf("%s\n", lim + 1);
return 0;
}
for (int i = 1; i <= n; i++)
limit(i, s[lim[i] - 'a' + 1] == 'V', -1);
for (int p = n; p >= 1; p--) {
for (int i = 1; i <= p - 1; i++)
limit(i, s[lim[i] - 'a' + 1] == 'V', 1);
int q[2] = {l + 1, l + 1};
for (int i = l; i >= lim[p] - 'a' + 2; i--)
q[s[i] == 'V'] = i;
int nxt = l + 1;
limit(p, 0, 1);
if (q[0] != l + 1 && check()) chkmin(nxt, q[0]);
limit(p, 0, -1);
limit(p, 1, 1);
if (q[1] != l + 1 && check()) chkmin(nxt, q[1]);
limit(p, 1, -1);
if (nxt != l + 1) {
for (int i = 1; i <= p - 1; i++)
ans[i] = lim[i];
ans[p] = nxt + 'a' - 1;
limit(p, s[nxt] == 'V', 1);
int r[2] = {l + 1, l + 1};
for (int i = l; i >= 1; i--)
r[s[i] == 'V'] = i;
for (int i = p + 1; i <= n; i++) {
ans[i] = l + 'a';
limit(i, 0, 1);
if (r[0] != l + 1 && check()) chkmin(ans[i], (char) (r[0] + 'a' - 1));
limit(i, 0, -1);
limit(i, 1, 1);
if (r[1] != l + 1 && check()) chkmin(ans[i], (char) (r[1] + 'a' - 1));
limit(i, 1, -1);
limit(i, s[ans[i] - 'a' + 1] == 'V', 1);
}
printf("%s\n", ans + 1);
return 0;
}
for (int i = 1; i <= p - 1; i++)
limit(i, s[lim[i] - 'a' + 1] == 'V', -1);
}
puts("-1");
return 0;
}
Codeforces 568E Longest Increasing Subsequence
考虑首先填入作为最长上升子序列出现的元素,再任意填入剩余元素。
那么,可以认为,填入的数字是两两不同的。
考虑对于 非 的位置,记 表示以 结尾的最长上升子序列的长度。
转移时考虑枚举 前 的个数 ,则显然应当取最近的 个 填入恰小于 的 个数,再取满足 小于所填入的第一个数 的 的最大值 更新答案。
那么,从前到后进行 DP ,中途维护 ,支持 询问,每当遇到 便进行转移即可。
令 同阶,用分块维护 ,时间复杂度 。
若注意到 的递增性,可以优化至 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
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;
}
namespace Blocks {
const int MAXN = 2e5 + 5;
int n, block, tot, l[MAXN], r[MAXN], belong[MAXN];
pair <int, int> pre[MAXN], part[MAXN];
void init(int x) {
n = x, block = sqrt(x);
for (int i = 1; i <= n; i++) {
if (i % block == 1 % block) l[++tot] = i;
r[tot] = i, belong[i] = tot;
}
}
void modify(int x, pair <int, int> val) {
for (int i = x; i <= r[belong[x]]; i++)
chkmax(part[i], val);
for (int i = belong[x]; i <= tot; i++)
chkmax(pre[i], val);
}
pair <int, int> query(int x) {
return max(pre[belong[x] - 1], part[x]);
}
}
set <int> st;
map <int, int> rnk, home;
int dp[MAXN], from[MAXN], type[MAXN], lft[MAXN];
int n, m, k, tot, a[MAXN], b[MAXN], c[MAXN], bak[MAXN];
int main() {
read(n);
for (int i = 1; i <= n; i++) {
read(a[i]);
if (a[i] != -1) st.insert(a[i]);
}
read(m);
for (int i = 1; i <= m; i++) {
read(b[i]);
st.insert(b[i]);
}
sort(b + 1, b + m + 1);
for (auto x : st) {
rnk[x] = ++tot;
home[tot] = x;
}
for (int i = 1; i <= n; i++)
if (a[i] != -1) a[i] = rnk[a[i]];
for (int i = 1; i <= m; i++) {
b[i] = rnk[b[i]], lft[b[i]]++;
if (b[i] != c[k]) c[++k] = b[i];
}
a[n + 1] = tot + 1;
for (int i = 1; i <= n + 1; i++)
if (a[i] != -1) bak[i] = lower_bound(c + 1, c + k + 1, a[i]) - c;
Blocks :: init(tot);
for (int i = 1; i <= n + 1; i++) {
if (a[i] == -1) {
int cnt = 0;
for (int j = i; j <= n + 1; j++)
if (a[j] == -1) cnt++;
else if (bak[j] > cnt) {
pair <int, int> tmp = Blocks :: query(c[bak[j] - cnt] - 1);
if (tmp.first + cnt + 1 > dp[j]) {
dp[j] = tmp.first + cnt + 1;
from[j] = tmp.second;
type[j] = cnt;
}
}
} else {
pair <int, int> tmp = Blocks :: query(a[i] - 1);
if (tmp.first + 1 > dp[i]) {
dp[i] = tmp.first + 1;
from[i] = tmp.second;
type[i] = 0;
}
Blocks :: modify(a[i], make_pair(dp[i], i));
}
}
int now = n + 1;
while (now != 0) {
int cnt = type[now], pos = bak[now];
for (int i = now; cnt; i--)
if (a[i] == -1) {
a[i] = c[--pos];
lft[c[pos]]--;
cnt--;
}
now = from[now];
}
int tmp = 1;
for (int i = 1; i <= n; i++)
if (a[i] == -1) {
while (lft[tmp] == 0) tmp++;
lft[tmp]--, a[i] = tmp;
}
for (int i = 1; i <= n; i++)
printf("%d ", home[a[i]]);
return 0;
}
Codeforces 571D Campus
离线询问,考虑对于每个询问 ,计算 上次被清空的时刻,然后不计清空操作,计算两个时刻人数的差值。
计算 上次被清空的时刻,以及不计清空操作维护人数都可以通过启发式合并 vector 完成。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
typedef long long ll;
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("");
}
char opt[MAXN];
int n, m, x[MAXN], y[MAXN]; ll ans[MAXN];
vector <pair <int, int>> mns[MAXN];
vector <int> a[MAXN]; int tag[MAXN];
pair <int, int> last[MAXN]; int f[MAXN];
ll all[MAXN], self[MAXN];
int find(int x) {
if (f[x] == x) return x;
else return f[x] = find(f[x]);
}
int queryTimer(int x) {
int y = find(x);
if (tag[y] > last[x].first) return tag[y];
else return last[x].second;
}
ll querySum(int x) {
int y = find(x);
return all[y] + self[x];
}
int main() {
read(n), read(m);
for (int i = 1; i <= m; i++) {
scanf("\n%c", &opt[i]);
if (opt[i] == 'U' || opt[i] == 'M') read(x[i]), read(y[i]);
else read(x[i]);
}
for (int i = 1; i <= n; i++) {
f[i] = i, tag[i] = 0;
last[i] = make_pair(0, 0);
a[i].clear(), a[i].push_back(i);
}
for (int i = 1; i <= m; i++) {
if (opt[i] == 'Q') mns[queryTimer(x[i])].emplace_back(x[i], i);
if (opt[i] == 'Z') tag[find(x[i])] = i;
if (opt[i] == 'M') {
int tx = find(x[i]), ty = find(y[i]);
if (a[tx].size() > a[ty].size()) swap(tx, ty);
for (auto x : a[tx]) {
a[ty].push_back(x);
last[x] = make_pair(i, queryTimer(x));
}
f[tx] = ty;
}
}
for (int i = 1; i <= n; i++) {
f[i] = i, all[i] = self[i] = 0;
a[i].clear(), a[i].push_back(i);
}
for (int i = 1; i <= m; i++) {
for (auto x : mns[i]) ans[x.second] -= querySum(x.first);
if (opt[i] == 'Q') ans[i] += querySum(x[i]);
if (opt[i] == 'A') all[find(x[i])] += a[find(x[i])].size();
if (opt[i] == 'U') {
int tx = find(x[i]), ty = find(y[i]);
if (a[tx].size() > a[ty].size()) swap(tx, ty);
for (auto x : a[tx]) {
a[ty].push_back(x);
self[x] = querySum(x) - all[ty];
}
f[tx] = ty;
}
}
for (int i = 1; i <= m; i++)
if (opt[i] == 'Q') writeln(ans[i]);
return 0;
}
Codeforces 571E Geometric Progressions
分开考虑各个质因数带来的影响,记第 个等比数列 的幂次为 ,则对于各个质因数,我们都可以得到 个二元一次方程,若能够直接确定某个 ,则直接检测其是否合法即可。
不能确定时,当且仅当各个质因数得到的 个二元一次方程本质相同。
此时,所有方程可以写成一个统一的形式 。
则可以用拓展欧几里得算法解出方程的最小解,它的级别在 的 LCM 左右,可以用 64 位整型计算。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
const int MAXM = 2e3 + 5;
const int MAXV = 1e5 + 5;
const int INF = 1e9;
const int P = 1e9 + 7;
typedef long long ll;
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;
}
struct info {ll k, b; };
void exgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) {
assert(a == 1);
x = 1, y = 0;
return;
}
ll q = a / b, r = a % b;
exgcd(b, r, y, x);
x = -x, y = -y + x * q;
}
info merge(info x, info y) {
if (x.b > y.b) swap(x, y);
ll g = __gcd(x.k, y.k), c = y.b - x.b;
if (c % g != 0) {
puts("-1");
exit(0);
}
ll tx = 0, ty = 0; c /= g;
exgcd(x.k / g, y.k / g, tx, ty);
if (y.k <= 30) {
ll k = y.k / g, b = (tx % k + k) * c % k;
x.b += x.k * b, x.k *= k;
return x;
} else {
ll k = x.k / g, b = (ty % k + k) * c % k;
y.b += y.k * b, y.k *= k;
return y;
}
}
set <int> primes;
int n, a[MAXN], b[MAXN];
int x[MAXN], y[MAXN], tx[MAXN], ty[MAXN];
int tot, prime[MAXV], f[MAXV];
void sieve(int n) {
for (int i = 2; i <= n; i++) {
if (f[i] == 0) prime[++tot] = f[i] = i;
for (int j = 1; j <= tot && prime[j] <= f[i]; j++) {
int tmp = prime[j] * i;
if (tmp > n) break;
f[tmp] = prime[j];
}
}
}
void inprime(int x) {
for (int i = 1; i <= tot; i++)
while (x % prime[i] == 0) {
x /= prime[i];
primes.insert(prime[i]);
}
if (x != 1) primes.insert(x);
}
int power(int x, ll y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
void report(int pos, ll val) {
if (val < 0) {
puts("-1");
exit(0);
}
for (auto p : primes) {
static int x[MAXN], y[MAXN];
for (int i = 1, tmp; i <= n; i++) {
x[i] = 0, y[i] = 0;
tmp = a[i]; while (tmp % p == 0) tmp /= p, x[i]++;
tmp = b[i]; while (tmp % p == 0) tmp /= p, y[i]++;
}
ll goal = x[pos] + y[pos] * val;
for (int i = 1; i <= n; i++) {
if (x[i] > goal || (y[i] == 0 && x[i] != goal) || (y[i] != 0 && (goal - x[i]) % y[i])) {
puts("-1");
exit(0);
}
}
}
printf("%lld\n", 1ll * a[pos] * power(b[pos], val) % P);
exit(0);
}
int main() {
read(n), sieve(sqrt(1e9 + 7));
for (int i = 1; i <= n; i++) {
read(a[i]), read(b[i]);
inprime(a[i]);
inprime(b[i]);
}
if (n == 1) {
cout << a[1] << endl;
return 0;
}
bool vis = false;
for (auto p : primes) {
int Min = INF;
for (int i = 1, tmp; i <= n; i++) {
x[i] = 0, y[i] = 0;
tmp = a[i]; while (tmp % p == 0) tmp /= p, x[i]++;
tmp = b[i]; while (tmp % p == 0) tmp /= p, y[i]++;
chkmin(Min, x[i]);
}
int g = 0;
for (int i = 1; i <= n; i++) {
x[i] -= Min;
g = __gcd(g, x[i]);
g = __gcd(g, y[i]);
}
if (g == 0) continue;
for (int i = 1; i <= n; i++) {
x[i] /= g;
y[i] /= g;
}
bool fx = true, fy = true;
for (int i = 1; i <= n; i++) {
if (y[i]) fy = false;
if (x[i] != x[1]) fx = false;
}
if (fy) {
if (fx) continue;
else {
puts("-1");
return 0;
}
}
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++) {
if (y[i] == 0 && y[j] != 0) report(j, (x[i] - x[j]) / y[j]);
if (y[j] == 0 && y[i] != 0) report(i, (x[j] - x[i]) / y[i]);
}
if (vis) {
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++) {
int k1, k2, b1, b2;
k1 = ty[i] * y[j];
b1 = ty[i] * (x[j] - x[i]);
k2 = y[i] * ty[j];
b2 = y[i] * (tx[j] - tx[i]);
if (k1 != k2) report(j, (b1 - b2) / (k2 - k1));
else if (b1 != b2) {
puts("-1");
exit(0);
}
k1 = ty[j] * y[i];
b1 = ty[j] * (x[i] - x[j]);
k2 = y[j] * ty[i];
b2 = y[j] * (tx[i] - tx[j]);
if (k1 != k2) report(i, (b1 - b2) / (k2 - k1));
else if (b1 != b2) {
puts("-1");
exit(0);
}
}
} else {
for (int i = 1; i <= n; i++) {
tx[i] = x[i];
ty[i] = y[i];
}
vis = true;
}
}
if (!vis) {
cout << a[1] << endl;
return 0;
}
info res = (info) {1, 0};
for (int i = 1; i <= n; i++)
res = merge(res, (info) {ty[i], tx[i] % ty[i]});
ll ans = res.b;
for (int i = 1; i <= n; i++)
while (tx[i] > ans) ans += res.k;
report(1, (ans - tx[1]) / ty[1]);
return 0;
}
Codeforces 573E Bear and Bowling
贪心思路
由于这是一个在序列上的最优化问题,采用贪心的策略往往能够得到不错的结果。
考虑依次向已经选择的子序列中加入一个数 的过程,如果在 的左侧已经有 个数被选取,并且 的右侧被选取的数之和为 ,那么,加入 将会带来 的贡献,不妨记加入 的贡献为 。
一个自然的贪心算法就出现了:我们每次选择 最大的 加入答案,同时更新其余尚未加入答案的 ,直到所有元素都被加入答案,取贡献总和的历史最大值作为答案。
在下一节中,我们将证明这个算法的正确性。
正确性证明
为了证明上述算法的正确性,我们首先需要证明如下引理。
**引理 1:**如果 ,并且 ,那么 一定比 先被选取。
**证明:**考虑上述引理首次不满足位置,即使得二元组 字典序最小的不满足的位置。如果 之间没有元素被选中,那么 ,因此 , 会先被选取。对于 之间每一个已经选中的元素 ,其对 的贡献为 ,对 的贡献为 。又因为 是引理首次不满足位置,而 ,因此必然有 ,从而 , 的增加量始终大于 的增加量,所以 ,引理得证。
**定理 1:**每次选择 最大的 加入答案,同时更新其余尚未加入答案的 ,得到的每一个大小的序列都是对应大小的最优解。
**证明:**假设定理不成立,那么,必然存在一个最早的时刻,使得我们选择了某个元素 ,此后,对于某个大小 ,我们无法通过选择更多的元素来得到 的最优解,无论我们在选择更多元素时使用的策略如何。令集合 表示该时刻之前已经选取的元素集合,那么,集合 不是任何一个大小为 的最优解的子集。但是,存在一个集合 ,使得 ,并且, 是一个大小为 的最优解,注意 应当不为空。
情况 1: 中包含至少一个 左侧的元素
令 表示 中最靠右侧的一个使得 的元素。由于我们的贪心策略会首先选取 ,由引理 1 ,我们知道 。我们将证明将 中的 替换为 不会使得方案变得更劣,从而选择 是合理的。
在选择 时, 具有最大的 ,因此 。考虑将 中除 以外的元素加入 中,并比较此时 的大小。对于每一个 ,其对 的贡献均为 ,对于每一个 ,其对 的贡献均为 ,对 的贡献均为 ,并且,由 的选择方式,不存在 的 。因此, 的增加量始终大于 的增加量, ,将 中的 替换为 不会使得方案变得更劣。
情况 2: 中的所有元素都在 的右侧
令 表示 中最靠左侧的一个元素,由于 中的所有元素都在 的右侧,有 。我们将证明将 中的 替换为 不会使得方案变得更劣,从而选择 是合理的。
在选择 时, 具有最大的 ,因此 。同样考虑将 中除 以外的元素加入 中,并比较此时 的大小。对于每一个 ,其对 的贡献均为 ,并且,由 的选择方式,不存在 的 。因此, 的增加量始终大于 的增加量, ,将 中的 替换为 不会使得方案变得更劣。
综上所述,假设不成立,从而定理 1 得证。
定理 1 的成立直接保证了上述贪策略的正确性,从而也导出了一个直接应用上述贪心策略的 算法,在下一节中,我们会讨论如何快速实现这个算法。
分块解法
考虑向答案中加入 对 数组的影响,对于 , 对 的贡献为 ,而对于 , 对 的贡献为 ,因此,我们需要一个能够支持区间加,区间加对应 ,以及询问全局最大值的数据结构。
考虑用分块维护这个结构,我们将每 个元素分为一块,在向答案中加入 时,对于 所在块左侧的块,进行整块加常数操作;对于 所在块右侧的块,进行整块加对应 操作,由此,我们需要在改变 的情况下维护若干一次函数 的最大值,可以通过在凸包上二分解决;对于 所在的块,可以暴力重建凸包。
每次向答案中加入 后,我们都需要重构一个大小为 的凸包,并且,在 个凸包上进行二分,这两部分的总时间复杂度均为 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const long long INF = 1e18;
typedef long long ll;
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;
}
ll b[MAXN], add[MAXN];
int n, tot, block, a[MAXN]; bool vis[MAXN];
int belong[MAXN], l[MAXN], r[MAXN];
int ql[MAXN], qr[MAXN], q[MAXN], k[MAXN];
inline bool exclude(int x, int y) {
return a[x] == a[y] && b[x] >= b[y];
}
inline bool exclude(int x, int y, int z) {
return (a[y] - a[z]) * (b[x] - b[z]) - (a[x] - a[z]) * (b[y] - b[z]) >= 0;
}
void rebuild(int pos) {
vector <int> points;
for (int i = l[pos]; i <= r[pos]; i++)
if (!vis[i]) {
points.push_back(i);
b[i] += add[pos] + 1ll * a[i] * k[pos];
}
k[pos] = add[pos] = 0;
sort(points.begin(), points.end(), [&] (int x, int y) {
if (a[x] != a[y]) return a[x] < a[y];
else return x < y;
});
ql[pos] = l[pos], qr[pos] = l[pos] - 1;
for (auto x : points) {
if (ql[pos] <= qr[pos] && exclude(q[qr[pos]], x)) continue;
while (ql[pos] <= qr[pos] && exclude(x, q[qr[pos]])) qr[pos]--;
while (ql[pos] + 1 <= qr[pos] && exclude(x, q[qr[pos]], q[qr[pos] - 1])) qr[pos]--;
q[++qr[pos]] = x;
}
}
pair <ll, int> find(int pos) {
int l = ql[pos], r = qr[pos];
if (l > r) return make_pair(-INF, 0);
pair <ll, int> res = make_pair(-INF, 0);
while (l < r) {
int mid = (l + r) / 2;
pair <ll, int> resa = make_pair(1ll * a[q[mid]] * k[pos] + b[q[mid]], q[mid]);
pair <ll, int> resb = make_pair(1ll * a[q[mid + 1]] * k[pos] + b[q[mid + 1]], q[mid + 1]);
if (resa > resb) {
r = mid - 1;
chkmax(res, resa);
} else {
l = mid + 2;
chkmax(res, resb);
}
}
if (l <= r) chkmax(res, make_pair(1ll * a[q[l]] * k[pos] + b[q[l]], q[l]));
res.first += add[pos];
return res;
}
int main() {
read(n), block = sqrt(n);
for (int i = 1; i <= n; i++) {
read(a[i]), b[i] = a[i];
if (i % block == 1 % block) l[++tot] = i;
r[tot] = i, belong[i] = tot;
}
for (int i = 1; i <= tot; i++)
rebuild(i);
ll ans = 0, now = 0;
for (int i = 1; i <= n; i++) {
pair <ll, int> res = make_pair(-INF, 0);
for (int j = 1; j <= tot; j++)
chkmax(res, find(j));
now += res.first;
vis[res.second] = true;
chkmax(ans, now);
for (int j = 1; j < belong[res.second]; j++)
add[j] += a[res.second];
for (int j = l[belong[res.second]]; j < res.second; j++)
b[j] += a[res.second];
for (int j = res.second + 1; j <= r[belong[res.second]]; j++)
b[j] += a[j];
for (int j = belong[res.second] + 1; j <= tot; j++)
k[j] += 1;
rebuild(belong[res.second]);
}
cout << ans << endl;
return 0;
}
算法优化
我们可以将上述算法的 因子优化掉。
首先,由于 中的系数 是不断增加的,我们可以用单调指针代替二分,其均摊复杂度是 的。其次,在重构凸包时,我们不需要每次都对点集进行排序,注意到 是始终不变的,我们只需要在刚开始对 进行一次排序即可。
由此,上述算法的时间复杂度被优化至了 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const long long INF = 1e18;
typedef long long ll;
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;
}
ll b[MAXN], add[MAXN];
int n, tot, block, a[MAXN]; bool vis[MAXN];
int belong[MAXN], l[MAXN], r[MAXN], p[MAXN];
int ql[MAXN], qr[MAXN], q[MAXN], k[MAXN];
inline bool exclude(int x, int y) {
return a[x] == a[y] && b[x] >= b[y];
}
inline bool exclude(int x, int y, int z) {
return (a[y] - a[z]) * (b[x] - b[z]) - (a[x] - a[z]) * (b[y] - b[z]) >= 0;
}
void rebuild(int pos) {
for (int i = l[pos]; i <= r[pos]; i++)
if (!vis[i]) b[i] += add[pos] + 1ll * a[i] * k[pos];
k[pos] = add[pos] = 0;
ql[pos] = l[pos], qr[pos] = l[pos] - 1;
for (int i = l[pos]; i <= r[pos]; i++) {
if (vis[p[i]]) continue; int x = p[i];
if (ql[pos] <= qr[pos] && exclude(q[qr[pos]], x)) continue;
while (ql[pos] <= qr[pos] && exclude(x, q[qr[pos]])) qr[pos]--;
while (ql[pos] + 1 <= qr[pos] && exclude(x, q[qr[pos]], q[qr[pos] - 1])) qr[pos]--;
q[++qr[pos]] = x;
}
}
pair <ll, int> find(int pos) {
int l = ql[pos], r = qr[pos];
if (l > r) return make_pair(-INF, 0);
pair <ll, int> res = make_pair(1ll * a[q[l]] * k[pos] + b[q[l]], q[l]);
while (l < r) {
pair <ll, int> tmp = make_pair(1ll * a[q[l + 1]] * k[pos] + b[q[l + 1]], q[l + 1]);
if (tmp > res) {
res = tmp;
l++;
} else break;
}
ql[pos] = l;
res.first += add[pos];
return res;
}
int main() {
read(n), block = sqrt(n);
for (int i = 1; i <= n; i++) {
read(a[i]), b[i] = a[i], p[i] = i;
if (i % block == 1 % block) l[++tot] = i;
r[tot] = i, belong[i] = tot;
}
for (int i = 1; i <= tot; i++) {
sort(p + l[i], p + r[i] + 1, [&] (int x, int y) {
if (a[x] != a[y]) return a[x] < a[y];
else return x < y;
});
rebuild(i);
}
ll ans = 0, now = 0;
for (int i = 1; i <= n; i++) {
pair <ll, int> res = make_pair(-INF, 0);
for (int j = 1; j <= tot; j++)
chkmax(res, find(j));
now += res.first;
vis[res.second] = true;
chkmax(ans, now);
for (int j = 1; j < belong[res.second]; j++)
add[j] += a[res.second];
for (int j = l[belong[res.second]]; j < res.second; j++)
b[j] += a[res.second];
for (int j = res.second + 1; j <= r[belong[res.second]]; j++)
b[j] += a[j];
for (int j = belong[res.second] + 1; j <= tot; j++)
k[j] += 1;
rebuild(belong[res.second]);
}
cout << ans << endl;
return 0;
}
平衡树解法
考虑一个
的动态规划解法,记
表示在序列的前
个元素中选择恰好
个,可以产生的最大贡献,转移时,考虑是否选取
,则有
其中 表示不取 , 表示选取 。
不妨记 表示一种使得贡献取到 的选取方案集合。
注意到由定理 1 , 可以通过向 中加入一个元素得到,存在最优解 ,因此,如果 ,则有 。
这意味着使得 的 是一段连续的后缀。
考虑用平衡树维护 数组每一行的差分数组,在从 计算 时,可以首先二分得出转移的分界位置,然后对分界位置后方的元素整体 ,并在分界位置插入一个新的元素。
时间复杂度 ,以下代码采用旋转式 Treap 实现了该算法。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
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;
}
struct Treap {
struct Node {
ll sum, tag, val; unsigned rnd;
int child[2], father, size;
} a[MAXN];
int root, size;
unsigned seed;
void init() {
srand('X' + 'Y' + 'X');
root = size = 0;
seed = 1258553509u;
}
void pushdown(int root) {
if (a[root].tag) {
a[a[root].child[0]].sum += a[a[root].child[0]].size * a[root].tag;
a[a[root].child[0]].tag += a[root].tag;
a[a[root].child[1]].sum += a[a[root].child[1]].size * a[root].tag;
a[a[root].child[1]].tag += a[root].tag;
a[root].val += a[root].tag;
a[root].tag = 0;
}
}
void update(int root) {
a[root].sum += a[root].val, a[root].size = 1;
if (a[root].child[0]) {
a[root].sum += a[a[root].child[0]].sum;
a[root].size += a[a[root].child[0]].size;
}
if (a[root].child[1]) {
a[root].sum += a[a[root].child[1]].sum;
a[root].size += a[a[root].child[1]].size;
}
}
void add(int root, int l, int r, ll x) {
if (l == 1 && r == a[root].size) {
a[root].tag += x;
a[root].sum += a[root].size * x;
return;
}
pushdown(root);
if (l <= a[a[root].child[0]].size + 1 && r >= a[a[root].child[0]].size + 1) a[root].val += x;
if (a[root].child[0] && l <= a[a[root].child[0]].size) add(a[root].child[0], l, min(r, a[a[root].child[0]].size), x);
l -= a[a[root].child[0]].size + 1, r -= a[a[root].child[0]].size + 1;
if (a[root].child[1] && r >= 1) add(a[root].child[1], max(l, 1), r, x);
update(root);
}
unsigned rnd() {
seed = seed * seed + rand();
return seed;
}
bool get(int x) {
return a[a[x].father].child[1] == x;
}
void rotate(int x) {
int f = a[x].father, g = a[f].father;
pushdown(f), pushdown(x);
bool tmp = get(x);
a[x].father = g;
if (g != 0) a[g].child[get(f)] = x;
a[f].child[tmp] = a[x].child[tmp ^ 1];
a[a[x].child[tmp ^ 1]].father = f;
a[x].child[tmp ^ 1] = f;
a[f].father = x;
update(f), update(x);
}
void treap(int x) {
while (a[x].father && a[x].rnd < a[a[x].father].rnd) rotate(x);
if (a[x].father == 0) root = x;
}
int newnode(ll val) {
size++;
a[size].val = val;
a[size].rnd = rnd();
update(size);
return size;
}
void ins(int pos, ll val) {
if (root == 0) {
root = newnode(val);
return;
}
int x = root;
while (true) {
pushdown(x);
a[x].size++, a[x].sum += val;
if (pos <= a[a[x].child[0]].size + 1) {
if (a[x].child[0]) x = a[x].child[0];
else {
a[x].child[0] = newnode(val);
a[size].father = x;
treap(size);
return;
}
} else {
pos -= a[a[x].child[0]].size + 1;
if (a[x].child[1]) x = a[x].child[1];
else {
a[x].child[1] = newnode(val);
a[size].father = x;
treap(size);
return;
}
}
}
}
void trans(int val) {
int res = size + 1;
if (root != 0) {
int x = root, cnt = 0;
while (true) {
pushdown(x);
int rnk = cnt + a[a[x].child[0]].size + 1;
if (1ll * val * rnk >= a[x].val) {
res = rnk;
if (a[x].child[0]) x = a[x].child[0];
else break;
} else {
cnt += a[a[x].child[0]].size + 1;
if (a[x].child[1]) x = a[x].child[1];
else break;
}
}
}
ins(res, 1ll * res * val);
if (res != size) add(root, res + 1, size, val);
}
ll ans, now;
void getans(int root) {
pushdown(root);
if (a[root].child[0]) getans(a[root].child[0]);
now += a[root].val, chkmax(ans, now);
if (a[root].child[1]) getans(a[root].child[1]);
}
void getans() {
ans = now = 0;
getans(root);
cout << ans << endl;
}
} Treap;
int main() {
int n; read(n);
Treap.init();
for (int i = 1; i <= n; i++) {
int x; read(x);
Treap.trans(x);
}
Treap.getans();
return 0;
}
Codeforces 575A Fibonotci
排序特殊点,矩阵快速幂即可。
需要用线段树实现计算区间矩阵乘积。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
typedef long long ll;
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;
}
struct vec {int a[2]; };
struct mat {int a[2][2]; };
int n, m, P; ll k;
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
vec operator * (vec x, mat y) {
vec ans;
ans.a[0] = (1ll * x.a[0] * y.a[0][0] + 1ll * x.a[1] * y.a[1][0]) % P;
ans.a[1] = (1ll * x.a[0] * y.a[0][1] + 1ll * x.a[1] * y.a[1][1]) % P;
return ans;
}
mat operator * (mat x, mat y) {
mat ans;
ans.a[0][0] = (1ll * x.a[0][0] * y.a[0][0] + 1ll * x.a[0][1] * y.a[1][0]) % P;
ans.a[0][1] = (1ll * x.a[0][0] * y.a[0][1] + 1ll * x.a[0][1] * y.a[1][1]) % P;
ans.a[1][0] = (1ll * x.a[1][0] * y.a[0][0] + 1ll * x.a[1][1] * y.a[1][0]) % P;
ans.a[1][1] = (1ll * x.a[1][0] * y.a[0][1] + 1ll * x.a[1][1] * y.a[1][1]) % P;
return ans;
}
mat make(int x, int y) {
mat ans;
ans.a[0][0] = 0, ans.a[0][1] = x;
ans.a[1][0] = 1, ans.a[1][1] = y;
return ans;
}
mat unit() {
mat ans;
ans.a[0][0] = ans.a[1][1] = 1;
ans.a[1][0] = ans.a[0][1] = 0;
return ans;
}
mat power(mat a, ll x) {
if (x == 0) return unit();
mat tmp = power(a, x / 2);
if (x % 2 == 0) return tmp * tmp;
else return tmp * tmp * a;
}
struct SegmentTree {
struct Node {
int lc, rc;
mat sum;
} a[MAXN * 2];
int n, root, size;
void build(int &root, int l, int r, int *c) {
root = ++size;
if (l == r) {
a[root].sum = make(c[l - 1], c[l]);
return;
}
int mid = (l + r) / 2;
build(a[root].lc, l, mid, c);
build(a[root].rc, mid + 1, r, c);
a[root].sum = a[a[root].lc].sum * a[a[root].rc].sum;
}
void init(int x, int *c) {
n = x;
build(root, 1, n, c);
}
mat query(int root, int l, int r, int ql, int qr) {
if (l == ql && r == qr) return a[root].sum;
int mid = (l + r) / 2;
if (mid >= qr) return query(a[root].lc, l, mid, ql, qr);
else if (mid + 1 <= ql) return query(a[root].rc, mid + 1, r, ql, qr);
else return query(a[root].lc, l, mid, ql, mid) * query(a[root].rc, mid + 1, r, mid + 1, qr);
}
mat query(int l, int r) {
return query(root, 1, n, l, r);
}
} ST;
int a[MAXN];
pair <ll, int> b[MAXN];
int query(ll x) {
return a[(x - 1) % n];
}
mat query(ll l, ll r) {
mat ans = unit();
if (l > r) return ans; l--, r--;
r -= (l - 1) / n * n;
l -= (l - 1) / n * n;
if (r <= n) return ST.query(l, r);
int x = r % n; if (x == 0) x = n;
ans = ans * ST.query(l, n) * power(ST.query(1, n), (r - x) / n - 1) * ST.query(1, x);
return ans;
}
int main() {
read(k), read(P), read(n);
if (k <= 1) {
cout << k % P << endl;
return 0;
}
for (int i = 0; i <= n - 1; i++)
read(a[i]); a[n] = a[0];
ST.init(n, a);
read(m);
for (int i = 1; i <= m; i++)
read(b[i].first), read(b[i].second), b[i].first++;
b[++m] = make_pair(2e18, 0);
sort(b + 1, b + m + 1);
b[0] = make_pair(1, a[0]);
vec ans = (vec) {0, 1};
for (int i = 1; i <= m; i++) {
if (k < b[i].first) {
if (k == b[i - 1].first) {
cout << ans.a[1] << endl;
return 0;
}
ans = ans * make(b[i - 1].second, query(b[i - 1].first + 1)) * query(b[i - 1].first + 2, k);
cout << ans.a[1] << endl;
return 0;
}
if (b[i].first == b[i - 1].first + 1) ans = ans * make(b[i - 1].second, b[i].second);
else ans = ans * make(b[i - 1].second, query(b[i - 1].first + 1))
* query(b[i - 1].first + 2, b[i].first - 1)
* make(query(b[i].first - 1), b[i].second);
}
return 0;
}
Codeforces 575E Spectator Riots
首先,圆是凸集,因此,若圆包含了所有点集中的点,它也一定包含了整个点集凸包。
因此,求出所有可能出现人的位置的凸包,我们只需要在凸包上考虑问题。
首先我们给出结论:答案一定是凸包上连续的三个点组成的三角形的外接圆。
这是因为,一个不满足上述条件的圆可以通过调整变得更大。
并且,如果一个圆没有包含整个凸包,则同样可以通过调整将其变大。
因此,只需要找到凸包上相邻的三个点外接圆半径的最大值即可,关于外接圆半径的计算可以考虑使用正弦定理。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 8e5 + 5;
const int INF = 1e5;
typedef long long ll;
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;
}
struct point {int 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, int b) {return (point) {a.x * b, a.y * b}; }
long long operator * (point a, point b) {return 1ll * a.x * b.y - 1ll * a.y * b.x; }
long long dist(point a) {return 1ll * a.x * a.x + 1ll * a.y * a.y; }
bool operator < (point a, point b) {
if (a.y == b.y) return a.x < b.x;
else return a.y < b.y;
}
int n, m;
point a[MAXN];
bool cmp(point x, point y) {
if ((x - a[1]) * (y - a[1]) == 0) return dist(x - a[1]) < dist(y - a[1]);
else return (x - a[1]) * (y - a[1]) > 0;
}
int main() {
read(n);
for (int i = 1; i <= n; i++) {
point pos; int v;
read(pos.x), read(pos.y), read(v);
if (pos.x + v <= INF) a[++m] = (point) {pos.x + v, pos.y};
else {
a[++m] = (point) {INF, min(pos.y + v - (INF - pos.x), INF)};
a[++m] = (point) {INF, max(pos.y + (INF - pos.x) - v, 0)};
}
if (pos.y + v <= INF) a[++m] = (point) {pos.x, pos.y + v};
else {
a[++m] = (point) {min(pos.x + v - (INF - pos.y), INF), INF};
a[++m] = (point) {max(pos.x + (INF - pos.y) - v, 0), INF};
}
if (pos.x >= v) a[++m] = (point) {pos.x - v, pos.y};
else {
a[++m] = (point) {0, min(pos.y + v - pos.x, INF)};
a[++m] = (point) {0, max(pos.y - v + pos.x, 0)};
}
if (pos.y >= v) a[++m] = (point) {pos.x, pos.y - v};
else {
a[++m] = (point) {min(pos.x + v - pos.y, INF), 0};
a[++m] = (point) {max(pos.x - v + pos.y, 0), 0};
}
}
for (int i = 1; i <= m; i++)
if (a[i] < a[1]) swap(a[i], a[1]);
sort(a + 2, a + m + 1, cmp), a[m + 1] = a[1];
int top = 1;
for (int i = 2; i <= m + 1; i++) {
while (top >= 2 && (a[i] - a[top - 1]) * (a[top] - a[top - 1]) >= 0) top--;
a[++top] = a[i];
}
point p, q, r; double ans = 0; a[top + 1] = a[2];
for (int i = 2; i <= top; i++) {
point x = a[i + 1] - a[i], y = a[i - 1] - a[i], z = a[i + 1] - a[i - 1];
double cur = sqrt(dist(x)) * sqrt(dist(y)) * sqrt(dist(z)) / (x * y);
if (cur > ans) {
ans = cur;
p = a[i - 1], q = a[i], r = a[i + 1];
}
}
printf("%d %d\n%d %d\n%d %d\n", p.x, p.y, q.x, q.y, r.x, r.y);
return 0;
}
Codeforces 575I Robots Protection
离线操作,考虑分治,即计算 中的修改对 中的询问的影响。
对于
类三角形,其划分的区域应当为
的区域。
因此,可以看做两个分立的二维数点问题,扫描线 树状数组即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
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;
}
struct BinaryIndexTree {
int n, a[MAXN];
void init(int x) {
n = x;
memset(a, 0, sizeof(a));
}
void modify(int x, int d) {
for (int i = x; i <= n; i += i & -i)
a[i] += d;
}
int query(int x) {
int ans = 0;
for (int i = x; i >= 1; i -= i & -i)
ans += a[i];
return ans;
}
} BIT[3];
int n, q, ans[MAXN];
struct info {int x, y, val, type; } a[MAXN];
struct event {int type, x, d, timer; } b[MAXN * 5];
bool cmp(event x, event y) {
if (x.timer == y.timer) return x.type < y.type;
else return x.timer < y.timer;
}
void add(int &tot, int l, int r, int type, int x, int d) {
b[++tot] = (event) {-type, x, d, l};
b[++tot] = (event) {-type, x, -d, r + 1};
}
void solve(int l, int r) {
if (l == r) return;
int mid = (l + r) / 2, tot = 0;
solve(l, mid), solve(mid + 1, r);
for (int i = l; i <= mid; i++)
if (a[i].type) {
int x = a[i].x, y = a[i].y, len = a[i].val;
if (a[i].type == 1) {
add(tot, x, x + len, 0, y, 1);
add(tot, x, x + len, 1, x + y + len + 1, -1);
}
if (a[i].type == 2) {
add(tot, x, x + len, 2, y - x + n - len, 1);
add(tot, x, x + len, 0, y + 1, -1);
}
if (a[i].type == 3) {
add(tot, x - len, x, 0, y, 1);
add(tot, x - len, x, 2, y - x + n + len + 1, -1);
}
if (a[i].type == 4) {
add(tot, x - len, x, 1, x + y - len, 1);
add(tot, x - len, x, 0, y + 1, -1);
}
}
for (int i = mid + 1; i <= r; i++)
if (a[i].type == 0) b[++tot] = (event) {a[i].val, a[i].y, 0, a[i].x};
sort(b + 1, b + tot + 1, cmp);
for (int i = 1; i <= tot; i++) {
if (b[i].type <= 0) BIT[-b[i].type].modify(b[i].x, b[i].d);
else {
int home = b[i].type, x = b[i].timer, y = b[i].x;
ans[home] += BIT[0].query(y);
ans[home] += BIT[1].query(x + y);
ans[home] += BIT[2].query(y - x + n);
}
}
}
int main() {
read(n), read(q);
BIT[0].init(n * 2);
BIT[1].init(n * 2);
BIT[2].init(n * 2);
for (int i = 1; i <= q; i++) {
int opt; read(opt);
if (opt == 1) {
read(a[i].type), read(a[i].x);
read(a[i].y), read(a[i].val);
} else {
read(a[i].x), read(a[i].y);
a[i].type = 0, a[i].val = i;
}
}
solve(1, q);
for (int i = 1; i <= q; i++)
if (a[i].type == 0) printf("%d\n", ans[i]);
return 0;
}