fASTER
Reverse Pair
- naive: \(O(n^2)\)
-
BIT: \(O(nlogn)\)
思路:创建包含输入范围(过大时离散化)的树状数组,每次添加元素时,加上该元素之后的所有元素出现过的次数(区间求和)。
#include <iostream> #include <cstring> #include <algorithm> #include <map> using namespace std; const int maxn = 50005; int N; int arr[maxn], arr2[maxn], bit[maxn]; map<int, int> m; int lowbit(int x) { return x & (-x); } void add(int n, int x) { for (n; n <= N; n += lowbit(n)) bit[n] += x; } int getsum(int n) { int res = 0; for (n; n > 0; n -= lowbit(n)) res += bit[n]; return res; } int main() { while (cin >> N && N) { m.clear(); memset(bit, 0, sizeof(bit)); for (int i = 0; i < N; i++) cin >> arr[i]; // discretization memcpy(arr2, arr, sizeof(arr)); sort(arr2, arr2 + N); // [0, N) -> [minv, maxv] for (int i = 0; i < N; i++) m[arr2[i]] = i; // [0, N) <- [minv, maxv] long long ans = 0; for (int i = 0; i < N; i++) { add(m[arr[i]] + 1, 1); ans += i + 1 - getsum(m[arr[i]] + 1); } cout << ans << endl; } }
-
Merge sort: \(O(nlogn)\)
思路:归并过程中遇到左边大于右边,则此处为逆序对。
#include <iostream> #include <cstring> #include <algorithm> #include <map> using namespace std; const int maxn = 50005; int N; int arr[maxn], tmp[maxn]; long long ans = 0; void merge(int l, int r, int *a, int *b) { if (r == l) return; int m = (l + r) / 2; merge(l, m, a, b); merge(m + 1, r, a, b); int i = l, j = m + 1, k = l; while (i <= m && j <= r) { if (a[i] <= a[j]) b[k++] = a[i++]; else { ans += m + 1 - i; // update b[k++] = a[j++]; } } while (i <= m) b[k++] = a[i++]; while (j <= r) b[k++] = a[j++]; for (int i = l; i <= r; i++) a[i] = b[i]; } int main() { while (cin >> N && N) { for (int i = 0; i < N; i++) cin >> arr[i]; ans = 0; merge(0, N - 1, arr, tmp); cout << ans << endl; } }
Variants
-
(Leetcode 493) Count (i, j) where
i<j
andarr[i] > 2*arr[j]
.-
mergesort
在排序之外独立的操作计数。此外,尽管题目说了数据是int内,由于比较时需要*2,仍有溢出风险,所以用ll。
class Solution { public: long long ans = 0; // T(n) = 2T(n/2) + O(2n) void mergesort(long long* a, long long* b, int l, int r){ if(l==r) return; int m = (l+r)/2; mergesort(a, b, l, m); mergesort(a, b, m+1, r); // count only, O(n) int i=l, j=m+1, k=l; while(i<=m && j<=r){ if(a[i] > 2*a[j]){ ans += m - i + 1; j++; } else i++; } // sort, O(n) i=l, j=m+1, k=l; while(i<=m && j<=r){ if(a[i] > a[j]) b[k++] = a[j++]; else b[k++] = a[i++]; } while(i<=m) b[k++] = a[i++]; while(j<=r) b[k++] = a[j++]; for(int i=l; i<=r; i++) a[i] = b[i]; } int reversePairs(vector<int>& nums) { if(nums.empty()) return 0; int N = nums.size(); ans = 0; long long* b = new long long[N]; long long* a = new long long[N]; for(int i=0; i<N; i++) a[i]=nums[i]; mergesort(a, b, 0, N-1); return ans; } };
-
BIT
离散化仍然是必要的,而正确性需要在原来的数组中搜索两倍关系,而不是在离散后的数组中搜索。
Lower Bound各种边界条件太难处理好了。
class Solution{ public: const static int maxn = 50005; long long bit[maxn]; int N; int lowbit(int x){return x&(-x);} void add(int i, int x){ for(i;i>0;i-=lowbit(i)) bit[i]+=x; } int getsum(int i){ int sum = 0; for(i;i<=N;i+=lowbit(i)) sum += bit[i]; return sum; } void init(int n){ for(int i=0; i<=n; i++) bit[i] = 0; } int reversePairs(vector<int>& nums) { if(nums.empty()) return 0; N = nums.size(); init(N); vector<int> pos(nums); sort(pos.begin(), pos.end()); int ans = 0; for(int i=0; i<N; i++){ int p = lower_bound(pos.begin(), pos.end(), nums[i]) - pos.begin(); int p2 = lower_bound(pos.begin(), pos.end(), 2LL*nums[i]+1) - pos.begin(); ans += getsum(p2+1); add(p+1, 1); } return ans; } };
-
Interval Scheduling Maximization Problem
given a set of intervals, find the maximum numbers of intervals that are non-overlapping.
-
Greedy: \(O(nlgn)\) (and is enough)
// here [0,1] and [1,1] are considered as overlapped. #include <iostream> #include <algorithm> #include <string> using namespace std; const int maxn = 10005; int N; struct node { int s, e; bool operator< (const node& b) const { return e < b.e; } } act[maxn]; int main() { cin >> N; // input for (int i = 0; i < N; i++) cin >> act[i].s >> act[i].e; // sort, smaller ending time first, and starting time is useless. sort(act, act + N); int ans = 0, end = -1; // remove the interval with smallest ending time // and all overlapped interval with it, ans++ for (int i = 0; i < N; i++) { if (act[i].s > end) { ans++; end = act[i].e; } else continue; } cout << ans << endl; }
Variants
-
GISDP: Grouped Interval Scheduling decision problem
Every two intervals shouldn't be in the same group, just decide whether such a compatible set exists.
- if all groups contain at most 2 intervals. Polynomial.
- more than 2 intervals: NP-hard.
Monotonic Queue
单调队列,push元素时先把队尾小于x的元素pop掉,保证队首始终为最大元素。适合于滑动区间取最值问题。
\(O(1)\) for pop() and getmax() ! ( but in worst cases \(O( n)\) for push(). )
-
Sliding Window Maximum (leetcode 239)
Very clever Linear solution & implementation !
class Solution { public: struct monoque{ deque<int> que; // keep que monotonically decreasing void push(int n){ while(!que.empty() && que.back()<n) que.pop_back(); que.push_back(n); } int getmax(){ return que.front(); } // n is the element ought to be deleted, this makes difference! void pop(int n){ if(que.front() == n) que.pop_front(); } }; vector<int> maxSlidingWindow(vector<int>& nums, int k) { int N = nums.size(); vector<int> ans; monoque que; for(int i=0; i<N; i++){ que.push(nums[i]); if(i>=k-1){ ans.push_back(que.getmax()); que.pop(nums[i-k+1]); } } return ans; } };
Maximum & Minimum Integer Sequence
Greedy. But the comparator is hard to come about.
import functools
n = int(input())
nums = input().split()
# python sorted cmp
def cmp(a, b):
a, b = int(a+b), int(b+a)
if a==b:
return 0 # eq
elif a<b:
return -1 # a < b
else:
return 1 # a > b
# functools.cmp_to_key
nums = sorted(nums, key=functools.cmp_to_key(cmp))
print(''.join(reversed(nums)),''.join(nums))
// leetcode 179
class Solution {
public:
static bool cmp(const string& a, const string& b){
return stoll(a+b) > stoll(b+a);
}
string largestNumber(vector<int>& nums) {
vector<string> snums;
for(int i=0; i<nums.size(); i++)
snums.push_back(to_string(nums[i]));
sort(snums.begin(), snums.end(), cmp);
string ans;
for(int i=0; i<nums.size(); i++) ans+=snums[i];
while(ans[0] == '0' && ans.size()>1) ans = ans.substr(1);
return ans;
}
};
Palindrome
Manacher is the golden key.
class Solution {
public:
int manacher(string s){
// init
string S = "@#";
for(int i=0; i<s.size(); i++) S += s[i] + "#";
S += "$";
// calc
int center = 0, right = 0;
int* z = new int[S.size()];
for(int i=1; i<S.size(); i++){
if(i<right) z[i] = min(right-i, z[2*center-i]);
while(S[i+z[i]+1] == S[i-z[i]-1]) z[i]++;
if(i+z[i]>right){
center = i;
right = i + z[i];
}
}
// find longest
int mx = 0;
for(int i=1; i<S.size(); i++)
mx = max(mx, (z[i]+1)/2);
return mx;
}
};
Cumulative Sum
子序列(Subarray, continuous)求和问题,枚举子序列O(n^2),每个子序列的计算O(n),Naive算法O(n^3)。
Cumsum预处理O(n),枚举子序列O(n^2),每个子序列的计算O(1),总共O(n^2)。
-
leetcode 560 Subarray Sum Equals K
由于要求特殊,还可以散列计数优化到O(n)。
class Solution { public: int subarraySum(vector<int>& nums, int k) { map<int, int> m; m[0] = 1; int sum = 0, ans = 0; for(int i=0; i<nums.size(); i++){ sum += nums[i]; if(m.count(sum-k)) ans+= m[sum-k]; if(m.count(sum)) m[sum]++; else m[sum] = 1; } return ans; } };
O(1) Space Cults
-
find the unique number
[1,2,1,3,3] -> 2
XOR solution.
-
find the duplicate number
[1,3,4,2,2] -> 2
Amazingly a Floyd Cycle Detection Solution:class Solution { public: int findDuplicate(vector<int>& nums) { int slow=0, fast=0; do{ slow = nums[slow]; fast = nums[nums[fast]]; } while(slow != fast); slow = 0; while(slow != fast){ slow = nums[slow]; fast = nums[fast]; } return slow; } };
Range Maximum
-
Segment tree
区间更新O(lgn),预处理O(nlgn),区间查询O(lgn)
-
BIT
单点更新O(lg^2n),预处理O(nlg^2n),区间查询O(lg^2n)
-
RMQ
不支持更新,预处理O(nlgn),区间查询O(1)
Longest Valid Parenthesis substring 32
DP is complicated, use stack and think reversely.
每两个无法匹配的字符之间的子串一定是正确匹配的,所以用栈记录失配字符,减一减即可!
class Solution {
public:
int longestValidParentheses(string s) {
stack<int> stk;
for(int i=0; i<s.size(); i++){
if(s[i] == '(') stk.push(i);
else{
if(!stk.empty() && s[stk.top()]=='(') stk.pop();
else stk.push(i);
}
}
if(stk.empty()) return s.size();
else{
int end = s.size(), start = 0, ans = 0;
while(!stk.empty()){
start = stk.top(); stk.pop();
ans = max(ans, end-start-1);
end = start;
}
ans = max(ans, end);
return ans;
}
}
};
排序的代价
求通过交换任意两个数字(代价为两个数字之和)使得数组有序所要花费的最小代价。
思路:索引排序,每个交换环的最小代价为:环中元素之和+环中最小元素*(环中元素数目-2),或者把环中最小元素暂时与整体最小元素互换再求代价。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1000005;
const int inf = 0x3f3f3f3f;
int N;
int vis[maxn];
struct node {
int val, idx;
bool operator < (const node& b) const {
return val < b.val;
}
} nodes[maxn];
int ans, mn;
int solveLoop(int x) {
int lmn = inf, cnt = 0, res = 0;
while (!vis[x]) {
vis[x] = 1;
lmn = min(lmn, nodes[x].val);
cnt++;
res += nodes[x].val;
x = nodes[x].idx;
}
int val1 = res + lmn * (cnt - 2);
int val2 = res + mn * cnt + lmn + mn; // important
return min(val1, val2);
}
int main() {
while (cin >> N && N) {
mn = inf;
for (int i = 0; i < N; i++) {
cin >> nodes[i].val;
nodes[i].idx = i;
mn = min(mn, nodes[i].val);
}
sort(nodes, nodes + N);
memset(vis, 0, sizeof(vis));
ans = 0;
for (int i = 0; i < N; i++) if (!vis[i]) ans += solveLoop(i);
cout << ans << endl;
}
}