APIO 2019题解
【Problem 1】
Statement:
给定一张\(n\)个点,\(m\)条边的带权无向图。每次询问给定\(s_i\)和\(w_i\),求出从\(s_i\)出发,只经过边权\(\geq w_i\)的边,能到达的点数。边权带修改,不强制在线。
subtask1 \(n,m\leq 10^3,q\leq 10^4\)
subtask2 \(图退化成链\)
subtask3 \(图为完全二叉树\)
subtask4 \(无修改\)
subtask5 \(图退化树\)
subtask6 \(n\leq 5\times 10^4,m,q\leq 10^5\)
Solution:
对于subtask2显然可以二分位置,用数据结构动态维护区间边权最小值。时间复杂度\(\mathcal O(n\log_2^2n)\)。也可以在线段树上二分,时间复杂度为\(\mathcal O(n\log_2n)\)。
对于subtask4可以离线一下,按询问的\(w\)排序,用并查集维护联通性。时间复杂度为\(\mathcal O(q\log_2 q+mα(n))\)。
结合以上算法可以获得:13 + 16 + 14 = 43 pts。
对于subtask1-6
如果按照修改划分区间,在区间内部用subtask4的算法,时间复杂度为\(\mathcal O(q\log_2 q + qmα(n))\)。发现在询问较少的时候跑得还是挺快的,于是考虑对所以操作进行分块,块大小为\(s\)。在每个块内对每个询问进行排序,用并查集维护联通性。若遇到在改块内有修改的边时,先合并,做完一个操作后再进行撤销。做完一个块后对边权进行永久修改,时间复杂度为\(\mathcal O(\frac{q}{s}m\log_2n+\frac{q}{s}s^2\log_2n)\),即\(\mathcal O(\frac{q}{s}m\log_2n+qs\log_2n)\)。当\(\frac{q}{s}m\log_2n=qs\log_2n\)时最快,\(s=\sqrt{m}\)时取得,为\(\mathcal O(q\sqrt{q}\log_2n)\)。
Code
#include <bits/stdc++.h>
const int MAXM = 1.2E5;
struct Edge
{
int u;
int v;
int w;
int id;
Edge() { u = v = w = id = 0; }
Edge (int _u, int _v, int _w, int _id)
{
u = _u, v = _v, w = _w, id = _id;
}
bool friend operator < (Edge a, Edge b)
{
if (a.w != b.w)
return a.w > b.w;
return a.id < b.id;
}
} E[MAXM], newE[MAXM], tmpE[MAXM];
struct Node
{
int o;
int pos;
int tim;
int w;
int outId;
bool friend operator < (Node a, Node b)
{
return a.w > b.w;
}
} Q[MAXM], Query[MAXM], Modify[MAXM];
int n, m, q, blo, top;
bool needModify[MAXM];
int Buk[MAXM];
int uf[MAXM];
int _size[MAXM];
int Ans[MAXM];
int revEdgeId[MAXM];
std::pair <int, int> mergeStack[MAXM];
int getPa(int u)
{
return u == uf[u] ? u : getPa(uf[u]);
}
inline void mergeTree(int u, int v)
{
u = getPa(u), v = getPa(v);
if (u == v)
return ;
if (_size[u] < _size[v])
std::swap(u, v);
_size[u] += _size[v],
uf[v] = u;
mergeStack[++top] = std::make_pair(u, v);
}
inline void cutLastTree()
{
int u = mergeStack[top].first;
int v = mergeStack[top--].second;
uf[v] = v,
_size[u] -= _size[v];
}
inline void solve(int limR)
{
int moTot = 0, quTot = 0;
top = 0;
for (int i = 1;i <= n; ++i)
uf[i] = i, _size[i] = 1;
for (int i = 1;i <= m; ++i)
revEdgeId[E[i].id] = i;
for (int i = 1;i <= limR; ++i)
if (Q[i].o == 1)
Q[i].pos = revEdgeId[Q[i].pos], needModify[Q[i].pos] = true, Modify[++moTot] = Q[i];
else
Query[++quTot] = Q[i];
std::sort(Query + 1, Query + 1 + quTot);
int curEdge = 1, preTop = 0;
for (int i = 1;i <= quTot; ++i)
{
while (curEdge <= m && E[curEdge].w >= Query[i].w)
{
if (!needModify[curEdge])
mergeTree(E[curEdge].u, E[curEdge].v);
++curEdge;
}
preTop = top;
for (int j = 1;j <= moTot; ++j)
Buk[Modify[j].pos] = E[Modify[j].pos].w;
for (int j = 1;j <= moTot; ++j)
if (Modify[j].tim < Query[i].tim)
Buk[Modify[j].pos] = Modify[j].w;
for (int j = 1;j <= moTot; ++j)
if (Buk[Modify[j].pos] >= Query[i].w)
mergeTree(E[Modify[j].pos].u, E[Modify[j].pos].v);
Ans[Query[i].outId] = _size[getPa(Query[i].pos)];
while (top > preTop)
cutLastTree();
}
for (int i = 1;i <= moTot; ++i)
E[Modify[i].pos].w = Modify[i].w;
// std::cout << E[Modify[i].pos].w << " \n"[i == moTot];
int newT = 0, preT = 0;
for (int i = 1;i <= m; ++i)
if (needModify[i])
newE[++newT] = E[i], needModify[i] = false;
else
E[++preT] = E[i];
std::sort(newE + 1, newE + 1 + newT);
std::merge(E + 1, E + 1 + preT, newE + 1, newE + 1 + newT, tmpE + 1);
for (int i = 1;i <= m; ++i)
E[i] = tmpE[i];
// std::cout << E[i].w << " \n"[i == m];
}
int main(void)
{
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
std::cin >> n >> m;
for (int i = 1;i <= m; ++i)
{
int a = 0, b = 0, c = 0;
std::cin >> a >> b >> c;
E[i] = Edge(a, b, c, i);
}
std::sort(E + 1, E + 1 + m);
std::cin >> q;
blo = sqrt(m)*2;
int limR = 0, cnt = 0;
for (int i = 1;i <= q; ++i)
{
std::cin >> Q[++limR].o, Q[limR].tim = i, Q[limR].outId = 0;
std::cin >> Q[limR].pos >> Q[limR].w;
if (Q[limR].o == 2)
Q[limR].outId = ++cnt;
if (limR == blo)
solve(blo), limR = 0;
}
if (limR)
solve(limR);
for (int i = 1;i <= cnt; ++i)
std::cout << Ans[i] << '\n';
return 0;
}
【Problem 2】
Statement:
给定\(n,A,B\),以及n对\((l_i,r_i)\),求\(\left| \bigcup\limits_{i=1}^n \left((x+ \left\lfloor \frac{x}{B}\right\rfloor) \bmod A,x \bmod B \right) |x\in[l_i, r_i]\cap N\right |\)
\(n\leq 10^6,A,B\leq 10^{18},0\leq l_i\leq r_i\leq 10^{18}\)
Solution
考虑什么情况得到的点对会相同。设当\(x=a\)和\(x=b,a\leq b\)时时得到的点对相同。即\(b = a+kB|k\in N_+\)......
Code
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2.1E6;
struct Node
{
long long l;
long long r;
Node() { l = r = 0; }
Node(long long _l, long long _r)
{
l = _l, r = _r;
}
inline bool friend operator < (Node a, Node b)
{
if (a.l != b.l)
return a.l < b.l;
return a.r < b.r;
}
} ;
long long A, B, ans;
int n, tot;
Node Line[MAXN];
int main(void)
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> A >> B;
long long circle = ((A / __gcd(A, B + 1) * B) < 0) ? 2e18 : (A / __gcd(A, B + 1) * B);
for (int i = 1;i <= n; ++i)
{
long long a = 0, b = 0;
cin >> a >> b;
if (b - a + 1 >= circle)
{
cout << circle << '\n';
return 0;
}
a %= circle, b %= circle;
if (a <= b)
Line[++tot] = Node(a, b);
else
Line[++tot] = Node(0, b), Line[++tot] = Node(a, circle - 1);
}
sort(Line + 1, Line + 1 + tot);
long long nowL = Line[1].l, nowR = Line[1].r;
for (int i = 2;i <= tot; ++i)
{
if (nowR < Line[i].l)
ans += nowR - nowL + 1, nowL = Line[i].l, nowR = Line[i].r;
else
nowR = max(nowR, Line[i].r);
}
ans += nowR - nowL + 1;
cout << ans << '\n';
return 0;
}

浙公网安备 33010602011771号