2026.5.2情报系统听课笔记

fufu

今日题单:https://www.luogu.com.cn/training/1007161

树状数组区间加区间和。
先把原序列差分以进行区间加。
区间和:

\[\sum_{i = 1}^{x}\sum_{j = 1}^ic_j = \sum_{i = 1}^{x}(c_i\cdot(x - i + 1)) = (x + 1)\sum c_i - \sum(i\cdot c_i) \]

所以只需要维护 \(\sum c_i\)\(\sum (i\cdot c_i)\) 即可。

点击查看代码
#include <iostream>
#define lowbit(x) x & (-x)

using std::cin;
using std::cout;
const int N = 2e5 + 10;
typedef long long ll;

int n;
ll a[N];
ll c[N];
ll sum1[N];
ll sum2[N];

void add1(int x, ll k)
{
	for (int i = x; i <= n; i += lowbit(i))
		sum1[i] += k;
}
void add2(int x, ll k)
{
	for (int i = x; i <= n; i += lowbit(i))
		sum2[i] += k;
}
void modify(int l, int r, ll k)
{
	add1(l, k), add2(l, 1ll * l * k);
	if (r < n)
		add1(r + 1, -k), add2(r + 1, -1ll * (r + 1) * k);
}
ll ask1(int x)
{
	ll ret = 0;
	for (int i = x; i; i -= lowbit(i))
		ret += sum1[i];
	return ret * (x + 1);
}
ll ask2(int x)
{
	ll ret = 0;
	for (int i = x; i; i -= lowbit(i))
		ret += sum2[i];
	return ret;
}
ll query(int l, int r)
{
	if (l == 1)
		return ask1(r) - ask2(r);
	return ask1(r) - ask1(l - 1) - (ask2(r) - ask2(l - 1));
}

int main()
{
	cin >> n;
	int m;
	cin >> m;
	for (int i = 1; i <= n; ++i)
		cin >> a[i], c[i] = a[i] - a[i - 1], add1(i, c[i]), add2(i, 1ll * i * c[i]);
	for (int i = 1; i <= m; ++i)
	{
		int opt;
		cin >> opt;
		if (opt == 1)
		{
			int x, y;
			ll k;
			cin >> x >> y >> k;
			modify(x, y, k);
		}
		else if (opt == 2)
		{
			int x, y;
			cin >> x >> y;
			cout << query(x, y) << '\n';
		}
	}
	return 0;
}

\(x\in [1, 2^k]\)\(x\) 随机。
则树状数组区间求和的期望复杂度?
注意到我们每次 -=lowbit(x) 的次数是 \(\operatorname{popcount}(x)\) 的。
所以期望是

\[\dfrac{\operatorname{popcount}(2^k) + \displaystyle\sum_{i = 0}^{2^k - 1}\operatorname{popcount}(x)}{2^k} \]

对于每个 \(i\),我们有一个 \(2^k - 1 - i\) 与其配对,使得一共有 \(k\)\(1\)
所以期望就是

\[E_{query} = \dfrac{1 + k2^{k - 1}}{2^k} = \dfrac{1}{2^k} + \dfrac{k}{2} \]

对于单点加的复杂度,因为我们每次访问到最高节点后还要再 +=lowbit(x) 一遍,所以期望就是

\[E_{modify} = E_{query} + 1 \]

因此,树状数组的复杂度是约 \(\dfrac{1}{2}\) 的,非常优秀了。


image

用线段树做:
你考虑对于每个数,它都会被加 \(\delta = K - lD\),其次,它会被加 \(e = iD\)
所以说,你可以维护两个 tag,一个是维护 \(\delta\),另一个是维护 \(sumD\),左儿子就是 \(sum_l + len_l\cdot\delta + D\cdot\dfrac{len_l\cdot(l + mid)}{2}\),右儿子类似。

点击查看代码
#include <iostream>

