lc3310
dfs标记可疑方法
入度+连通块

class Solution {
public:
vector<int> remainingMethods(int n, int k, vector<vector<int>>& invocations)
{
vector<vector<int>> g(n);
for (auto& e : invocations)
++ge\[0].push_back(e1);++
// 标记所有可疑方法
vector<int> is_suspicious(n);
auto dfs = \&(auto&& dfs, int x) -> void {
is_suspiciousx = true;
++for (int y : gx) {++
if (!is_suspiciousy) { // 避免无限递归
dfs(dfs, y);
}
}
};
dfs(dfs, k);
++// 检查是否有【非可疑方法】->【可疑方法】的边
for (auto& e : invocations) {
if (!is_suspiciouse\[0] && is_suspiciouse\[1]) {
// 无法移除可疑方法
vector<int> ans(n);
iota(ans.begin(), ans.end(), 0);
return ans;
}
}++
// 移除所有可疑方法
vector<int> ans;
for (int i = 0; i < n; i++) {
if (!is_suspiciousi) {
ans.push_back(i);
}
}
return ans;
}
};
lc3578
其实是很典型 单调dq优化线性dp
半月前应该也刷到了一道类似的,第一时间还是没优化出来orz哭哭哭
class Solution {
public:
const static int N=5e4+10;
int fN;
int countPartitions(vector<int>& nums, int k) {
int n=nums.size();
int mod=1e9+7;
f0=1;
deque<int> qx,qn;
int sum=0,l=0;
for(int i=0;i<n;i++){
while(!qx.empty()&&numsi>=numsqx.back()) qx.pop_back();
qx.push_back(i);
while(!qn.empty()&&numsi<=numsqn.back()) qn.pop_back();
qn.push_back(i);
sum=(sum+fi)%mod;
while(!qx.empty()&&numsqx.front()-numsqn.front()>k){
if(l==qx.front()) qx.pop_front();
if(l==qn.front()) qn.pop_front();
sum=(sum+mod-fl)%mod;
l++;
}
fi+1=sum;
}
// for(int i=0;i<=n;i++) cout<<fi<<' ';puts("");
return fn;
}
};
deque优化划分dp
++idx 单调dq 维护窗口内的最大、最小值++,结合前缀和数组
++滑动窗口中统计元素差不超过k的合法分区数并取模++

class Solution {
public:
int countPartitions(vector<int>& nums, int k) {
const int MOD = 1'000'000'007;
int n = nums.size();
deque<int> min_q, max_q;
vector<int> f(n + 1);
f0 = 1;
long long sum_f = 0; // 窗口中的 fi 之和
int left = 0;
for (int i = 0; i < n; i++) {
int x = numsi;
++// 1. 入
sum_f += fi;++
while (!min_q.empty() && x <= numsmin_q.back())
min_q.pop_back();
min_q.push_back(i);
while (!max_q.empty() && x >= numsmax_q.back()) {
max_q.pop_back();
}
max_q.push_back(i);
// 2. 出
++while (numsmax_q.front() - numsmin_q.front() > k) {++
++sum_f -= fleft;++
++left++;
if (min_q.front() < left)++
min_q.pop_front();
if (max_q.front() < left)
max_q.pop_front();
}
// 3. 更新答案
++fi + 1 = sum_f % MOD;
}++
++return fn;++
}
};
问题转化:要统计"所有元素差不超过K的子段分割方案数",可以用动态规划,设 ++fi 为前 i -1个元素的✓condition分割方案数++
滑动窗口找合法左边界:
用 multiset 维护窗口内的最大/最小值,通过双指针 j 找到每个右端点 i 对应的最小合法左边界(保证窗口内元素差≤K)
- 前缀和优化转移: fi 的方案数等于"前 i-1 个元素的所有合法前缀方案数之和",用前缀和++数组 g 快速计算这个区间和++ ,从而将转移时间复杂度优化到O(1)。
结合动态规划+滑动窗口+前缀和,就能高效解决子段分割计数问题啦~
class Solution {
public:
int countPartitions(vector<int>& nums, int K) {
int n = nums.size();
const int MOD = 1e9 + 7;
// fi:前 i 个元素的分割方案数
// gi:f 的前缀和
long long fn + 1, gn + 1;
f0 = g0 = 1;
// 用 multiset 记录滑动窗口里的数,方便求出最小值和最大值
multiset<int> ms;
// 枚举双指针的右端点 i,计算合法子段左端点的最小值 j
for (int i = 1, j = 1; i <= n; i++) {
ms.insert(numsi - 1);
while (j < i && *prev(ms.end()) - *ms.begin() > K) {
ms.erase(ms.find(numsj - 1));
j++;
}
// j 是最小的左端点
// 那么上一个子段最小的右端点就是 j - 1
// 前缀和就得减去 j - 2 的值
fi = (gi - 1 - (j - 2 >= 0 ? gj - 2 : 0) + MOD) % MOD;
gi = (gi - 1 + fi) % MOD;
}
return fn;
}
};
lc2762 不间断子数组
滑窗
class Solution {
public:
long long continuousSubarrays(vector<int>& nums) {
long long ans = 0;
map<int, int> cnt;
int left = 0;
for (int right = 0; right < nums.size(); right++) {
cntnums\[right]++;
while (cnt.rbegin()->first - cnt.begin()->first > 2) {
int y = numsleft;
if (--cnty == 0) {
cnt.erase(y);
}
left++;
}
ans += right - left + 1;
}
return ans;
}
};
双堆法
class Solution {
public long continuousSubarrays(int\[\] nums) {
long res=0;
int left=0;
PriorityQueue<Integer> less=new PriorityQueue<>();//最小堆
PriorityQueue<Integer> more=new PriorityQueue<>((a,b)->(b-a));//最大堆
for(int i=0;i<nums.length;i++){
less.offer(numsi);
more.offer(numsi);
while(more.peek()-less.peek()>2){
more.remove(numsleft);
less.remove(numsleft);
left++;
}
res+=i-left+1;
}
return res;
}
}