逆推法求逆元:
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 ;
}
前缀和与差分的两个小技巧:
-
数列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取余的两个相等的并且相距最远的数的距离即为答案。
-
对于一个数列,可以选择某一段同时加或减一个数,要求最后数列中每一项都相等,考虑用差分。
给出几段区间,求最少用多少个区间覆盖整段线段的方法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的三个剪枝优化:
- 数据的排序优化
- (题目问是否能搜出一个解(搜到了直接exit(0);结束整个程序),即解是否存在)枚举每个可选项时,若dfs回溯回来,可能在某种条件下这个循环就没必要继续进行了,可以直接return。
- 若明确搜索起点的复杂性,尽量选择分支少的起点。(如树的第一层的点尽量少)
双向搜索:
- 位运算不确定运算顺序时一定要加括号。
- 二进制状态压缩适用于物品等数量不多(20个左右),用二进制下的每一位代表这个物品,1为选,0为不选,共有2 ^ N 个状态
- 通过一组数据计算答案时,若这组数据可以分为两组数据并且通过两组数据可以计算答案,可以用双指针。(注意两组数据都要有序,第一组上升序列,第二组下降序列,反之亦可)
- 多个搜索分支的处理。
#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]最小。
接下来我们就可以用单调队列求解 。
- 判断队头决策与i的距离是否超过M的范围 , 若超出则出队。
- 此时队头就是右端点为i时 , 左端点j的最优选择。
- 不断删除队尾决策 , 直到队尾对应的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 ;
}
一个动态规划题: