2021年9月16日

逆推法求逆元:

inv[1] = 1;
for (int i = 2; i <= MAXN; i++) inv[i] = MOD-(MOD / i) * inv[MOD % i] % MOD;

两个特殊构造:

1.如何构造一个1~n的排列使得这个排列的n个前缀和在%n条件下互不相同,具体来讲,前缀和数组的构造方法为0,1,-1,2,-2·······,所以排列的构造为0,1,-2,3,-4······

2.如何构造一个1~n的排列使得这个排列的n个前缀积在%n条件下互不相同,具体来讲,我们可以讲排列构造为1,2/1,3/2,4/3······· n(最后一个必须为n),因为 / 在 % 条件下可以转化为 *逆元,所以就可以构造出来了。

高精度模板

1.加法	
\#include <bits/stdc++.h>

using namespace std ;

string add(string s1 , string s2) {

  int lenth_1 = s1.size() ;

  int lenth_2 = s2.size() ;

  if(lenth_1 < lenth_2) {for(int i = 1 ; i <= lenth_2 - lenth_1 ; i ++) s1 = "0" + s1 ;

  }

  else {for(int i = 1 ; i <= lenth_1 - lenth_2 ; i ++) s2 = "0" + s2 ;

  }

  int n = s1.size() ;

  string s ;

  int t = 0 , temp ;

  for(int i = n - 1 ; i >= 0 ; i --) {

​    temp = s1[i] - '0' + s2[i] - '0' + t ;

​    t = temp / 10 ;

​    temp %= 10 ;

​    s = char(temp + '0') + s ;

  }

  if(t != 0) s = char(t + '0') + s ;

  return s ;

}

int main() {

  string s1 , s2 ;

  cin >> s1 >> s2 ;

  cout << add(s1 , s2) ;

  return 0 ;

}
2.减法
#include <bits/stdc++.h>

using namespace std ;

string sub(string s1 , string s2) {

  int tmp = s1.size() - s2.size() ;

  string s ;

  int t = 0 ;

  int temp ;

  for(int i = s2.size() - 1 ; i >= 0 ; i --) {if(s1[i+tmp] - t < s2[i]) {

​      temp = s1[i+tmp] - t + 10 - s2[i] ;

​      s = char(temp + '0') + s ;

​      t = 1 ;}else {

​      temp = s1[i+tmp] - t - s2[i] ;

​      s = char(temp + '0') + s ;

​      t = 0 ;}

  }

  for(int i = tmp - 1 ; i >= 0 ; i --) {if(s1[i] - '0' - t < 0) {

​      temp = s1[i] - '0' + 10 - t ;

​      s = char(temp + '0') + s ;

​      t = 1 ;}else {

​      temp = s1[i] - '0' - t ;

​      s = char(temp + '0') + s ;

​      t = 0 ;}

  }

  bool p = false ;

  for(int i = 0 ; i < s.size() ; i ++) {if(s[i] != '0') {

​      p = true ;break ;}

  }

  if(!p) return "0" ;

  s.erase(0 , s.find_first_not_of('0')) ;

  return s ;

}

int main() {

  string s1 , s2 ;

  cin >> s1 >> s2 ;

  if(s2.size() > s1.size() || (s2.size() == s1.size() && s2 > s1)) cout << '-' << sub(s2 , s1) ;

  else cout << sub(s1 , s2) ;

  return 0 ;

}
3.乘法
\#include <bits/stdc++.h>

using namespace std ;

string add(string s1 , string s2) {

  int lenth_1 = s1.size() ;

  int lenth_2 = s2.size() ;

  if(lenth_1 < lenth_2) {for(int i = 1 ; i <= lenth_2 - lenth_1 ; i ++) s1 = "0" + s1 ;

  }

  else {for(int i = 1 ; i <= lenth_1 - lenth_2 ; i ++) s2 = "0" + s2 ;

  }

  int n = s1.size() ;

  string s ;

  int t = 0 , temp ;

  for(int i = n - 1 ; i >= 0 ; i --) {

​    temp = s1[i] - '0' + s2[i] - '0' + t ;

​    t = temp / 10 ;

​    temp %= 10 ;

​    s = char(temp + '0') + s ;

  }

  if(t != 0) s = char(t + '0') + s ;

  return s ;

}

string mul(string s1 , string s2) {

  if(s1 == "0" || s2 == "0") return "0" ;

  string s ;

  for(int i = s2.size() - 1 ; i >= 0 ; i --) {

​    string ss ;int temp , t = 0 ;for(int j = s1.size() - 1 ; j >= 0 ; j --) {if(j == 0) {

​        temp = (s2[i] - '0') * (s1[j] - '0') + t ;char x = temp / 10 + '0' ;char y = temp % 10 + '0' ;

​        ss = y + ss ;

​        ss = x + ss ;break ;}

​      temp = ((s2[i] - '0') * (s1[j] - '0') + t) % 10 ;

​      t = ((s2[i] - '0') * (s1[j] - '0') + t) / 10 ;

​      ss = char(temp +'0') + ss ;}for(int j = 1 ; j <= s2.size() - i - 1 ; j ++) ss = ss + "0" ;

​    s = add(s , ss) ;

  }

  s.erase(0 , s.find_first_not_of('0')) ;

  return s ;

}

int main() {

  string s1 , s2 ;

  cin >> s1 >> s2 ;

  cout << mul(s1 , s2) ;

  return 0 ;

}
4.除法
\#include <bits/stdc++.h>

using namespace std ;

string div(string s1 , int b) {

  if(s1 == "0") return "0" ;

  string s ; 

  long long d = 0 ;

  int temp ;

  for(int i = 0 ; i < s1.size() ; i ++) {

​    temp = (d * 10 + s1[i] - '0') / b ;

​    s = s + char(temp + '0') ;

​    d = (d * 10 + s1[i] - '0') % b ;

  }

  s.erase(0 , s.find_first_not_of('0')) ;

  return s ;

}

int main() {

  string s1 ;

  long long b ;

  cin >> s1 >> b ;

  cout << div(s1 , b) ;

  return 0 ;

}

前缀和与差分的两个小技巧

  1. 数列a[1]·····a[n]中某一段区间和a[x]+a[x+1]+······+a[y-1]+a[y]能对k整除,求这段特殊区间的最大长度类型的题: 设s[i]=a[1]+····+a[i], 则a[x]+a[x+1]+····+a[y] = s[y] - s[x-1], 因此(s[y]-s[x-1])%k == 0,s[y]%k == s[x-1] % k ,因此原问题可以转化为寻找前缀和数组中对K取余的两个相等的并且相距最远的数的距离即为答案。

  2. 对于一个数列,可以选择某一段同时加或减一个数,要求最后数列中每一项都相等,考虑用差分。

给出几段区间,求最少用多少个区间覆盖整段线段的方法O(m^2):

int left=1;

  while (left<=m){int maxr=0;for (int i=1;i<=m;i++)if (l[1][i]<=left)

​        maxr=max(maxr,r[1][i]);

​    cnt++;

​    left=maxr+1;

  }

DFS的三个剪枝优化

  1. 数据的排序优化
  2. (题目问是否能搜出一个解(搜到了直接exit(0);结束整个程序),即解是否存在)枚举每个可选项时,若dfs回溯回来,可能在某种条件下这个循环就没必要继续进行了,可以直接return。
  3. 若明确搜索起点的复杂性,尽量选择分支少的起点。(如树的第一层的点尽量少)

双向搜索

  1. 位运算不确定运算顺序时一定要加括号。
  2. 二进制状态压缩适用于物品等数量不多(20个左右),用二进制下的每一位代表这个物品,1为选,0为不选,共有2 ^ N 个状态
  3. 通过一组数据计算答案时,若这组数据可以分为两组数据并且通过两组数据可以计算答案,可以用双指针。(注意两组数据都要有序,第一组上升序列,第二组下降序列,反之亦可)
  4. 多个搜索分支的处理。
#include <bits/stdc++.h>

using namespace std ;

struct node {

  int state , x ;

}a[1<<21] , b[1<<21] ;

bool vis[1<<21] ;

int cnta , cntb ;

int v[21] ;

int n ;

int maxdep ;

bool cmpa(node p , node q) {return p.x < q.x ;}

bool cmpb(node p , node q) {return p.x > q.x ;}

void dfs(int dep , int sum , int now , int flag) {

  if(dep == maxdep + 1) {if(flag) {

​      b[++cntb].x = sum ;

​      b[cntb].state = now ;}else {

​      a[++cnta].x = sum ;

​      a[cnta].state = now ;}return ;

  }

  dfs(dep + 1 , sum , now , flag) ;

  dfs(dep + 1 , sum + v[dep] , now + ( 1 << (dep - 1) ) , flag) ;

  dfs(dep + 1 , sum - v[dep] , now + ( 1 << (dep - 1) ) , flag) ;

}

int main() {

  cin >> n ; 

  for(int i = 1 ; i <= n ; i ++) cin >> v[i] ;

  maxdep = n / 2 ;

  dfs(1 , 0 , 0 , 0) ;

  maxdep = n ;

  dfs(n / 2 + 1 , 0 , 0 , 1) ;

  sort(a + 1 , a + cnta + 1 , cmpa) ;

  sort(b + 1 , b + cntb + 1 , cmpb) ;

  long long i = 1 , j = 1 , ans = -1 ;

  while(i <= cnta && j <= cntb) {while(a[i].x + b[j].x > 0 && j <= cntb) j ++ ;int pos = j ;while(j <= cntb && a[i].x + b[j].x == 0) {if(!vis[a[i].state|b[j].state]) {

​        vis[a[i].state|b[j].state] = 1 ;

​        ans ++ ;}

​      j ++ ;}if(i < cnta && a[i] .x == a[i+1].x) j = pos ;

​    i ++ ;

  }

  cout << ans ;

  return 0 ;

} 

A*算法
在 BFS 中,如果能设计一个合理的估价函数,就可以更快扩展到最优解。这就是 A*算法。重在构造估值函数。(如哈夫曼距离作为估值函数)

IDA*算法
像 BFS 那样,每次只扩展一层节点,却采用 DFS 方式来遍历搜索树,这就是迭代加深搜索。
再加上一个估价函数来减小搜索量,就是 IDA*了。

数位DP:

1.HDU 2089 不要62

数位上不能有4也不能有连续的62,没有4的话在枚举的时候判断一下,不枚举4就可以保证状态合法了,所以这个约束没有记忆化的必要,而对于62的话,涉及到两位,当前一位是6或者不是6这两种不同情况我计数是不相同的,所以要用状态来记录不同的方案数。

dp[pos][sta]表示当前第pos位,前一位是否是6的状态,这里sta只需要去0和1两种状态就可以了,不是6的情况可视为同种,不会影响计数。

#include <bits/stdc++.h>

using namespace std ;

typedef long long ll ;

int a[20] ;

int dp[20][2] ;

int dfs(int pos , int pre , int sta ,bool limit) {

 if(pos == -1) return 1 ;

 if(!limit && dp[pos][sta] != -1) return dp[pos][sta] ;

 int up = limit ? a[pos] : 9 ;

 int tmp = 0 ;

 for(int i = 0; i <= up; i++) {

  if(pre == 6 && i == 2) continue ;

  if(i == 4) continue ;

  tmp += dfs(pos-1 , i , i==6 , limit && i == a[pos]) ;

 }

 if(!limit) dp[pos][sta] = tmp ;

 return tmp ;

}

int solve(int x) {

 int pos = 0 ;

 while(x) {

  a[pos++] = x % 10 ;

  x /= 10 ;

 }

 return dfs(pos-1 , -1 , 0 , true) ;

}

int main() {

 int le , ri ;

 while(~scanf("%d%d", &le , &ri) && (le + ri)) {

  memset(dp , -1 ,sizeof(dp)) ;

  printf("%d\n" , solve(ri) - solve(le-1));

 }

 return 0 ;

}

2.HDU 4734
题目给了个f(x)的定义:F(x) = An * 2n-1 + An-1 * 2n-2 + … + A2 * 2 + A1 * 1,Ai是十进制数位,然后给出a,b求区间[0,b]内满足f(i)<=f(a)的i的个数。



\#include <iostream>

\#include <cstdio>

\#include <cstring>

using namespace std ;

const int N = 1e4 + 10 ;

int a[20] ;

int all ; // 记录每个f(a)

int dp[20][N] ; // 第一维表示pos 第二维表示后面还需凑出该数的权值和和的个数

int f(int x) { //计算f(a)

 if(x == 0) return 0 ;

 int ans = f(x / 10) ;

 return ans * 2 + x % 10 ;

}

int dfs(int pos , int sum , bool limit) { //sum表示当前枚举的数的前缀和 实际上是我们初始值为f(a)时减去了的前缀和,因为此时我们从0开始,所以如此表示

 if(pos == -1) return sum <= all ; // 所有位数枚举完时 此时前缀和如果大于f(a),这种情况不满足,否则满足

 if(sum > all) return 0 ; // 中间时已经不满足了 直接就返回0就行了

 if(!limit && dp[pos][all-sum] != -1) return dp[pos][all-sum] ; // 记忆化 记住:第二维一定是all-sum ,为什么请看dp数组的定义

 int up = limit ? a[pos] : 9 ;//以下都是套路

 int tmp = 0 ;

 for(int i = 0; i <= up; i ++) {

  tmp += dfs(pos-1 , sum + i * (1 << pos) , limit && i == a[pos]) ;

 }

 if(!limit) dp[pos][all-sum] = tmp ;

 return tmp ;

}

int solve(int x) {

 int pos = 0 ;

 while(x) {

  a[pos++] = x % 10 ;

  x /= 10 ;

 }

 return dfs(pos-1 , 0 , true) ;

}

int main() {

 int t ;

 cin >> t ;

 int kase = 1 ;

 memset(dp , -1 , sizeof dp) ;

 while(t --) {

  int x , r ;

  cin >> x >> r ;

  all = f(x) ;

  printf("Case #%d: %d\n",kase++,solve(r));

 }

}

3.POJ 3252

#include <iostream>

#include <cstdio>

#include <cstring>

#include <queue>

#include <set>

#include <vector>

#include <map>

#include <stack>

#include <cmath>

#include <algorithm>

using namespace std ;

typedef long long ll ;

int dp[33][66] ;

int a[66] ;

int dfs(int pos , int sta , bool lead ,bool limit) {

 if(pos == -1) return sta >= 32 ; // 这里运用了HASH 用32表示0 , 至于为什么是32 , 因为负数最多为-32

 if(!limit && !lead && dp[pos][sta] != -1) return dp[pos][sta] ; // 注意:此处加了不能有前导0这个条件 即lead

 int up = limit ? a[pos] : 1 ;

 int tmp = 0 ;

 for(int i = 0; i <= up; i++) {

  if(i == 0 && lead) tmp += dfs(pos-1 , sta , lead , limit && i == a[pos]) ; // 如果有前导0并且这一位也是0 , 前面就是无效的 , 因此不必理会

  else tmp += dfs(pos-1 , sta + (i == 1 ? -1 : 1) , lead && i == 0 , limit && i == a[pos]) ;

 }

 if(!lead && !limit) dp[pos][sta] = tmp ; // 注意: 此处也增加了前导0这一条件

 return tmp ;

}

int solve(int x) {

 int pos = 0 ;

 while(x) {

  a[pos++] = x & 1 ;

  x >>= 1 ;

 }

 return dfs(pos-1 , 32 , true , true) ; // 最高位是有前导0的,因为高位前面补0不影响数的大小

}

int main() {

 memset(dp , -1 , sizeof dp) ;

 int a , b ;

 cin >> a >> b;

 cout << solve(b) - solve(a-1) << endl ;

 return 0 ;

}

4.在这里插入图片描述

#include <bits/stdc++.h>

using namespace std ;

typedef long long ll ;

ll a[20] ;

ll dp[64][10] ;

ll f[20] ;

ll dfs(ll t , ll pos , bool limit , ll x , bool lead) {

 if(pos == -1) return 0 ;

 if(!x && !limit && dp[pos][x] != -1 && !lead) return dp[pos][x] ;

 if(x && !limit && dp[pos][x] != -1) return dp[pos][x] ;

 ll up = limit ? a[pos] : 9 ;

 ll tmp = 0 ;

 for(ll i = 0; i <= up; i++) {

  if(x) {if(pos > 0 && i == x){if(i == a[pos] && limit) tmp += (t % f[pos] + 1) ;else tmp += f[pos] ;}if(i == x && pos == 0) tmp ++ ; 

  }

  else {if(!lead){if(pos > 0 && i == x){if(i == a[pos] && limit) tmp += (t % f[pos] + 1) ;else tmp += f[pos] ;}if(i == x && pos == 0) tmp ++ ;}if(lead && i == 0 && pos == 0) tmp ++ ;

  }

  tmp += dfs(t , pos-1 , limit && i == a[pos] , x , lead && i == 0) ;

  //if(x == 0 && pos == 1 && i == 1) cout << tmp << endl ;

  //if(pos == 0 && i == 0) cout << tmp << endl ; 

 }

 if(!x && !limit && !lead) dp[pos][x] = tmp ; 

 if(x && !limit) dp[pos][x] = tmp ; 

 return tmp ;

}

ll solve(ll x , ll y) {

 ll t = x ;

 ll pos = 0 ;

 if(x == 0)

  a[pos++] = 0 ;

 while(x) {

  a[pos++] = x % 10 ;

  x /= 10 ;

 }

 return dfs(t , pos-1 , true , y , true) ;

} 

int main() {

 ll res = 1 ; f[0] = 1 ;

 for(ll i = 1 ; i <= 18 ; i ++) f[i] = f[i-1] * 10 ;

 ll le , ri ;

 scanf("%lld%lld" , &le , &ri);

 memset(dp , -1 ,sizeof(dp)) ;

 //cout << solve(21905 , 0) ;

 //cout << solve(99 , 1) ;

 for(ll i = 0 ; i <= 9 ; i ++)

 printf("%lld " , solve(ri , i) - solve(le-1 , i));

 return 0 ;

}

LIS输出序列:

#include <bits/stdc++.h>

using namespace std ;

\#define LL long long

LL dp[50000] ;

LL a[50000] ;

LL f[50000] ;

LL ss ;

LL ans ;

int main() 

{

  int n ; 

  cin >> n ;

  dp[1] = 1 ;

  for(int i = 1 ; i <= n ; i ++) cin >> a[i] ;

  for (int i = 2; i <= n ; i ++)

  {

​    dp[i] = 1;for(int j = 1 ; j < i; j ++)if(a[j] < a[i] && dp[j] + 1 > dp[i])

​        dp[i] = dp[j]+1, f[i] = j;// if(ans == dp[i] && ) if (ans < dp[i])

​      ans = dp[i], ss = i;

  }

  stack<int> q ;

  q.push(a[ss]) ;

  while(1) 

  {

​    ss = f[ss] ;if(ss == 0) break ;

​    q.push(a[ss]) ;

  }

  while(q.size())

  {

​    cout << q.top() << ' ' ;

​    q.pop() ;

  }

}

状态压缩DP:

1.POJ 2411

我们把“行号”作为DP的“阶段” , 把“上半部分”不断向下扩展 , 直至确定整个棋盘的分割方法。为了描述上半部分最后一行的详细形态 , 我们可以使用一个M位二进制数 , 其中第k(0 <= k < M)位为1表示第k列是一个竖着地1*2长方形的上面一半, 第k位为0表示其他情况。

 

设F[i,j]表示第i行的形态为j时 ,前i行分割方案的总数。j是用十进制整数记录的M位二进制数。

 

第i-1行的形态k能转移到第i行的形态j ,当且仅当:

1.j和k执行按位与运算的结果是0。

这保证了每个数字1的下放必须是数字0 , 代表继续补全竖着的1*2长方形。

2.j和k执行按位或运算的结果的二进制表示中 , 每一段连续的0都必须有偶数个。

这些0代表若干个横着的1*2长方形 , 奇数个0无法分割成这种状态。

 

我们可以在DP前预处理出[0 , 2 ^ m - 1] 内所有满足“二进制表示下每一段连续的0都有偶数个”的整数 , 记录在集合S中

 

可得转移方程 : f[i][j] = (0 =< j < M && (j & k) =0 && (j | k) 在集合S中) f[i-1][k]

 

初值:f[0][0] = 1 , 其余为0

目标:f[n,0]
#include <iostream>

#include <cstdio>

using namespace std ;

#define int long long

int dp[20][1<<12] ;

int n , m ;

bool vis[1<<12] ;

signed main()

{

  while(scanf("%lld%lld" , &n , &m) && n) 

  {for(int i = 0 ; i < 1 << m ; i ++){bool cnt = 0 , odd = 0 ;for(int j = 0 ; j < m ; j ++){if(i >> j & 1) odd |= cnt , cnt = 0 ;else cnt ^= 1 ;}

​      vis[i] = odd | cnt ? 0 : 1 ;}

​    dp[0][0] = 1 ;for(int i = 1 ; i <= n ; i ++){for(int j = 0 ; j < 1 << m ; j ++){

​        dp[i][j] = 0 ;for(int k = 0 ; k < 1 << m ; k ++){if((j & k) == 0 && vis[j|k]) 

​            dp[i][j] += dp[i-1][k] ;}}}printf("%lld\n" , dp[n][0]) ;

  }

} 

线段树优化DP
POJ2376
设f[x]表示覆盖[1 , x]需要花费的最小代价。

把所有的贴纸按照右端点bi递增排序 , 按顺序扫描这些贴纸 。设当前贴纸为[ai , bi] , 价格为ci。状态转移方程为:f[bi] = min{f[x]} + ci (ai - 1 <= x <= bi - 1)

初值:f[0] = 0 , 其余为正无穷。 目标:min{f[bi]} (bi >= R)

在这个状态转移方程中 , 需要查询f数组在[ai -1 , bi] 上的最小值 , 同时f数组会不断发生更新 。这是一个带有修改的区间最值问题 , 使用线段树维护f数组即可在O(log N)的时间内执行查询、更新操作。

本题中网格位置的坐标都很小 , 可以直接在[0 , R]上建立线段树 。在坐标较大时也可以离散化 ,再用线段树求解。另外 , 需注意贴纸左、右端点超出[1 , R]的边界情况。

#include <iostream>

#include <cstring>

#include <algorithm>

#include <cstdio>

#include <cmath>

using namespace std ;

const int inf = 1e9 ;

const int N = 1e6 + 10 ;

struct _line

{

  int l , r ;

}p[25200] ;

bool cmp(_line x , _line y) {return x.r < y.r ;}

struct node 

{

  int l , r ;

  int MIN ;

}t[N*4] ;

void build(int p , int l , int r)

{

  t[p].MIN = inf ;

  if(l == r) return ;

  int mid = l + r >> 1 ;

  build(p * 2 , l , mid) ;

  build(p * 2 + 1 , mid + 1 , r) ;

  t[p].MIN = min(t[p*2].MIN , t[p*2+1].MIN) ;

}

void update(int p , int l , int r , int pos , int val)

{

  if(l == r)

  {

​    t[p].MIN = min(val , t[p].MIN) ;return ;

  }

  int mid = l + r >> 1 ;

  if(pos <= mid) update(p * 2 , l , mid , pos , val) ;

  if(pos > mid) update(p * 2 + 1 , mid + 1 , r , pos , val) ;

  t[p].MIN = min(t[p*2].MIN , t[p*2+1].MIN) ;

}

int query(int p , int l , int r , int L , int R)

{

  if(L <= l && R >= r)return t[p].MIN ;

  int mid = l + r >> 1 ;

  int val = inf ;

  if(L <= mid) val = min(val , query(p * 2 , l , mid , L , R)) ;

  if(R > mid) val = min(val , query(p * 2 + 1 , mid + 1 , r , L , R)) ;

  return val ; 

}

int main()

{

  int n , t ;

  scanf("%d%d" , &n , &t) ;

  build(1 , 0 , t) ;

  update(1 , 0 , t , 0 , 0) ;

  for(int i = 1 ; i <= n ; i ++)scanf("%d%d" , &p[i].l , &p[i].r) ;

  sort(p + 1 , p + 1 + n , cmp) ;

  for(int i = 1 ; i <= n ; i ++)

  {int mi = query(1 , 0 , t , p[i].l - 1 , p[i].r - 1) + 1 ;update(1 , 0 , t , p[i].r , mi) ;

  }

  int ans = query(1 , 0 , t , t , t) ;

  printf("%d\n" , ans == inf ? -1 : ans) ;

  return 0 ;

}

一个小技巧

对4294967296取模相当于unsigned自然溢出

卢卡斯定理
在这里插入图片描述

平面最近点对

#include <bits/stdc++.h>

using namespace std ;

const int maxn = 1000001 ;

const double inf = 2 << 20 ;

int n , th[maxn] , e ;

struct node {

  double x , y ;

}t[maxn] ;

bool cmp(node a , node b) {

  if(a.x != b.x) return a.x < b.x ;

  return a.y < b.y ;

}

bool cmps(const int &a, const int &b) { return t[a].y < t[b].y; }

double lenth(int i, int j) {

  double xx = (t[i].x - t[j].x) * (t[i].x - t[j].x) ;

  double yy = (t[i].y - t[j].y) * (t[i].y - t[j].y) ;

  return sqrt(xx + yy) ;

}

double dive(int l , int r) {

  if(l == r) return inf ;

  if(r - l == 1) return lenth(l , r) ;

  int mid = (l + r) >> 1 ;

  double d1 = dive(l , mid) ; 

  double d2 = dive(mid + 1 , r) ;

  double d = min(d1 , d2) ;

  e = 0 ;

  for(int i = l ; i <= r ; i ++) {if(fabs(t[i].x - t[mid].x) < d) th[++e] = i ;

  }

  for(int i = 1 ; i <= e ; i ++) {for(int j = i + 1 ; j <= e ; j ++) {if(fabs(t[th[i]].y - t[th[j]].y) >= d) continue ;double dd = lenth(th[i] , th[j]) ;if(d > dd) d = dd ;}

  }

  return d ; 

}

int main() {

  cin >> n ;

  for(int i = 1 ; i <= n ; i ++) cin >> t[i].x >> t[i].y ;

  sort(t + 1 , t + 1 + n , cmp) ;

  printf("%.4lf" , dive(1 , n)) ;

  return 0 ;

}

逆序对

#include <bits/stdc++.h>

using namespace std ;

const int maxn = 5e5 + 10 ;

int a[maxn] , b[maxn] , n ;

long long ans ;

void MergeSort(int l , int r) {

  if(l == r) return ;

  int mid = (l + r) >> 1 ;

  MergeSort(l , mid) ;

  MergeSort(mid + 1 , r) ;

  int i = l , j = mid + 1 , t = l ;

  while(i <= mid && j <= r) {if(a[i] > a[j]) {

​      ans += mid - i + 1 ; // 出现逆序对

​      b[t++] = a[j] ;

​      j ++ ;}else {

​      b[t++] = a[i] ;

​      i ++ ;}

  }

  while(i <= mid) {

​    b[t++] = a[i] ;

​    i ++ ;

  }

  while(j <= r) {

​    b[t++] = a[j] ;

​    j ++ ;

  }

  for(int i = l ; i <= r ; i ++) a[i] = b[i] ;

}

int main() {

  cin >> n ;

  for(int i = 1 ; i <= n ; i ++) scanf("%d" , &a[i]) ;

  MergeSort(1 , n) ;

  cout << ans ;

  return 0 ;

}

离散化
在这里插入图片描述

#include <bits/stdc++.h>

using namespace std ;

struct node {

  int pos , num ;

}a[200100] ;

int n , m ;

int cnt = 0 ;

int num[100100] ; 

bool cmp(node x , node y) {

  return x.pos < y.pos ;

}

int main() {

  scanf("%d%d" , &n , &m) ;

  for(int i = 1 ; i <= m ; i ++) {int l , r ;scanf("%d%d" , &l , &r) ;

​    a[++cnt].pos = l ;

​    a[cnt].num = 1 ;

​    a[++cnt].pos = r + 1 ;

​    a[cnt].num = -1 ;

  }

  sort(a + 1 , a + 1 + cnt , cmp) ;

  int sum = 0 ;

  int maxn = 0 ;

  for(int i = 1 ; i <= cnt ; i ++) {

​    sum += a[i].num ;if(a[i].pos != a[i+1].pos) {

​      num[sum] += a[i+1].pos - a[i].pos ;

​      maxn = max(maxn , sum) ;}

  }

  int jin = 0 , yin = 0 , tong = 0 ;

  for(int i = maxn ; i >= 0 ; i --) {

​    num[i] = num[i] + num[i+1] ;if(jin == 0 && num[i] >= (n + 9) / 10) jin = i ;if(yin == 0 && num[i] >= (n + 3) / 4) yin = i ;if(tong == 0 && num[i] >= (n + 1) / 2) tong = i ; 

  }

  jin = max(1 , jin) ;

  yin = max(1 , yin) ;

  tong = max(1 , tong) ;

  cout << num[jin] << " " << num[yin] - num[jin] << " " << num[tong] - num[yin] ;

  return 0 ;

}

筛法DP
在这里插入图片描述

#include <bits/stdc++.h>

using namespace std ;

long long f[4000002] ;

long long ans ;

const int mod = 1e9 + 7 ;

int calc(int x) {

  int ans = 10 ;

  for(int i = 1 ; i <= x ; i ++) 

  ans = ans * 10 ;

  return ans ;

}

int main() {

  int n ;

  cin >> n ;

  for(int i = 2 ; i <= n ; i ++) {if(f[i] == 0) {

​      f[i] = i ;for(int j = 2 ; j * i <= n ; j ++) {if(f[j] == 0) continue ;

​        f[i*j] = (f[j] * calc(log10(i)) + i) % mod ;}}

  }

  for(int i = 2 ; i <= n ; i ++) {

​    ans = (ans + f[i]) % mod ;

  }

  cout << ans ; 

}

字符串hash
在这里插入图片描述

#include <bits/stdc++.h>

using namespace std ;

string s1 , s2 ;

int cnt = -2 ;

long long a[100100] , b[100100] ;

const int mod = 1e9 + 7 ;

long long ans = 0 ;

long long Pow(long long a , long long b) {

  long long ans = 1 ;

  while(b) {if(b & 1) ans = ans * a % mod ;

​    a = a * a % mod ;

​    b = b >> 1 ; 

  }

  return ans ;

}

void Hash(string s , long long a[]) {

  int l = s.size() ;

  a[0] = s[0] - 'a' ;

  for(int i = 1 ; i < l ; i ++) a[i] = (a[i-1] * 233 + s[i] - 'a') % mod ;

}

long long find(long long a[] , int l , int r) {

  if(l == 0) return a[r] ;

  long long tmp ;

  tmp = ((a[r] - a[l-1] * Pow(233 , r - l + 1) % mod) % mod + mod) % mod ;

  return tmp ;

} 

int main() {

  cin >> s1 >> s2 ;

  int lenth1 = s1.size() ;

  int lenth2 = s2.size() ;

  Hash(s1 , a) ;

  Hash(s2 , b) ;

  for(int i = 0 ; i < min(lenth1 , lenth2) ; i ++)if(s1[i] != s2[i]) {

​      cnt = i - 1 ;break ;}

  if(cnt == -2) cnt = min(lenth1 , lenth2) - 1 ;

  if(cnt == -1) {

​    cout << 0 ;return 0 ;

  }

  for(int i = 0 ; i <= cnt ; i ++) {int l = 0 , r = lenth1 - 1 ;while(l <= r) {int mid = l + r >> 1 ;long long sh = a[mid] ;long long th ;if(i + 1 + mid >= lenth2) th = -1 ;else th = find(b , i + 1 , i + 1 + mid) ;if(sh == th) l = mid + 1 ;else r = mid - 1 ;}

​    ans += l ;

  }

  cout << ans ;

  return 0 ;

}

快读模板

template<typename T> void read(T &x)

{

  x=0;char ch=getchar();long long f=1;

  while(!isdigit(ch)) {if(ch=='-') f*=-1;ch=getchar();}

  while(isdigit(ch)) {x=x*10+ch-48;ch=getchar();} x*=f;

}

快写模板:

template<typename T> void write(T x)

{

  if(x<0) {putchar('-');x=-x;}

  if(x>9) write(x/10);

  putchar(x%10+'0');

}

线段树

1.区间和
#include<bits/stdc++.h>

#define maxn 100010

using namespace std;

template<typename T> void read(T &x)

{

  x=0; char ch=getchar(); long long f=1;

  while(!isdigit(ch)) {if(ch=='-') f*=-1;ch=getchar();}

  while(isdigit(ch)) {x=x*10+ch-48;ch=getchar();} x*=f;

}

template<typename T> void write(T x)

{

  if(x<0) {putchar('-');x=-x;}

  if(x>10) write(x/10);

  putchar(x%10+'0');

}

struct tree

{

  int l,r,sum,add;

}t[maxn*4];

int a[maxn];

void spread(int p)

{

  if(t[p].add==0) return;

  t[p*2].sum+=(t[p*2].r-t[p*2].l+1)*t[p].add;t[p*2].add+=t[p].add;

  t[p*2+1].sum+=(t[p*2+1].r-t[p*2+1].l+1)*t[p].add;t[p*2+1].add+=t[p].add;

  t[p].add=0;

}

void build(int p,int l,int r)

{

  t[p].l=l;t[p].r=r;

  if(l==r) {t[p].sum=a[l];return;}

  int mid=(l+r)>>1;

  build(p*2,l,mid);

  build(p*2+1,mid+1,r);

  t[p].sum=t[p*2].sum+t[p*2+1].sum;

}

void change(int p,int l,int r,int d)

{

  if(t[p].l>=l&&t[p].r<=r) {t[p].sum+=(t[p].r-t[p].l+1)*d;t[p].add+=d;return;}

  int mid=(t[p].l+t[p].r)>>1;

  spread(p);

  if(l<=mid) change(p*2,l,r,d);

  if(r>mid) change(p*2+1,l,r,d);

  t[p].sum=t[p*2].sum+t[p*2+1].sum;

}

int ask(int p,int l,int r)

{

  if(t[p].l>=l&&t[p].r<=r) return t[p].sum;

  int mid=(t[p].l+t[p].r)>>1; 

  spread(p);

  int val=0;

  if(l<=mid) val+=ask(p*2,l,r);

  if(r>mid) val+=ask(p*2+1,l,r);

  return val;

}

int main()

{

  int n,m; read(n); read(m);

  for(int i=1;i<=n;i++) read(a[i]);  

  build(1,1,n);

  for(int i=1;i<=m;i++)

  {int e; read(e);if(e==1){int l,r,d; read(l); read(r); read(d);change(1,l,r,d);}if(e==2){int l,r; read(l); read(r);write(ask(1,l,r));

​      cout<<endl;}

  }

  return 0;

}
2.区间最大值
#include<bits/stdc++.h>

#define ll long long

#define inf 0x7fffffff

using namespace std;

template<typename T> void read(T &x)

{

  x=0;char ch=getchar(); ll f=1;

  while(!isdigit(ch)) {if(ch=='-') f*=-1; ch=getchar();}

  while(isdigit(ch)) {x=x*10+ch-48; ch=getchar();} x*=f;

}

template<typename T> void write(T x)

{

  if(x<0) {putchar('-');x=-x;}

  if(x>9) write(x/10);

  putchar (x%10+'0'); 

}

struct Tree

{

  int l,r,maxn;

}t[10005*4];

int a[10005];

void build(int p,int l,int r)

{

  t[p].l=l; t[p].r=r;

  if(l==r) {t[p].maxn=a[l];return;}

  int mid=(l+r)>>1;

  build(p*2,l,mid); build(p*2+1,mid+1,r);

  t[p].maxn=max(t[p*2].maxn,t[p*2+1].maxn);

}

void change(int p,int x,int y)

{

  if(t[p].l==t[p].r){t[p].maxn=y;return ;}

  int mid=(t[p].l+t[p].r)>>1;

  if(x<=mid) change(p*2,x,y); else change(p*2+1,x,y);

  t[p].maxn=max(t[p*2].maxn,t[p*2+1].maxn);

}

int ask(int p,int l,int r)

{

  if(t[p].l>=l&&t[p].r<=r) return t[p].maxn;

  int mid=(t[p].l+t[p].r)>>1;

  int val=0;

  if(l<=mid) val=ask(p*2,l,r);

  if(r>mid) val=max(val,ask(p*2+1,l,r));

  return val;

}

int main()

{

  int n,m; read(n); read(m);

  for(int i=1;i<=n;i++) read(a[i]);

  build(1,1,n);

  for(int i=1;i<=m;i++)

  {int e; read(e);if(e==1){int x,y; read(x); read(y);change(1,x,y);}if(e==2){int l,r; read(l); read(r);int ans=ask(1,l,r);write(ans);

​      cout<<endl;}

  }

  return 0;

}

欧拉函数:

 int ans = n ;

 for(int i = 2 ; i <= sqrt(n) ; i ++) {

  if(n % i == 0) {

   ans = ans / i * (i - 1) ;

   while(n % i == 0) n /= i ;

  }

 }

 if(n > 1) ans = ans / n * (n - 1) ;

利用埃式筛法,求出2-n中每个数的欧拉函数:

 for(int i = 2 ; i <= n ; i ++) phi[i] = i ;

 for(int i = 2 ; i <= n ; i ++)

  if(phi[i] == i)

   for(int j = i ; j <= n ; j += i)

​    phi[j] = phi[j] / i * (i - 1) ;

利用线性筛法的思想:

int v[MAX_N] , prime[MAX_N] , phi[MAX_N] ;

 void euler(int n) {

  memset(v , 0 , sizeof v) ;

  m = 0 ;

  for(int i = 2 ; i <= n ; i ++) {

   if(v[i] == 0) {

​    v[i] = i , prime[++m] = i ;

​    phi[i] = i - 1 ;

   }

   for(int j = 1 ; j <= m ; j ++) {if(prime[j] > v[i] || prime[j] > n / i) break ;

​    v[i * prime[j]] = prime[j] ;

​    phi[i * prime[j]] = phi[i] * (i % prime[j] ? prime[j] - 1 : prime[j]) ;

   }

  }

 }

费马小定理求逆元(快速幂):

  int power(int a , long long b) {

   int c = 1 ;

   for(; b ; b >>= 1) {if(b & 1) c = (long long) c * a % mod ;

​    a = (long long) a * a % mod ;

   }

   return c ;

  }

最大异或和 (Trie树):

在这里插入图片描述

//区间异或和最大转换为前缀和数组中任意两个(距离小于m)的异或最大

//利用trie树优化 

//把每个数转化为01串(31位)存在trie树中

//异或和最大就是高位尽量是1

#include <bits/stdc++.h>

using namespace std ;

const int N = 100010 * 31 , M = 100010 ;

int n , m ;

int s[M] ;

int son[N][2] , cnt[N] , idx ;

void insert(int x , int v)

{

  int p = 0 ;

  for(int i = 30 ; i >= 0 ; i --)

  {int u = x >> i & 1 ;if(!son[p][u]) son[p][u] = ++idx ; 

​    p = son[p][u] ;

​    cnt[p] += v ;

  }

}

int query(int x)

{

  int res = 0 , p = 0 ;

  for(int i = 30 ; i >= 0 ; i --)

  {int u = x >> i & 1 ;if(cnt[son[p][!u]]) p = son[p][!u] , res = res * 2 + 1 ;else res = res * 2 , p = son[p][u] ;

  }

  return res ;

}

int main()

{

  cin >> n >> m ;

  for(int i = 1 ; i <= n ; i ++)

  {int x ;

​    cin >> x ;

​    s[i] = s[i-1] ^ x ;

  }

  int res = 0 ;

  insert(s[0] , 1) ;

  for(int i = 1 ; i <= n ; i ++)

  {if(i > m) insert(s[i-1-m] , -1) ;

​    res = max(res , query(s[i])) ;insert(s[i] , 1) ;

  }

  cout << res ; 

  return 0 ;

}

Trie树 —— 模板题:在这里插入图片描述

int son[N][26], cnt[N], idx;

// 0号点既是根节点,又是空节点

// son[][]存储树中每个节点的子节点

// cnt[]存储以每个节点结尾的单词数量

// 插入一个字符串

void insert(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] ++ ;

}

// 查询字符串出现的次数

int query(char *str)

{

  int p = 0;

  for (int i = 0; str[i]; i ++ )

  {int u = str[i] - 'a';if (!son[p][u]) return 0;

​    p = son[p][u];

  }

  return cnt[p];

}

一个典型的线性DP
在这里插入图片描述

在这里插入图片描述

#include <bits/stdc++.h>

using namespace std ;

#define LL long long 

LL n , k , m ;

LL dp[205][205] ; // dp[i][j] :只考虑前i个数 , 删除了j个数 , 且第i位数被保留的最大收益

int a[250] ;

LL w[250][250] ;

int main() 

{

  cin >> n >> k >> m ;

  for(int i = 1 ; i <= m ; i ++) cin >> a[i] ;

  for(int i = 1 ; i <= n ; i ++)

  for(int j = 1 ; j <= n ; j ++)

  cin >> w[i][j] ;

  for(int i = 2 ; i <= m ; i ++) // 对于第一个数 , 它前面没有数

  for(int j = 0 ; j <= k ; j ++) // 枚举删了多少个数

  for(int l = 1 ; l < i ; l ++) // 固定第i位数 , 枚举删完之后它前面与它相邻的数if(j >= i - l - 1) // i - l - 1 是 第i位数与第l位数相邻需要删除多少位数 , 如果j < i - l - 1 就越界了

​      dp[i][j] = max(dp[i][j] , dp[l][j-(i-l-1)] + w[a[l]][a[i]]) ; 

  LL ans = 0 ;

  for(int i = 0 ; i <= k ; i ++) ans = max(ans , dp[m][i]) ;

  cout << ans ;

}

