P11217 【MX-S4-T1】「yyOI R2」youyou 的垃圾桶(線段樹上二分)

Zhang_Wenjie發表於2024-10-21

link

賽時是想到普通的線段樹 + 二分 \(O(q\log^2n)\),預期是 70pts,實際 50pts

後面發現又是在 long long 型別的計算中,1ll 寫成了 1,然後爆負數,複雜度就錯了,T 了四個點


開題,讀起來是一個很套路的題目

要對區間線上修改,區間加、(區間乘?),發現資料很大,那就是線段樹、樹狀陣列維護了

思考了一下,答案可以分兩部分求解,

一是找到使 youyou 死亡的那一輪,之前輪的貢獻可以按題意累加得到,

	LL sum = query(1, 1, n), tot = 0ll, last;
	int cnt = 0ll; // 實際到第 cnt 輪就死亡 
	while (tot < W)
	{
		last = tot;
		tot = sum * (LL)(1ll << cnt) + last;
		cnt ++;
	}
	ans += (LL)((cnt - 1ll) * n); // 所有點覆蓋輪 
	W -= last;

手算了一下時間複雜度應該是 \(O(x)\) 滿足 \(sum\sum\limits_{i = 0}^x 2^i = W\),等比數列求和一下近似 \(O(\log n)\)

我一開始測大樣例 2s 內本地跑不出來,以為是寫假了(就是寫假了 qwq,爆負數),然後卡了好久在那想怎麼做到 \(sum\cdot 2^x=W\),白給了很多時間

這裡推導一下等比數列求和 qwq
\(S_x = sum\sum\limits_{i=0}^x 2^i\)
\(2S_x = sum\sum\limits_{i=1}^{x + 1} 2^i\)
\(S_x = 2S_x - S_x = sum\cdot 2^{x+1} - sum\cdot 2^0 = W\)
\(x = \log_2(\frac{W}{2sum} + \frac{1}{2})\)

二是確定為第 i 輪死亡時,想到從左往右覆蓋具有單調性,可以把死亡位置二分出來,每次求個字首和

時間複雜度是 \(O(\log^2n)\)

需要注意的是,我一開始是考慮第 i 輪要用區間乘實現,後來發現還要還原,有點不可做的感覺

但是再仔細一想,注意到第 i 輪的初始值是整體偏移的,且並不關心單個數值偏移後的具體大小,那我直接求出第一輪的和然後乘上對應的偏移量就可以了

可以拿到 70pts

code
#include <bits/stdc++.h>
#define re register int 
#define lp p << 1
#define rp p << 1 | 1
#define int long long

using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const LL inf = 1e18;

int n, T, a[N];
LL W0, ans;
struct Tree
{
	int l, r, tag;
	LL sum;
}t[N << 2];

inline void push_up(int p)
{
	t[p].sum = t[lp].sum + t[rp].sum;	
} 

inline void push_down(int p)
{
	if (t[p].tag)
	{
		t[lp].sum += (LL)(t[lp].r - t[lp].l + 1) * (LL)t[p].tag;
		t[rp].sum += (LL)(t[rp].r - t[rp].l + 1) * (LL)t[p].tag;
		t[lp].tag += t[p].tag;
		t[rp].tag += t[p].tag;
		t[p].tag = 0;
	}
}

