由于除了刷题外还有些个人的事情,所以大概本周的安排是这样的:
- 本周前两天先研究下关于在周赛272中遇到的问题,打算思考明白并给出一个详细的证明
- 对于周赛272让我重新拎起LIS,打算对相应的题目进行学习 LC.673√
- 另外大概是要重新学习DP了,先把挖的坑补完
- 此外是需要重新琢磨下树状数组了,若干天后理解更为深刻了?(对区间修改和区间查询的学习)
673. 最长递增子序列的个数
O
(
n
2
)
O(n^2)
O(n2)的做法就不多说了
class Solution {
public:
int findNumberOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> f(n, 1), g(n, 1);
int maxlen = 0;
for(int i = 0; i < n; ++i) {
for(int j = 0; j < i; ++j)
if(nums[i] > nums[j]) f[i] = max(f[i], f[j] + 1);
if(f[i] > 1) {
g[i] = 0;
for(int j = 0; j < i; ++j)
if(nums[i] > nums[j] && f[i] == f[j] + 1) g[i] += g[j];
}
maxlen = max(maxlen, f[i]);
}
int ans = 0;
for(int i = 0; i < n; ++i)
if(f[i] == maxlen) ans += g[i];
return ans;
}
};
考虑第一种较为好理解的树状数组做法,先对所有数进行离散化
查询操作:
q
u
e
r
y
(
x
)
query(x)
query(x) 返回小于等于
x
x
x的 最长长度和对应数量
更新操作:
a
d
d
(
x
,
p
a
i
r
<
l
e
n
,
c
n
t
>
)
add(x,pair<len,cnt>)
add(x,pair<len,cnt>)每次将大于等于
x
x
x的结尾长度更新为 原有最长长度和当前更新的长度的最大值,以及对应的数量
class Solution {
public:
int findNumberOfLIS(vector<int>& nums) {
vector<int> temp = nums;
sort(temp.begin(), temp.end());
temp.erase(unique(temp.begin(), temp.end()), temp.end());
int n = temp.size();
vector<pair<int, int>> tr(n + 1, make_pair(0, 0)); // pair<len, cnt>
auto query = [&](int p) -> pair<int, int> {
pair<int, int> maxp = make_pair(0, 0); //max_pair
while(p > 0) {
if(tr[p].first == maxp.first) maxp.second += tr[p].second;
else if(tr[p].first > maxp.first) maxp = tr[p];
p -= p & (-p);
}
return maxp;
};
auto add = [&](int p, pair<int, int> update) {
while(p <= n) {
if(update.first == tr[p].first) tr[p].second += update.second;
else if(update.first > tr[p].first) tr[p] = update;
p += p & (-p);
}
};
for(int i = 1; i <= nums.size(); ++i) {
int idx = lower_bound(temp.begin(), temp.end(), nums[i - 1]) - temp.begin() + 1;
pair<int, int> maxp = query(idx - 1);
add(idx, make_pair(maxp.first + 1, max(1, maxp.second)));
}
return query(n).second;
}
};
再来考虑另外一种不是很好想明白的方式
考虑从前往后枚举每个数,每个数一定是会被接在一个比它小的数之后构成一个更长的序列
问题是求最长序列的个数,所以需要找到最长的且比它小的数的序列长度。
由于每个数一定是放在比它小的数后面,这样才有可能使得最长序列的长度更长,所以每个长度序列的元素一定是单调不增的(如果存在增长,那么这个数一定可以放在其它序列的末尾使得序列更长)
所以考虑对每个数 n u m num num,二分小于它的数的最长序列 S S S的长度 l e n len len,再之二分这个序列中小于它的数的个数。这样就可以获得以序列 S S S中可以让 n u m num num接在之后的个数,并且统计以此为依据的序列个数。
我个人觉得加的
t
r
i
c
k
trick
trick好像并不是很直观,加个特判更容易理解点
以下代码参考了官方代码
class Solution {
public:
int findNumberOfLIS(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> d, cnt;
auto b_s = [&](int n, function<bool(int)> f) {
int l = 0, r = n;
while(l < r) {
int mid = l + r >> 1;
if(f(mid)) r = mid;
else l = mid + 1;
}
return l;
};
for(int v : nums) {
int i = b_s(d.size(), [&](int i) {return d[i].back() >= v;});
int c = 1;
if(i > 0) {
int j = b_s(d[i - 1].size(), [&](int j) {return d[i - 1][j] < v;});
c = cnt[i - 1].back() - (j == 0 ? 0 : cnt[i - 1][j - 1]);
}
if(i == d.size()) {
d.push_back({v});
cnt.push_back({c});
} else {
d[i].push_back(v);
cnt[i].push_back(cnt[i].back() + c);
}
}
return cnt.back().back();
}
};
luogu P2687是一道进阶,不过数据比较变态
O
(
n
2
)
O(n^2)
O(n2)的做法
/*
author: solego
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5010;
ll nums[N], f[N];
double g[N];
int n, m;
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%lld", &nums[i]), f[i] = g[i] = 1;
ll maxlen = 0;
for(int i = 1; i <= n; ++i) {
for(int j = 1; j < i; ++j)
if(nums[i] < nums[j]) {
if(f[i] < f[j] + 1) f[i] = f[j] + 1, g[i] = g[j];
else if(f[i] == f[j] + 1) g[i] += g[j];
}
for(int j = 1; j < i; ++j)
if(nums[i] == nums[j]) g[j] = 0;
maxlen = max(maxlen, f[i]);
}
double ans = 0;
for(int i = 1; i <= n; ++i)
if(f[i] == maxlen) ans += g[i];
printf("%lld %.0lf\n", maxlen, ans);
return 0;
}
O ( n log n ) O(n\log n) O(nlogn)的做法
/*
author: solego
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
typedef pair<ll, double> pld;
class Solution {
public:
pld findNumberOfLIS(vector<ll>& nums) {
int n = nums.size();
vector<vector<ll>> d;
vector<vector<double>> cnt;
auto b_s = [&](int n, function<bool(int)> f) {
int l = 0, r = n;
while(l < r) {
int mid = l + r >> 1;
if(f(mid)) r = mid;
else l = mid + 1;
}
return l;
};
for(int v : nums) {
int i = b_s(d.size(), [&](int i) {return d[i].back() <= v;});
double c = 1.0;
if(i > 0) {
int j = b_s(d[i - 1].size(), [&](int j) {return d[i - 1][j] > v;});
c = cnt[i - 1].back() - (j == 0 ? 0.0 : cnt[i - 1][j - 1]);
}
if(i == d.size()) {
d.push_back({v});
cnt.push_back({c});
} else {
while(!d[i].empty() && d[i].back() == v) d[i].pop_back(), cnt[i].pop_back();
d[i].push_back(v);
cnt[i].push_back((cnt[i].empty() ? 0.0 : cnt[i].back()) + c);
}
}
return make_pair(d.size(), cnt.back().back());
}
};
int main()
{
scanf("%d", &n);
vector<ll> nums(n);
for(int i = 0; i < n; ++i) scanf("%lld", &nums[i]);
Solution x;
pld res = x.findNumberOfLIS(nums);
printf("%lld %.0lf\n", res.first, res.second);
return 0;
}
887. 鸡蛋掉落
老Google面试题了
目前只学会了
O
(
k
n
log
n
)
O(kn\log n)
O(knlogn),看状态补
O
(
k
n
)
O(kn)
O(kn)的做法
d
p
[
k
]
[
n
]
=
x
=
m
i
n
1
≤
i
≤
n
{
m
a
x
(
d
p
(
k
−
1
,
i
−
1
)
,
d
p
(
k
,
n
−
i
)
)
}
dp[k][n]= x={\underset{1\leq i \leq n}{min}} \{ max(dp(k-1,i-1),dp(k,n-i)) \}
dp[k][n]=x=1≤i≤nmin{max(dp(k−1,i−1),dp(k,n−i))}
分析下这个转移方程:
随着
i
i
i的增大,
d
p
(
k
−
1
,
i
−
1
)
dp(k-1,i-1)
dp(k−1,i−1)是单调不减的,因为鸡蛋数不变,但是楼层增多了
随着
i
i
i的增大,
d
p
(
k
,
n
−
i
)
dp(k,n-i)
dp(k,n−i)是单调不增的,因为鸡蛋数不变,但是楼层减少了
这个问题转换成了求最大值中的最小,经典二分。二分的终点是使得两者趋近于相等。
A
A
A是单调不减,
B
B
B是单调不增,当
A
A
A大于等于
B
B
B是应该缩减右区间,否则就缩减左区间。
最后需要注意,由于这里并不一定整数坐标不一定能达到
A
=
B
A=B
A=B,但是我们的答案一定是
A
≥
B
A\geq B
A≥B,所以再判断下最大的使得
A
<
B
A<B
A<B的
i
i
i即可
class Solution {
public:
vector<vector<int>> dp;
const int INF = 0x3f3f3f3f;
int get(int k, int n) {
if(k == 1) return n;
if(n == 0) return 0;
if(dp[k][n] != INF) return dp[k][n];
int l = 1, r = n;
int& res = dp[k][n];
while(l < r) {
int mid = l + r >> 1;
int A = get(k - 1, mid - 1);
int B = get(k, n - mid);
if(A >= B) r = mid;
else l = mid + 1;
}
res = min(res, max(get(k - 1, l - 1), get(k, n - l)) + 1);
if(l > 1) res = min(res, max(get(k - 1, l - 2), get(k, n - l + 1)) + 1);
return dp[k][n];
}
int superEggDrop(int k, int n) {
dp = vector<vector<int>> (k + 1, vector<int>(n + 1, INF));
return get(k, n);
}
};
1044. 最长重复子串
字符串hash,统计下当前枚举的串是否已经存在了即可。
typedef unsigned long long ull;
class Solution {
public:
string longestDupSubstring(string s) {
int n = s.size(), m = n + 1;
s = " " + s;
vector<ull> p(m);
vector<ull> h(m);
const int P = 131;
p[0] = 1;
for(int i = 1; i <= n; ++i) {
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + s[i];
}
string res = "";
auto check = [&](int x) -> bool {
unordered_set<ull> hash;
for(int i = 1; i + x - 1 <= n; ++i) {
// ull val = h[i + x - 1] - h[i - 1] * p[x];
// cout << x << ": " << val << endl;
if(hash.count(h[i + x - 1] - h[i - 1] * p[x])) {
res = s.substr(i, x);
return true;
}
hash.insert(h[i + x - 1] - h[i - 1] * p[x]);
}
return false;
};
int l = 1, r = n;
while(l < r) {
int mid = l + r + 1 >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
check(l);
return res;
}
};
1032. 字符流
Trie模板
class StreamChecker {
private:
struct TrieNode {
bool isWord;
vector<TrieNode*> son;
TrieNode(): isWord(false), son(26, nullptr) {}
~TrieNode() {
for(TrieNode* u : son)
if(u) delete u;
}
};
TrieNode* root;
string str;
public:
StreamChecker(vector<string>& words) {
str = "";
root = new TrieNode();
for(auto& s : words) {
reverse(s.begin(), s.end());
auto p = root;
for(auto c : s) {
int u = c - 'a';
if(p->son[u] == nullptr) p->son[u] = new TrieNode();
p = p->son[u];
}
p->isWord = true;
}
}
bool query(char letter) {
str += letter;
auto p = root;
for(int i = (int)str.size() - 1; i >= 0; --i) {
int u = str[i] - 'a';
if(p->son[u] == nullptr) return false;
p = p->son[u];
if(p->isWord) return true;
}
return false;
}
};
/**
* Your StreamChecker object will be instantiated and called as such:
* StreamChecker* obj = new StreamChecker(words);
* bool param_1 = obj->query(letter);
*/