# 时间复杂度
# 排序
# sort
复杂度:O (nlogn)
1 #include<bits/stdc++.h> | |
2 using namespace std; | |
3 bool my_less(int i, int j) {return (i < j);} // 自定义小于函数 | |
4 bool my_greater(int i, int j) {return (i > j);} // 自定义大于函数 | |
5 int main (){ | |
6 int a[]={3,7,2,5,6,8,5,4}; | |
7 sort(a,a+4); // 对前 4 个数排序,结果:2 3 5 7 6 8 5 4 | |
8 for(int i=0;i<8;i++) cout<<a[i]<< “ “;cout<<”\n”; // 下面可以复制这一行输出 | |
9 sort(a,a+8,less<int>()); // 从小到大排序,结果:2 3 4 5 5 6 7 8 | |
10 sort(a,a+8,my_less); // 自定义排序,结果:2 3 4 5 5 6 7 8 | |
11 sort(a,a+8,greater<int>()); // 从大到小排序,结果:8 7 6 5 5 4 3 2 | |
12 sort(a,a+8,my_greater); // 自定义排序,结果:8 7 6 5 5 4 3 2 | |
13 | |
14 vector<int> c = {1,2,3,4,5,6,7,8}; | |
15 sort(c.begin(),c.end(),my_greater); // 结果:8 7 6 5 4 3 2 1 | |
16 for(int i=0; i<c.size(); i++) cout<<c[i]<< “ “;cout<<”\n”; | |
17 | |
18 string s=”hello world”; | |
19 sort(s.begin(),s.end()); | |
20 cout<<s; // 输出:dehllloorw。注意第一个是空格 | |
21 return 0; | |
22 } |
# 题目:错误票据
注意:这里注意怎么读数据
这段代码使用了 scanf
函数来不断读取输入,直到遇到文件结束符(EOF)。在这里, scanf
函数尝试读取一个整数,并将其存储在 a[cnt]
中。如果成功读取一个整数,则 scanf
函数返回 1;如果遇到文件结束符,则 scanf
函数返回 EOF。
#include<bits/stdc++.h> | |
using namespace std; | |
const int N = 1e4+10; | |
int a[N]; | |
int main(){ | |
int n; cin >> n; | |
int cnt = 0; | |
while(scanf("%d", &a[cnt]) != EOF) cnt++; // 注意读数据的写法 | |
sort(a, a+cnt); | |
int ans1, ans2; | |
for(int i = 1; i < cnt; i++) { | |
if(a[i] - a[i-1] > 1) ans1 = a[i-1]+1; // 查找断号 | |
if(a[i] == a[i-1]) ans2 = a[i]; // 查找重号 | |
} | |
cout << ans1 << ' ' << ans2; | |
return 0; | |
} |
# 题目:结构体排序
#include<bits/stdc++.h> | |
using namespace std; | |
struct stu{ | |
int id; // 学号 | |
int c,m,e; // 语文、数学、英语成绩 | |
int sum; | |
} st[305]; | |
bool cmp(stu a, stu b){ | |
if(a.sum > b.sum) return true; | |
else if(a.sum < b.sum) return false; | |
else{ //a.sum == b.sum | |
if(a.c > b.c) return true; | |
else if(a.c < b.c) return false; | |
else{ //a.c == b.c | |
if(a.id > b.id) return false; | |
else return true; | |
} | |
} | |
} | |
int main(){ | |
int n; | |
cin >> n; | |
for(int i=1; i<=n; i++){ | |
st[i].id = i; // 学号 | |
cin >> st[i].c >> st[i].m >> st[i].e; | |
st[i].sum = st[i].c + st[i].m + st[i].e; // 总分 | |
} | |
sort(st+1, st+1+n, cmp); | |
for(int i=1; i<=5; i++) | |
cout << st[i].id << ' ' << st[i].sum << "\n"; | |
return 0; | |
} |
# 排列和组合
# 全排列
STL 提供了求下一个排列组合的函数 next_permutation ()。例如对于由 3 个字符 {a,b, c} 组成的序列,next_permutation () 能按字典序返回 6 个组合:abc、acb、bac、bca、cab、cba。
返回值:如果没有下一个排列组合,则返回 False,否则返回 True。每执行一次 next_ permutation (),新的排列就会被放到原来的空间里。
✧ 提示:它排列的范围是 [first, last],包括 first,不包括 last。
next_permutation () 从当前的全排列开始,逐个输出更大的全排列,而不是输出所有的全排列。如果要得到所有的全排列,就需要从最小的全排列开始。
1 #include <bits/stdc++.h> | |
2 using namespace std; | |
3 int main(){ | |
4 string s=”bca”; | |
5 sort(s.begin(),s.end()); // 字符串内部排序,得到最小的排列 “abc” | |
6 do{ | |
7 cout<<s<<’ ‘; | |
8 }while(next_permutation(s.begin(),s.end())); | |
9 return 0; | |
10 } // 输出:abc acb bac bca cab cba |
# 题目:拼数
【题目描述】设有 n 个正整数 a1、a2……an,将它们连接成一排,相邻数字首尾相接,组成一个最大的整数。n≤20。【输入描述】第一行有一个整数,表示数字个数 n。第二行有 n 个整数。【输出描述】输出一个正整数,表示最大的整数。
暴力超时。
✧ 提示:这其实是按两个数字组合的字典序排序,也就是把数字看作字符串来排序,下面 C++ 代码第 4 行的 cmp () 函数体现了这一思路。
此算法的总复杂度等于库函数 sort () 的复杂度,为 O (nlogn)。
1 #include<bits/stdc++.h> | |
2 using namespace std; | |
3 string a[21]; // 记录 20 个数,用字符形式 | |
4 bool cmp (string a, string b){ // 从大到小,按字典序的反序排列 | |
5 return a + b > b + a; // 组合字符串,注意这个技巧 | |
6 } | |
7 int main( ){ | |
8 int n; cin >> n; | |
9 for(int i=0; i<n; i++) cin >> a[i]; | |
10 sort(a, a+n, cmp); // 从大到小,按字典序的反序排列 | |
11 for(int i=0; i<n; i++) cout << a[i]; | |
12 return 0; | |
13 } |
# 题目:带分数
【题目描述】100 可以表示为带分数的形式:3 + 69258 / 714。还可以表示为:82 + 3546 / 197。注意特征:带分数中,数字 1~9 分别出现且只出现一次(不包含 0)。类似这样的带分数,100 有 11 种表示法。输入一个整数,输出它有多少种带分数表示法。【输入描述】从标准输入读入一个正整数 N (N < 1000000)。【输出描述】程序输出该数字用数字 1~9 不重复、不遗漏地组成带分数表示的全部种数。
n = a + b/c 移项得 ac + b = nc,在第 18 行判断是否符合此式
1 #include<bits/stdc++.h> | |
2 using namespace std; | |
3 int num[9]={1,2,3,4,5,6,7,8,9}; | |
4 int check(int L,int R){ // 计算数字 | |
5 int res=0; | |
6 for(int i=L;i<=R;i++) res=res*10+num[i]; | |
7 return res; | |
8 } | |
9 int main(){ | |
10 int n; cin>>n; | |
11 int cnt=0; | |
12 while(next_permutation(num,num+9)){ | |
13 for(int i=0;i<7;i++){ | |
14 for(int j=i+1;j<8;j++){ | |
15 int a = check(0,i); | |
16 int b = check(i+1,j); | |
17 int c = check(j+1,8); | |
18 if(a*c+b == c*n) cnt++; | |
19 } | |
20 } | |
21 } | |
22 cout<<cnt; | |
23 return 0; | |
24 } |
# 尺取法
尺取法可以把两重循环转化为一重循环,从而把复杂度从 O (n2) 变成 O (n)。
✧ 提示:用尺取法的最关键之处在于,两个指针 i、j 在总体上只能有一个循环,i 循环一遍,对应的 j 只能跟随 i 循环一遍。这样才能实现计算复杂度从 O (n2) 到 O (n) 的优化。所以尺取法的应用有很大局限。
1 for (int i = 0, j = n - 1; i < j; i++, j--) | |
2 { ... } |
1 // 用 while 循环实现尺取法 | |
2 int i = 0, j = n - 1; | |
3 while (i < j) { //i 和 j 在中间相遇,并且要防止 i、j 越界 | |
4 ... // 满足题意的操作 | |
5 i++; //i 从头扫描到尾 | |
6 j--; //j 从尾扫描到头 | |
7 } |
# 反向扫描
# 题目:回文
【题目描述】给定一个长度为 n 的字符串 S。请你判断字符串 S 是否回文。【输入描述】输入仅一行,包含一个字符串 S。1≤|S|≤106,保证 S 只包含大小写字母。【输出描述】若字符串 S 回文,则输出 “Y”,否则输出 “N”。
1 #include <bits/stdc++.h> #include <bits/stdc++.h> | |
2 using namespace std; using namespace std; | |
3 int main(){ int main(){ | |
4 string s; cin >> s; // 读字符串 string s; cin >> s; // 读字符串 | |
5 int n = s.size(); int n = s.size(); | |
6 int i = 0, j = n-1; // 双指针 | |
7 for(int i=0,j=n-1;i<j;i++,j--)// 双指针 while (i < j){ | |
8 if(s[i] != s[j]) { if(s[i] != s[j]){cout<<’N’; return 0;} | |
9 cout<<’N’; return 0; i++, j--; // 移动双指针 | |
10 } } | |
11 cout<<’Y’; cout << ‘Y’ ; | |
12 return 0; return 0; | |
13 } } |
# 同向扫描
同向扫描的题目和 “滑动窗口” 有关,指针 i 和 j 之间的区间,随着 i 和 j 向前扫描,形成了一个滑动窗口,借助这个滑动窗口能遍历和计算序列上的区间问题。
# 题目:美丽区间
【题目描述】给定一个长度为 n 的序列 a1, a2,…, an 和一个常数 S。对于一个连续区间,如果它的区间和大于或等于 S,则称它为美丽的区间。对于一个美丽的区间,其区间长度越短,它就越美丽。请你从序列中找出最美丽的区间。
【输入描述】第一行输入两个整数 n、S,其含义如题所述。接下来的一行输入 n 个整数,分别表示 a1、a2……an 。10≤N≤105,1≤ai≤104,1≤S≤108。
【输出描述】输出共一行,包含一个整数,表示最美丽的区间的长度。若不存在任何美丽的区间,则输出 0。
“滑动窗口”,求窗口内的区间和大于 S 的最小区间长度,复杂度为 O (n)。
1 #include <bits/stdc++.h> | |
2 using namespace std; | |
3 int a[100010]; | |
4 int main(){ | |
5 int n, S; cin>>n>>S; | |
6 for (int i=0; i<n; ++i) cin>>a[i]; | |
7 int sum= 0, ans=1e8; | |
8 for (int i=0, j=0; i<n;) { | |
9 if(sum<S){ sum+=a[i]; i++;} | |
10 else { ans=min(i-j,ans); sum-=a[j]; j++; } | |
11 } | |
12 if (ans==1e8) cout<<0; | |
13 else cout<<ans; | |
14 return 0; | |
15 } |
# 题目:日志统计
(1) 定义 i 指针,它对应主循环,遍历随时间而失效的所有帖子。
(2) 定义 j 指针,其作用是在时刻 i = T,把 [T−D, T] 之前的帖子都置为无效
总复杂度也是 O (nlogn)。
#include<bits/stdc++.h> | |
using namespace std; | |
const int N = 1e5+50; | |
int num[N]; //num [i]:记录 id=i 的帖子的 “赞” 的数量 | |
int flag[N]; //flag [i]:id=i 的帖子曾是热帖 | |
struct post{int id, ts;} p[N]; // 记录帖子 | |
int cmp(post x, post y) { | |
return x.ts < y.ts; | |
} | |
int main(){ | |
int n, d, k; | |
cin >> n >> d >> k; | |
for(int i = 0; i < n; i++) { | |
scanf("%d%d", &p[i].ts, &p[i].id); | |
} | |
sort(p, p + n, cmp); // 按时间从小到大排序 | |
for(int i = 0, j = 0; i < n; i++){ | |
num[p[i].id]++; | |
while(p[i].ts - p[j].ts >= d){ | |
num[p[j].id]--; // 随着时间流逝,d 之前的每个帖子的点赞数都减 1 | |
j++; | |
} | |
if(num[p[i].id] >= k) { | |
flag[p[i].id] = 1; // 在 [i-d,i] 上达到 k 个 “赞” | |
} | |
} | |
for(int i = 0; i < N; i++) { | |
if(flag[i] == 1) { | |
printf("%d\n", i); | |
} | |
} | |
return 0; | |
} |
# 二分
二分法可以把一个长度为 n 的有序序列上 O (n) 的查找时间优化到 O (logn)。
二分法的前提:序列是有序的,按从小到大或从大到小排序。
# 二分模板
1 #include<bits/stdc++.h> | |
2 using namespace std; | |
3 int a[1000]; | |
4 int bin_search(int *a, int n, int x){ // 在数组 a 中查找数字 x,返回位置 | |
5 int left = 0, right = n; | |
6 while (left < right) { | |
7 int mid = left+(right-left)/2; | |
8 if (a[mid] >= x) right = mid; | |
9 else left = mid+1; | |
10 cout<<a[mid]<< “ “; // 输出猜数的过程 | |
11 } | |
12 return left; | |
13 } | |
14 int main(){ | |
15 int n = 100; | |
16 for(int i=0;i<n;i++) a[i]=i+1; // 赋值,数字 1~100 | |
17 int test = 54; // 猜 54 这个数 | |
18 int pos = bin_search(a,n,test); | |
19 cout<<”\n”<<”test=”<<a[pos]; | |
20 } |
# 整数二分
# 在单调递增序列中查找 x 或者 x 的后继
在单调递增数列 a [] 中查找某个数 x,如果数列中没有 x,则查找比它大的下一个数
1 #include<bits/stdc++.h> | |
2 using namespace std; | |
3 int a[1000]; | |
4 int bin_search(int *a, int n, int x){ //a [0]~a [n-1] 是单调递增的 | |
5 int left = 0, right = n; // 注意:不是 n-1,因为此时是左闭右开的区间 [0,n) | |
6 while (left < right) { | |
7 int mid = left + (right-left)/2; //int mid = (left + right) >> 1; | |
8 if (a[mid] >= x) right = mid; | |
9 else left = mid + 1; | |
10 } // 终止于 left = right | |
11 return left; | |
12 } | |
13 int main(){ | |
14 int n = 100; | |
15 for(int i=0;i<n;i++) a[i]=2*i+2; // 赋值,数字 2~200,偶数 | |
16 int test = 55; // 查找 55 或 55 的后继 | |
17 int pos = bin_search(a,n,test); | |
18 cout<<”test=”<<a[pos]; | |
19 } |
# 在单调递增序列中查找 x 或者 x 的前驱
1 #include<bits/stdc++.h> | |
2 using namespace std; | |
3 int a[1000]; | |
4 int bin_search2(int *a, int n, int x){ //a [0]~a [n-1] 是单调递增的 | |
5 int left = 0, right = n; | |
6 while (left < right) { | |
7 int mid = left + (right-left + 1)/2 ; | |
8 if (a[mid] <= x) left = mid; | |
9 else right = mid - 1; | |
10 } // 终止于 left = right | |
11 return left; | |
12 } | |
13 int main(){ | |
14 int n = 100; | |
15 for(int i=0;i<n;i++) a[i]=2*i+2; // 赋值,数字 2~200, 偶数 | |
16 int test = 55; // 查找 55 或 55 的前驱 | |
17 int pos = bin_search2(a,n,test); | |
18 cout<<”test=”<<a[pos]; | |
19 } |
# 题目:巧克力
一个一个试边长 d 太慢了,现在使用二分法
用暴力法需要执行 d 次 check (),用二分法只需要执行 O (logd) 次 check ()
#include<bits/stdc++.h> | |
using namespace std; | |
int n, k; | |
const int N = 100010; | |
int h[N], w[N]; | |
bool check(int d){ | |
int num = 0; | |
for(int i=0; i<n; i++) | |
num += (h[i] / d) * (w[i] / d); | |
if(num >= k) | |
return true; // 够分 | |
else | |
return false; // 不够分 | |
} | |
int main(){ | |
cin >> n >> k; | |
for(int i=0; i<n; i++) | |
cin >> h[i] >> w[i]; | |
int L = 1, R = N; //R 的初值是 100010 | |
// 第一种写法: | |
while(L < R) { | |
int mid = (L + R + 1) >> 1; // 除以 2,向右取整 | |
if(check(mid)) | |
L = mid; // 新的搜索区间是右半部分,R 不变,调整 L=mid | |
else | |
R = mid - 1; // 新的搜索区间是左半部分,L 不变,调整 R=mid–1 | |
} | |
cout << L; | |
// 第二种写法: | |
/* while (L<R) { | |
int mid=(L+R)>>1; // 除以 2,向左取整 | |
if (check (mid)) L=mid+1; // 新的搜索区间是右半部分,R 不变,更新 L=mid+1 | |
else R=mid; // 新的搜索区间是左半部分,L 不变,更新 R=mid | |
} | |
cout << L-1; */ | |
return 0; | |
} |
# 跳石头 (最小值最大化)
这是一道二分法应用的套路题:“最小值最大化”。类似的套路题还有 “最大值最小化”。
在 n 块岩石中移走 m 块岩石,有很多种移动方法。在第 i 种移动方法中,剩下的石头之间的距离,有一个最小距离 ai。所有移动方法的最小距离 ai 中,问最大的 ai 是多少。在所有可能的最小值中查找最大的那个值,就是 “最小值最大化”
不去找搬走岩石的各种组合,而是给出一个距离 d,检查能不能搬走 m 块岩石而得到最短距离 d。把所有的 d 都试一遍,肯定能找到一个最短的 d。用二分法找这个 d 即可。
# 题目:
# 题目:
# 实数二分
1 const double eps = 1e-7; // 精度。如果用 for 循环,可以不要 eps | |
2 while(right - left > eps){ //for(int i = 0; i<100; i++){ | |
3 double mid = left+(right-left)/2; | |
4 if (check(mid)) right = mid; // 判定,然后继续二分 | |
5 else left = mid; | |
6 } |
(1) 使用 while 循环的实数二分。第 1 行的极小数字 eps 用于判断 [left, right] 是不是足够小,如果 left 和 right 相差小于 eps,则认为 left 和 right 是相等的。eps 需要根据题目来设定。eps 实际上决定了二分的次数。例如区间长度为 1,要达到 eps = 0.001 的精度,做 10 次二分就能实现 (1/210 = 0.001)。第 2 行用 while 循环判断 eps,过小的 eps 会导致超时,过大的 eps 会导致出错。
(2) 使用 for 循环的实数二分。其实不用 eps 也行,直接进行足够多的二分就好了。第 2 行用 for 循环实现,二分 100 次,最后的 [left, right] 长度是初始区间长度的 1/2100,得到的精度比任何 eps 都小,right 与 left 的差值已经小得不能再小了。一般循环 100 次,不过有时候因为题目的逻辑比较复杂,一次 for 循环内部的计算量很大,所以较大的 for 循环次数会导致超时,此时应把 100 次减少到 50 次甚至更少。
# 题目:一元三次方程求解
根与根之差的绝对值大于或等于 1。那么在每个 [i, i+1] 的小区间内做二分查找就行了
#include <bits/stdc++.h> | |
using namespace std; | |
double a, b, c, d; | |
double y(double x){ | |
return a * x * x * x + b * x * x + c * x + d; | |
} | |
int main(){ | |
scanf("%lf%lf%lf%lf", &a, &b, &c, &d); // 输入 | |
for (int i = -100; i < 100; i++){ // 题目说 “根与根之差的绝对值大于或等于 1”,分为 200 个小区间 | |
double left = i, right = i + 1; | |
double y1 = y(left), y2 = y(right); | |
if(y1 == 0) | |
printf("%.2lf ", left); // 判断左端点。一个小坑点,容易被忽略 | |
if(y1 * y2 < 0){ // 小区间内有根 | |
for(int j = 0; j < 100; j++){ // 在小区间内二分 | |
double mid = (left + right) / 2; | |
if(y(mid) * y(right) <= 0) | |
left = mid; | |
else | |
right = mid; | |
} | |
printf("%.2lf ", right); | |
} | |
} | |
return 0; | |
} |
# 倍增法和 ST 算法
倍增法和二分法是 “相反” 的算法。二分法每次缩小为上一次的 1/2,倍增法每次扩大一倍,两者都以 2 的指数减少或增加,效率极高。
倍增法是扩大区间,例如在大区间上求解和区间查询有关的问题,求区间最大值或最小值。
✧ 提示:倍增法也有局限性,就是需要提前计算出第 1、2、4……2k 个跳板,这要求数据是静态不变的,而不是动态变化的。如果数据发生了变化,那么所有跳板都得重新计算。如果要反复重新计算跳板,倍增法就失去了意义。
# 暴力解决区间问题
静态空间的 RMQ:给定长度为 n 的静态数列,做 m 次询问,每次给定 L, R≤n,查询 [L, R] 内的最值。
1 #include <bits/stdc++.h> | |
2 using namespace std; | |
3 int main(){ | |
4 int a[7] = {5,7,1,2,3,6,9}; | |
5 int id = min_element(a+2, a+5) - a; // 注意,区间 [a+2, a+5) 左闭右开,不包括 a+5 | |
6 cout<<a[id]<< “\n”; // 输出最小值 1 | |
7 id = max_element(a+2, a+5) - a; | |
8 cout<<a[id]<< “\n”; // 输出最大值 3 | |
9 int s = accumulate(a+2, a+5,0); // 最后的 0 表示从 0 开始累加 | |
10 cout<<s<<”\n”; // 输出区间和 6 | |
11 return 0; | |
12 } |
# ST 算法
ST 算法是求解 RMQ 的高效算法,适用于静态空间的 RMQ 查询。ST 算法做一次区间查询的复杂度是 O (1),m 次查询的总复杂度只有 O (m)。
ST 算法基于一个简单原理:一个大区间若能被两个小区间覆盖,则大区间的最值可以用两个小区间的最值计算出来。
1 #include <bits/stdc++.h> | |
2 using namespace std; | |
3 const int N = 500001; | |
4 int n,m; | |
5 int a[N],dp[N][40]; | |
6 void st_init(){ | |
7 for(int i=1;i<=n;i++) dp[i][0] = a[i]; // 初始化区间长度为 1 时的值 | |
8 int p = (int)(log(double(n)) / log(2.0)); //int p=log2 (n); // 此处有两种写法 | |
9 for(int k=1;k<=p;k++) // 用 dp [] 递推计算区间 | |
10 for(int s=1;s+(1<<k)<=n+1;s++) | |
11 dp[s][k]=max(dp[s][k−1], dp[s+(1<<(k-1))][k-1]); // 最大值 | |
12 } | |
13 int st_query(int L,int R){ | |
14 int k = (int)(log(double(R-L+1)) / log(2.0)); //int k = log2 (R-L+1); // 两种方法求 k | |
15 return max(dp[L][k],dp[R-(1<<k)+1][k]); // 最大值 | |
16 } | |
17 int main(){ | |
18 scanf(“%d%d”,&n,&m); | |
19 for(int i=1;i<=n;i++) scanf(“%d”,&a[i]); | |
20 st_init(); | |
21 for(int i=1;i<=m;i++){ | |
22 int L,R; scanf(“%d%d”,&L,&R); | |
23 printf(“%d\n”,st_query(L,R)); | |
24 } | |
25 return 0; | |
26 } |
# 前缀和
一个长度为 n 的数组 a [0]~a [n-1],它的前缀和 sum [i] 等于 a [0] ~a [i] 的和。
利用递推,只需 n 次就能计算出所有的前缀和:sum [i] = sum [i−1] + a [i]。也能用 sum [] 反推计算出 a []:a [i] = sum [i]−sum [i−1]。
快速计算出数组中任意一个区间 a [i]~a [j] 的和:a [i] + a [i+1] + … + a [j−1] + a [j] = sum [j]− sum [i−1]。
这个式子的左边用暴力法计算,计算复杂度是 O (n),右边用前缀和计算,计算复杂度是 O (1)。这就是前缀和的优势。
# 题目:矩阵
#include<bits/stdc++.h> | |
using namespace std; | |
int s[550][550]; | |
int main(){ | |
int n,m,k; cin>>n>>m>>k; | |
for(int i=1;i<=n;i++) | |
for(int j=1;j<=m;j++) { | |
int a; scanf("%d",&a); | |
s[i][j] = s[i-1][j] + a; //s [i][j]:第 j 列上,第 0~i 行数字的前缀和 | |
} | |
long long ans=0; | |
for(int i1=1;i1<=n;i1++) | |
for(int i2=i1;i2<=n;i2++) | |
for(int j1=1,j2=1,z=0;j2<=m;j2++){ // 尺取法,滑动窗口为 [j1,j2]。移动指针 j2 | |
z += s[i2][j2]-s[i1-1][j2]; // 第 j2 列上,i1、i2 的区间和。累加得到二维区间和 | |
while(z>k){ // 若区间和 > k,则移动指针 j1 | |
z -= s[i2][j1]-s[i1-1][j1]; | |
j1 += 1; | |
} | |
ans += j2-j1+1; // 若 j1、j2 的区间和 < k,则这里面有 j2-j1+1 个满足条件 | |
} | |
cout << ans; | |
return 0; | |
} |
# 贪心
✧ 提示:当一道题目的解题过程是从局部推算到全局时,若使用贪心算法不能求得最优解,则一般能用 DP 求得最优解。任意面值的最少硬币支付问题,其正解是 DP。
如何判断一道题目能否用贪心算法求解?要用贪心算法求解的问题,需要满足以下特征。
(1) 最优子结构性质。当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质,也称此问题满足最优性原理。也就是说,从局部最优能扩展到全局最优。
(2) 贪心选择性质。问题的整体最优解可以通过一系列局部最优的选择来得到。