LCS --> LIS
在这里插入图片描述
第一个序列中的所有元素均不重复 第二个序列中可以重复

考虑两个序列:

A a1 a2 …… an

B b1 b3 …… bn

构造一个数列C , ci表示bi在数列a中的位置 , 如果不存在,设置为-1.

由此 , 求A , B的LCS就变为了求C的最长上升子序列 。(如果最长上升子序列中有-1答案要减一)

#include <bits/stdc++.h>

using namespace std ;

const int N = 1e6 + 10 ;

int n ; 

int a[N] , b[N] ;

int dp[N] ; int len ; int d[N] ;

int pos[N] ;

int c[N] , tot ;

int inf = 0x3f3f3f3f;

int main()

{

  scanf("%d", &n) ;

  memset(pos , -1 , sizeof pos) ;

  for(int i = 1 ; i <= n ; i ++) scanf("%d", &a[i]) , pos[a[i]] = i ;

  bool flag = false;

  for(int i = 1 ; i <= n ; i ++) {scanf("%d", &b[i]);

​    c[++tot] = pos[b[i]];if (c[tot] == -1) 

​      tot -- , flag = true;

  }

  fill(dp, dp + tot, inf);

  int _max = -1;

  for (int i = 1; i <= tot; ++i) {int j = lower_bound(dp, dp + tot, c[i]) - dp;if (_max < j + 1) {

​      _max = j + 1;}

​    dp[j] = c[i];

  }

  cout << _max << endl;

}

单调栈
1.
在这里插入图片描述
在这里插入图片描述

#include <iostream>

#include <cstring>

#include <algorithm>

using namespace std;

const int N = 2010;

int n, m;

char g[N][N];

int l[N], r[N], q[N];

int U[N], D[N], L[N], R[N];

int s[N][N];

int calc(int h[], int n)

{

  h[0] = h[n + 1] = -1;

  int tt = 0;

  q[0] = 0;

  for (int i = 1; i <= n; i ++ )

  {while (h[q[tt]] >= h[i]) tt -- ;

​    l[i] = q[tt];

​    q[ ++ tt] = i;

  }

  tt = 0;

  q[0] = n + 1;

  for (int i = n; i; i -- )

  {while (h[q[tt]] >= h[i]) tt -- ;

​    r[i] = q[tt];

​    q[ ++ tt] = i;

  }

  int res = 0;

  for (int i = 1; i <= n; i ++ )

​    res = max(res, h[i] * (r[i] - l[i] - 1));

  return res;

}

void init()

{

  for (int i = 1; i <= n; i ++ )

  {for (int j = 1; j <= m; j ++ )if (g[i][j] == '1') s[i][j] = s[i - 1][j] + 1;else s[i][j] = 0;

​    U[i] = max(U[i - 1], calc(s[i], m));

  }

  memset(s, 0, sizeof s);

  for (int i = n; i; i -- )

  {for (int j = 1; j <= m; j ++ )if (g[i][j] == '1') s[i][j] = s[i + 1][j] + 1;else s[i][j] = 0;

​    D[i] = max(D[i + 1], calc(s[i], m));

  }

  memset(s, 0, sizeof s);

  for (int i = 1; i <= m; i ++ )

  {for (int j = 1; j <= n; j ++ )if (g[j][i] == '1') s[i][j] = s[i - 1][j] + 1;else s[i][j] = 0;

​    L[i] = max(L[i - 1], calc(s[i], n));

  }

  memset(s, 0, sizeof s);

  for (int i = m; i; i -- )

  {for (int j = 1; j <= n; j ++ )if (g[j][i] == '1') s[i][j] = s[i + 1][j] + 1;else s[i][j] = 0;

​    R[i] = max(R[i + 1], calc(s[i], n));

  }

}

int main()

{

  scanf("%d%d", &n, &m);

  for (int i = 1; i <= n; i ++ ) scanf("%s", g[i] + 1);

  init();

  int Q;

  scanf("%d", &Q);

  while (Q -- )

  {int x, y;scanf("%d%d", &x, &y);

​    x ++, y ++ ;printf("%d\n", max(max(U[x - 1], D[x + 1]), max(L[y - 1], R[y + 1])));

  }

  return 0;

}

题目描述:

在一条水平线上方有若干个矩形 , 求包含于这些矩形的并集内部的最大矩形面积 , 矩形个数 <= 1e5 。

我们先来思考这么一个问题:如果矩形的高度从左到右单调递增 , 那么答案是多少 ?

显而易见 , 我们可以尝试以每个矩形的高度作为最终矩形的高度 , 并把宽度延伸到右边界 , 得到一个矩形 , 在所有这样的矩形面积中取最大值就是答案 。

如果下一个矩形的高度比上一个小 , 那么矩形想利用之前的矩形一起构成一块较大的面积时 , 这块面积的高度就不可能超过该矩形自己的高度。 或换句话说 ,在考虑完上图中的四种情况后 , 下图中打叉的那部分形状就没有丝毫用处了。

既然没有用处 , 为什么不把这些比该矩形高的矩形都删掉呢 , 用一个宽度累加、高度为该矩形自己的高度的新矩形代替呢?这样并不会对后续的计算产生影响。 于是我们维护的轮廓就变成了一个高度始终单调递增的矩

阵序列 , 问题变得可解了 。

 

详细的说 , 我们建立一个栈 , 用来保存若干个矩形 , 这些矩形的高度都是单调递增的 。 我们从左到右依次扫描每个矩形:

如果当前矩形比栈顶矩形高 , 直接进栈 。

否则不断取出栈顶 , 直至栈为空或者栈顶矩形的高度比当前矩形小 。在出栈过程中 , 我们累计被弹出的的矩形的宽度之和 ,并且每次弹出一个矩形 , 就用它的高度乘上累计的宽度去更新答案 。 整个出栈的过程结束后 , 我们把一个高度为当前矩形高度 , 宽度为累计值的新矩形入栈。

整个扫描结束后 , 我们把栈中剩余的矩形依次弹出 ,按照与上面相同的方法更新答案 。 为了简化程序实现 , 也可以增加一个高度为0的矩形a[n+1] , 以避免在扫描结束后栈中有剩余矩形 。
a[n+1] = p = 0 ;

for(int i= 1 ; i <= n + 1 ; i ++) {

  if(a[i] > s[p]) {

​    s[++p] = a[i] ;

​    w[p] = 1 ;

  }

  else {int width = 0 ;while(s[p] > a[i]) {

​      width += w[p] ;

​      ans = max(ans , (long long)width * s[p]) ;

​      p -- ;}

​    s[++p] = a[i] ; 

​    w[p] = width + 1 ; 

  }

}

单调队列
给定一个长度为N的正整数序列(可能有负数) , 从中找出一段长度不超过M的连续子序列 , 使得子序列中的所有数的和最大 。 (N,M <= 3e5)

思路:

计算“区间和”问题 , 一般转化为“两个前缀相减” 的形式进行求阶 。我们先求出S[i] 表示序列里前i项的和 , 则连续子序列[L , R]中数的和就等于S[R] - S[L-1] 。 那么原问题可以转化为:找出两个位置x , y , 使得S[y] - S[x] 最大并且 y - x <= M 。