using std::cin;
using std::cout;
const int N = 5e5 + 10;
typedef long long ll;
struct Node
{
	ll tag1; // delta
	ll tag2; // D
	ll sum;
	Node()
	{
		sum = tag1 = tag2 = 0;
	}
	friend Node operator+(const Node &l, const Node &r)
	{
		Node ret;
		ret.sum = l.sum + r.sum;
		return ret;
	}
} z[N << 2];

ll a[N];

#define root 1, n, 1
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1

void build(int l, int r, int rt)
{
	if (l == r)
	{
		z[rt].sum = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(lson);
	build(rson);
	z[rt] = z[rt << 1] + z[rt << 1 | 1];
}
void push_down(int l, int r, int rt)
{
	if (z[rt].tag1)
	{
		int mid = (l + r) >> 1;
		z[rt << 1].tag1 += z[rt].tag1;
		z[rt << 1 | 1].tag1 += z[rt].tag1;
		z[rt << 1].sum += z[rt].tag1 * (mid - l + 1);
		z[rt << 1 | 1].sum += z[rt].tag1 * (r - mid);
		z[rt].tag1 = 0;
	}
	if (z[rt].tag2)
	{
		int mid = (l + r) >> 1;
		z[rt << 1].tag2 += z[rt].tag2;
		z[rt << 1 | 1].tag2 += z[rt].tag2;
		z[rt << 1].sum += z[rt].tag2 * (mid + l) * (mid - l + 1) / 2;
		z[rt << 1 | 1].sum += z[rt].tag2 * (r + mid + 1) * (r - mid - 1 + 1) / 2;
		z[rt].tag2 = 0;
	}
}
void modify(int l, int r, int rt, int nowl, int nowr, ll k, ll d)
{
	if (nowl <= l && r <= nowr)
	{
		z[rt].tag1 += k - nowl * d;
		z[rt].tag2 += d;
		z[rt].sum += (k - nowl * d) * (r - l + 1) + d * (r - l + 1) * (r + l) / 2; 
		return;
	}
	int mid = (l + r) >> 1;
	push_down(l, r, rt);
	if (nowl <= mid)
		modify(lson, nowl, nowr, k, d);
	if (nowr > mid)
		modify(rson, nowl, nowr, k, d);
	z[rt] = z[rt << 1] + z[rt << 1 | 1];
}
Node query(int l, int r, int rt, int nowl, int nowr)
{
	if (nowl <= l && r <= nowr)
		return z[rt];
	int mid = (l + r) >> 1;
	push_down(l, r, rt);
	if (nowl <= mid)
	{
		if (nowr > mid)
			return query(lson, nowl, nowr) + query(rson, nowl, nowr);
		else
			return query(lson, nowl, nowr);
	}
	else
		return query(rson, nowl, nowr);
}

int main()
{
	cin.tie(nullptr);
	cout.tie(nullptr);
	std::ios::sync_with_stdio(false);
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; ++i)
		cin >> a[i];
	build(root);
	while (m--)
	{
		int opt;
		cin >> opt;
		if (opt == 1)
		{
			int l, r;
			ll k, d;
			cin >> l >> r >> k >> d;
			modify(root, l, r, k, d);
		}
		else if (opt == 2)
		{
			int p;
			cin >> p;
			cout << query(root, p, p).sum << '\n';
		}
		else if (opt == 3)
		{
			int x, y;
			cin >> x >> y;
			cout << query(root, x, y).sum << '\n';
		}
	}
	return 0;
}

用树状数组做。
你先维护一个 \(d_i = a_i - a_{i - 1}, dd_i = d_i - d_{i - 1}\)
前缀和就是

\[\sum_{i = 1}^n\sum_{j = 1}^i\sum_{k = 1}^jdd_k \]

