0312比赛总结
T1
按逆时针顺序给你一个凸包,让你求离每个点最远的点,有多个输出编号最小的。
点数在 \(5\times 10^5\) 以内。
这个题是旋转卡壳板子,但是没学过旋转卡壳怎么办呢,其实整体二分也是可以做的。
可以发现,当点 \(i\) 逆时针移动,离 \(i\) 最远的点也会跟着逆时针移动。
所以这个东西具有决策单调性,整体二分即可。
#include <cstdio>
#define int long long
inline int min(const int x, const int y) {return x < y ? x : y;}
inline int max(const int x, const int y) {return x > y ? x : y;}
int x[500005], y[500005], f[500005], id[1000005], n;
inline int dist(int a, int b) {
return (x[a] - x[b]) * (x[a] - x[b]) + (y[a] - y[b]) * (y[a] - y[b]);
}
void solve(int l, int r, int x, int y) {
if (l > r) return;
if (x == y) {
for (int i = l; i <= r; ++ i) f[i] = x;
return;
}
int mid = l + r >> 1;
for (int i = max(x, mid); i <= min(y, mid + n - 1); ++ i)
if (!f[mid] || (dist(mid, id[i]) > dist(mid, id[f[mid]])) ||
(dist(mid, id[i]) == dist(mid, id[f[mid]]) && id[i] < id[f[mid]])) f[mid] = i;
solve(l, mid - 1, x, f[mid]);
solve(mid + 1, r, f[mid], y);
}
signed main() {
int _;
scanf("%lld", &_);
while (_ --) {
scanf("%lld", &n);
for (int i = 1; i <= n; ++ i) scanf("%lld%lld", x + i, y + i), id[i] = id[i + n] = i;
solve(1, n, 1, 2 * n);
for (int i = 1; i <= n; ++ i) printf("%lld\n", id[f[i]]);
}
return 0;
}
T2 [BJOI2019]奥术神杖
答案就是这些咒语带来权值的几何平均值。
你可以枚举 \(c\) 直接把他开个 \(c\) 次方避免高精,但由于 pow 的精度十分那啥且复杂度不对,所以我们选择 \(\log\) 后转算数平均值。
相当于让所有出现了的咒语的权值总和平均值最大。
这不分数规划板题吗,套个 AC 自动机上 dp 就行了。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
const double eps = 1e-10;
char stick[1505], str[1505], prec[1505][1505];
int ch[1505][10], fail[1505], cnt[1505], pre[1505][1505], len, tot, root, n, m;
int vec[1505], Len;
double val[1505], sum[1505], w[1505], dp[1505][1505];
std::queue<int> Q;
void insert(int p, int now, double val) {
if (now > len) {sum[p] += val, ++ cnt[p]; return;}
if (!ch[p][str[now] - '0']) ch[p][str[now] - '0'] = ++ tot;
insert(ch[p][str[now] - '0'], now + 1, val);
}
void build() {
for (int i = 0; i <= 9; ++ i)
if (ch[root][i]) Q.push(ch[root][i]);
while (Q.size()) {
int u = Q.front();
vec[++ Len] = u;
Q.pop();
for (int i = 0; i <= 9; ++ i)
if (ch[u][i]) fail[ch[u][i]] = ch[fail[u]][i], Q.push(ch[u][i]);
else ch[u][i] = ch[fail[u]][i];
}
for (int i = 1; i <= Len; ++ i) sum[vec[i]] += sum[fail[vec[i]]], cnt[vec[i]] += cnt[fail[vec[i]]];
}
bool check(double mid) {
for (int i = 0; i <= tot; ++ i) w[i] = sum[i] - mid * cnt[i];
for (int i = 0; i <= n; ++ i)
for (int j = 0; j <= tot; ++ j) dp[i][j] = -1e9;
dp[0][0] = 0;
for (int i = 0; i < n; ++ i)
for (int j = 0; j <= tot; ++ j) if (dp[i][j] != -1e9) {
if (stick[i + 1] == '.') {
for (int k = 0; k <= 9; ++ k) {
int nxt = ch[j][k];
if (dp[i][j] + w[nxt] > dp[i + 1][nxt]) dp[i + 1][nxt] = dp[i][j] + w[nxt];
}
} else {
int nxt = ch[j][stick[i + 1] - '0'];
if (dp[i][j] + w[nxt] > dp[i + 1][nxt]) dp[i + 1][nxt] = dp[i][j] + w[nxt];
}
}
for (int i = 0; i <= tot; ++ i)
if (dp[n][i] >= eps) return true;
return false;
}
int calc(double mid) {
for (int i = 0; i <= tot; ++ i) w[i] = sum[i] - mid * cnt[i];
for (int i = 0; i <= n; ++ i)
for (int j = 0; j <= tot; ++ j) dp[i][j] = -1e9;
dp[0][0] = 0;
for (int i = 0; i < n; ++ i)
for (int j = 0; j <= tot; ++ j) if (dp[i][j] != -1e9) {
if (stick[i + 1] == '.') {
for (int k = 0; k <= 9; ++ k) {
int nxt = ch[j][k];
if (dp[i][j] + w[nxt] > dp[i + 1][nxt]) {
dp[i + 1][nxt] = dp[i][j] + w[nxt];
pre[i + 1][nxt] = j, prec[i + 1][nxt] = k + '0';
}
}
} else {
int nxt = ch[j][stick[i + 1] - '0'];
if (dp[i][j] + w[nxt] > dp[i + 1][nxt]) {
dp[i + 1][nxt] = dp[i][j] + w[nxt];
pre[i + 1][nxt] = j, prec[i + 1][nxt] = stick[i + 1];
}
}
}
int pos = -1;
for (int i = 0; i <= tot; ++ i)
if (dp[n][i] >= eps) {pos = i; break;}
return pos;
}
void print(int i, int j) {
if (!i) return; print(i - 1, pre[i][j]), putchar(prec[i][j]);
}
int main() {
scanf("%d%d%s", &n, &m, stick + 1);
for (int i = 1; i <= m; ++ i) {
scanf("%s%lf", str + 1, val + i);
len = strlen(str + 1), insert(root, 1, log2(val[i]));
}
build();
double l = 0, r = 30;
while (r - l > eps) {
double mid = (l + r) / 2;
if (check(mid)) l = mid;
else r = mid - eps;
}
print(n, calc(l));
return 0;
}
T3 AGC031E
个人感觉这题需要一点智慧。
枚举要恰好选择 \(k\) 个点。考虑只有 \(x\) 坐标的情况,我们假设它们已经按 \(x\) 坐标从小到大排序。
那么建立点 \(1^{\prime},2^{\prime}...k^{\prime}\) 表示 \(x\) 坐标第 \(1...k\) 小的点。
考虑限制 L。限制 L 意味者 \(x\) 坐标第 \(b_i+1,b_i+2...k\) 小的点 \(x\) 坐标都必须大于 \(a_i\)。
同理,限制 R 意味者 \(x\) 坐标第 \(1,2...k-b_i\) 小的点 \(x\) 坐标都必须小于 \(a_i\)。
那么算出 \(x\) 坐标第 \(1\sim k\) 小的点的 \(x\) 坐标限制,从源点向 \(i^{\prime}\) 连一条容量 \(1\) 费用 \(0\) 的边,从 \(i^{\prime}\) 向所有 \(x\) 坐标符合 \(x\) 坐标第 \(i\) 小的点的限制的点 \(j\) 连边,再从每个点 \(j\) 向汇点连容量为 \(1\),费用为这个点的权值的边。
两维同理,算出 \(y\) 坐标 \(1\sim k\) 小的点 \(y\) 坐标的限制,剩下的建图不言自明。
总结:此题初看毫无头绪,突破口在于限制的转化——把个数的限制转化为可以在网络流上表示的坐标限制。而转化限制,在不能直接转化的前提下可以考虑枚举一些额外限制来转化。
限制经过合适的转化过后不管是对网络流还是其它算法都会好做得多。
至于为什么要用网络流,众所周知不能用常规做法做的还数据范围这么小的题不是爆搜就是网络流
#include <cstdio>
#include <queue>
#include <cstring>
#define int long long
const int INF = 1e18;
inline int min(const int x, const int y) {return x < y ? x : y;}
inline int max(const int x, const int y) {return x > y ? x : y;}
namespace NetworkFlow {
struct Edge {
int to, nxt, cap, cost;
} e[140000];
int head[325], cur[325], dis[325], tot, s, t, Maxflow, Mincost;
bool vis[325], mark[325];
std::queue<int> Q;
inline void clear() {
memset(head, 0, sizeof head), tot = 1, Mincost = Maxflow = 0;
}
inline void AddEdge(int u, int v, int cap, int cost) {
e[++ tot].to = v, e[tot].cap = cap, e[tot].cost = cost;
e[tot].nxt = head[u], head[u] = tot;
e[++ tot].to = u, e[tot].cap = 0, e[tot].cost = -cost;
e[tot].nxt = head[v], head[v] = tot;
}
bool SPFA() {
memcpy(cur, head, sizeof cur);
memset(vis, 0, sizeof vis);
memset(mark, 0, sizeof mark);
memset(dis, 0x3f, sizeof dis);
dis[s] = 0;
Q.push(s);
while (Q.size()) {
int u = Q.front();
Q.pop();
mark[u] = false;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (e[i].cap && dis[u] + e[i].cost < dis[v]) {
dis[v] = dis[u] + e[i].cost;
if (!mark[v]) mark[v] = true, Q.push(v);
}
}
}
return dis[t] <= INF;
}
int dfs(int u, int flow) {
if (u == t) return flow;
vis[u] = true;
int used = 0, tmp = 0;
for (int &i = cur[u]; i; i = e[i].nxt)
if (e[i].cap && !vis[e[i].to] && dis[u] + e[i].cost == dis[e[i].to]) {
tmp = dfs(e[i].to, min(e[i].cap, flow - used));
e[i].cap -= tmp, e[i ^ 1].cap += tmp, used += tmp;
if (used == flow) return used;
}
return used;
}
int Dinic() {
int flow = 0;
while (SPFA()) Maxflow += (flow = dfs(s, INF)), Mincost += flow * dis[t];
return Mincost;
}
}
int x[85], y[85], v[85], a[325], b[325], lx[85], rx[85], ly[85], ry[85];
char opt[325];
signed main() {
int n, m, ans = 0;
scanf("%lld", &n);
for (int i = 1; i <= n; ++ i) scanf("%lld%lld%lld", x + i, y + i, v + i);
scanf("%lld", &m);
for (int i = 1; i <= m; ++ i) scanf(" %c%lld%lld", opt + i, a + i, b + i);
for (int k = 1; k <= n; ++ k) {
for (int i = 1; i <= k; ++ i) lx[i] = ly[i] = 0, rx[i] = ry[i] = 100;
for (int i = 1; i <= m; ++ i)
if (opt[i] == 'L') for (int j = b[i] + 1; j <= k; ++ j) lx[j] = max(lx[j], a[i] + 1);
else if (opt[i] == 'R') for (int j = 1; j <= k - b[i]; ++ j) rx[j] = min(rx[j], a[i] - 1);
else if (opt[i] == 'D') for (int j = b[i] + 1; j <= k; ++ j) ly[j] = max(ly[j], a[i] + 1);
else for (int j = 1; j <= k - b[i]; ++ j) ry[j] = min(ry[j], a[i] - 1);
NetworkFlow::clear();
NetworkFlow::s = 0, NetworkFlow::t = 2 * k + 2 * n + 1;
for (int i = 1; i <= k; ++ i) NetworkFlow::AddEdge(NetworkFlow::s, i, 1, 0);
for (int i = 1; i <= k; ++ i) NetworkFlow::AddEdge(i + k, NetworkFlow::t, 1, 0);
for (int i = 1; i <= n; ++ i) NetworkFlow::AddEdge(i + 2 * k, i + 2 * k + n, 1, -v[i]);
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= k; ++ j) {
if (lx[j] <= x[i] && x[i] <= rx[j]) NetworkFlow::AddEdge(j, i + 2 * k, 1, 0);
if (ly[j] <= y[i] && y[i] <= ry[j]) NetworkFlow::AddEdge(i + 2 * k + n, j + k, 1, 0);
}
ans = max(ans, -NetworkFlow::Dinic());
}
printf("%lld", ans);
return 0;
}

浙公网安备 33010602011771号