2019 Multi-University Training Contest 3
2019 Multi-University Training Contest 3
Blow up the city
首先考虑建立一个虚根,与所有反图中入度为\(0\)的点连边形成一颗树,然后考虑建出其支配树。对于\(DAG\)来说比较简单,反图中按着拓扑序来搞,这样就可以保证处理一个点时,其父亲们都在支配树中了,一个点在支配树中的父亲就是所有能到达它的点在支配树中的\(lca\)。
支配树有一个性质就是,一个结点到其根这条链上所有的点都支配它,那么根据这个性质处理出深度,利用树上倍增求\(lca\),可以做到单词询问\(O(logn)\)的复杂度。
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5, M = 2e5 + 5;
int T;
vector <int> G[N], rG[N];
int n, m, tot;
int d[N], f[N][20], a[N], dep[N];
int lca(int x, int y) {
if(dep[y] > dep[x]) swap(x, y);
for(int i = 19; i >= 0; i--) {
if(dep[f[x][i - 1]] >= dep[y]) x = f[x][i - 1];
}
if(x == y) return x;
for(int i = 19; i >= 0; i--) {
if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
}
return f[x][0];
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> n >> m;
for(int i = 1; i <= n + 1; i++)
G[i].clear(), rG[i].clear(), d[i] = 0;
for(int i = 1; i <= m; i++) {
int u, v; cin >> u >> v;
rG[v].push_back(u);
G[u].push_back(v);
d[u]++;
}
queue <int> q;
for(int i = 1; i <= n; i++) {
if(!d[i]) {
q.push(i);
G[i].push_back(n + 1);
}
}
tot = 0; dep[n + 1] = 0;
while(!q.empty()) {
int u = q.front(); q.pop();
a[++tot] = u;
for(auto v : rG[u]) {
if(--d[v] == 0) q.push(v);
}
}
for(int i = 1; i <= tot; i++) {
int x = a[i], p = -1;
for(auto y : G[x]) {
if(p == -1) p = y;
else p = lca(p, y);
}
f[x][0] = p; dep[x] = dep[p] + 1;
for(int j = 1; j < 20; j++) f[x][j] = f[f[x][j - 1]][j - 1] ;
}
int Q; cin >> Q;
while(Q--) {
int x, y; cin >> x >> y;
int ans = dep[x] + dep[y] - dep[lca(x, y)];
cout << ans << '\n';
}
}
return 0;
}
Distribution of books
如果\(a_i\)只能为正的话就是一个很简单的二分答案了,但这里能够为负数,也就是说之前贪心地来选取是不行的。所以我们就\(dp\)一下处理出前\(i\)个数能分配的最大组数就行了,转移的时候用线段树优化一下。
Code
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N = 4e5 + 5;
ll b[N], a[N];
int T, D, n, k;
int get(ll x) {
return lower_bound(b + 1, b + D + 1, x) - b;
}
int mx[N << 2];
void push_up(int o) {
mx[o] = max(mx[o << 1], mx[o << 1|1]);
}
void build(int o, int l, int r) {
mx[o] = -INF;
if(l == r) return;
int mid = (l + r) >> 1;
build(o << 1, l, mid); build(o << 1|1, mid + 1, r);
}
void update(int o, int l, int r, int pos, int v) {
if(l == r && l == pos) {
mx[o] = max(mx[o], v);
return ;
}
int mid = (l + r) >> 1;
if(pos <= mid) update(o << 1, l, mid, pos, v);
else update(o << 1|1, mid + 1, r, pos, v);
push_up(o);
}
int query(int o, int l, int r, int v) {
if(l >= v) return mx[o];
int mid = (l + r) >> 1;
int ans = query(o << 1|1, mid + 1, r, v);
if(v <= mid) ans = max(ans, query(o << 1, l, mid, v));
return ans;
}
bool check(ll x) {
D = 0;
for(int i = 1; i <= n; i++) {
b[++D] = a[i];
b[++D] = a[i] - x;
}
b[++D] = 0;
sort(b + 1, b + D + 1);
D = unique(b + 1, b + D + 1) - b - 1;
build(1, 1, D);
update(1, 1, D, get(0), 0);
for(int i = 1; i <= n; i++) {
int f = query(1, 1, D, get(a[i] - x)) + 1;
if(f >= k) return 1;
update(1, 1, D, get(a[i]), f);
}
return 0;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> n >> k;
for(int i = 1; i <= n; i++) cin >> a[i], a[i] += a[i - 1];
ll l = -2e14, r = 1e9 + 1, mid;
while(l < r) {
mid = (l + r) >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << l << '\n';
}
return 0;
}
Fansblog
考虑威尔逊定理:对于一个质数\(p\),有\((p-1)!=p-1(\) mod \(p)\),并且还需要知道素数的间隔一般来讲是不超过\(264\)的(可能记忆有点错误)。所以就暴力找上一个素数,然后暴力求逆元来搞就行了。
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef __int128 ll;
ll p;
int T;
struct Istream {
template <class T>
Istream &operator >>(T &x) {
static char ch;static bool neg;
for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
x=neg?-x:x;
return *this;
}
}fin;
struct Ostream {
template <class T>
Ostream &operator <<(T x) {
x<0 && (putchar('-'),x=-x);
static char stack[233];static int top;
for(top=0;x;stack[++top]=x%10+'0',x/=10);
for(top==0 && (stack[top=1]='0');top;putchar(stack[top--]));
return *this;
}
Ostream &operator <<(char ch) {
putchar(ch);
return *this;
}
}fout;
bool Is_prime(long long x) {
bool ok = true;
for(int i = 2; (long long)i * i <= x; i++) {
if(x % i == 0) {
ok = false;
break ;
}
}
return ok;
}
ll qp(ll a, long long b) {
ll ans = 1;
while(b) {
if(b & 1) ans = ans * a % p;
a = a * a % p;
b >>= 1;
}
return ans;
}
int main() {
cin >> T;
while(T--) {
fin >> p;
ll ans = p - 1;
long long q;
for(long long i = p - 1;;i--) {
if(Is_prime(i)) {
q = i; break;
}
}
for(long long i = q + 1; i <= p - 1; i++) {
ans = ans % p * qp(i, p - 2) % p;
}
fout << ans << '\n';
}
return 0;
}
Find the answer
队友写的伸展树。。实际上不用这么麻烦,考虑将问题转换一下:保留最小的几个数使得满足\(\sum_{j=1}^{i-1}W_j\leq m-W_i\)。因为这是前缀,所以直接用线段树搞一搞即可。
还是给队友写的代码吧:
Code
#include<bits/stdc++.h>
typedef long long ll;
const int MAXN = 2e5 + 5, MAXM = 2e5 + 5, INF = 0x3f3f3f3f, MOD = 998244353;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
using namespace std;
const int oo = (1e9) - (1e6);
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define pb push_back
#define RR register
#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define all(v) (v.begin(),v.end())
#define lc(x) c[x][0]
#define rc(x) c[x][1]
typedef long double db;
typedef unsigned int uint;
int t, n, m, a;
namespace Rin {
int c[MAXN][2], fa[MAXN], siz[MAXN], cnt[MAXN], rt, tot = 0;
ll v[MAXN], sum[MAXN];
void init() {
rt = tot = 0;
memset(siz, 0, sizeof(siz));
memset(cnt, 0, sizeof(cnt));
memset(v, 0, sizeof(v));
memset(sum, 0, sizeof(sum));
memset(c, 0, sizeof(c));
memset(fa, 0, sizeof(c));
}
void pushUp(int x) {
siz[x] = siz[lc(x)] + siz[rc(x)] + cnt[x];
sum[x] = sum[lc(x)] + sum[rc(x)] + v[x] * cnt[x];
}
inline void connect(int x, int f, int k) {
c[f][k] = x;
fa[x] = f;
}
void rotate(int x) {
int y = fa[x], z = fa[y], k = x == c[y][1];
connect(c[x][k ^ 1], y, k);
connect(y, x, k ^ 1);
connect(x, z, y == c[z][1]);
pushUp(y); pushUp(x);
}
void splay(int x, int t) {
while (fa[x] != t) {
int y = fa[x], z = fa[y];
if (z != t)(x == c[y][0]) ^ (y == c[z][0]) ? rotate(x) : rotate(y);
rotate(x);
}
if (t == 0)rt = x;
}
void insert(int x) {
int u = rt, f = 0;
while (u) {
f = u; siz[u]++; sum[u] += x;
u = c[u][x > v[u]];
}
if (!u) {
u = ++tot;
siz[u] = cnt[u] = 1;
v[u] = sum[u] = x;
fa[u] = f;
if (f)c[f][x > v[f]] = u;
}
splay(u, 0);
}
int find(int x, ll k) {
k += INF;
int ans = 0;
while (1) {
if (sum[c[x][1]] >= k)x = c[x][1];
else if (sum[c[x][1]] + v[x] * cnt[x] < k)k -= sum[c[x][1]] + v[x] * cnt[x], ans += siz[c[x][1]] + cnt[x], x = c[x][0];
else return ans += (k - sum[c[x][1]] + v[x] - 1) / v[x] + siz[c[x][1]];
}
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> t;
while (t--) {
cin >> n >> m;
Rin::init();
Rin::insert(0);
Rin::insert(INF);
ll sum = 0;
for (int i = 1; i <= n; i++) {
cin >> a;
sum += a;
if (sum <= m) {
cout << "0" << " ";
Rin::insert(a);
continue;
}
cout << Rin::find(Rin::rt, sum - m) - 1 << " ";
Rin::insert(a);
}
cout << '\n';
}
return 0;
}
Game
题目问的是先手必赢的情况,我们知道先手必输的情况就是区间中所有数的异或和为0,由于这个比较好求,所以可以将问题稍微转换一下,即求区间中异或和为0的个数。
因为多个询问并且不强制在线,并且一个数的贡献比较好算,那就考虑莫队了。2操作也只会影响一个位置的前缀异或和,也算是比较好写的。
注意一下区间变成了\([l-1,r]\),结合一下前缀异或和理解一下就行了。
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int a[N], sum[N], upd[N];
ll cnt[(1 << 21) + 5];
int n, m, block;
int l, r, t;
ll ans;
struct query{
int id, l, r, k;
ll ans;
}q[N];
int blo(int x) {
return (x - 1) / block;
}
bool cmp(query A, query B) {
if(blo(A.l) == blo(B.l) && blo(A.r) == blo(B.r)) return blo(A.k) < blo(B.k);
if(blo(A.l) == blo(B.l)) return blo(A.r) < blo(B.r);
return blo(A.l) < blo(B.l);
}
void add(int p, int v) {
if(v == 1) ans += cnt[sum[p]]++;
else ans -= --cnt[sum[p]];
}
void Update(int pos) {
if(l <= pos && pos <= r) add(pos, -1);
sum[pos] ^= a[pos];
swap(a[pos], a[pos + 1]);
sum[pos] ^= a[pos];
if(l <= pos && pos <= r) add(pos, 1);
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
while(cin >> n >> m) {
memset(cnt, 0, sizeof(cnt));
block = (int)pow(n, 0.66666);
for(int i = 1; i <= n; i++) cin >> a[i], sum[i] = sum[i - 1] ^ a[i];
int tot = 0, num = 0;
for(int i = 1; i <= m; i++) {
int op; cin >> op;
if(op == 1) {
int l, r; cin >> l >> r; ;
q[++tot].id = tot;
q[tot].l = l - 1; q[tot].r = r;
q[tot].k = num;
} else {
int p; cin >> p;
upd[++num] = p;
}
}
sort(q + 1, q + tot + 1, cmp);
l = t = 0; r = -1; ans = 0;
for(int i = 1; i <= tot; i++) {
for(; r < q[i].r; r++) add(r + 1, 1);
for(; r > q[i].r; r--) add(r, -1);
for(; l < q[i].l; l++) add(l, -1);
for(; l > q[i].l; l--) add(l - 1, 1);
for(; t < q[i].k; t++) Update(upd[t + 1]);
for(; t > q[i].k; t--) Update(upd[t]);
q[q[i].id].ans = 1ll * (r - l + 1) * (r - l) / 2 - ans;
}
for(int i = 1; i <= tot; i++) cout << q[i].ans << '\n';
}
return 0;
}
K Subsequence
网络流的一个经典应用。
Code
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N = 2015;
struct edge {
int to, capacity, cost, rev;
edge() {}
edge(int to, int _capacity, int _cost, int _rev) :to(to), capacity(_capacity), cost(_cost), rev(_rev) {}
};
struct Min_Cost_Max_Flow {
int V, H[N << 1], dis[N << 1], PreV[N << 1], PreE[N << 1];
vector<edge> G[N << 1];
void Init(int n) {
V = n;
for (int i = 0; i <= V; ++i)G[i].clear();
}
void Add_Edge(int from, int to, int cap, int cost) {
G[from].push_back(edge(to, cap, cost, G[to].size()));
G[to].push_back(edge(from, 0, -cost, G[from].size() - 1));
}
//flow是自己传进去的变量,就是最后的最大流,返回的是最小费用,f=INF
int Min_cost_max_flow(int s, int t, int f, int& flow) {
int res = 0; fill(H, H + 1 + V, 0);
while (f) {
priority_queue <pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>> > q;
fill(dis, dis + 1 + V, INF);
dis[s] = 0; q.push(pair<int, int>(0, s));
while (!q.empty()) {
pair<int, int> now = q.top(); q.pop();
int v = now.second;
if (dis[v] < now.first)continue;
for (int i = 0; i < G[v].size(); ++i) {
edge& e = G[v][i];
if (e.capacity > 0 && dis[e.to] > dis[v] + e.cost + H[v] - H[e.to]) {
dis[e.to] = dis[v] + e.cost + H[v] - H[e.to];
PreV[e.to] = v;
PreE[e.to] = i;
q.push(pair<int, int>(dis[e.to], e.to));
}
}
}
if (dis[t] == INF)break;
for (int i = 0; i <= V; ++i)H[i] += dis[i];
int d = f;
for (int v = t; v != s; v = PreV[v])d = min(d, G[PreV[v]][PreE[v]].capacity);
f -= d; flow += d; res += d*H[t];
for (int v = t; v != s; v = PreV[v]) {
edge& e = G[PreV[v]][PreE[v]];
e.capacity -= d;
G[v][e.rev].capacity += d;
}
}
return res;
}
int Max_cost_max_flow(int s, int t, int f, int& flow) {
int res = 0;
fill(H, H + 1 + V, 0);
while (f) {
priority_queue <pair<int, int>> q;
fill(dis, dis + 1 + V, -INF);
dis[s] = 0;
q.push(pair<int, int>(0, s));
while (!q.empty()) {
pair<int, int> now = q.top(); q.pop();
int v = now.second;
if (dis[v] > now.first)continue;
for (int i = 0; i < G[v].size(); ++i) {
edge& e = G[v][i];
if (e.capacity > 0 && dis[e.to] < dis[v] + e.cost + H[v] - H[e.to]) {
dis[e.to] = dis[v] + e.cost + H[v] - H[e.to];
PreV[e.to] = v;
PreE[e.to] = i;
q.push(pair<int, int>(dis[e.to], e.to));
}
}
}
if (dis[t] == -INF)break;
for (int i = 0; i <= V; ++i)H[i] += dis[i];
int d = f;
for (int v = t; v != s; v = PreV[v])d = min(d, G[PreV[v]][PreE[v]].capacity);
f -= d; flow += d;
res += d*H[t];
for (int v = t; v != s; v = PreV[v]) {
edge& e = G[PreV[v]][PreE[v]];
e.capacity -= d;
G[v][e.rev].capacity += d;
}
}
return res;
}
}sol;
int T, n, k;
int a[N];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> n >> k;
for(int i = 1; i <= n; i++) cin >> a[i];
int s1 = 0, s2 = 2 * n + 1, t = 2 * n + 2;
sol.Init(t);
sol.Add_Edge(s1, s2, k, 0);
for(int i = 1; i <= n; i++) {
sol.Add_Edge(i, i + n, 1, -a[i]);
}
for(int i = 1; i <= n; i++) {
for(int j = i + 1; j <= n; j++) {
if(a[j] >= a[i]) sol.Add_Edge(i + n, j, 1, 0);
}
}
for(int i = 1; i <= n; i++) {
sol.Add_Edge(s2, i, INF, 0);
sol.Add_Edge(i + n, t, INF, 0);
}
int flow = 0;
cout << -sol.Min_cost_max_flow(s1, t, INF, flow) << '\n';
}
return 0;
}
Squrirrel
考虑不删边的情况,那么我们就需要通过两次dfs来进行dp,并且要维护子树内最远距离和次远距离以及从父亲那边的最远距离。
因为在这种题目中,维护最大距离时,肯定是优先考虑最大的那几个的。
删边的话就再加一维dp状态就好了。并且还要维护第三大的距离。
代码里面的\(g\)数组就表示在子树中删边的最大、次大距离,\(f\)数组是不删边的情况,\(h\)数组就是从父亲那边转移过来时的最大距离,\(0/1\)分别代表不删边和删边。
详见代码:
Code
#include <bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 2e5 + 5;
int T, n;
struct Edge{
int v, w, next;
}e[N << 1];
int head[N], tot;
void adde(int u, int v, int w) {
e[tot].v = v; e[tot].w = w; e[tot].next = head[u]; head[u] = tot++;
}
pii f[N][3];
int g[N][2], h[N][2];
int d[N];
pii res;
void init() {
for(int i = 1; i <= n; i++) {
for(int j = 0; j < 2; j++) {
f[i][j] = mp(0, 0);
g[i][j] = g[i][j] = 0;
}
f[i][3] = mp(0, 0);
}
res = mp(INF, INF);
}
void dfs(int u, int fa) {
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v, w = e[i].w;
if(v == fa) continue;
d[v] = w;
dfs(v, u);
if(f[v][0].fi + w > f[u][0].fi) {
f[u][2] = f[u][1];
f[u][1] = f[u][0];
f[u][0].fi = f[v][0].fi + w;
f[u][0].se = v;
} else if(f[v][0].fi + w > f[u][1].fi) {
f[u][2] = f[u][1];
f[u][1].fi = f[v][0].fi + w;
f[u][1].se = v;
} else if(f[v][0].fi + w > f[u][2].fi) {
f[u][2].fi = f[v][0].fi + w;
f[u][2].se = v;
}
}
int v0 = f[u][0].se, v1 = f[u][1].se;
g[u][0] = min(f[v0][0].fi, max(f[v0][1].fi, g[v0][0]) + d[v0]);
g[u][1] = min(f[v1][0].fi, max(f[v1][1].fi, g[v1][0]) + d[v1]);
}
void dfs2(int u, int fa) {
pii tmp = mp(min(max(h[u][0], max(g[u][0], f[u][1].fi)), max(h[u][1], f[u][0].fi)), u);
res = min(res, tmp);
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(v == fa) continue ;
if(v == f[u][0].se) {
h[v][0] = max(h[u][0], f[u][1].fi) + d[v];
h[v][1] = max(h[u][1], f[u][1].fi) + d[v];
h[v][1] = min(h[v][1], max(h[u][0], f[u][1].fi));
h[v][1] = min(h[v][1], max(max(g[u][1], h[u][0]), f[u][2].fi) + d[v]);
} else {
h[v][0] = max(h[u][0], f[u][0].fi) + d[v];
h[v][1] = max(h[u][1], f[u][0].fi) + d[v];
if(v == f[u][1].se) h[v][1] = min(h[v][1], max(max(h[u][0], g[u][0]), f[u][2].fi) + d[v]);
else h[v][1] = min(h[v][1], max(max(h[u][0], g[u][0]), f[u][1].fi) + d[v]);
h[v][1] = min(h[v][1], max(f[u][0].fi, h[u][0]));
}
dfs2(v, u);
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> T;
while(T--) {
cin >> n;
memset(head, -1, sizeof(head)); tot = 0;
for(int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
adde(u, v, w); adde(v, u, w);
}
init();
dfs(1, -1);
dfs2(1, -1);
cout << res.se << ' ' << res.fi << '\n';
}
return 0;
}
重要的是自信,一旦有了自信,人就会赢得一切。