inline void build(int p, int l, int r)
{
	t[p].l = l, t[p].r = r;
	if (l == r)
	{
		t[p].sum = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(lp, l, mid);
	build(rp, mid + 1, r);
	push_up(p);
}

inline void update(int p, int l, int r, int k)
{
	if (l <= t[p].l && t[p].r <= r)
	{
		t[p].sum += (LL)(t[p].r - t[p].l + 1) * k;
		t[p].tag += k;
		return;
	}
	push_down(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if (l <= mid) update(lp, l, r, k);
	if (r > mid) update(rp, l, r, k);
	push_up(p);	
}

inline LL query(int p, int l, int r)
{
	if (l <= t[p].l && t[p].r <= r) return t[p].sum;
	push_down(p);
	LL res = 0ll;
	int mid = (t[p].l + t[p].r) >> 1;
	if (l <= mid) res += query(lp, l, r);
	if (r > mid) res += query(rp, l, r);
	
	return res;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
//	freopen("wxyt3.in", "r", stdin);
//	freopen("wxyt3.out", "w", stdout);
	
	cin >> n >> T >> W0;
	for (re i = 1; i <= n; i ++) cin >> a[i];
	build(1, 1, n);
	while (T --)
	{
		ans = 0ll; 
		LL W = W0;
		
		int L, R, d; cin >> L >> R >> d;
		update(1, L, R, d);
		
		LL sum = query(1, 1, n), tot = 0ll, last;
		int cnt = 0ll; // 實際到第 cnt 輪就死亡 
		while (tot < W)
		{
			last = tot;
			tot = sum * (LL)(1ll << cnt) + last;
			cnt ++;
		}
		ans += (LL)((cnt - 1ll) * n); // 所有點覆蓋輪 
		W -= last;
		
		int l = 1, r = n;
		while (l < r)
		{
			int mid = (l + r) >> 1;
			if (W - query(1, 1, mid) * (LL)(1ll << (cnt - 1ll)) <= 0) r = mid;
			else l = mid + 1;
		}
		ans += (LL)(l - 1ll);
		cout << ans << '\n';
	}
	return 0;
}

瓶頸就是它卡兩隻 log,,,再考慮到線段樹本身常數較大(可能樹狀陣列可以衝一下?

可能有其他做法,std 是 \(O(n\log W + q)\) 的,沒去看

消去一隻 log 的辦法就是線上段樹上直接二分,找左右子樹,很妙的辦法

ac code
#include <bits/stdc++.h>
#define re register int
#define lp p << 1
#define rp p << 1 | 1
#define int long long

using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const LL inf = 1e18;

int n, T, a[N];
LL W0, ans;
struct Tree
{
	int l, r, tag;
	LL sum;
}t[N << 2];

inline void push_up(int p)
{
	t[p].sum = t[lp].sum + t[rp].sum;
} 

inline void push_down(int p)
{
	if (t[p].tag)
	{
		t[lp].sum += (LL)(t[lp].r - t[lp].l + 1) * (LL)t[p].tag;
		t[rp].sum += (LL)(t[rp].r - t[rp].l + 1) * (LL)t[p].tag;
		t[lp].tag += t[p].tag;
		t[rp].tag += t[p].tag;
		t[p].tag = 0;
	}
}

inline void build(int p, int l, int r)
{
	t[p].l = l, t[p].r = r;
	if (l == r)
	{
		t[p].sum = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(lp, l, mid);
	build(rp, mid + 1, r);
	push_up(p);
}

inline void update(int p, int l, int r, int k)
{
	if (l <= t[p].l && t[p].r <= r)
	{
		t[p].sum += (LL)(t[p].r - t[p].l + 1) * k;
		t[p].tag += k;
		return;
	}
	push_down(p);
	int mid = (t[p].l + t[p].r) >> 1;
	if (l <= mid) update(lp, l, r, k);
	if (r > mid) update(rp, l, r, k);
	push_up(p);	
}

inline LL query(int p, int l, int r)
{
	if (l <= t[p].l && t[p].r <= r) return t[p].sum;
	push_down(p);
	LL res = 0ll;
	int mid = (t[p].l + t[p].r) >> 1;
	if (l <= mid) res += query(lp, l, r);
	if (r > mid) res += query(rp, l, r);
	
	return res;
}

inline int query2(int p, int l, int r, LL W, LL layer)
{
	if (l == r) return l;
	push_down(p);
	int mid = (l + r) >> 1;
	if (W - t[lp].sum * layer <= 0) return query2(lp, l, mid, W, layer);
	else return query2(rp, mid + 1, r, W - t[lp].sum * layer, layer); 
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
//	freopen("wxyt3.in", "r", stdin);
//	freopen("wxyt3.out", "w", stdout);
	
	cin >> n >> T >> W0;
	for (re i = 1; i <= n; i ++) cin >> a[i];
	build(1, 1, n);
	while (T --)
	{
		ans = 0ll; 
		LL W = W0;
		
		int L, R, d; cin >> L >> R >> d;
		update(1, L, R, d);
		
		LL sum = query(1, 1, n), tot = 0ll, last;
		int cnt = 0ll; // 實際到第 cnt 輪就死亡 
		while (tot < W)
		{
			last = tot;
			tot = sum * (LL)(1ll << cnt) + last;
			cnt ++;
		}
		ans += (LL)((cnt - 1ll) * n); // 所有點覆蓋輪 
		W -= last;
		
		int pos = query2(1, 1, n, W, (LL)(1ll << (cnt - 1ll))) - 1;
		cout << ans + pos << '\n';
	}
	return 0;
}

相關文章