我们考虑算贡献。
因为 \(i,j\in\left[k, n\right]\),所以每个 \(a_k\) 会被算 \(C_{n - k + 1}^{1} + C_{n - k + 1}^{2}\) 次(相等或不相等)。
考虑到 \(C_{n - k + 1}^{1} + C_{n - k + 1}^{2} = a\cdot dd_k\cdot k^2 + b\cdot dd_k\cdot k + c\cdot dd_k\)\(a,b,c\) 为定值(具体看代码)。
直接维护 \(dd_k\cdot k^2,dd_k\cdot k, dd_k\) 的和即可(对于 \(a, b, c\),用查询的参数获取即可)。

点击查看代码
#include <iostream>
#define lowbit(x) x & (-x)

using std::cin;
using std::cout;
const int N = 5e5 + 10;
typedef long long ll;

int n;
ll a[N];
ll c[N];
ll d[N];
ll sum1[N];
ll sum2[N];
ll sum3[N];

void add1(int x, ll k)
{
	for (int i = x; i <= n; i += lowbit(i))
		sum1[i] += k;
}
void add2(int x, ll k)
{
	for (int i = x; i <= n; i += lowbit(i))
		sum2[i] += k;
}
void add3(int x, ll k)
{
	for (int i = x; i <= n; i += lowbit(i))
		sum3[i] += k;
}
ll ask1(int x)
{
	ll ret = 0;
	for (int i = x; i; i -= lowbit(i))
		ret += sum1[i];
	return ret;
}
ll ask2(int x)
{
	ll ret = 0;
	for (int i = x; i; i -= lowbit(i))
		ret += sum2[i];
	return ret;
}
ll ask3(int x)
{
	ll ret = 0;
	for (int i = x; i; i -= lowbit(i))
		ret += sum3[i];
	return ret;
}
ll query1(int l, int r)
{
	if (l == 1)
		return ask2(r) * (-1) + ask1(r) * (r + 1);
	return ask2(r) * (-1) - ask2(l - 1) * (-1) + ask1(r) * (r + 1) - ask1(l - 1) * (l - 1 + 1);
}
ll query2(int l, int r)
{
	if (l == 1)
		return ask3(r) + ask2(r) * (-(3 + 2 * r)) + ask1(r) * (1ll * r * r + 3 * r + 2);
	return ask1(r) * (1ll * r * r + 3 * r + 2) - ask1(l - 1) * (1ll * (l - 1) * (l - 1) + 3 * (l - 1) + 2) + ask2(r) * (-(3 + 2 * r)) - ask2(l - 1) * (-(3 + 2 * (l - 1))) + ask3(r) - ask3(l - 1);
}

int main()
{
	cin >> n;
	int m;
	cin >> m;
	for (int i = 1; i <= n; ++i)
	{
		cin >> a[i];
		c[i] = a[i] - a[i - 1];
		d[i] = c[i] - c[i - 1];
		add1(i, d[i]);
		add2(i, d[i] * i);
		add3(i, d[i] * i * i);
	}
	for (int i = 1; i <= m; ++i)
	{
		int opt;
		cin >> opt;
		if (opt == 1)
		{
			int l, r;
			ll k, D;
			cin >> l >> r >> k >> D;
			add1(l, k), add2(l, k * l), add3(l, k * l * l);
			if (l + 1 <= n)
				add1(l + 1, -k), add2(l + 1, -k * (l + 1)), add3(l + 1, -k * (l + 1) * (l + 1));
			if (l + 1 <= n)
				add1(l + 1, D), add2(l + 1, D * (l + 1)), add3(l + 1, D * (l + 1) * (l + 1));
			if (r + 1 <= n)
				add1(r + 1, D * (-(r - l + 1))-k), add2(r + 1, (D * (-(r - l + 1))-k) * (r + 1)), add3(r + 1, (D * (-(r - l + 1))-k) * (r + 1) * (r + 1));
			if (r + 2 <= n)
				add1(r + 2, D * (r - l)+k), add2(r + 2, (D * (r - l)+k) * (r + 2)), add3(r + 2, (D * (r - l)+k) * (r + 2) * (r + 2));
		}
		else if (opt == 2)
		{
			int p;
			cin >> p;
			cout << query1(1, p) << '\n';
		}
		else if (opt == 3)
		{
			int x, y;
			cin >> x >> y;
			cout << query2(x, y) / 2 << '\n';
		}
	}
	return 0;
}

