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;
}
posted @ 2020-08-29 15:38  Beginner2670  阅读(191)  评论(0)    收藏  举报