#include<bits/stdc++.h>
usingnamespace std;
int n,q,k;
constint N =100010;
int a[N];
intmain()
{
scanf("%d%d",&n,&q);
for(int i =0; i < n; i ++){
scanf("%d",&a[i]);
}
while(q --){
scanf("%d",&k);
int l =0,r = n -1;
//lower方式获取下区间边界
while(l < r){
int mid = l + r >>1;
if(a[mid] >= k){
r = mid;
}
else{
l = mid+1;
}
}
//找不到的情况
if(a[l] != k) printf("-1 -1\n");
else{//upper方式获取上区间边界
printf("%d ",l);
l =0, r = n-1;
while(l < r){
int mid = l + r +1>>1;
if(a[mid] <= k){
l = mid;
}
else{
r = mid -1;
}
}
printf("%d\n",l);
}
}
return0;
}
//关于二分边界问题——此路不通走彼路
//r=mid不行就换成l=mid试一下
#include<bits/stdc++.h>usingnamespace std;
constint N =100010;
int n,k;
int pos =0;
structCake{
int h;
int w;
}cake[N];
boolcheck(int mid){
int cnt =0;
for(int i =0; i < pos; i ++){
int h = cake[i].h, w = cake[i].w;
if(mid > h || mid > w) continue;
int hnum = h / mid;
int wnum = w / mid;
cnt += hnum*wnum;
if(cnt >= k) returntrue;
}
returnfalse;
}
intmain()
{
scanf("%d%d",&n,&k);
int maxsize =0;
for(int i =0; i < n; i ++){
int h,w;
scanf("%d%d",&h,&w);
cake[pos ++] = {h,w};
maxsize = max(maxsize,h);
maxsize = max(maxsize,w);
}
int l =0, r = maxsize;
while(l < r){
int mid = l + r +1>>1;
if(check(mid)) l = mid;
else r = mid -1;
}
printf("%d\n",l);
return0;
}
// Forward declaration of compare API.
// bool compare(int a, int b);
// return bool means whether a is less than b.
//**这道题目其实就可以转化为对于一个已排好序多数组,插入元素**
//**选择合适的位置插入元素,这里选择位置时,可以使用二分查找算法**
classSolution {
public: vector<int> specialSort(int N) {
scanf("%d", &N);
vector <int> ans;
ans.push_back(1); //插入二分,先把1插进去,这样其他数就能和1进行比较了
for(int i =2 ; i <= N ; i ++)
{
int l =0 , r = ans.size(); //在ans中进行二分
while(l < r)
{
int mid = (l + r) >>1; //大于mid 的 第一个数
if(compare(i,ans[mid])) //
r = mid;
else l = mid +1;
}
ans.insert(ans.begin() + l, i);//然后在给定位置插入i
}
return ans;
}
};
双指针算法
1
2
3
4
5
6
7
8
9
10
11
实际上将原来的多重循环,使用双指针的方式在一个循环内完成for (int i =0, j =0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
核心思想--->将穷举O(n^2)算法优化到O(n)
常见问题分类: (1) 对于一个序列,用两个指针维护一段区间 (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
#include<bits/stdc++.h>usingnamespace std;
constint N =100010;
int a[N],n;
int x[N];
intmain()
{
scanf("%d",&n);
for(int i =0; i < n; i ++) scanf("%d",&a[i]);
int ans =0;
// --j------------i--,考虑i为右端点去思考j的情况,双指针算法常用
//特别经典,一定要熟透于心,很多时候都要想到,固定一个端点
for(int i =0, j =0; i < n; i ++){
x[a[i]] ++;
int res =0;
while(j < i && x[a[i]] >1){ // 经典咏流传, 这里j < i和x[a[i]] > 1都很经典
x[a[j]] --;
j++;
}
res = i - j +1;
ans = max(ans,res);
}
printf("%d\n",ans);
return0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//两个序列,一个从前往后,一个从后往前寻找两个序列的下标,使得a[i] + b[j] = k的i,j值
//注意,这里成立的前提条件是a,b是升序序列
intmain()
{
scanf("%d%d%lld",&n,&m,&x);
for(int i =0;i < n; i ++) scanf("%d",&a[i]);
for(int i =0;i < m; i ++) scanf("%d",&b[i]);
for(int i =0, j = m -1; i < n; i ++){
while(a[i] + b[j] > x) j --; //与暴力的区别,j不会回退
if(a[i] + b[j] == x) printf("%d %d\n",i,j);
}
return0;
}
intmain()
{
scanf("%d%d%d", &n, &d, &k);
for (int i =0; i < n; i ++ ) scanf("%d%d", &logs[i].x, &logs[i].y);
sort(logs, logs + n); //先给时间排个序!
//这一步跟上面经典的双指针算法如出一辙,变了一点点,一定要灵活应对。
for (int i =0, j =0; i < n; i ++ )//将j---i,这里i为右端点进行枚举
{
int id = logs[i].y;
cnt[id] ++ ; //也是一样记录当前元素的个数
while (logs[i].x - logs[j].x >= d) //给定两端点的枚举条件
{
cnt[logs[j].y] -- ; //当j需要移动的情况
j ++ ; //现在移动j左端点指针
}
if (cnt[id] >= k) st[id] =true; //当满足题目所给条件时进行输出
}
for (int i =0; i <=100000; i ++ )
if (st[i])
printf("%d\n", i);
return0;
}
高精度
高精度总结
下面函数中vector 取引用 &A ,&B 目的是为了节省内存空间。只取引用。不单独开辟空间!
大整数相加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//大整数相加模板
vector<int> add (vector<int>&A, vector<int>&B)
{
if (A.size() < B.size()) return add(B,A);
vector<int> C;
int t =0; //t表示进位数
for (int i =0;i < A.size() || i < B.size();i++)
{
if (i < A.size()) t = t + A[i];
if (i < B.size()) t = t + B[i];
C.push_back(t%10); //只存进位数的个位
t = t/10; //如果进位大于10的话,进到下一位
}
if (t) C.push_back(t);
return C;
}
大整数相减
易错点:
字符串转整型 善用ASCII码
该模板一定要先判断是否A>=B!! (要注意负数可能存在的情况!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//大整数的减法模板
vector<int> sub (vector<int>&A,vector<int>&B)
{
vector<int> C;
for(int i =0, t =0;i < A.size(); i ++)
{
t = A[i] - t;
if (i < B.size()) t = t - B[i];
C.push_back((t +10) %10);
if (t <0) t =1; //说明借了一位
else t =0; //说明没有借
}
while (C.size() >1&& C.back() ==0) C.pop_back(); //把最后的 0 的弄出去
return C;
}
大整数 乘以 小整数 A*b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//把 b 看成一个整体去和A相乘
string A;
int b;
vector<int> mu;
vector<int> mul(vector<int>&A, int b)
{
vector<int> C;
int t =0;
for (int i =0;i < A.size() || t;i ++)
{
if (i < A.size()) t = t + A[i] * b;
C.push_back(t %10);
t = t /10;
}
while (C.size() >1&& C.back ==0) C.pop_back();
return C;
}
//A / b = C (商) ....r为余数, A >= 0, b > 0
#include<algorithm> ----- reverse
vector<int> div(vector<int>&A, int b, int&r)
{
vector<int> C;
r =0;
for (int i = A.size() -1; i >=0; i --)
{
r = r *10+ A[i];
C.push_back(r/b);
r %= b;
}
reverse(C.begin(), C.end()); //除法是从高位开始计算的,但为了通用(+ - x)起见,一律逆序存数。最后输出的时候需要倒过来。
while (C.size() >1&& C.back() ==0) C.pop_back(); //删除前导0
return C;
}
#include<iostream>
#include<queue>
#include<unordered_map>
usingnamespace std;
int t;
//当前状态,当前到最终状态所需步数
unordered_map<int, int>vis;
//改这个灯及其上下左右相邻的灯的状态
//改第idx个灯;左,不为最左一个;上,不为第一排;下,不为最后一排;右,不为右一个
intturn(int st, int idx) { //这里用位运算压缩的方式存放一种状态
st ^= (1<< idx);
if (idx %5) st ^=1<< idx -1;
if (idx >=5) st ^=1<< idx -5;
if (idx <20) st ^=1<< idx +5;
if ((idx %5) <4) st ^=1<< idx +1;
return st;
}
//从最终状态逆序遍历,遍历所有的状态,所以不用管地图什么样,直接bfs完,查对应map就完事了
voidbfs() {
//0-2^25-1(25个1),共2^25种状态
int st = (1<<25) -1;//左移 右移的优先级是最低的,比加减还要低。所以这里的括号是必需的
queue<int>q;
q.push(st);
while (q.size())
{
int t = q.front();
q.pop();
if (vis[t] ==6) break;//判断6步以内使所有的灯都变亮。
for (int i =0; i <25; i++) {//尝试当前状态的每盏灯
st = turn(t, i);//新的状态
if (!vis.count(st)) {//该状态未被遍历过
vis[st] = vis[t] +1;
q.push(st);
}
}
}
}
intmain() {
bfs();//用map+深搜的方式存下所有可能的情况以及对应的解决方法
cin >> t;
while (t--)
{
int sum =0;
for (int i =0; i <25; i++) {
char ch;
cin >> ch;
sum += ((ch -'0') << i);//25个字符二进制压缩成数字
}
if (vis[sum] ==0&& sum != (1<<25) -1) cout <<-1<< endl;
else cout << vis[sum] << endl;
}
return0;
}
#include<iostream>#include<cstring>#include<algorithm>#include<unordered_map>#include<queue>usingnamespace std;
constint N =40;
int n;
int postorder[N], inorder[N]; //前序遍历,中序遍历
unordered_map<int, int> l, r, pos; //用哈希表模拟二叉树
//il ir中序遍历区间; pl pr后序遍历区间
intbuild(int il, int ir, int pl, int pr)
{
int root = postorder[pr];
int k = pos[root]; //得到根节点在中序遍历中的下标
//k大于il表示根节点左边还有节点,即当前根节点存在左子树,下同
//下面两行是难点,举例解释见图
if (il < k) l[root] = build(il, k -1, pl, pl + k -1- il);
if (ir > k) r[root] = build(k +1, ir, pl + k - il, pr -1);
return root;
}
voidbfs(int root) //BFS用来层序遍历输出
{
queue<int> q;
q.push(root);
while (q.size())
{
auto t = q.front();
q.pop();
cout << t <<' ';
if (l.count(t)) q.push(l[t]); //判断该节点的左右儿子是否存在
if (r.count(t)) q.push(r[t]); //存在则加入队列,等待下一层遍历
}
}
intmain()
{
cin >> n;
for (int i =0; i < n; i ++ ) cin >> postorder[i]; //输入后序遍历树
for (int i =0; i < n; i ++ )
{
cin >> inorder[i]; //输入中序遍历树
pos[inorder[i]] = i; //记录中序遍历每个点位置(剪枝)
}
int root = build(0, n -1, 0, n -1); //参数为中序遍历区间和后序遍历区间
bfs(root);
return0;
}
for(int i =1, j; i <= n; i=j+1)
{
j = n/(n/i);
ans += (n/i)*(j-i+1);
}
试除法求所有约数
1
2
3
4
5
6
7
8
9
10
11
12
vector<int> get_divisors(int x)
{
vector<int> res;
for (int i =1; i <= x / i; i ++ )
if (x % i ==0)
{
res.push_back(i);
if (i != x / i) res.push_back(x / i);//如果n是i的平方,可能有两个,这里目的是只存下一个
}
sort(res.begin(), res.end());
return res;
}
#include<iostream>#include<cstring>#include<algorithm>usingnamespace std;
typedeflonglong LL;
constint N =1010, MOD =998244353;
int n, m, A, B;
int w[N][N];
int rmax[N][N], rmin[N][N];
int q[N];
voidget_max(int a[], int b[], int tot, int k)//滑动窗口求最大值模版
{
int hh =0, tt =-1;
for (int i =0; i < tot; i ++ )
{
if (hh <= tt && q[hh] <= i - k) hh ++ ;
while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
q[ ++ tt] = i;
b[i] = a[q[hh]];
}
}
voidget_min(int a[], int b[], int tot, int k) //滑动窗口求最小值模版
{
int hh =0, tt =-1;
for (int i =0; i < tot; i ++ )
{
if (hh <= tt && q[hh] <= i - k) hh ++ ;
while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
q[ ++ tt] = i;
b[i] = a[q[hh]];
}
}
intmain()
{
scanf("%d%d%d%d", &n, &m, &A, &B);
for (int i =0; i < n; i ++ )
for (int j =0; j < m; j ++ )
scanf("%d", &w[i][j]);
for (int i =0; i < n; i ++ )
{
get_max(w[i], rmax[i], m, B);
get_min(w[i], rmin[i], m, B);
}
int res =0;
int a[N], b[N], c[N];
for (int i = B -1; i < m; i ++ )
{
for (int j =0; j < n; j ++ ) a[j] = rmax[j][i];
get_max(a, b, n, A);
for (int j =0; j < n; j ++ ) a[j] = rmin[j][i];
get_min(a, c, n, A);
for (int j = A -1; j < n; j ++ )
res = (res + (LL)b[j] * c[j]) % MOD;
}
printf("%d\n", res);
return0;
}
int trie[SIZE][26], tot =1; //初始化,假设字符串由小写字母构成
voidinsert(char*str)
{//插入一个字符串
int len = strlen(str), p =1;
for(int k =0; k < len; k ++)
{
int ch = str[k] -'a';
if(trie[p][ch] ==0) trie[p][ch] =++tot;
p = trie[p][ch];
}
end[p] =true;
}
boolsearch(char*str)
{//检索字符串是否存在
int len = strlen(str), p =1;
for(int k =0; k < len; k ++)
{
p = trie[p][str[k] -'a'];
if(p ==0) returnfalse;
}
returntrue;
}
int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点 <---
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
// 插入一个字符串
voidinsert(char*str)
{
int p =0;
for (int i =0; str[i]; i ++ )
{
int u = str[i] -'a';
if (!son[p][u]) son[p][u] =++ idx;
p = son[p][u];
}
cnt[p] ++ ;
}
// 查询字符串出现的次数
intquery(char*str)
{
int p =0;
for (int i =0; str[i]; i ++ )
{
int u = str[i] -'a';
if (!son[p][u]) return0;
p = son[p][u];
}
return cnt[p];
}
#include<bits/stdc++.h>usingnamespace std;
constint N =3100010;
int trie[N][2],n,m;
int s[N],idx; //idx索引编号
int cnt[N];//cnt[n]的作用是标记滑动窗口内0,1的数量
voidinsert(int x, int v){
int p =0;
for(int i =30; ~i; i --){
int t = x >> i &1;
if(!trie[p][t]) trie[p][t] =++idx;
p = trie[p][t];
cnt[p] += v;//⭐️:v表示有多少个节点可以到达此处
}
}
intquery(int x){
int p =0;
int res = x;
for(int i =30; ~i; i --){
int t = x >> i &1;
if(cnt[trie[p][!t]] >0){//当x对面的那个数!x存在时(0,1),另外一个分支⭐️
t = (t +1) %2;//x就变成另外一个数 !x
}
res ^= t << i;
p = trie[p][t];
}
return res;
}
intmain()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin >> n >> m;
for(int i =1; i <= n; i ++){
cin >> s[i];
s[i] = s[i -1] ^ s[i];
}
int ans =0;
insert(0,1); //先插入一个0
for(int i =1;i <= n;i ++){
if(i > m) insert(s[i - m -1], -1);//将滑动窗口外的数除去,这时就要修改cnt,故-1
ans = max(ans,query(s[i]));//在滑动窗口内求最大值
insert(s[i], 1);//求完后记得插入该值,方便后面的值进行异或
}
cout << ans <<"\n";
return0;
}
BFS广度优先遍历
bfs模版:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
queue<int> q;
st[1] =true; // 表示1号点已经被遍历过
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();
for (int i = h[t]; i !=-1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] =true; // 表示点j已经被遍历过
q.push(j);
}
}
}
#include<bits/stdc++.h>usingnamespace std;
constint N =1100;
int h[N],ne[N*100],e[N*100],idx;
int vis[N];
int n,l,k;
voidadd(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
intbfs(int x){
queue<int> q;
q.push(x);
memset(vis,0,sizeof vis);
vis[x] =1;
int res =0;
for(int u =0; u < l; u ++){//限制遍历层数
int sz = q.size(); //每一层的节点
while(sz --){
auto t = q.front();
q.pop();
for(int i = h[t]; ~i; i = ne[i]){
int j = e[i];
if(!vis[j]){
q.push(j);
vis[j] =1;
res ++;
}
}
}
}
return res;
}
intmain()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin >> n >> l;
memset(h,-1,sizeof h);
for(int i =1; i <= n; i ++){
int m;
cin >> m;
while(m --){
int a;
cin >> a;
add(a,i);
}
}
cin >> k;
while(k --){
int x;
cin >> x;
cout << bfs(x) <<"\n";
}
return0;
}
DFS深度优先遍历
dfs模版:
1
2
3
4
5
6
7
8
9
10
int dfs(int u)
{
st[u] = true; // st[u] 表示点u已经被遍历过
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j]) dfs(j);
}
}
int n, m;
int w[N];
int sum[N];
int ans = N; //注意首先就是,每只猫都安排一辆车,此时ans最大
voiddfs(int u, int k)
{
// 最优性剪枝
if (k >= ans) return;
if (u == n)
{
ans = k;
return;
}
for (int i =0; i < k; i ++ )
if (sum[i] + w[u] <= m) // 可行性剪枝
{
sum[i] += w[u];
dfs(u +1, k);
sum[i] -= w[u]; // 恢复现场
}
// 新开一辆车
sum[k] = w[u];
dfs(u +1, k +1);
sum[k] =0; // 恢复现场
}
intmain()
{
cin >> n >> m;
for (int i =0; i < n; i ++ ) cin >> w[i];
// 优化搜索顺序
sort(w, w + n);
reverse(w, w + n);
dfs(0, 0);
cout << ans << endl;
return0;
}
1209.带分数(强化版全排列)
很有意思的题
运用到了dfs的全排列;
将除法运算转化为了乘法运算;
将全排列分成了三个部分进行分析计算;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//stl函数的方法
do {
for (int i =0; i <9; i++) {
for (int j = i +1; j <9; j++) {
int a = calc(0, i);
int b = calc(i +1, j);
int c = calc(j +1, 8);
if (a ==0|| b ==0|| c ==0) {
continue;
}
if (a * c + b == c * target) {
++res;
}
}
}
// 调用函数生成全排列
} while (next_permutation(num, num +9));
//全排列升级版
#include<bits/stdc++.h>
usingnamespace std;
constint N =110;
int num[N],vis[N];
int ans,n;
intcalnum(int l, int r){
int res =0;
for(int i = l; i <= r; i ++){
res = res*10+num[i];
}
return res;
}
voiddfs(int u){
//递归出口
if(u ==9){
//这里我们应该将原问题分成三个部分
for(int i =0; i <7; i ++){
for(int j = i +1; j <8; j ++){
int a = calnum(0,i);
int b = calnum(i+1,j);
int c = calnum(j+1,8);
//将除法转化为乘法⭐️
if(n*c == a*c+b) ans++;
}
}
return;
}
//全排列的递归搜索
for(int i =1; i <=9; i ++){
if(!vis[i]){
num[u] = i;
vis[i] =1;
dfs(u+1);
num[u] =0;
vis[i] =0;
}
}
}
intmain()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin >> n;
dfs(0);
cout << ans <<"\n";
return0;
}
搜索与图论概述
1
2
3
4
5
6
7
8
9
10
11
graph LR
A[最短路问题````] --> B[单源最短路````]
A --> C[多源最短路````]
B --> D[从一个点到另外一个点````]
C --> E[从一个起点到终点```````]
D --> F[所有边权值为正数```````] --> G[朴素的Dijkstra算法On2`````````]
F --> H[堆优化的Dijkstra算法Omlogn```````````]
D --> I[存在一些负权边``````]
I --> J[Bellman-Ford算法 Onm``````````]
I --> K[SPFA算法 Om->Onm`````````]
E --> M[Floyd算法 On3``````]
int d[N]; //存放每个点的入度 ,注意输入时候需要给入度自增
//邻接表完成算法,头插法可以使用y总的模板,尾插法可以使用 vector<int> Gt[N]; 直接push_back可以在尾部增加结点。
booltopsort()
{
int hh =0, tt =-1;//数组模拟队列
// d[i] 存储点i的入度
for (int i =1; i <= n; i ++ )
if (!d[i])
q[ ++ tt] = i; //存入队列为0的点
while (hh <= tt) //队列不为空
{
int t = q[hh ++ ]; //获取队头元素,并出队操作
for (int i = h[t]; i !=-1; i = ne[i]) //第i个单链表往后查找
{
int j = e[i];
if (-- d[j] ==0) //如果j的入度也为0了,则也将j入队
q[ ++ tt] = j;
}
}
// 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
return tt == n -1;
}
int n, m; // n表示点数,m表示边数
int dist[N]; // dist[x]存储1到x的最短路距离
structEdge// 边,a表示出点,b表示入点,w表示边的权重
{
int a, b, w;
}edges[M];
// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
intbellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] =0;
// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
for (int i =0; i < n; i ++ )
{
for (int j =0; j < m; j ++ )
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
if (dist[b] > dist[a] + w)
dist[b] = dist[a] + w;
}
}
if (dist[n] >0x3f3f3f3f/2) return-1;
return dist[n];
}
初始化:for (int i =1; i <= n; i ++ )
for (int j =1; j <= n; j ++ )
if (i == j) d[i][j] =0;
else d[i][j] = INF;
// 算法结束后,d[a][b]表示a到b的最短距离
voidfloyd()
{
for (int k =1; k <= n; k ++ )
for (int i =1; i <= n; i ++ )
for (int j =1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
int n, m; // n是点数,m是边数
int p[N]; // 并查集的父节点数组
structEdge// 存储边
{
int a, b, w;
booloperator< (const Edge &W)const {
return w < W.w;
}
}edges[M];
//只要存储所有边就好了,故此没必要用很复杂的数据结构
intfind(int x) // 并查集核心操作
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
intkruskal()
{
sort(edges, edges + m);
for (int i =1; i <= n; i ++ ) p[i] = i; // 初始化并查集
int res =0, cnt =0;
for (int i =0; i < m; i ++ )
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if (a != b) // 如果两个连通块不连通,则将这两个连通块合并
{
p[a] = b;
res += w;
cnt ++ ; //存放的是最小生成树中的边的数目
}
}
if (cnt < n -1) return INF;
return res;
}
//下面程序可以求出一张无向图中的所有的桥。
//特别注意:因为遍历的是无向图,故此从每个子节点出发都一定能访问到其父节点fa。根据low的计算方法,(x,fa)属于搜索树上的边,且fa不是x的子节点,故不能用fa的时间戳来更新low[x]
//但是这样如果只记录每个结点的父结点,会出现无法处理重边的情况——当x与fa之间有多条边时,(x,fa)一定不是桥。在这些重复计算的边中,只有一条算是“搜索树上的边”,其他的几条都不算。故有重边时,dfn[fa]能用来更新low[x]
//巧妙的处理方法是:改为记录“递归进入每个结点的边的编号”。编号可以认为是边在邻接表中存储的下标位置。将每一条无向边看作是双向的有向边。
constint SIZE =100010;
int h[SIZE],ver[SIZE*2],next[SIZE*2];
int dfn[SIZE],low[SIZE],n,m,tot,num;
bool bridge[SIZE*2];
voidadd(int x, int y)
{
ver[++tot] = y;
next[tot] = head[x];
head[x] = tot;
}//类似于头插法
// x 代表当前搜索树的根节点,in_edge 代表其对应的序号(tot)
voidtarjan(int x, int in_edge)
{
// 在搜索之前,先初始化节点 x 的时间戳与追溯值
dfx[x] = low[x] =++num;
// 通过 head 变量获取节点 x 的直接连接的第一个相邻节点的序号
// 通过 Next 变量,迭代获取剩下的与节点 x 直接连接的节点的序号
for(int i = head[x]; i; i = next[i])
{
// 此时,i 代表节点 y 的序号
int y = ver[i];
// 如果当前节点 y 没有被访问过
if(!dfn[y])
{
// 递归搜索以 y 为根的子树
tarjan(y,i);
// 计算 x 的追溯值
low[x] = min(low[x],low[y]);
// 桥的判定法则
if(low[y] > dfn[x])
{
bridge[i] = bridge[i^1] =true;// 标记当前节点是否为桥
}
}
elseif (i != (in_edge ^1 )) // 当前节点被访问过,且 y 不是 x 的“父节点”
{
low[x] = min (low[x],dfn[y]);
}
}
}
intmain(){
cin >> n >> m;
tot =1;
for(int i =1; i <= m; i ++)
{
int x,y;
cin >> x >> y;
add(x,y),add(y,x); //无向边的连接
}
for(int i =1; i <= n; i++)
{
if(!dfn[i]) tarjan(i,0);
}
for(int i =2; i < tot; i +=2)
{
if(bridge[i])
{
cout << ver[i^1] <<" "<< ver[i] << endl;
}
}
}
数论
质数
试除法求质数
1
2
3
4
5
6
7
8
9
10
11
boolis_prime(int x)
{
if (x <2) returnfalse;
for (int i =2; i <= x / i; i ++ )
if (x % i ==0)
returnfalse;
returntrue;
}
//端点判定
用i <= x/i 就肯定不会溢出//如果是i*i <= x 的话,如果i ~ INT_MAX的时候,i*i容易溢出(溢出就会变成负数)
分解质因数—试除法(枚举)
1
2
3
4
5
6
7
8
9
10
11
12
voiddivide(int x)
{
for (int i =2; i <= x / i; i ++ )
if (x % i ==0)
{
int s =0;
while (x % i ==0) x /= i, s ++ ;
cout << i <<' '<< s << endl;
}
if (x >1) cout << x <<' '<<1<< endl;
cout << endl;
}
朴素筛法求素数
1
2
3
4
5
6
7
8
9
10
11
12
13
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
voidget_primes(int n)
{
for (int i =2; i <= n; i ++ )
{
if (st[i]) continue;
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i)
st[j] =true;
}
}
埃氏(Eratosthenes)筛法
O(nlognlogn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
constint MAX_N =10005;
int prime[MAX_N]; //第i个素数
bool is_prime[MAX_N+1]; //is_prime[i]为true时表示i是素数
//返回n以内素数的个数
intsieve(int n)
{
int p =0;
for(int i =0; i <= n; i++) is_prime[i] =true;
is_prime[0] = is_prime[1] =false;
for(int i =2; i <= n; i++){
if(is_prime[i])
{
prime[p++] = i;
for(int j =2*i; j <= n; j+=i) is_prime[j] =false; //筛去所有素数的倍数
}
}
return p;
}
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
//这个模板下标是从0开始的
voidget_primes(int n)
{
for (int i =2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j =0; primes[j] < n / i; j ++ )
{
//因为primes[j]是从小到大开始的,所以获取的primes[j]一定是最小质因数
//最小质因数 pj*i 一定是个合数
st[primes[j] * i] =true;
if (i % primes[j] ==0) break; //成立时,primes[j]一定是i的最小质因子
//不成立时,pj一定小于i的所有质因子,pj也一定是Pj*i的最小质因子
}
}
}
约数
试除法求所有约数
1
2
3
4
5
6
7
8
9
10
11
12
vector<int> get_divisors(int x)
{
vector<int> res;
for (int i =1; i <= x / i; i ++ )
if (x % i ==0)
{
res.push_back(i);
if (i != x / i) res.push_back(x / i);//如果n是i的平方,可能有两个,这里目的是只存下一个
}
sort(res.begin(), res.end());
return res;
}
voidsieve (int n){
sum[1] =1;
for (int i =2; i <= n; i++){
if (!vis[i]){
prim[++cnt] = i;
psum[i] = sum[i] = i +1;
}
for (int j =1; j <= cnt && i * prim[j] <= n; j++){
vis[i * prim[j]] =1;
if (i % prim[j] ==0){
psum[i * prim[j]] = psum[i] * prim[j] +1;
sum[i * prim[j]] = sum[i] / psum[i] * psum[i * prim[j]];
break;
}
psum[i * prim[j]] = prim[j] +1;
sum[i * prim[j]] = sum[i] * (prim[j] +1);
}
}
}
最大公约数|欧几里得算法|辗转相除法
(a,b) || (b,a mod b)
左边的最大公约数 = 右边的最大公约数
当b = 0 时候, (a,0)的最大公约数就是a (0能整除任何数)
1
2
3
4
intgcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
1
2
3
4
5
6
7
8
证明:a可以表示成a = kb + r,则r = a mod b
假设d是a,b的一个公约数,则有d|a, d|b,而r = a - kb,因此d|r
因此d是(b,a mod b)的公约数假设d 是(b,a mod b)的公约数,则d | b , d |r ,但是a = kb +r
因此d也是(a,b)的公约数因此(a,b)和(b,a mod b)的公约数是一样的,其最大公约数也必然相等,得证。
扩展的欧几里得算法
1
2
3
4
5
6
7
8
9
10
11
12
// 求x, y,使得ax + by = gcd(a, b)
intexgcd(int a, int b, int&x, int&y)
{
if (!b)
{
x =1; y =0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= (a/b) * x;
return d;
}
voidExgcd(ll a, ll b, ll &x, ll &y)
{
if (!b) x =1, y =0;
else Exgcd(b, a % b, y, x), y -= a / b * x;
}
intmain() {
ll x, y;
Exgcd (a, p, x, y);
x = (x % p + p) % p;
printf ("%d\n", x); //x是a在mod p下的逆元
}
intphi(int x)
{
int res = x;
for (int i =2; i <= x / i; i ++ )
if (x % i ==0)
{
res = res / i * (i -1);
while (x % i ==0) x /= i;
}
if (x >1) res = res / x * (x -1);
return res;
}
int primes[N], cnt; // primes[]存储所有素数
int euler[N]; // 存储每个数的欧拉函数
bool st[N]; // st[x]存储x是否被筛掉
voidget_eulers(int n)
{
euler[1] =1;
for (int i =2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++ ] = i;
euler[i] = i -1;
}
for (int j =0; primes[j] <= n / i; j ++ )
{
int t = primes[j] * i;
st[t] =true;
if (i % primes[j] ==0)
{
euler[t] = euler[i] * primes[j];
break;
}
euler[t] = euler[i] * (primes[j] -1);
}
}
}
快速幂
作用:能快速求出$~a^k~\%~p~$
10^9次的数可以在logn的时间复杂度下求出来
原理:反复平方
预处理出初始值:
$a^k\ mod\ p$
$a^{2^0} mod \ p$
$a^{2^1} mod \ p$
$a^{2^2} mod \ p$
$a^{2^{log_k}} mod \ p$
做法就是想办法将后面的$a^k$想办法用前面初始化的值进行替代求解
【模板】
1
2
3
4
5
6
7
8
9
10
11
12
求 m^k mod p,时间复杂度 O(logk)。int qmi(int m, int k, int p)
{
int res =1% p, t = m;
while (k)
{
if (k&1) res = res * t % p;
t = t * t % p;
k >>=1;
}
return res;
}
ll mul(ll a, ll b, ll mod) //快速乘
{
ll r =0;
while(b)
{
if(b &1) r = (r+a) % mod;
a = (a + a) % mod;
b >>=1;
}
return r % mod;
}
扩展的欧几里得算法
裴蜀定理
对于任意正整数a,b,一定存在整数x,y,使得ax+by=c (c是gcd(a,b)的倍数)
【扩展欧几里得模板】
当b == 0时;
找一组解使得 ax+by=gcd(a,b)
即就是 ax + 0*y = gcd(a,0) = a;
显然 x = 1, y = 0 符合要求
当b != 0 时;
用d存储一下此时的结果
b*y+(a mod b)*x = d;
a mod b = a - $\lfloor\frac{a}{b}\rfloor$ * b;
故此 by + (a - $\lfloor\frac{a}{b}\rfloor$ ) b = d;
整理可得: $ax+b*(y-\lfloor\frac{a}{b}\rfloor*x) = d$
x 未发生改变
y = y - (a/b)*x
1
2
3
4
5
6
7
8
9
10
11
12
13
// 求x, y,使得ax + by = gcd(a, b)
intexgcd(int a, int b, int&x, int&y)
{
if (!b)
{
x =1; y =0;
return a;
}
int d = exgcd(b, a % b, y, x);
//递归结束的时候就会已经得到一组x,y,使其ax+by=d
y -= (a/b) * x; // <--y发生了改变
return d;
}
首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N]
如果取模的数是质数,可以用费马小定理求逆元int qmi(int a, int k, int p) // 快速幂模板
{
int res =1;
while (k)
{
if (k &1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>=1;
}
return res;
}
// 预处理阶乘的余数和阶乘逆元的余数
fact[0] = infact[0] =1;
for (int i =1; i < N; i ++ )
{
fact[i] = (LL)fact[i -1] * i % mod;
infact[i] = (LL)infact[i -1] * qmi(i, mod -2, mod) % mod; //逆元
}
若p是质数,则对于任意整数1<= m <= n,有: C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)
int qmi(int a, int k, int p) // 快速幂模板
{
int res =1% p;
while (k)
{
if (k &1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>=1;
}
return res;
}
intC(int a, int b, int p) // 通过定理求组合数C(a, b)
{
if (a < b) return0;
LL x =1, y =1; // x是分子,y是分母
for (int i = a, j =1; j <= b; i --, j ++ )
{
x = (LL)x * i % p;
y = (LL) y * j % p;
}
return x * (LL)qmi(y, p -2, p) % p;
}
intlucas(LL a, LL b, int p)
{
if (a < p && b < p) return C(a, b, p);
return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
//MLE
#include<bits/stdc++.h>usingnamespace std;
constint N =20010;
int f[N][N];
char p[N],s[N];
int res =0;
intmain()
{
scanf("%s", p +1);
scanf("%s", s +1);
int n = strlen(p+1);
int m = strlen(s+1);
for(int i =1; i <= n; i ++){
for(int j =1; j <= m; j ++){
if(p[i] == s[j] &&!(p[i] >='0'&& p[i] <='9')){
f[i][j] = max(f[i][j], f[i -1][j -1] +1);
res = max(f[i][j], res);
}
else f[i][j] =0;
}
}
printf("%d\n",res);
return0;
}
上面的代码内存超限,观察优化可以得到for(int j = 1; j <= m; j ++)类似于01背包的优化方法,可以采用滑动数组的方式对空间进行优化。
intdp(int m, int n){
if(m==0|| n==1) return1;
if(m < n) return dp(n,m);
else{
return dp(m,n-1)+dp(m-n,n);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
//暴搜
voiddfs(int u, int start, int sum) {
if (u == n) {
if (sum == m)res++;
return;
}
for (int i = start; i <= m; i++) {
sum += i;
dfs(u +1, i ,sum);
sum -= i;
}
}
#include<bits/stdc++.h>usingnamespace std;
constint N =100010;
int a[N],dp[N];
int n;
char ch[30];
int pre[N],back[N];//分别存每个数的前缀和末尾
int g[10]; //数组优化循环,存的是以某个数字结尾的最大的长度
intmain()
{
scanf("%d",&n);
for(int i =0; i < n; i ++){
scanf("%s",ch);
pre[i] = ch[0] -'0';
back[i] = ch[strlen(ch) -1] -'0';
}
int ans =0;
for(int i =0; i < n; i ++){
dp[i] = max(1, dp[i]);
dp[i] = max(dp[i], g[pre[i]] +1); //思维很重要,优化思维!!!
g[back[i]] = max(g[back[i]], dp[i]);
ans = max(ans, dp[i]);
}
printf("%d", n - ans);
return0;
}
F [0..V ] ←0
for i ← 1 to N
for v ← Ci to V
for k ← 0 to V/Ci
F [i, v] ← max(F [i-1,v-kCi] + kWi)
//如何求出取物品的路径
v_0 = V
for i ← N to 1
for k ← 0 to V/ci
if(f[i][v_0] = f[i-1][v_0-k*ci]+k*wi){
print("选取了k个物品i")
v_0 = v_0 - k*ci;
break;
}
for(int i =1; i <= n; i ++)
{
for(int j = v[i]; j <= m; j ++)
{
dp[j] = max(dp[j],dp[j-v]+w);
}
}
return dp[m];
多重背包问题
有 N 种物品和一个容量为 V 的背包。第 i 种物品最多有 Mi 件可用,每件耗费的 空间是 Ci,价值是 Wi。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超 过背包容量,且价值总和最大。
“拆分物品”的思想和方法
【解法一】三重循环枚举
1
2
3
4
5
6
7
8
9
10
//与01背包的区别和联系
f[i]表示总体积是i的情况下,最大价值是多少
for(int i = 1; i <= n; i ++)
{
for(int j = m; j >= v[i]; j --)
{
f[j] = max(f[j], f[j-v[i]] + w[i], f[j-2*v[i]] + 2*w[i], ... ,f[j-k*v[i]]+k*w[i]); //三重循环
}
}
//相比于0/1背包问题,多重背包问题可以选0个、1个、2个...、k个
1
2
3
4
5
6
7
8
9
10
11
12
for(int i =1; i <= n; i ++)
{
v,w,s;
for(int j = m; j >=0; j --)
{
for(int k =1; k <= s && k*v[i] <= j; k ++) //别盲目用&& 要满足升序排列的时候才能用&&,否则会提前跳出循环
{
f[j] = max(f[j], f[j - k*v] + k*w);
}
}
}
return f[m];
分组背包问题
有 N 件物品和一个容量为 V 的背包。第 i 件物品的费用是 Ci,价值是 Wi。这些 物品被划分为 K 组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包 可使这些物品的费用总和不超过背包容量,且价值总和最大。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
背包九讲的问题
for k ← 1 to K
for v ← V to 0
for all item i in group k
F[v] ← max{F[v], F[v − Ci] + Wi}
for(int i = 0; i < n; i ++)
{
for(int j = m; j >= v; j --)
{
f[j] = max(f[j], f[j-v[0]] + w[0], f[j-v[1]] + w[1], ... , f[j-v[s-1] + w[s-1]])
}
}
return f[m];
1
2
3
4
5
6
7
8
9
10
11
for(int i =0 ; i < n; i ++)
{
for(int j =0; j < s; j ++) cin >> v[j] >> w[j];
for(int j = m; j >=0; j --)
{
for(int k =0; k < s && j >= v[k]; k ++)//别盲目用&& 要满足升序排列的时候才能用&&,否则会提前跳出循环
{
f[j] = max(f[j], f[j-v[k]] + w[k]);
}
}
}
初始值:$\forall l \in[1,N],F[l,l]=A_l$,其余为正无穷
目标:$F[1,N]$
一定要分清阶段、状态和决策,三者应该按照从外到内的顺序依次循环
1
2
3
4
5
6
7
8
9
10
11
memset(f, 0x3f, sizeof f); //INF
for(int i =1; i <= n; i ++){
f[i][i] =0;
sum[i] = sum[i-1] + a[i]; //前缀和
}
for(int len =2; len <= n; len ++)//阶段:很明显是以len作为一个阶段
for(int l =1; l <= n - len +1; l ++) //状态:枚举左端点
int r = l + len -1; //状态:枚举右端点
for(int k = l; k < r; k ++)
f[l][r] = min(f[l][r], f[l][k] + f[k +1][r]); //这一步表示将l~k||k+1~r进行区间合并,并取得合并后还是合并前的最大值
f[l][r] += sum[r] - sum[l -1];
【位运算相关知识引入】
n << 1 表示n*2
n >> 1 表示n/2
n << m 表示n*2^m
n >> m 表示n/2^m
(n & 1) == 1 判断奇偶性
-----------------------
int A,B //两个集合
int c 新的元素
A |= 1 << c 将c插入到A集合中
A &=~(1 << c) 在集合中删除c
A ^= 1 << c 在集合中删除c,明确已知A中含c
a & (-a) lowbit A的计算
A = 0 将集合置为0
A | B 并集
A & B 交集
int si = 15 集合的大小
int all = (1 << si) - 1 全集
all ^ A 全集里面求一个子集
(A&B) == B 说明B是A的子集
(A & (1 << i))//获取集合A的第i位的数据(0 or 1)
//枚举所有子集
for(int i = 0; i <= all; i ++) ;
//枚举某一个集合的所有子集
int subset = A
do
{
subset = (subset - 1)&A;
}while(subset != A)
//枚举一个集合中的某一元素有多少个
int cnt = 0;
for(int i = 0; i < si; i ++)
{
if(A & (1 << i)) cnt ++;
cnt;
}
或者
for(int i = A; i; i >>= 1)
{
cnt += i&1;
}
//
input.txt
16
A 2.5 4.0
B 1.2 -2.4
C 8.7 1.2
D 3.6 12.1
E -5.5 0.94
F -6.6 -12.6
G 0.18 5.219
H 12.5 14.3609
I 22.5 -5.26
J 1.61 4.5
K 2.1 -5.6
L 0 25
M 9.2 -32
N -1 7
O -5 -8
P 21 35
#include<bits/stdc++.h>#define sqr(x) ((x) * (x))
usingnamespace std;
typedeflonglong LL;
constint maxn =18;
constdouble PI = acos(-1);
constint mod =1e9+7;
usingnamespace std;
constint INF =0x3f3f3f3f;
// 定义变量
int type; // type == 1 满秩矩阵格式, type == 2 二维坐标式
int s;
int N; // 城市结点数量
int init_point;
double dp[1<< maxn][maxn];
// 动态规划状态数组dp[i][j],i表示集合V’,j表示当前到达的城市结点
double dis[maxn][maxn]; // 两个城市结点之间的距离
vector<int> path[1<< maxn][maxn]; //路径
double ans;
vector<int> ans_path;
// 定义结构体
structvertex{
double x, y; // 城市结点的坐标
string id; // 城市结点的id
voidinput() {
cin >> id;
scanf("%lf %lf", &x, &y);
}
}node[maxn];
//距离函数
doubleDist(const vertex &a, const vertex &b) {
return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y));
}
voidinit() { // 数据初始化
scanf("%d", &N);
for (int i =0; i < N; i++) //存储一下输入
node[i].input();
for (int i =0; i < N; i++) {
for (int j =0; j < N; j++)
dis[i][j] = Dist(node[i], node[j]); // 计算城市之间的距离
}
for (int i =0; i < (1<< N); i++) {
for (int j =0; j < N; j++)
dp[i][j] = INF,path[i][j].clear();
} //初始化,除了dp[1][0],其余值都为INF
ans = INF;
return;
}
//复杂度 2^N * N^2
//状态压缩
voidslove() {
int M = (1<< N);
// M就是第四部分所说的V’状态总数,1<<N表示2^N,总共有2^N种状态
dp[1][0] =0;
path[1][0].push_back(0);
// 假设固定出发点为0,从0出发回到0的花费为0。TSP只要求是一个环路,所以出发点可以任选
for (int i =1; i < M; i++) {
// 枚举V’的所有状态
for (int j =1; j < N; j++) { //枚举本层情况
// 选择下一个加入集合的城市
if (i & (1<< j)) //如果集合i的第j位已经是1,则说明j已经在集合内了
continue;
// 城市已经存在于V’之中
if (!(i &1)) // 如果i是0的话,也就说固定在为0的城市出发的话
continue;
// 出发城市固定为0号城市
for (int k =0; k < N; k++) { //枚举上一层的情况,当上一层对本层有影响时
// 在V’这个城市集合中尝试每一个结点,并求出最优解
if (i & (1<< k)) { //k在集合i中
// 确保k已经在集合之中并且是上一步转移过来的结点
if(dp[i][k] + dis[k][j] < dp[(1<< j) | i][j]){
dp[(1<< j) | i][j] = dp[i][k] + dis[k][j];
path[(1<< j) | i][j] = path[i][k];
path[(1<< j) | i][j].push_back(j);
}
dp[(1<< j) | i][j] = min(dp[(1<< j) | i][j],
dp[i][k] + dis[k][j]); // 有影响则进行转移方程
} // 将j点加入到i集合中
}
}
}
for (int i =0; i < N; i++){
if(dp[M -1][i] + dis[i][0] < ans){
ans = dp[M -1][i] + dis[i][0];
ans_path = path[M-1][i];
}
}
// 因为固定了出发点,所以要加上到城市0的距离。另外要从所有的完成整个环路的集合V’中选择,完成最后的转移
}
intmain() {
init();
slove();
cout <<"TSP路径长度: "<< ans << endl <<"TSP回路: ";
for(int i =0; i < (int)ans_path.size(); i++){
if(i) cout <<' ';
cout << node[ans_path[i]].id;
}cout << endl;
return0;
}
【状压DP的常用模板】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int maxn =1<< n; //规定状态的上界
for (int i =0;i < maxn; i ++){
if (i & (i <<1)) continue;//如果i情况不成立就忽略
Type[++top] = i;//记录情况i到Type数组中
}
for (int i =1; i <= top; i ++){
if (fit(situation[1], Type[i]))
dp[1][Type[i]] =1;//初始化第一层
}
for (int i =2;i <=层数(dp上界); i ++){
for (int l =1; l <= top; l ++)//穷举本层情况
for (int j =1; j <= top; j ++)//穷举上一层情况(上一层对本层有影响时)
if (situation[i], Type[l]和Type[j]符合题意)
dp[i][l] = dp[i][l] + dp[i-1][j];//改变当前层(i)的状态(l)的方案种数
}
for (int i =1; i <= top; i ++) ans += dp[上界][Type[i]];
对于每一个 C l r d操作
1.在树状数组C0中,把位置l上加上d
2.在树状数组c0中,把位置r+1上减去d
3.在树状数组c1中,把位置l上的数加上 l*d
4.在树状数组c1中,把位置r+1上的数减去 (r+1)*d
对于操作 Q l r 区间查询,我们还需要建立一个数组sum来存储序列a的原始前缀和
拆成1~r和1~l-1的形式:
(sum[r] + (r+1)*getsum(c0,r)-getsum(c1,r) - (sum[l-1]+l*getsum(c0,l-1)-getsum(c1,l-1)))