image
我们注意到,\(a_i\) 分为取模有效和取模无效,且若有效,则 \(a_i\) 取模后不会超过 \(\dfrac{a_i}{2}\),因此一个数最多取模 \(log\) 次。
所以直接像区间开根号那样暴力操作就行。

点击查看代码
#include <iostream>

using std::cin;
using std::cout;
const int N = 1e5 + 10;
typedef long long ll;
struct Node
{
	ll sum;
	ll max;
	friend Node operator+(const Node &l, const Node &r)
	{
		Node ret;
		ret.sum = l.sum + r.sum;
		ret.max = std::max(l.max, r.max);
		return ret;
	}
} z[N << 2];

int a[N];

#define root 1, n, 1
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1

void build(int l, int r, int rt)
{
	if (l == r)
	{
		z[rt].max = a[l];
		z[rt].sum = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(lson);
	build(rson);
	z[rt] = z[rt << 1] + z[rt << 1 | 1];
}
void mod(int l, int r, int rt, int nowl, int nowr, int p)
{
	if (z[rt].max < p)
		return;
	if (l == r)
	{
		z[rt].max %= p;
		z[rt].sum %= p;
		return;
	}
	int mid = (l + r) >> 1;
	if (nowl <= mid)
		mod(lson, nowl, nowr, p);
	if (nowr > mid)
		mod(rson, nowl, nowr, p);
	z[rt] = z[rt << 1] + z[rt << 1 | 1];
}
void modify(int l, int r, int rt, int p, int k)
{
	if (l == r)
	{
		z[rt].sum = k;
		z[rt].max = k;
		return;
	}
	int mid = (l + r) >> 1;
	if (p <= mid)
		modify(lson, p, k);
	else
		modify(rson, p, k);
	z[rt] = z[rt << 1] + z[rt << 1 | 1];
}
Node query(int l, int r, int rt, int nowl, int nowr)
{
	if (nowl <= l && r <= nowr)
		return z[rt];
	int mid = (l + r) >> 1;
	if (nowl <= mid)
	{
		if (nowr > mid)
			return query(lson, nowl, nowr) + query(rson, nowl, nowr);
		else
			return query(lson, nowl, nowr);
	}
	else
		return query(rson, nowl, nowr);
}

int main()
{
	std::ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; ++i)
		cin >> a[i];
	build(root);
	while (m--)
	{
		int opt;
		cin >> opt;
		if (opt == 1)
		{
			int l, r;
			cin >> l >> r;
			cout << query(root, l, r).sum << '\n';
		}
		else if (opt == 2)
		{
			int l, r, x;
			cin >> l >> r >> x;
			mod(root, l, r, x);
		}
		else if (opt == 3)
		{
			int k, x;
			cin >> k >> x;
			modify(root, k, x);
		}
	}
	return 0;
}

image

先差分 \(d_i = a_{i+1} - a_i\)
我们先考虑查询(将一个 \(i\) 拔高 \(x\)),对于原序列中的每三个位置,我们有四种情况。
1.
image

此时答案是原序列的答案 \(+\) \(2(x - d_i)\)
2.
image
此时答案是原序列的答案 \(+\) \(2(x - d_i)\)
3.
image
此时答案是原序列的答案 \(+\) \(2x\)
4.
image
当向上移 \(x\) 后高于两边的话,就变了 \(2x + 2d_{i - 1} - 2d_i\)
否则大概率不优。

结合上面几种情况:
image
要注意特判几种特殊情况。

点击查看代码
#include <iostream>
#include <cmath>

using std::cin;
using std::cout;
const int N = 1e5 + 10;
typedef long long ll;
struct Node
{
	ll ming;
	friend Node operator+(const Node &l, const Node &r)
	{
		Node ret;
		ret.ming = std::min(l.ming, r.ming);
		return ret;
	}
} z[N << 2];

