2026.5.5情报系统听课笔记


注意,数字都是正的。

点击查看代码
#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;
}

显然,当 \(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;
}


点击查看代码
#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;
}

每个时间段可以走的步数为 \(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\) 是原邻接矩阵。

浙公网安备 33010602011771号