做题记录1
P3324 [SDOI2015] 星际战争
思路
如果花费 \(T\) 时间可以消灭所有的机器人,显然大于 \(T\) 的时间也可以。具有单调性,考虑二分答案。
设当前二分的时间为 \(x\) ,对于第 \(i\) 个武器,它能造成的伤害为 \(b_i \times x\) 。设武器所在的集合为 \(L\) ,机器人所在的集合为 \(R\) ,将每个武器和它能攻击到的机器人连一条边,就形成了二分图,因此问题转化为了判断是否满足
可以再连一个流量无限的源点和汇点,用最大流解决这个问题。
具体的建图细节:
对于源点到 \(L\) 到每个点,建一条流量为 \(b_i \times x\) 的边;对于 \(L\) 和 \(R\) 之间的点,建流量为 \(+ \infty\) 的边;对于 \(R\) 的每个点到汇点的点,建一条流量为 \(a_i\) 的边。
代码
#include<bits/stdc++.h>
#define x first
#define y second
using i64 = long long;
using u64 = unsigned long long;
using u128 = __uint128_t;
using PII = std::pair<int, int>;
const int N = 1e5 + 10;
const int M = 1e5 + 10;
const int INF = 1e9;
const double eps = 1e-6;
struct flow {
int cnt = 1, hd[N], nxt[M * 2], to[M * 2];
double limit[M * 2];
void add(int u, int v, double w) {
nxt[++cnt] = hd[u], hd[u] = cnt, to[cnt] = v, limit[cnt] = w;
nxt[++cnt] = hd[v], hd[v] = cnt, to[cnt] = u, limit[cnt] = 0;
}
int T, dis[N], cur[N];
double dfs(int id, double res) {
if(id == T) return res;
double flow = 0;
for(int i = cur[id]; i && res > eps; i = nxt[i]) {
cur[id] = i;
double c = std::min(res, (double)limit[i]); int it = to[i];
if(dis[id] + 1 == dis[it] && c > eps) {
double k = dfs(it, c);
if(k > eps) {
flow += k, res -= k, limit[i] -= k, limit[i ^ 1] += k;
}
}
}
if(flow <= eps) dis[id] = -1;
return flow;
}
double maxflow(int s, int t) {
T = t;
double flow = 0;
while(1) {
std::queue<int> q;
memcpy(cur, hd, sizeof(hd));
memset(dis, 0xff, sizeof(dis));
q.push(s), dis[s] = 0;
while(q.size()) {
int u = q.front();
q.pop();
for(int i = hd[u]; i; i = nxt[i]) {
if(dis[to[i]] == -1 && limit[i] > eps) {
dis[to[i]] = dis[u] + 1;
q.push(to[i]);
}
}
}
if(dis[t] == -1) return flow;
flow += dfs(s, 1e18);
}
}
};
void solve(void) {
int n, m; std::cin >> n >> m;
std::vector<int> a(n + 1);
std::vector<int> b(m + 1);
double sum = 0;
for(int i = 1; i <= n; i++) {
std::cin >> a[i];
sum += a[i];
}
for(int i = 1; i <= m; i++) std::cin >> b[i];
bool st[51][51] = {0};
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= n; j++) {
std::cin >> st[i][j];
}
}
flow g;
auto check = [&](double x) -> bool {
g.cnt = 1;
memset(g.hd, 0, sizeof(g.hd));
int s = 0;
int L = 1, R = L + m, t = R + m + n;
for(int i = 1; i <= m; i++) {
g.add(s, L + i, (double)b[i] * x);
}
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= n; j++) {
if(st[i][j]) g.add(L + i, R + j, INF);
}
}
for(int i = 1; i <= n; i++) {
g.add(R + i, t, a[i]);
}
return g.maxflow(s, t) + eps >= sum;
};
double l = 0, r = 1e9, ans = -1;
while(r - l >= eps) {
double mid = (l + r) / 2;
if(check(mid)) {
ans = mid;
r = mid;
} else l = mid;
}
std::cout << std::fixed << std::setprecision(6) << ans;
}
int main(void) {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
//std::cin >> t;
while(t--) solve();
return 0;
}
P5038 [SCOI2012] 奇怪的游戏
思路
将格子按照奇偶黑白染色,每个格子分别和与它相邻的格子连边,就形成了一张二分图。
设最终变成的数字为 \(x\) ,每个格子变成 \(x\) 的次数就为 \(x - a_{i, j}\) 。在二分图的基础上找一个源点和汇点,源点到白格子的每条边连一条 \(x - a_{i, j}\) 的边,格子和格子之间连一条 \(+ \infty\) 的边,黑格子到汇点连一条 \(x - a_{i, j}\) 的边,在这个图上跑一遍最大流,如果最大流确实等于 \(\sum x - a_{i, j}\) 说明当前的 \(x\) 是可行的。可以发现 \(x\) 具有单调性,因此可以用二分答案的方式找 \(x\) 。
接下来分情况讨论:
设白点的数量为 \(c_0\) ,初始的数值总和为 \(s_0\) ;黑点的数量为 \(c_1\) ,初始的数值总和为 \(s_1\) 。假设最后要变成 \(x\) ,那么有:
- 如果 \(c_0 \neq c_1\) , \(x\) 不需要二分,可以直接算出来,因此算出来 check 一下就可以了。
- 如果 \(c_0 = c1\) :
如果 \(s_0 = s_1\) ,式子没有意义,直接可以确定无解;
如果 \(s_0 \neq s_1\) ,说明满足要求的 \(x\) 不止一个,用二分答案的方式找到答案。
最后如果有合适的 \(x\) ,那么答案就为 \(x \times c_0 - s_0\) 。
代码
#include<bits/stdc++.h>
#define x first
#define y second
using i64 = long long;
using u64 = unsigned long long;
using u128 = __uint128_t;
using PII = std::pair<int, int>;
const int N = 10000;
const int M = 30000;
const i64 INF = 1e18;
const double eps = 1e-6;
struct flow {
int cnt = 1, hd[N], nxt[M * 2], to[M * 2];
i64 limit[M * 2];
void add(int u, int v, i64 w) {
nxt[++cnt] = hd[u], hd[u] = cnt, to[cnt] = v, limit[cnt] = w;
nxt[++cnt] = hd[v], hd[v] = cnt, to[cnt] = u, limit[cnt] = 0;
}
int T, dis[N], cur[N];
i64 dfs(int id, i64 res) {
if(id == T) return res;
i64 flow = 0;
for(int i = cur[id]; i && res; i = nxt[i]) {
cur[id] = i;
i64 c = std::min(res, (i64)limit[i]), it = to[i];
if(dis[id] + 1 == dis[it] && c) {
i64 k = dfs(it, c);
flow += k, res -= k, limit[i] -= k, limit[i ^ 1] += k;
}
}
if(!flow) dis[id] = -1;
return flow;
}
i64 maxflow(int s, int t) {
T = t;
i64 flow = 0;
while(1) {
std::queue<int> q;
memcpy(cur, hd, sizeof(hd));
memset(dis, 0xff, sizeof(dis));
q.push(s), dis[s] = 0;
while(q.size()) {
int u = q.front();
q.pop();
for(int i = hd[u]; i; i = nxt[i]) {
if(dis[to[i]] == -1 && limit[i]) {
dis[to[i]] = dis[u] + 1;
q.push(to[i]);
}
}
}
if(dis[t] == -1) return flow;
flow += dfs(s, 1e18);
}
}
};
i64 a[45][45];
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
void solve(void) {
int n, m; std::cin >> n >> m;
i64 s0 = 0, s1 = 0, c0 = 0, c1 = 0;
i64 maxn = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
std::cin >> a[i][j];
maxn = std::max(maxn, a[i][j]);
if((i + j) % 2 == 0) s0 += a[i][j], c0 += 1;
else s1 += a[i][j], c1 += 1;
}
}
flow g;
auto check = [&](i64 x) -> bool {
g.cnt = 1;
memset(g.hd, 0, sizeof(g.hd));
if(x < maxn) return false;
i64 sum = 0;
int s = 0, t = n * m + 1;
int v = n * m + 2;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(x - a[i][j] < 0) return false;
if((i + j) % 2 == 0) {
sum += x - a[i][j];
g.add(s, (i - 1) * m + j, x - a[i][j]);
for(int k = 0; k < 4; k++) {
int a = i + dx[k], b = j + dy[k];
if(a >= 1 && a <= n && b >= 1 && b <= m)
g.add((i - 1) * m + j, (a - 1) * m + b, INF);
}
}
else g.add((i - 1) * m + j, t, x - a[i][j]);
}
}
i64 res = g.maxflow(s, t);
return res == sum;
};
if(c1 != c0) {
if((s0 - s1) % (c0 - c1)) {
std::cout << "-1\n";
return;
}
i64 x = (s0 - s1) / (c0 - c1);
if(check(x))
std::cout << x * c0 - s0 << '\n';
else std::cout << "-1\n";
return;
} else {
if(s0 != s1) {
std::cout << "-1\n";
return;
}
}
i64 l = maxn, r = 1e13, ans = -1;
while(l <= r) {
i64 mid = (l + r) / 2;
if(check(mid)) {
r = mid - 1;
ans = mid;
} else l = mid + 1;
}
if(ans < 0) std::cout << -1 << '\n';
else std::cout << ans * c0 - s0 << '\n';
}
int main(void) {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
std::cin >> t;
while(t--) solve();
return 0;
}
P3163 [CQOI2014] 危桥
思路
一个多源网络流的trick,交换建图。
先根据岛与岛之间的关系建图,之后将图复制两份。
第一次 \(S \rightarrow a1\) 、\(S \rightarrow b1\) 、 \(a2 \rightarrow T\) 、\(b2 \rightarrow T\) ;
第二次 \(S \rightarrow a1\) 、 \(S \rightarrow b2\) 、 \(a2 \rightarrow T\) 、\(b1 \rightarrow T\) ;
对两张图跑最大流,当且仅当两张图的最大流都为 \(2 \times (a_n + b_n)\) 时符合要求。
代码
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
const int N = 2510;
const int M = 1e5 + 10;
const int INF = 1e9;
struct flow {
int cnt = 1, hd[N], nxt[M * 2], to[M * 2], limit[M * 2];
void add(int u, int v, int w) {
nxt[++cnt] = hd[u], hd[u] = cnt, to[cnt] = v, limit[cnt] = w;
nxt[++cnt] = hd[v], hd[v] = cnt, to[cnt] = u, limit[cnt] = 0;
}
int T, dis[N], cur[N];
i64 dfs(int id, i64 res) {
if(id == T) return res;
i64 flow = 0;
for(int i = cur[id]; i && res; i = nxt[i]) {
cur[id] = i;
int c = std::min(res, (i64)limit[i]), it = to[i];
if(dis[id] + 1 == dis[it] && c) {
int k = dfs(it, c);
flow += k, res -= k, limit[i] -= k, limit[i ^ 1] += k;
}
}
if(!flow) dis[id] = -1;
return flow;
}
i64 maxflow(int s, int t) {
T = t;
i64 flow = 0;
while(1) {
std::queue<int> q;
memcpy(cur, hd, sizeof(hd));
memset(dis, 0xff, sizeof(dis));
q.push(s), dis[s] = 0;
while(q.size()) {
int u = q.front();
q.pop();
for(int i = hd[u]; i; i = nxt[i]) {
if(dis[to[i]] == -1 && limit[i]) {
dis[to[i]] = dis[u] + 1;
q.push(to[i]);
}
}
}
if(dis[t] == -1) return flow;
flow += dfs(s, 1e18);
}
}
};
std::string a[55];
void solve(void) {
int n, a1, a2, an, b1, b2, bn;
flow g;
while(std::cin >> n >> a1 >> a2 >> an >> b1 >> b2 >> bn) {
for(int i = 1; i <= n; i++) {
std::cin >> a[i];
a[i] = " " + a[i];
}
a1++, a2++, b1++, b2++;
g.cnt = 1;
memset(g.hd, 0, sizeof(g.hd));
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(a[i][j] == 'O') {
g.add(i, j, 2);
}
if(a[i][j] == 'N') g.add(i, j, INF);
}
}
flow g1 = g, g2 = g;
int S = n + 1, T = n + 2;
g1.add(S, a1, 2 * an);
g1.add(S, b1, 2 * bn);
g1.add(a2, T, 2 * an);
g1.add(b2, T, 2 * bn);
int cnt1 = g1.maxflow(S, T);
g2.add(S, a1, 2 * an);
g2.add(S, b2, 2 * bn);
g2.add(a2, T, 2 * an);
g2.add(b1, T, 2 * bn);
int cnt2 = g2.maxflow(S, T);
//std::cout << cnt1 << " " << cnt2 << '\n';
if(cnt1 == 2 * (an + bn) && cnt2 == 2 * (an + bn)) {
std::cout << "Yes\n";
} else std::cout << "No\n";
}
}
int main(void) {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
//std::cin >> t;
while(t--) solve();
return 0;
}
P4281 [AHOI2008] 紧急集合 / 聚会
思路
对三个点,先两两的求一下 LCA ,可以发现三个 LCA 只有两种情况:完全相同或者分两个在左右子树上。对于相同的情况,直接计算就好了。对于不相同的情况,显然让单个的点先从远端的 LCA 走到近端的 LCA 比两个点从近端的 LCA 走到远端更划算。
算一下距离:
化简一下就成了:
代码
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
const int N = 2510;
const int M = 1e5 + 10;
const int INF = 1e9;
//0-base
struct LCA {
int n, LOG;
std::vector<int> dep;
std::vector<std::vector<int>> up;
std::vector<std::vector<int>> adj;
LCA(int n_) : n(n_) {
LOG = 0;
while ((1 << LOG) <= n) LOG++;
dep.assign(n, 0);
up.assign(LOG, std::vector<int>(n, -1));
adj.assign(n, std::vector<int>());
}
void add(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
void dfs(int v, int p) {
up[0][v] = p;
for (int i = 1; i < LOG; i++) {
if (up[i - 1][v] != -1) {
up[i][v] = up[i - 1][up[i - 1][v]];
} else {
up[i][v] = -1;
}
}
for (auto to : adj[v]) {
if (to == p) continue;
dep[to] = dep[v] + 1;
dfs(to, v);
}
}
//加完边再初始化
void init(int root = 0) {
dfs(root, -1);
}
int query(int u, int v) {
if (dep[u] < dep[v]) std::swap(u, v);
int diff = dep[u] - dep[v];
for (int i = 0; i < LOG; i++) {
if (diff & (1 << i)) {
u = up[i][u];
if (u == -1) break;
}
}
if (u == v) return u;
for (int i = LOG - 1; i >= 0; i--) {
if (up[i][u] != up[i][v]) {
u = up[i][u];
v = up[i][v];
}
}
return up[0][u];
}
};
void solve(void) {
int n, m; std::cin >> n >> m;
LCA lca(n + 1);
for(int i = 1; i < n; i++) {
int u, v; std::cin >> u >> v;
lca.add(u, v);
}
lca.init(1);
while(m--) {
int x, y, z; std::cin >> x >> y >> z;
int a = lca.query(x, y);
int b = lca.query(y, z);
int c = lca.query(x, z);
int high = a != b ? (lca.dep[a] < lca.dep[b] ? a : b) : a;
int low = a != b ? (lca.dep[a] > lca.dep[b] ? a : b) : c;
std::cout << low << " " << lca.dep[x] + lca.dep[y] + lca.dep[z] - 2 * lca.dep[high] - lca.dep[low] << '\n';
}
}
int main(void) {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
//std::cin >> t;
while(t--) solve();
return 0;
}
P1967 [NOIP 2013 提高组] 货车运输
思路
显然,货车是不会走回路的,因为这样没有意义。因此对于任何一辆车来说,最好情况下他能运输的货物数量就是最大生成树中,两点之间路径中的最小边。因此可以先找出最大生成树(可能是森林),然后使用倍增的方法查找两点间的最小边。
如果两点不在一棵生成树中,那么无解。
代码
#include <bits/stdc++.h>
#define x first
#define y second
using i64 = long long;
using u64 = unsigned long long;
using PII = std::pair<int, int>;
const int N = 2510;
const int M = 1e5 + 10;
const int INF = 1e9;
//0-base
//维护树上路径中的最小边
struct LCA {
int n, LOG;
std::vector<int> dep;
std::vector<std::vector<int>> up;
std::vector<std::vector<int>> minw;
std::vector<std::vector<PII>> adj;
LCA(int n_) : n(n_) {
LOG = 0;
while ((1 << LOG) <= n) LOG++;
dep.assign(n, 0);
up.assign(LOG, std::vector<int>(n, -1));
adj.assign(n, std::vector<PII>());
minw.assign(LOG, std::vector<int>(n, INF));
}
void add(int u, int v, int w) {
adj[u].emplace_back(v, w);
adj[v].emplace_back(u, w);
}
void dfs(int v, int p, int w) {
up[0][v] = p;
minw[0][v] = (p == -1 ? INF : w);
for (int i = 1; i < LOG; i++) {
if (up[i - 1][v] != -1) {
up[i][v] = up[i - 1][up[i - 1][v]];
minw[i][v] = std::min(minw[i - 1][v], minw[i - 1][up[i - 1][v]]);
} else {
up[i][v] = -1;
minw[i][v] = INF;
}
}
for (auto [to, w] : adj[v]) {
if (to == p) continue;
dep[to] = dep[v] + 1;
dfs(to, v, w);
}
}
//加完边再初始化
void init(int root = 0) {
for(int i = 1; i <= n; i++) {
if(up[0][i] == -1) {
dep[i] = 0;
dfs(i, -1, INF);
}
}
}
//返回路径上的最小边权
int query(int u, int v) {
if (dep[u] < dep[v]) std::swap(u, v);
int res = INF;
int diff = dep[u] - dep[v];
for (int i = 0; i < LOG; i++) {
if (diff & (1 << i)) {
res = std::min(res, minw[i][u]);
u = up[i][u];
if (u == -1) break;
}
}
if (u == v) return res;
for (int i = LOG - 1; i >= 0; i--) {
if (up[i][u] != up[i][v]) {
res = std::min(res, minw[i][u]);
res = std::min(res, minw[i][v]);
u = up[i][u];
v = up[i][v];
}
}
res = std::min(res, minw[0][u]);
res = std::min(res, minw[0][v]);
return res;
}
};
struct DSU {
std::vector<int> p, siz;
DSU(int n): p(n + 1), siz(n + 1, 1) {
std::iota(p.begin(), p.end(), 0);
}
int find(int x) {
if(x == p[x]) return p[x];
return p[x] = find(p[x]);
}
bool unite(int a, int b) {
int pa = find(a), pb = find(b);
if(pa == pb) return false;
if(siz[pa] < siz[pb]) std::swap(pa, pb);
p[pb] = pa;
siz[pa] += siz[pb];
return true;
}
bool same(int u, int v) {return find(u) == find(v);}
int size(int u) {return siz[find(u)];}
};
struct Edge {
int u, v, w;
};
void solve(void) {
int n, m; std::cin >> n >> m;
std::vector<Edge> e(m + 1);
for(int i = 1; i <= m; i++) {
std::cin >> e[i].u >> e[i].v >> e[i].w;
}
std::sort(e.begin() + 1, e.end(), [](Edge x, Edge y) {
return x.w > y.w;
});
DSU dsu(n);
LCA lca(n + 1);
for(int i = 1; i <= m; i++) {
if(dsu.unite(e[i].u, e[i].v)) {
lca.add(e[i].u, e[i].v, e[i].w);
}
}
lca.init();
int q; std::cin >> q;
for(int i = 1; i <= q; i++) {
int x, y; std::cin >> x >> y;
if(!dsu.same(x, y)) {
std::cout << "-1\n";
}
else std::cout << lca.query(x, y) << '\n';
}
}
int main(void) {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
//std::cin >> t;
while(t--) solve();
return 0;
}
P5057 [CQOI2006] 简单题
思路
简单的线段树题,要求对01区间实现两个操作:
1.区间内的每个数字异或上 \(1\) ;
2.查询某一位置上的数字。
简单修改一下之前的模版,对于修改操作,直接讲 tag 改成 \(tag[o] \oplus = 1\) ;
对于查询操作,由于,由于模版本身的单点和区间查询都来源于区间查询,所以维护 \(sum\) 数组,每次修改后, \(sum[o] = (r - l + 1) - sum[o]\) 。
代码
#include<bits/stdc++.h>
#define x first
#define y second
using i64 = long long;
using u64 = unsigned long long;
using u128 = __uint128_t;
using PII = std::pair<int, int>;
const int N = 10000;
const int M = 30000;
const i64 INF = 1e18;
const double eps = 1e-6;
//本模板下标默认从1开始
struct SegTree {
int n;
std::vector<int> sum, tag;
SegTree(int n_): n(n_) {
sum.assign(4 * n, 0);
tag.assign(4 * n, -1);
}
void pull(int o) {
sum[o] = sum[o * 2] + sum[o * 2 + 1];
}
void apply(int o, int l, int r) {
sum[o] = (r - l + 1) - sum[o];
tag[o] ^= 1;
}
void push(int o, int l, int r) {
if(tag[o] != -1) {
int m = (l + r) / 2;
apply(o * 2, l, m);
apply(o * 2 + 1, m + 1, r);
tag[o] = -1;
}
}
void build(int o, int l, int r, const std::vector<int> &a) {
tag[o] = -1;
if(l == r) {
sum[o] = a[l];
return;
}
int m = (l + r) / 2;
build(o * 2, l, m, a);
build(o * 2 + 1, m + 1, r, a);
pull(o);
}
void build(const std::vector<int> &a) {
build(1, 1, n, a);
}
int query(int o, int l, int r, int L, int R) {
if(R < l || r < L) return 0;
if(L <= l && r <= R) return sum[o];
push(o, l, r);
int m = (l + r) / 2;
return query(o * 2, l, m, L, R) + query(o * 2 + 1, m + 1, r, L, R);
}
int query_sum(int x, int y) {
return query(1, 1, n, x, y);
}
void update(int o, int l, int r, int L, int R) {
if(R < l || r < L) return;
if(L <= l && r <= R) {
apply(o, l, r);
return;
}
push(o, l , r);
int m = (l + r) / 2;
update(o * 2, l, m, L, R);
update(o * 2 + 1, m + 1, r, L, R);
pull(o);
}
void update(int x, int y) {
update(1, 1, n, x, y);
}
int query(int o, int l, int r, int p) {
if(l == r) return sum[o];
push(o, l, r);
int m = (l + r) / 2;
if(p <= m) return query(o * 2, l, m, p);
return query(o * 2 + 1, m + 1, r, p);
}
int query_point(int p) {
return query(1, 1, n, p);
}
};
void solve(void) {
int n, m; std::cin >> n >> m;
std::vector<int> a(n + 1, 0);
SegTree seg(n + 1);
seg.build(a);
while(m--) {
int op; std::cin >> op;
if(op == 1) {
int l, r; std::cin >> l >> r;
seg.update(l, r);
} else {
int i; std::cin >> i;
std::cout << seg.query_point(i) << '\n';
}
}
}
int main(void) {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
//std::cin >> t;
while(t--) solve();
return 0;
}
P2846 [USACO08NOV] Light Switching G
思路
经验++
一摸一样的题目,单点查询改成了区间查询总和。代码就不放了。
P2574 XOR的艺术
思路
经验++
还是一摸一样的题目,把全零的数组改成01串。

浙公网安备 33010602011771号