周二
H. city safety(树形dp/最大权闭合子图)
比赛时想到了树形dp的做法,但是不知道怎么统计w,而且当前设置的范围会影响父亲。
其实是没想清楚,用dp[u][j]表示以u为根的子树每个点的贡献之和,注意这里不包括父亲那边,但转移状态时会限制父亲的范围,所以是不会错的。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 200 + 10;
int w[N], v[N], n;
vector<int> g[N];
ll dp[N][N];
void dfs(int u, int fa)
{
_for(i, 1, n) dp[u][i] = v[i] - w[u];
for(int v: g[u])
{
if(v == fa) continue;
dfs(v, u);
_for(i, 0, n)
{
ll cur = -1e18;
for(int j = max(i - 1, 0); j <= min(i + 1, n); j++)
cur = max(cur, dp[v][j]);
dp[u][i] += cur;
}
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &w[i]);
_for(i, 1, n) scanf("%d", &v[i]);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
ll ans = 0;
_for(i, 0, n) ans = max(ans, dp[1][i]);
printf("%lld\n", ans);
return 0;
}
然后还有最大权闭合子图的写法,但是要限制一些东西。
网络流的点数可以跑1e4~1e5左右的数据
首先这题一个点选了一个范围其他点九必须选,这个范围带来的权值是正的,必须选带来的范围是负的,容易想到最大权闭合子图,关键是怎么构图
对于每个点,开n个点,表示不同范围的收益,那么显然这n个点只能选一个,贡献只能算一次,这该怎么表示呢
把每个点的贡献变成增量收益,即第i个点的权值是v[i] - v[i - 1],那么这样设权值的话就要设置前i个点全选,后面全部不选。
那么我们可以从i+1连一条容量为正无穷的边到i,这样子的话,标号大的点就会优先流满,流满就是删边就是不选,所以选的是一个前缀
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 5e4;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N];
int n, m, s, t;
void add(int from, int to ,int flow)
{
edge.push_back({from, to, flow});
g[from].push_back(edge.size() - 1);
edge.push_back({to, from, 0});
g[to].push_back(edge.size() - 1);
}
bool bfs()
{
memset(d, 0, sizeof d);
queue<int> q;
q.push(s);
d[s] = 1;
while(!q.empty())
{
int u = q.front(); q.pop();
for(int x: g[u])
{
Edge e = edge[x];
if(!d[e.to] && e.flow)
{
d[e.to] = d[u] + 1;
q.push(e.to);
}
}
}
return d[t];
}
ll dfs(int u, ll in)
{
if(u == t) return in;
ll out = 0;
for(int& i = cur[u]; i < g[u].size(); i++)
{
Edge& e = edge[g[u][i]];
if(d[u] + 1 == d[e.to] && e.flow)
{
ll f = dfs(e.to, min((ll)e.flow, in));
e.flow -= f;
edge[g[u][i] ^ 1].flow += f;
out += f; in -= f;
if(in == 0) break;
}
}
return out;
}
const int M = 210;
ll w[M], v[M], dis[M][M];
int id(int i, int j)
{
return i * n + j + 1;
}
void build()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%lld", &w[i]);
rep(i, 0, n) scanf("%lld", &v[i]);
memset(dis, 0x3f, sizeof dis);
_for(i, 1, n) dis[i][i] = 0;
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
dis[u][v] = dis[v][u] = 1;
}
_for(k, 1, n)
_for(i, 1, n)
_for(j, 1, n)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
s = n * n + n + 1;
t = s + 1;
_for(i, 1, n)
_for(j, 0, n - 1)
{
if(!j) add(s, id(i, j), v[j]);
else
{
add(s, id(i, j), v[j] - v[j - 1]);
add(id(i, j), id(i, j - 1), 1e18);
}
_for(k, 1, n)
if(dis[i][k] == j)
add(id(i, j), k, 1e18);
}
_for(i, 1, n) add(i, t, w[i]);
}
int main()
{
build();
ll ans = 0;
while(bfs())
{
memset(cur, 0, sizeof cur);
ans += dfs(s, 1e18);
}
printf("%lld\n", n * v[n - 1] - ans);
return 0;
}
Static Query on Tree(线段树)
换了一种写法
增加一个专门用来清空的lazy tag,这样不容易出错
#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
int ta[N << 2], tab[N << 2];
int lazya[N << 2], lazyb[N << 2], lazy_clear[N << 2];
int d[N], fa[N], top[N], id[N], siz[N], son[N], cnt;
vector<int> g[N];
int n, q;
void dfs1(int u, int father)
{
d[u] = d[father] + 1;
fa[u] = father;
siz[u] = 1;
for(int v: g[u])
{
dfs1(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int fa, int t)
{
top[u] = t;
id[u] = ++cnt;
if(siz[u] == 1) return;
dfs2(son[u], u, t);
for(int v: g[u])
{
if(v == fa || v == son[u]) continue;
dfs2(v, u, v);
}
}
void updateA(int k, int l, int r)
{
ta[k] = r - l + 1;
lazya[k] = 1;
}
void updateB(int k, int l, int r)
{
tab[k] = ta[k];
lazyb[k] = 1;
}
void cla(int k, int l, int r)
{
ta[k] = tab[k] = lazya[k] = lazyb[k] = 0;
lazy_clear[k] = 1;
}
void up(int k)
{
ta[k] = ta[l(k)] + ta[r(k)];
tab[k] = tab[l(k)] + tab[r(k)];
}
void down(int k, int l, int r)
{
int m = l + r >> 1;
if(lazy_clear[k]) cla(l(k), l, m), cla(r(k), m + 1, r), lazy_clear[k] = 0;
if(lazya[k]) updateA(l(k), l, m), updateA(r(k), m + 1, r), lazya[k] = 0;
if(lazyb[k]) updateB(l(k), l, m), updateB(r(k), m + 1, r), lazyb[k] = 0;
}
void change(int k, int l, int r, int L, int R, int op)
{
if(L <= l && r <= R)
{
if(!op) updateA(k, l, r);
else if(op == 1) updateB(k, l, r);
else cla(k, l, r);
return;
}
down(k, l, r);
int m = l + r >> 1;
if(L <= m) change(l(k), l, m, L, R, op);
if(R > m) change(r(k), m + 1, r, L, R, op);
up(k);
}
int ask(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return tab[k];
down(k, l, r);
int m = l + r >> 1, res = 0;
if(L <= m) res += ask(l(k), l, m, L, R);
if(R > m) res += ask(r(k), m + 1, r, L, R);
return res;
}
void add(int u, int v, int op)
{
while(top[u] != top[v])
{
if(d[top[u]] < d[top[v]]) swap(u, v);
if(!op) change(1, 1, n, id[top[u]], id[u], 0);
else change(1, 1, n, id[top[u]], id[u], 1);
u = fa[top[u]];
}
if(d[u] < d[v]) swap(u, v);
if(!op) change(1, 1, n, id[v], id[u], 0);
else change(1, 1, n, id[v], id[u], 1);
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &q);
_for(i, 1, n) g[i].clear(), son[i] = d[i] = top[i] = fa[i] = id[i] = siz[i] = 0;
cnt = 0;
_for(i, 2, n)
{
int x; scanf("%d", &x);
g[x].push_back(i);
}
dfs1(1, 0);
dfs2(1, 0, 1);
while(q--)
{
int na, nb, nc, x;
scanf("%d%d%d", &na, &nb, &nc);
while(na--)
{
scanf("%d", &x);
add(1, x, 0);
}
while(nb--)
{
scanf("%d", &x);
add(1, x, 1);
}
int ans = 0;
while(nc--)
{
scanf("%d", &x);
ans += ask(1, 1, n, id[x], id[x] + siz[x] - 1);
change(1, 1, n, id[x], id[x] + siz[x] - 1, 2);
}
printf("%d\n", ans);
cla(1, 1, n);
}
}
return 0;
}
DOS Card(线段树)
这题妙啊,用线段树维护多个信息即可
#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const ll INF = 1e18;
struct node
{
ll mx1, mx2, mi1, mi2;
ll zss, ssf, zf, fz, zssf;
}t[N << 2];
int n, m;
node add(node a, node b)
{
node res;
vector<ll> ve = {a.mx1, a.mx2, b.mx1, b.mx2};
sort(ve.begin(), ve.end());
res.mx1 = ve[3];
res.mx2 = ve[2];
ve.clear();
ve = {a.mi1, a.mi2, b.mi1, b.mi2};
sort(ve.begin(), ve.end());
res.mi1 = ve[0];
res.mi2 = ve[1];
res.zf = max({a.zf, b.zf, a.mx1 - b.mi1});
res.fz = max({a.fz, b.fz, -a.mi1 + b.mx1});
res.zss = max({a.zss, b.zss, a.mx1 + max(b.fz, b.zf), a.zf + b.mx1,
a.mx1 + a.mx2 - b.mi1});
res.ssf = max({a.ssf, b.ssf, max(a.zf, a.fz) - b.mi1,
a.mx1 - b.mi1 - b.mi2, -a.mi1 + b.zf});
res.zssf = max({a.zssf, b.zssf, a.mx1 + b.ssf, a.zss - b.mi1, a.mx1 + a.mx2 - b.mi1 - b.mi2,
a.zf + b.zf});
return res;
}
void up(int k)
{
t[k] = add(t[l(k)], t[r(k)]);
}
void build(int k, int l, int r)
{
if(l == r)
{
ll x; scanf("%lld", &x);
x = x * x;
t[k] = {x, -INF, x, INF, -INF, -INF, -INF, -INF, -INF};
return ;
}
int m = l + r >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k);
}
node ask(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[k];
node res = {-INF, -INF, INF, INF, -INF, -INF, -INF, -INF, -INF};
int m = l + r >> 1;
if(L <= m) res = add(res, ask(l(k), l, m, L, R));
if(R > m) res = add(res, ask(r(k), m + 1, r, L, R));
return res;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
build(1, 1, n);
while(m--)
{
int l, r;
scanf("%d%d", &l, &r);
node ans = ask(1, 1, n, l, r);
printf("%lld\n", ans.zssf);
}
}
return 0;
}
F. Stone(博弈 奇偶)
这题结论很简单,题目很复杂……
考虑奇偶,当前是有奇数堆,那么先手可以选一个奇数把所有奇数堆都变成偶数堆。
先手选奇数后,以后所有的数都是奇数了,后手不管怎么选,会把一些奇数堆变成偶数堆
那么先手就把那些奇数堆变成偶数堆即可,比如取s=1
如果当前没有奇数堆,那就把堆数量/=2,那么就变成相同的情况
直到出现奇数堆,那么当前先手可以选的就是小于最小奇数的所有奇数
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
int a[N], n;
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
while(1)
{
int mi = 2e9;
_for(i, 1, n)
if(a[i] % 2 == 1)
mi = min(mi, a[i]);
if(mi != 2e9)
{
printf("%d\n", (mi + 1) / 2);
break;
}
_for(i, 1, n) a[i] /= 2;
}
return 0;
}
周三
P2764 最小路径覆盖问题(网路流)
给一个有向图,问最少有多少条不相交路径把图上的点都覆盖
回顾一下以前的题目
一开始n个点就是n条路,然后一条有向边就可以减少一个答案
根据这个特点,把每个点拆成x和y,然后汇点向x连边,y向汇点连边
原图的边i,j,那就xi向yj连边。边都是容量为1
跑最大流,就是最多合并多少
那么答案就是n-最大流
还有一种等价的建图方式,一个点拆成入点和出点,然后原图的边就是x的出点向y的入点。然后原图的一条有向边只能起一次作用,对应网络中的一条流,所以源点向出点连边,入点向汇点连边.所有边容量都为1
魔术球问题(最小路径覆盖+残量网络)
建边,求最小路径覆盖即可
注意,可以不用二分答案,对于新建立的边,直接加入,然后在残量网络中跑,不用重新跑一次。
[CQOI2015]网络吞吐量(拆点)
首先传输只按照最短路径传输,所以直接跑一遍最短路,把不是最短路上的边删去
最短路上的边满足d[v] = d[u] + w(u, v)
然后这题对点流过的流量有限制,转化成边权,即把点拆成入点出点,中间连点权为容量的边。原图的边容量为正无穷。起点和终点可以看作点权为正无穷
周四
B. Boss Rush(二分答案+状压dp)
看到数据范围往状压dp想
问题出现在处理技能造成的伤害,后来发现如果二分一个时间,时间确定下伤害就可以直接确定。
注意初始化,注意下标
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 20;
int t[N], len[N], n;
ll k[1 << 18], dp[1 << 18];
vector<ll> s[N];
ll H, sum;
bool check(int key)
{
memset(dp, 0, sizeof dp);
rep(S, 0, 1 << n)
_for(i, 1, n)
if(S & (1 << (i - 1)))
{
int S0 = S ^ (1 << (i - 1));
if(key <= k[S0]) continue;
dp[S] = max(dp[S], dp[S0] + s[i][min(key - k[S0] - 1, len[i] - 1ll)]);
if(dp[S] >= H) return true;
}
return false;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%lld", &n, &H);
_for(i, 1, n) s[i].clear();
sum = 0;
_for(i, 1, n)
{
scanf("%d%d", &t[i], &len[i]);
_for(j, 1, len[i])
{
int x; scanf("%d", &x);
if(s[i].size()) s[i].push_back(x + s[i].back());
else s[i].push_back(x);
sum += x;
}
}
if(sum < H)
{
puts("-1");
continue;
}
rep(S, 0, 1 << n)
{
k[S] = -1;
_for(i, 1, n)
if(S & (1 << (i - 1)))
k[S] += t[i];
}
int l = -1, r = 3e6;
while(l + 1 < r)
{
int m = l + r >> 1;
if(check(m)) r = m;
else l = m;
}
printf("%d\n", r);
}
return 0;
}
L. Two Permutations(dp+优化)
很容易想到一个dp,然后考虑优化
因为是排列,所以可以更新的非常少,直接枚举可以更新的地方即可
注意是否存在用count,不要直接去访问,否则会T
注意取模以及空间大小
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
const int mod = 998244353;
int a[N], b[N], c[N << 1], n;
ll id(int i, int j)
{
return 1LL * i * 1e6 + j;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
unordered_map<ll, int> dp;
scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
a[x] = i;
}
_for(i, 1, n)
{
int x; scanf("%d", &x);
b[x] = i;
}
_for(i, 1, 2 * n) scanf("%d", &c[i]);
dp[id(0, 0)] = 1;
_for(k, 1, 2 * n)
{
int i = a[c[k]];
int j = k - i;
if(j >= 0 && dp.count(id(i - 1, j))) (dp[id(i, j)] += dp[id(i - 1, j)]) %= mod;
j = b[c[k]];
i = k - j;
if(i >= 0 && dp.count(id(i, j - 1))) (dp[id(i, j)] += dp[id(i, j - 1)]) %= mod;
}
printf("%d\n", dp[id(n, n)]);
}
return 0;
}
Package Delivery(贪心)
思路比较容易想到,按照右端点排序,然后找到右端点最小的点操作肯定是最优的,然后找可以操作的区间里面右端点最小的。
问题是怎么实现
对于当前的右端点,首先要找到可以操作的区间。那么我们可以把左右端点拆开,然后遇到左端点就加入候选区间中,遇到右区间就操作。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
int del[N], l[N], r[N], n, k;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
vector<pair<int, int>> ve;
scanf("%d%d", &n, &k);
_for(i, 1, n)
{
scanf("%d%d", &l[i], &r[i]);
ve.push_back({l[i], -i});
ve.push_back({r[i], i});
del[i] = 0;
}
sort(ve.begin(), ve.end());
int ans = 0;
set<pair<int, int>> s;
for(auto [pos, id]: ve)
{
if(id < 0) s.insert({r[-id], -id});
else
{
if(del[id]) continue;
ans++;
int cnt = k;
while(cnt && s.size())
{
del[s.begin()->second] = 1;
s.erase(s.begin());
cnt--;
}
}
}
printf("%d\n", ans);
}
return 0;
}
还有一种是队友想的,就是维护当前区间的交区间,当新的区间去前面的交区间没有交集的时候,前面的交区间就必须要操作了,此时将交区间中右端点最小的k个去点即可。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
pair<int, int> a[N];
int n, k;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &k);
_for(i, 1, n) scanf("%d%d", &a[i].first, &a[i].second);
sort(a + 1, a + n + 1);
int ans = 0;
priority_queue<int, vector<int>, greater<int>> q;
_for(i, 1, n)
{
while(!q.empty() && q.top() < a[i].first)
{
int cnt = k;
while(cnt && !q.empty()) q.pop(), cnt--;
ans++;
}
q.push(a[i].second);
}
printf("%d\n", ans + (q.size() + k - 1) / k);
}
return 0;
}
K. Taxi(二分)
比较巧妙的二分,不是那么常规
首先没有上限的限制的话就是经典题,直接找xi+yi,xi-yi,-xi-yi,-xi+yi的四个点即可
那么将点按照w排序
取中点wk
比较wk和k~n后缀的不考虑上限的答案d(预处理后缀最值的四个点即可)
如果d <= wk,那么这右半段区间的贡献就是d了,那么用d更新答案,然后舍去右区间继续二分
如果d > wk,这时右半区间的贡献最少都为wk,用这个更新答案,然后1~k都不会产生更优的答案了,舍去左区间,继续二分。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
struct node
{
int x, y, w;
}a[N];
int id[N][4], n, q;
bool cmp(node a, node b)
{
return a.w < b.w;
}
int cal(node a, node b)
{
return abs(a.x - b.x) + abs(a.y - b.y);
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &q);
_for(i, 1, n) scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].w);
sort(a + 1, a + n + 1, cmp);
rep(j, 0, 4) id[n][j] = n;
for(int i = n - 1; i >= 1; i--)
{
rep(j, 0, 4) id[i][j] = id[i + 1][j];
if(a[i].x + a[i].y > a[id[i][0]].x + a[id[i][0]].y) id[i][0] = i;
if(a[i].x - a[i].y > a[id[i][1]].x - a[id[i][1]].y) id[i][1] = i;
if(-a[i].x + a[i].y > -a[id[i][2]].x + a[id[i][2]].y) id[i][2] = i;
if(-a[i].x - a[i].y > -a[id[i][3]].x - a[id[i][3]].y) id[i][3] = i;
}
while(q--)
{
int x, y;
scanf("%d%d", &x, &y);
int ans = 0;
int l = 1, r = n;
while(l <= r)
{
int m = l + r >> 1, d = 0;
rep(j, 0, 4) d = max(d, cal(a[id[m][j]], {x, y}));
if(a[m].w < d)
{
ans = max(ans, a[m].w);
l = m + 1;
}
else
{
ans = max(ans, d);
r = m - 1;
}
}
printf("%d\n", ans);
}
}
return 0;
}
周五
[SCOI2007]蜥蜴(建图)
挺容易想到的,一只蜥蜴就是一条流,然后把点权变成边权即可
E. Fox And Dinner(最大流+建图)
这题的关键在于数据范围没有1使得两个数加起来不可能为2,所以质数一定是奇数,所以一定是一奇一偶,那么就是一个二分图
显然可以匹配就连边容量为1,在环中每个点都有两天边,所以入点到左侧点连容量为2的边,右侧点向出点连容量为2的边。
跑最大流,如果等于n就有解。
因为每个环显然是奇偶交替,是偶数,所以奇数一半偶数一半,此时最大流为n。如果不是各一半,那么最大流小于n
最后是输出方案,对于方案就建立边,注意去除反向边和于源点汇点有关的边,容量为0的边显然是流过的边,也就是说两端的点在一个环中。那么建图跑dfs即可
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
typedef long long ll;
const int N = 300;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N];
int n, m, s, t;
void add(int from, int to, int flow)
{
edge.push_back(Edge{from, to, flow});
g[from].push_back(edge.size() - 1);
edge.push_back(Edge{to, from, 0});
g[to].push_back(edge.size() - 1);
}
bool bfs()
{
memset(d, 0, sizeof(d));
queue<int> q;
q.push(s);
d[s] = 1;
while(!q.empty())
{
int u = q.front(); q.pop();
for(auto x: g[u])
{
Edge e = edge[x];
if(!d[e.to] && e.flow)
{
d[e.to] = d[u] + 1;
q.push(e.to);
}
}
}
return d[t];
}
ll dfs(int u, ll in)
{
if(u == t) return in;
ll out = 0;
for(int& i = cur[u]; i < g[u].size(); i++)
{
Edge& e = edge[g[u][i]];
if(d[u] + 1 == d[e.to] && e.flow)
{
ll f = dfs(e.to, min((ll)e.flow, in));
e.flow -= f;
edge[g[u][i] ^ 1].flow += f;
out += f; in -= f;
if(in == 0) break;
}
}
return out;
}
const int M = 1e5 + 10;
int vis[M], a[N], k[N], cnt;
vector<int> G[N], res[N], p;
void init()
{
vis[0] = vis[1] = 1;
_for(i, 2, 1e5)
{
if(!vis[i]) p.push_back(i);
for(int x: p)
{
if(i * x > 1e5) break;
vis[i * x] = 1;
if(i % x == 0) break;
}
}
}
void build()
{
init(); //不要忘写
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
s = n + 1; t = s + 1;
vector<int> v1, v2;
_for(i, 1, n)
{
if(a[i] % 2 == 1) v1.push_back(i);
else v2.push_back(i);
}
for(int x: v1) add(s, x, 2);
for(int x: v2) add(x, t, 2);
for(int x: v1)
for(int y: v2)
if(!vis[a[x] + a[y]])
add(x, y, 1);
}
void dfs2(int u, int fa)
{
k[u] = 1;
res[cnt].push_back(u);
for(int v: G[u])
{
if(v == fa || k[v]) continue;
dfs2(v, u);
}
}
int main()
{
build();
ll ans = 0;
while(bfs())
{
memset(cur, 0, sizeof(cur));
ans += dfs(s, 1e18);
}
if(ans != n)
{
puts("Impossible");
return 0;
}
for(auto [u, v, flow]: edge)
if(u != s && v != t && !flow && a[u] % 2 == 1 && a[v] % 2 == 0) //注意去除反向边
{
G[u].push_back(v);
G[v].push_back(u);
}
_for(i, 1, n)
if(!k[i])
{
cnt++;
dfs2(i, 0);
}
printf("%d\n", cnt);
_for(i, 1, cnt)
{
printf("%d ", res[i].size());
for(int x: res[i]) printf("%d ", x);
puts("");
}
return 0;
}
Dining(网络流)
模仿二分图匹配,注意把牛放在中间,这样保证了饮料和食物都用了一次,但是没有保证牛用了一次,那么拆点中间连一个容量为1的边即可。
星际转移问题(分层思想+网络流)
这题最关键的一点在于分层,每一天一层
然后根据太空船在层与层之间连边,还要加入留在原地的边
如果固定了源点汇点,就可以加边然后在残量网络上继续跑,所以我们固定汇点,让每一层的终点连向一个超级汇点,每次加一层,在残量网络上跑最大流。
该博客记录了一周内算法题的解题思路。涉及树形dp、线段树、网络流、二分答案、状压dp、贪心等多种算法。如周二的city safety题有树形dp和最大权闭合子图两种解法;周三的最小路径覆盖问题用网络流求解;周四的Boss Rush题采用二分答案和状压dp。
1506

被折叠的 条评论
为什么被折叠?