首先我们枚举右端点I , 当j固定时 , 问题就变为 : 找到一个左端点j , 其中 i-m <= j <= i-1 , 并且S[j]最小。

接下来我们就可以用单调队列求解 。

  1. 判断队头决策与i的距离是否超过M的范围 , 若超出则出队。
  2. 此时队头就是右端点为i时 , 左端点j的最优选择。
  3. 不断删除队尾决策 , 直到队尾对应的S值小于S[i] 。然后把i作为一个新的决策入队。
int l = 1 , r = 1 ;

q[l] = 0 ; // save choice j = 0 

for(int i = 1 ; i <= n ; i ++) {

  while(l <= r && q[l] < i - m) l ++ ; // step 1 

  ans = max(ans , sum[i] - sum[q[l]]) ; // step 2

  while(l <= r && sum[q[r]] >= sum[i]) r -- ; // step 3

  q[++r] = i ;

}

质因数分解的几个技巧
1.求区间[L,R]内的所有质数(L,R很大,但R-L很小)

原理:任何一个合数n必定包含一个不超过sqrt(n)的质因子

实现:用筛法求出2~sqrt®之间的所有质数。对于每个质数p,把[L.R]中能被p整除的数标记。最终所有未被标记的数就是[L,R]中的质数。

2.阶乘分解(把阶乘N!分解质因数)

把1~N每个数分别分解质因数,再把结果合并,时间复杂度过高,为O(Nsqrt(N))。

显然,N!中的每个质因子都不会超过N,我们可以先筛选出1~N的每个质数p,然后先考虑阶乘N!中一共包含了多少个质因子p。

至少包含1个质因子p的有[N/p]个,至少包含2个质因子p的有[N/p2]个,这两者之间有重复统计,因此,在累加后者时只需要累加上[N/p2],而非2*[N/p^2]。

同理能推出N!中质因子p的个数为:[N/p]+[N/p2]+……+[N/plog(p,N)]

莫比乌斯反演

1.1. U函数的埃氏筛求法
#include <bits/stdc++.h>

using namespace std ;

int main() {

 int miu[1000] , v[1000] ;

 for(int i = 1 ; i <= 1000 ; i ++) miu[i] = 1 , v[i] = 0 ;

 for(int i = 2 ; i <= 1000 ; i ++) {

  if(v[i]) continue ;

  miu[i] = -1 ;

  for(int j = 2 * i ; j <= 1000 ; j += i) {

   v[j] = 1 ;

   if((j / i) % i == 0) miu[j] = 0 ;

   else miu[j] *= -1 ;

  }

 }

 for(int i = 1 ; i <= 1000 ; i ++) {

  cout << miu[i] << " " ;

  if(i % 10 == 0) cout << endl ;

 }

}
1.2 线性筛处理
void get_mu(int n)

{

	 mu[1]=1; for(int i=2;i<=n;i++) 
	
	{ 
	
		if(!vis[i])
	
		{
	
			prim[++cnt]=i;mu[i]=-1;
	
		} 
	
		for(int j=1;j<=cnt&&prim[j]*i<=n;j++)
	
		{ 
	
			vis[prim[j]*i]=1; 
			if(i%prim[j]==0)break; 
			else mu[i*prim[j]]=-mu[i]; 
	
		}

	} 

}

1.在这里插入图片描述
下面时g函数的反演公式(f函数类似):
在这里插入图片描述

#include <bits/stdc++.h>

using namespace std ;

typedef long long ll ;

const int N = 1e6 + 10 ;

int f[N] , g[N] ;

int v[N] , miu[N] ;

int main() {

 int n ;

 cin >> n ;

 for(int i = 1 ; i <= N ; i ++) { // 预处理f,g函数

  int t = i ;

  int f1 = 0 , g1 = 1 ;

  while(t) {

   f1 += t % 10 ;

   g1 *= t % 10 ;

   t /= 10 ;

  }

  f[i] = g1 ;

  g[i] = f1 ; //这里f,g函数定义反了,就直接反着做了

 }

 for(int i = 1 ; i <= N ; i ++) miu[i] = 1 , v[i] = 0 ;// 莫比乌斯函数

 for(int i = 2 ; i <= N ; i ++) {

  if(v[i]) continue ;

  miu[i] = -1 ;

  for(int j = 2 * i ; j <= N ; j += i) {

   v[j] = 1 ;

   if((j / i) % i == 0) miu[j] = 0 ;

   else miu[j] *= -1 ;

  }

 }

 ll ans = 0 ; //预处理结束

 for(int i = 1 ; i <= n ; i ++) {

  if(!miu[i]) continue ;

  ll tmp = 0 ;

  for(int j = 1 ; j <= n / i ; j ++) {

   tmp += f[j * i] * (n / i + 1 - j) ; // f函数的贡献 即题目中g函数的贡献

   tmp += g[j * i] * j ;        // g函数的贡献 即题目中f函数的贡献

  }

  ans += miu[i] * tmp ; 

 }

 cout << ans ;

 return 0 ;

}

2.在这里插入图片描述

#include <bits/stdc++.h>

const int N = 5e4 + 100 ;

using namespace std ;

#define LL long long

LL u[N] ;

LL sum_u[N] ;

bool v[N] ;

int k ;

void start()

{

  for(int i = 1 ; i < N ; i ++) u[i] = 1 ;

  for(int i = 2 ; i < N ; i ++) {if(v[i]) continue ;

​    u[i] = -1 ;for(int j = 2 * i ; j < N ; j += i) {

​      v[j] = 1 ;if((j / i) % i == 0) u[j] = 0 ;else u[j] *= -1 ;}

  }

  for(int i = 1 ; i < N ; i ++)

​    sum_u[i] = sum_u[i-1] + u[i] ;

  

}

int work(int n , int m)

{

  int res = 0 ;

  for(int l = 1 , r ; l <= min(n / k , m / k) ; l = r + 1)

  {

​    r = min(n / (n / l) , m / (m / l)) ;

​    res += (sum_u[r] - sum_u[l-1]) * (n / (l * k)) * (m / (l * k)) ;

  }

  return res ;

}

int main()

{

  start() ;

  int n ;

  scanf("%d" , &n) ;

  while(n --)

  {int a , b , c , d ;

​    cin >> a >> b >> c >> d >> k ;int ans ;

​    ans = work(b , d) - work(a - 1 , d) - work(c - 1 , b) + work(a - 1 , c - 1) ;

​    cout << ans << endl ;

  }

}

整除分块:

1.向上取整

#include <bits/stdc++.h>

using namespace std ;

\#define LL int

LL t , n , m ;

LL up(LL x , LL y) {

  return x / y + (x % y != 0) ;

}

int main() {

  scanf("%d" , &t) ;

  while(t --) {scanf("%d%d" , &n , &m) ;

​    LL val = 0x7f7f7f7f ;for(LL a = 1 ; a <= n ; a ++) {

​      val = min(val , (up(m , a) - 1) * a) ;if(up(m , a) == 1) break ;else a = (m - 1) / (up(m , a) - 1) ;}printf("%d\n" , n - m + val) ;

  }

  return 0 ;

}

一个动态规划题:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值