ll a[N];
ll d[N];

#define root 2, n - 1, 1
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1

void build(int l, int r, int rt)
{
	if (l == r)
	{
		z[rt].ming = std::max(-d[l - 1], 0ll) + std::max(d[l], 0ll);
		return;
	}
	int mid = (l + r) >> 1;
	build(lson);
	build(rson);
	z[rt] = z[rt << 1] + z[rt << 1 | 1];
}
void modify(int l, int r, int rt, int p, ll k)
{
	if (l == r)
	{
		z[rt].ming = k;
		return;
	}
	int mid = (l + r) >> 1;
	if (p <= mid)
		modify(lson, p, k);
	else
		modify(rson, p, k);
	z[rt] = z[rt << 1] + z[rt << 1 | 1];
}
Node query(int l, int r, int rt, int nowl, int nowr)
{
	if (nowl <= l && r <= nowr)
		return z[rt];
	int mid = (l + r) >> 1;
	if (nowl <= mid)
	{
		if (nowr > mid)
			return query(lson, nowl, nowr) + query(rson, nowl, nowr);
		else
			return query(lson, nowl, nowr);
	}
	else
		return query(rson, nowl, nowr);
}

signed main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; ++i)
		cin >> a[i];
	ll ans = 0;
	for (int i = 1; i < n; ++i)
		d[i] = a[i + 1] - a[i], ans += llabs(d[i]);
	build(root);
	int q;
	cin >> q;
	while (q--)
	{
		int opt, l, r;
		ll x;
		cin >> opt >> l >> r >> x;
		if (opt == 2)
		{
			if (l - 1 >= 1)
			{
				ans -= llabs(d[l - 1]);
				d[l - 1] += x;
				ans += llabs(d[l - 1]);
				modify(root, l, std::max(-d[l - 1], 0ll) + std::max(d[l], 0ll));
				if (l - 2 >= 1)
					modify(root, l - 1, std::max(-d[l - 2], 0ll) + std::max(d[l - 1], 0ll));
			}
			if (r < n)
			{
				ans -= llabs(d[r]);
				d[r] -= x;
				ans += llabs(d[r]);
				if (r - 1 >= 1)
					modify(root, r, std::max(-d[r - 1], 0ll) + std::max(d[r], 0ll));
				if (r + 1 < n)
					modify(root, r + 1, std::max(-d[r], 0ll) + std::max(d[r + 1], 0ll));
			}
		}
		else if (opt == 1)
		{
			if (l == r)
			{
				ans -= llabs(d[l - 1]);
				ans -= llabs(d[r]);
				d[l - 1] += x;
				d[r] -= x;
				ans += llabs(d[l - 1]);
				ans += llabs(d[r]);
				cout << ans << '\n';
				ans -= llabs(d[l - 1]);
				ans -= llabs(d[r]);
				d[l - 1] -= x;
				d[r] += x;
				ans += llabs(d[l - 1]);
				ans += llabs(d[r]);
			}
			else
			{
				ll now;
				Node now1 = query(root, (l > 1 ? l : 2), (r < n ? r : n - 1));
				if (x < now1.ming)
					now = ans;
				else
					now = ans + 2 * (x - now1.ming);
				if (l == 1)
				{
					ans -= llabs(d[1]);
					d[1] -= x;
					ans += llabs(d[1]);
					now = std::max(now, ans);
					ans -= llabs(d[1]);
					d[1] += x;
					ans += llabs(d[1]);
				}
				if (r == n)
				{
					ans -= llabs(d[n - 1]);
					d[n - 1] += x;
					ans += llabs(d[n - 1]);
					now = std::max(now, ans);
					ans -= llabs(d[n - 1]);
					d[n - 1] -= x;
					ans += llabs(d[n - 1]);
				}
				cout << now << '\n';
			}
		}
	}
	return 0;
}

