2026.5.5情报系统听课笔记

image

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

image
注意,数字都是正的。
image

点击查看代码
#include <iostream>

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

int l, r;
int a[N];
ll q[N];
ll dp[N];
ll sum[N];

int main()
{
	int n, k;
	cin >> n >> k;
	for (int i = 1; i <= n; ++i)
		cin >> a[i], sum[i] = sum[i - 1] + a[i];
	dp[0] = 0;
	l = 1, r = 0;
	q[++r] = 0;
	for (int i = 1; i <= n + 1; ++i)
	{
		while (l <= r && q[l] < i - k - 1)
			l++;
		dp[i] = sum[i - 1] + (dp[q[l]] - sum[q[l]]);
		while (l <= r && dp[q[r]] - sum[q[r]] <= dp[i] - sum[i])
			r--;
		q[++r] = i;
	}
	cout << dp[n + 1] << '\n';
	return 0;
}

image
显然,当 \(g\) 变大时,答案单调不降。
所以可以二分 \(g\)
对于 check 函数,我们做个 \(dp\),用单调队列优化即可。
这里的单调队列每一次算当前位置 \(dp\) 值的时候,不能直接把当前位置加进单调队列中。
而是要再搞一个指针 \(j\),把当前位置之前左端点符合的位置加进来。

点击查看代码
#include <iostream>
#define int long long

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

int n, d, k;
int x[N];
int s[N];
ll q[N];
ll dp[N];

bool check(int L, int R)
{
//	cout << L << ' ' << R << '\n';
	int j = 0;
	int l = 1, r = 0;
	dp[0] = 0;
	for (int i = 1; i <= n; ++i)
		dp[i] = -oo;
	for (int i = 1; i <= n; ++i)
	{
		while (x[j] + L <= x[i] && j < i)
		{
			if (dp[j] == -oo)
			{
				j++;
				continue;
			}
			while (l <= r && dp[j] >= dp[q[r]])
				r--;
			q[++r] = j;
			j++;
		}
		while (l <= r && x[q[l]] + R < x[i])
			l++;
		if (l > r)
			continue;
		dp[i] = dp[q[l]] + s[i];
//		cout << dp[i] << '\n';
		if (dp[i] >= k)
			return true;
	}
	return false;
}

signed main()
{
	cin >> n >> d >> k;
	for (int i = 1; i <= n; ++i)
		cin >> x[i] >> s[i];
	int l = 0, r = 1e9;
	int ans = -1;
	while (l <= r)
	{
		int mid = (l + r) >> 1;
//		cout << mid << '\n';
		if (check(std::max(1ll, d - mid), d + mid))
		{
			ans = mid;
			r = mid - 1;
		}
		else
			l = mid + 1;
	}
	cout << ans << '\n';
	return 0;
}

image
image

点击查看代码
#include <iostream>

using std::cin;
using std::cout;
const int N = 1e6 + 10;

int d[N];
int dp[N];
int q[N * 4];

int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; ++i)
		cin >> d[i];
	int p;
	cin >> p;
	while (p--)
	{
		int k;
		cin >> k;
		int head = 1, tail = 0;
		dp[1] = 0;
		q[++tail] = 1;
		for (int i = 2; i <= n; ++i)
		{
			while (head <= tail && i - q[head] > k)
				++head;
			dp[i] = dp[q[head]] + (d[q[head]] <= d[i]);
			while (head <= tail && (dp[i] < dp[q[tail]] || (dp[i] == dp[q[tail]] && d[q[tail]] <= d[i])))
				tail--;
			q[++tail] = i;
		}
		cout << dp[n] << '\n';
	}
	return 0;
}

image
每个时间段可以走的步数为 \(0\) 到某个数的区间。
我们令 \(f_{t,x,y}\) 为第 \(t\) 时段最后在 \(x,y\) 的最大步数。
显然第一维可以滚动掉。
以向右走为例:
从每一行的第一个位置开始向右 \(dp\),可以发现因为有障碍,所以这一行就被拆成了一堆连续的区间。
对于每一个区间,就是一个单调队列优化 \(dp\)
如果遇到障碍,直接清空单调队列即可。

点击查看代码
#include <iostream>

using std::cin;
using std::cout;
const int N = 210;
const int oo = 1e9;
struct Node
{
	int val, id;
};

int n, m, x, y, k;
int l, r;
int ans;
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
Node q[N];
char c[N][N];
int dp[N][N];

void F(int x, int y, int opt, int L)
{
	l = 1, r = 0;
	for (int i = 1; x <= n && y <= m && x >= 1 && y >= 1; x += dx[opt], y += dy[opt], ++i)
	{
		if (c[x][y] == 'x')
		{
			r = l - 1;
			continue;
		}
		if (dp[x][y] != -oo)
		{
			while (l <= r && q[r].val - q[r].id <= dp[x][y] - i)
				--r;
			q[++r] = (Node){dp[x][y], i}; 
		}
		while (l <= r && q[l].id < i - L)
			++l;
		if (l <= r)
		{
			dp[x][y] = q[l].val + i - q[l].id;
			ans = std::max(ans, dp[x][y]);
		}
	}
}

int main()
{
	cin >> n >> m >> x >> y >> k;
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= m; ++j)
			cin >> c[i][j], dp[i][j] = -oo;
	}
	dp[x][y] = 0;
	while (k--)
	{
		int s, t, d;
		cin >> s >> t >> d;
		int l = t - s + 1;
		if (d == 1)
		{
			for (int i = 1; i <= m; ++i)
				F(n, i, d - 1, l);
		}
		if (d == 2)
		{
			for (int i = 1; i <= m; ++i)
				F(1, i, d - 1, l);
		}
		if (d == 3)
		{
			for (int i = 1; i <= n; ++i)
				F(i, m, d - 1, l);
		}
		if (d == 4)
		{
			for (int i = 1; i <= n; ++i)
				F(i, 1, d - 1, l);
		}
	}
	cout << ans << '\n';
	return 0;
}

如何求有多少个最长上升子序列?
\(f_i\) 表示以 \(i\) 结尾的最长的上升子序列长度,\(g_i\) 表示以 \(g_i\) 结尾的有多少个长度为 \(f_i\) 的上升子序列。
则一个点的数据可以表示成二元组 \((f_i, g_i)\)
定义新运算 \(\oplus\)

\[(f_i,g_i)\oplus (f_j,g_j)=\begin{cases} (f_i,g_i + g_j)&&f_i = f_j\\ (f_i,g_i)&&f_i>f_j\\ (f_j,g_j)&&f_i<f_j \end{cases} \]

\[(f_i,g_i)=\bigoplus_{i<j,a_i<a_j}(f_j,g_j) \]

后面这个东西显然满足结合律,所以可以直接像求最长上升子序列长度那样,用树状数组(或线段树)维护(只不过不是加法就是了)。

一张图走 \(N\) 步从 \(u\) 走到 \(v\) 有多少种方案?
\(f_{i,j,k}\) 为从 \(i\)\(k\) 步走到 \(j\) 的方案数。
我们看一下 \(f_{2,i,j}\) 怎么算:

\[f_{2,i,j} = \sum_{k}(f_{1,i,k}\times f_{1,k,j}) \]

这不就是对原邻接矩阵做一个矩阵乘法嘛!
所以答案就是 \(S^N\),其中 \(S\) 是原邻接矩阵。

posted @ 2026-05-05 10:38  SigmaToT  阅读(9)  评论(0)    收藏  举报