Loj#576. 「LibreOJ NOI Round #2」签到游戏(前缀和转化+最小生成树+贪心+gcd性质+线段树)

https://loj.ac/problem/576

题解:

考虑询问了区间\([l,r]\),就知道了和,也就知道了\(s[r]\)\(s[l-1]\)的差。

那么给\(r\)\(l-1\)连一条边,我们相当于要搞出点为\(0..n\)的最小生成树。

运用kruskal的想法,每次找最小的连接不同联通块的边,发现一定是一个前缀或者后缀是最优的。

所以考虑找到一个最右的分界点\(k\)满足\(gcd(b[1..k])\ge gcd(b[k+1..n])\),那么\(x \le k\)的点都是选择\(gcd(b[x+1..n])\),而\(> k\)的点都是\(gcd(b[1..x])\)

我们知道前缀gcd的变化点只有log个,考虑怎么找出来。

不难想到线段树维护区间\(gcd\),然后每次线段树二分,直接搞复杂度是\(O(n~log^3~n)\),注意判断一个区间是否合法时只用看这个区间的\(gcd\)是不是我要的\(gcd\)的倍数,即可省掉一个\(log\)

Code:

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

const int N = 1e5 + 5;

int n, Q, b[N], x, y;

int gcd(int x, int y) { return !y ? x : gcd(y, x % y);}

int t[N * 4];

#define i0 i + i
#define i1 i + i + 1
void bt(int i, int x, int y) {
	if(x == y) {
		t[i] = b[x];
		return;
	}
	int m = x + y >> 1;
	bt(i0, x, m); bt(i1, m + 1, y);
	t[i] = gcd(t[i0], t[i1]);
}

int pl, pr;

void add(int i, int x, int y) {
	if(x == y) {
		t[i] = b[x];
		return;
	}
	int m = x + y >> 1;
	if(pl <= m) add(i0, x, m); else add(i1, m + 1, y);
	t[i] = gcd(t[i0], t[i1]);
}

struct P {
	int x, y;
	P(int _x = 0, int _y = 0) {
		x = _x, y = _y;
	}
};

int px, py, pz;
void ef(int i, int x, int y) {
	if(y < pl || x > pr) return;
	if(t[i] % py == 0) return;
	if(x == y) { px = x; return;}
	int m = x + y >> 1;
	if(!pz) {
		ef(i0, x, m);
		if(px == -1) ef(i1, m + 1, y);
	} else {
		ef(i1, m + 1, y);
		if(px == -1) ef(i0, x, m);
	}
}

P p[N], q[N]; int p0, q0;

void work();
void Qry() {
	p0 = 0;
	for(int i = 1, s = 0; i <= n; i ++) {
		s = gcd(s, b[i]);
		pl = i, pr = n;
		px = -1, py = s; pz = 0;
		ef(1, 1, n);
		if(px == -1) px = n + 1;
		px --;
		p[++ p0] = P(px, s);
		i = px;
	}
	
	q0 = 0;
	for(int i = n, s = 0; i > 0; i --) {
		s = gcd(s, b[i]);
		pl = 1, pr = i;
		px = -1, py = s; pz = 1;
		ef(1, 1, n);
		if(px == -1) px = 0;
		px ++;
		q[++ q0] = P(px, s);
		i = px;
	}
	
	p[p0 + 1].x = n + 1;
	q[q0 + 1].x = 0;
	q[0].x = n + 1;
	
	work();
}

int qry1(int x) {
	fo(i, 1, p0) if(p[i].x >= x)
		return p[i].y;
}

int qry2(int x) {
	if(x > n) return 1e9 + 1;
	fo(i, 1, q0) if(q[i].x <= x)
		return q[i].y;
}

int pd(int k) {
	if(k < 0) return 0;
	int v1 = qry1(k), v2 = qry2(k + 1);
	return v1 >= v2;
}

ll sum1(int k) {
	ll ans = 0;
	fo(i, 1, p0) {
		int x = p[i - 1].x + 1, y = p[i].x;
		if(y >= k)  ans += (ll) p[i].y * (y - max(x, k) + 1);
	}
	return ans;
}

ll sum2(int k) {
	ll ans = 0;
	fo(i, 1, q0) {
		int x = q[i].x, y = q[i - 1].x - 1;
		if(x <= k) ans += (ll) q[i].y * (min(y, k) - x + 1);
	}
	return ans;
}

void work() {
	int k = 0;
	fo(i, 1, p0) {
		if(pd(p[i].x)) k = max(k, p[i].x);
		if(pd(p[i - 1].x + 1)) k = max(k, p[i - 1].x + 1);
	}
	fo(i, 1, q0) {
		if(pd(q[i].x - 1)) k = max(k, q[i].x - 1);
		if(pd(q[i - 1].x - 2)) k = max(k, q[i - 1].x - 2);
	}
	ll ans = sum2(k + 1) + sum1(k + 1);
	ans -= p[p0].y;
	pp("%lld\n", ans);
}

int main() {
	scanf("%d %d", &n, &Q);
	fo(i, 1, n) scanf("%d", &b[i]);
	bt(1, 1, n);
	fo(ii, 1, Q) {
		scanf("%d %d", &x, &y);
		b[x] = y;
		pl = pr = x;
		add(1, 1, n);
		Qry();
	}
}
posted @ 2020-08-01 10:59  Cold_Chair  阅读(359)  评论(0编辑  收藏  举报