image
我们可以令 \(f_{i,j}\) 表示前 \(i\) 个分成 \(j\) 段的最小代价
image
image
image
注意到代码里加了滚动数组优化

点击查看代码
#include <iostream>

using std::cin;
using std::cout;
const int N = 3e4 + 5e3 + 10;
typedef long long ll;
const ll oo = 1e18;
struct Node
{
	ll tag;
	ll min;
	Node()
	{
		tag = 0;
		min = oo;
	}
	friend Node operator+(const Node &l, const Node &r)
	{
		Node ret;
		ret.min = std::min(l.min, r.min);
		return ret;
	}
} z[N << 2];

ll f[N];

#define root 0, n, 1
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1

void build(int l, int r, int rt)
{
	if (l == r)
	{
		z[rt].min = f[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(lson);
	build(rson);
	z[rt] = z[rt << 1] + z[rt << 1 | 1];
}
void push_down(int rt)
{
	if (z[rt].tag)
	{
		z[rt << 1].tag += z[rt].tag;
		z[rt << 1].min += z[rt].tag;
		z[rt << 1 | 1].tag += z[rt].tag;
		z[rt << 1 | 1].min += z[rt].tag;
		z[rt].tag = 0;
	}
}
void modify(int l, int r, int rt, int nowl, int nowr, ll k)
{
	if (nowl <= l && r <= nowr)
	{
		z[rt].min += k;
		z[rt].tag += k;
		return;
	}
	push_down(rt);
	int mid = (l + r) >> 1;
	if (nowl <= mid)
		modify(lson, nowl, nowr, k);
	if (nowr > mid)
		modify(rson, nowl, nowr, k);
	z[rt] = z[rt << 1] + z[rt << 1 | 1];
}
Node query(int l, int r, int rt, int nowl, int nowr)
{
	if (nowl <= l && r <= nowr)
		return z[rt];
	push_down(rt);
	int mid = (l + r) >> 1;
	if (nowl <= mid)
	{
		if (nowr > mid)
			return query(lson, nowl, nowr) + query(rson, nowl, nowr);
		else
			return query(lson, nowl, nowr);
	}
	else
		return query(rson, nowl, nowr);
}

int a[N];
int lst[N];
int vis[N];

int main()
{
	f[0] = 0;
	int n, k;
	cin >> n >> k;
	for (int i = 1; i <= n; ++i)
		f[i] = oo;
	for (int i = 1; i <= n; ++i)
	{
		cin >> a[i];
		lst[i] = vis[a[i]];
		vis[a[i]] = i;
	}
	for (int i = 1; i <= k; ++i)
	{
		build(root);
		for (int j = i; j <= n; ++j)
		{
			if (lst[j])
				modify(root, 0, lst[j] - 1, j - lst[j]);
			f[j] = query(root, i - 1, j - 1).min;
		}
	}
	cout << f[n] << '\n';
	return 0;
}

image
image


image
image


image
考虑到如果我们想模拟,就必须每一次操作都至少撞飞一只青蛙。
最先相撞的两个青蛙肯定是相邻的两个青蛙,所以我们看对于每两只青蛙,他们的位置、速度分别为 \(p_x, p_y, v_x, v_y\)(我们让 \(x\)\(y\)),他们的距离为 \(d = (py - px) \% m\)

  • \(v_x \geq d\),则可以撞上,所需时间为 \(1\)
  • 否则
    • \(v_x \leq v_y\),则 \(x\) 撞不上 \(y\)
    • 否则,能撞上,且时间为 \(\left\lceil \dfrac{d}{vx - vy} \right\rceil\)
      我们每一次处理全局时间最小的事件,用链表维护每个青蛙的位置关系,用 setpriority_queue 维护最小时间即可。
      注意,有可能一次不止撞飞一只青蛙,不过没什么区别,一个一个删除就行了
      实在太难写了,贴一篇题解的代码吧。
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
const int inf = 1e9;
const ll INF = 1e15;
const int N = 1e5;
inline int read() {
	int s = 0,f = 1;char ch = getchar();
	while (!isdigit(ch)) f = ch == '-' ? -1 : 1, ch = getchar();
	while (isdigit(ch)) s = (s << 3) + (s << 1) + ch - '0', ch = getchar();
	return s*f;
}
int n,m,tot,vis[N + 10];
struct node {
	int p,v,id;
}a[N + 10];
int cmp(node x,node y) {
	return x.p < y.p;
}
int pre[N + 10],nxt[N + 10],ans[N + 10],tim[N + 10];
ll dd[N + 10];
int dis(int x,int y) {
	int npx = a[x].p,npy = a[y].p;
	return npx < npy ? npy - npx : m - npx + npy;
}
ll calc(int x,int y,int now) {
	ll ndx = dd[x] + 1ll * a[x].v * (now - tim[x]),ndy = dd[y] + 1ll * a[y].v * (now - tim[y]);
	if (a[x].id < a[y].id && dis(x,y) <= a[x].v) return 1;
	else if (a[x].id < a[y].id && a[x].v > a[y].v) return ceil((dis(x,y) - ndx + ndy - a[x].v) * 1.0 / (a[x].v - a[y].v)) + 1;
	return a[x].v <= a[y].v ? INF : ceil((dis(x,y) - ndx + ndy) * 1.0 / (a[x].v - a[y].v));
}
struct node2 {
	int x,y,v,w;
	bool operator < (const node2 &y) const {
		return v == y.v ? a[x].id > a[y.x].id : v > y.v;
	}
};
void del(int x) {
	pre[nxt[x]] = pre[x];
	nxt[pre[x]] = nxt[x];
	pre[x] = nxt[x] = -1;
}
signed main() {
	n = read();
	m = read();
	for (int i = 1;i <= n;i ++ ) a[i].p = read(), a[i].v = read(), a[i].id = i;
	sort(a + 1,a + n + 1,cmp);
	priority_queue<node2> q;
	for (int i = 1;i <= n;i ++ ) nxt[i] = i % n + 1, pre[i] = (i + n - 2) % n + 1, q.push({i,nxt[i],calc(i,nxt[i],0),vis[nxt[i]] = ++tot});
	while (q.size() > 1) {
		node2 t = q.top();
		if (pre[t.x] < 0 || t.y != nxt[t.x] || vis[t.y] != t.w) {
			q.pop();
			continue;
		}
		if (t.v >= INF) break;q.pop();
		int x = t.x,lsp = a[x].p;
		ll dx = 1ll * (t.v - tim[x]) * a[x].v;
		dd[x] += dx;
		tim[x] = t.v;
		a[x].v --;
		del(nxt[x]);
		while (nxt[x] != x && dd[x] - (dd[nxt[x]] + 1ll * (t.v - tim[nxt[x]] - (a[x].id < a[nxt[x]].id)) * a[nxt[x]].v) >= dis(x,nxt[x])) a[x].v --, del(nxt[x]);
		a[x].v = max(a[x].v,0ll);
		q.push({pre[x],x,t.v + calc(pre[x],x,t.v),vis[x] = ++tot});
		q.push({x,nxt[x],t.v + calc(x,nxt[x],t.v),vis[nxt[x]] = ++tot});
	}
	tot = 0;
	while (!q.empty()) {
		node2 t = q.top();
		if (!(pre[t.x] < 0 || t.y != nxt[t.x] || vis[t.y] != t.w)) ans[++tot] = a[t.x].id;
		q.pop();
	}
	sort(ans + 1,ans + tot + 1);
	printf("%lld\n",tot);
	for (int i = 1;i <= tot;i ++ ) printf("%lld ",ans[i]);
	return 0;
}

\(from\) https://www.luogu.com.cn/article/932mzhzu

posted @ 2026-05-02 09:17  SigmaToT  阅读(12)  评论(0)    收藏  举报