算法竞赛进阶指南——笔记
0X00
- 0x01位运算
知识:
- 位移(左移右移)
快速幂 快速乘等等 - 二进制压缩
(隐隐约约指向状压DP)基操- 取数
- 位运算特点 二进制下不进位
利用这特点可以我们可以做题 - 小技巧(应用邻接表
我不用)- n为偶数时 n ^ 1 = n + 1;
- n为基数时 n ^ 1 = n - 1;
- lowbit 运算 最低位的1与之后的0 广泛应用于各地
例题
- CH0101/Acwing
(vjudge)
求a^b的次方mod p
数据范围为1e9/1e18
我们已知所有数可以拆分为2进制表示,所以我们可以拆分 b 得到 b = . . . . . . b = ...... b=......
即快速幂
// 对于 1e9 的数据 可以强制类型转换
ans = 1;
ans %= p;
while(b){
if(b&1)ans = (long long)ans * a % p;
a = (long long)a * a % p;
b >>= 1;
}
printf("%d",ans%p);
对于1e18的数据我们可以用高精 龟速乘
#define intl long long
intl mul(intl a,intl b,intl p){
intl ans = 0%p;
while(b){
if(b&1)ans =( ans + a ) % p;
a = ( a + a ) % p;
b >>= 1;
}
return ans;
}
intl power(intl a,intl b,intl p){
intl ans = 1%p;
while(b){
if(b&1)ans = mul(ans,a,p) % p;
a = mul(a,a,p) % p;
b >>= 1;
}
return ans;
}
int main(){
intl x,y,p;
while(cin >> x >> y >> p){
printf("%lld\n",power(x,y,p));
}
return 0;
}
有另一个快速乘
#define intl unsigned long long
intl mul(intl a,intl b,intl p){
return (a*b -(intl)((long double)a/p*b)*p+p)%p;
}
- 状压DP初级(eee)
简单描述
f[i][j]表示在 j 点上我们有 i 为我们的状态
在 j 点之前,先到点 k
可以得出转移方程
f[i][j] = min(f[i][j],f[i^(1<<j)][k] + fin[j][k]);
#include<bits/stdc++.h>
using namespace std;
int n;
int fin[25][25];
int f[1<<20][20];
int main(){
cin >> n;
for(int i = 0;i < n;i ++)
for(int j = 0;j < n;j ++)
cin >> fin[i][j];
memset(f,0x3f,sizeof(f));
f[1][0] = 0;
for(int i = 1;i < (1<<n);i ++)
for(int j = 0;j < n;j ++)
if( (i>>j) & 1)
for(int k = 0;k < n;k ++)
if((i ^ (1<<j))>>k&1)
f[i][j] = min(f[i][j],f[i^(1<<j)][k] + fin[j][k]);
printf("%d",f[(1<<n)-1][n-1]);
return 0;
}
- 二进制特点 互不影响LGP2114
int n,m;
pair<string,int>op[100100];
int answer,answ;
int deal(int pet,int id){
for(int i = 1;i <= n;i ++){
int x = op[i].second >> pet & 1;
if(op[i].first[0] == 'A'){
id = id & x;
}
else if(op[i].first[0] == 'O'){
id = id | x;
}
else if(op[i].first[0] == 'X'){
id = id ^ x;
}
}
return id;
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++){
cin >> op[i].first;cin >> op[i].second;
}
for(int i = 30;i >= 0;i --){
int a = deal(i,0);//我们得出该位填0/1时
int b = deal(i,1);//我们可以得出的结果
if(answ + (1<<i) <= m && b > a){
answ += (1<<i);answer += b * (1<<i);
}
else answer += a * (1<<i);
}
printf("%d",answer);
return 0;
}
- lowbit
lowbit(x) = x & (-x);
- 内置函数
int __builtin_ctz(unsigned int x)
int __builtin_ctzll(unsigned long long x)
返回x的二进制下最低位的1后面有多少个0
int __builtin_popcount(unsigned int x)
int __builtin_popcountll(unsigned long long x)
返回x的二进制下有多少位是1
the end for the first and we have the second
- 0x02递推递归
知识
- 递归和递推为程序遍历状态空间的两种基本方式
当一个问题我们可以在边界或小范围或特殊情况下,可以知道答案
并且我们可以以相似的方法扩展状态空间
我们可以考虑用递归递推求解~~(有DP味了)~~ - 简单的应用
我们可以用递归递推枚举
如 多项式型,指数型,组合型,排列型 - 分治,对于一些问题我们可以将问题划分为若干规模更小的问题
递归求解
回溯时推出原解 - 分形
(暂不会) - 递归机器的实现
例题
-
我们先来简单的枚举
- 指数型枚举(类似 状压DP,可以用位运算)2^n种
- 排列型枚举(额 如我要跑一个图 不太好理解)
全排列(递归) - 组合型枚举
从1 ~ n中选出m个(剪枝)
----------------指数型 int n; vector<int>v; void dfs(int x){ if(x == n + 1){ for(int i = 0;i < v.size();i ++){ cout << v[i] << ' '; } cout << endl; return ; } dfs(x+1); v.push_back(x); dfs(x+1); v.pop_back(); } int main(){ scanf("%d",&n); dfs(1); return 0; } ----------------排列型 void dfs(int x){ if(x == n + 1){ for(int i = 0;i < v.size();i ++)cout << v[i] << ' '; cout << endl; return ; } for(int i = 1;i <= n;i ++){ if(vis[i] == 0){ vis[i] = 1; ord[i] = x; v.push_back(i); dfs(x+1); v.pop_back(); ord[i] = 0; vis[i] = 0; } } } ----------------组合型 void dfs(int x){ if(v.size() > m || v.size() + (n - x + 1) < m) return ; if(v.size() == m){ for(int i = 0;i < v.size();i ++)cout << v[i] << ' '; cout << endl; return ; } v.push_back(x); dfs(x+1); v.pop_back(); dfs(x+1); }
-
我们可以继续学 分治
我认为分治挺好(还有CDQ分治&& 动态树分治)/*********************************************************** > File Name: 02T2.cpp > Author: lan_m > QQ: 2867930696 > Created Time: 2021/9/7 20:40:35 > Modified Time:2021/9/7 22:17:35 > fighting for night *******************************************************/ #include <bits/stdc++.h> // (分治,有一点数学知识) using namespace std; const int mod = 9901; int a,b; int power(int x,int y){ int sum = 1; while(y){ if(y&1)sum = sum % mod * x % mod; x = x % mod * x % mod; y>>=1; } return sum; } int sum(int p,int t){ if(t == 0){ return 1; } if(t % 2 == 0){ return ((1 + power(p,t/2)) % mod * sum(p,(t/2)-1) % mod + power(p,t))%mod; } else { return (1 + power(p,(t+1)/2))%mod * sum(p,(t-1)/2)%mod; } } int answer = 1; void deal(int x){ for(int i = 2;i <= x;i ++){ int cnt = 0; while(x % i == 0){ x /= i; cnt++; } if(cnt){ answer =( answer * sum(i,cnt * b)%mod ) % mod; } } } int main(){ scanf("%d%d",&a,&b); deal(a); if(a != 0)printf("%d",answer); else printf("0"); return 0; } //--------------------------------// /*********************************************************** > File Name: 02T4.cpp > Author: lan_m > QQ: 2867930696 > Created Time: 2021/9/8 8:24:18 > Modified Time:2021/9/8 8:24:18 > fighting for night *******************************************************/ //汉诺塔问题 #include <bits/stdc++.h> using namespace std; int n; int d[100100];//三塔问题 int f[100100];//四塔 int main(){ n = 12; for (int i = 1;i <= n;i ++) { d[i] = d[i-1] * 2 + 1; } memset(f,0x3f,sizeof(f)); f[0] = 0; for (int i = 1;i <= n;i ++) { for (int j = 0;j <= i;j ++) { f[i] = min(f[i] , 2 * f[j] + d[i-j]); } } for (int i = 1;i <= n;i ++) { cout << f[i] << endl; } return 0; }
-
分形
tomorrow see code the code was so hard to write so i don't want to do it
-
递归机器实现
code is missing
the end for the second and we have the third
- 0x03前缀和与差分
知识
- 对于一个序列,我们可以递推出前缀和
用前缀和我们可以求出部分区间和
对于一个矩阵可以做二维前缀和 - 我们对一个序列做差分,可以发现差分的前缀和是原数列,前缀和的差分也是原数列
差分将区间操作转化为差分序列上单点操作
例题
- 前缀和 注意细节
for (int i = 1;i <= 5005;i ++) { for (int j = 1;j <= 5005;j ++) { s[i][j] = s[i][j] + s[i][j-1] + s[i-1][j] - s[i-1][j-1]; } } int maxx = 0; for (int i = r;i <= 5005; i ++) { for (int j = r;j <= 5005;j ++) { maxx = max(maxx , s[i][j] - s[i - r][j] - s[i][j-r] + s[i-r][j-r]); } } printf("%d",maxx);
- 差分
for (int i = 2;i <= n;i ++) { d[i] = a[i] - a[i-1]; } //去重 map<pair<int,int>,bool>dxy; if(dxy[make_pair(a,b)])continue; dxy[make_pair(a,b)] = 1;
- 0x04二分
- 整数二分
可以用两套二分写法 - 实数二分
- 三分
- 二分答案
例题:
innovative business (acwing 113)
- 实现,两种方法,两种边界
/*********************************************************** > File Name: 04T1.cpp > Author: lan_m > QQ: 2867930696 > Created Time: 2021/9/13 16:06:49 > fighting for night *******************************************************/ #include <bits/stdc++.h> using namespace std; int n; int a[10010]; int main () { scanf("%d",&n); for (int i = 1;i <= n;i ++) scanf("%d",&a[i]); sort(a+1,a+1+n); int x; cin >> x; int l = 0;int r = n; while ( l < r ) { int mid = ( l + r + 1 ) >> 1; if ( a[mid] <= x ) l = mid; else r = mid - 1; } cout << a[l] << endl; l = 1;r = n + 1; while ( l < r ) { int mid = ( l + r ) >> 1; if ( a[mid] >= x ) r = mid; else l = mid + 1; } cout << a[l] << endl; return 0; }
- 这是关于实数域上的二分
两种方法
确定精度 或 固定循环次数/*********************************************************** > File Name: 04T2.cpp > Author: lan_m > QQ: 2867930696 > Created Time: 2021/9/13 16:19:02 > fighting for night *******************************************************/ #include <bits/stdc++.h> using namespace std; int main () { //1 while (1 + 1e-5 < r) { double mid = ( l + r ) / 2; if (calc(mid)) r = mid;else l = mid; } //2 for (int i = 0;i < 100;i ++) { double mid = ( l + r ) / 2; if (calc(mid)) r = mid;else l = mid; } return 0; }
- 三分求单峰函数
missing
- 二分答案转判定(这挺好用的)
例题 二分实数域还是用**while** #include <bits/stdc++.h> using namespace std; const double eps = 1e-5; int n,F; double a[100100]; double sum[100100]; double maxn = 0; bool check(double mid) { for (int i = 1;i <= n;i ++) sum[i] = sum[i-1] + a[i] - mid; double ans,val; ans = -1e8;val = 1e8; for (int i = F;i <= n;i ++) { val = min (val , sum[i-F]); ans = max (ans , sum[i] - val); } if (ans >= 0) return true; else return false; } int main() { scanf("%d%d", &n,&F); for (int i = 1; i <= n; i ++ ) { scanf("%lf", &a[i]); } double l = -1e6;double r = 1e6; while (l + eps < r ) { double mid = ( l + r ) / 2; if( check(mid) )l = mid; else r = mid; } printf("%lld", (long long)(r * 1000)); return 0; }
long time ago
- 0x05排序
- 离散化
(这是一个悲伤的故事)
一种映射
可以映射大小,也可以映射其他东西
比较好的应用,就我们算法与值域有关时,我们可以把时间复杂度降为与n(n个整数)有关。 - 中位数
众所周知,中位数有一些优美的性质 - 经典的 第K大数 问题
- 逆序对,我们可以用msort(归并排序),也可以用树状数组,一些其他东西
例题
Cinema cf670c
货仓选址 acwing 104
七夕祭 acwing 105
Running Median acwing106
Ultra-QuickSort acwing107
奇数码问题 acwing108
- Cinema
/*********************************************************** > File Name: 05T1-CF670C.cpp > Author: lan_m > QQ: 2867930696 > Created Time: 2021/9/14 10:23:34 > fighting for night *******************************************************/ #include <bits/stdc++.h> using namespace std; const int N = 2e5+10; int n,m; int peo_lang[N]; int cin_lang[N]; int cin_fan[N]; int lang[N<<2]; int sum[N]; int cnt; int main () { scanf("%d",&n); for (int i = 1;i <= n;i ++) { scanf("%d",&peo_lang[i]);lang[++cnt] = peo_lang[i]; } scanf("%d",&m); for (int i = 1;i <= m;i ++) { scanf("%d",&cin_lang[i]);lang[++cnt] = cin_lang[i];} for (int i = 1;i <= m;i ++) { scanf("%d",&cin_fan[i]);lang[++cnt] = cin_fan[i];} sort(lang+1,lang+cnt+1); cnt = unique(lang+1,lang+cnt+1) - lang; for (int i = 1;i <= n;i ++) { peo_lang[i] = lower_bound(lang+1,lang+cnt+1,peo_lang[i]) - lang; sum[peo_lang[i]] ++; } for (int i = 1;i <= m;i ++) { cin_lang[i] = lower_bound(lang+1,lang+cnt+1,cin_lang[i]) - lang; cin_fan[i] = lower_bound(lang+1,lang+cnt+1,cin_fan[i]) - lang; } int maxn = 0,op = 1; for (int i = 1;i <= m;i ++) { if (maxn < sum[cin_lang[i]]) { maxn = sum[cin_lang[i]]; op = i; } else if (maxn == sum[cin_lang[i]]) { if (sum[cin_fan[i]] > sum[cin_fan[op]]) { op = i; } else if (sum[cin_fan[i]] == sum[cin_fan[op]]) { op = min(op,i); } } } cout << op << endl; return 0; }
- 七夕祭
missing
- Running Median
动态维护中位数
我们可以用对顶堆在线处理
也可以尝试离线处理/*********************************************************** > File Name: LGP1168.cpp > Author: lan_m > QQ: 2867930696 > Created Time: 2021/9/14 14:21:51 > fighting for night *******************************************************/ //比较麻烦的写法 #include <bits/stdc++.h> using namespace std; int n; priority_queue<int> que1; priority_queue<int,vector<int>,greater<int> >que2; int main () { scanf("%d",&n); for (int i = 1;i <= n; i++) { int a;scanf("%d",&a); if (i == 1) { que2.push(a);cout << a << endl;continue;} if (a < que2.top())que1.push(a); else que2.push(a); if ( i%2==1 ) { if (que1.size() + 1 > que2.size()) { int tp = que1.top();que1.pop(); que2.push(tp); } else if (que1.size() + 1 < que2.size()) { int tp = que2.top();que2.pop(); que1.push(tp); } } else if ( i%2==0 ) { if (que1.size() > que2.size()) { int tp = que1.top();que1.pop(); que2.push(tp); } else if (que1.size() < que2.size()) { int tp = que2.top();que2.pop(); que1.push(tp); } } if ( i%2 == 1 ) cout << que2.top() << endl; } return 0; }
- 求逆序对
missing
- 奇数码问题
missing
- 0x06倍增
知识 (香甜的知识)
- 倍增 (与二进制拆分)
- ST算法(RMQ)
Nlog(N)的预处理,查询为O(1)
例题
Genius ACM
(ST算法) 可以解决RMQ(区间查询最值问题)
-
倍增思想
/*********************************************************** > File Name: genius(acm).cpp > Author: lan_m > QQ: 2867930696 > Created Time: 2021/9/15 20:45:22 > fighting for night *******************************************************/ #include <bits/stdc++.h> using namespace std; #define int long long const int N = 5e5+10; int n,m,Q; int a[N],b[N],c[N]; void merge(int l,int mid,int r) { int i = l,j = mid+1,k = l; while (i <= mid && j <= r) { if (c[i] <= c[j]) b[k++] = c[i++]; else b[k++] = c[j++]; } while (i <= mid) b[k++] = c[i++]; while (j <= r) b[k++] = c[j++]; } bool check(int s,int mid,int e) { for (int i = mid+1;i <= e;i ++) c[i] = a[i]; sort(c+mid+1,c+e+1); merge(s,mid,e); int sum = 0; for (int i = 1;i <= (e - s + 1)>>1 && i <= m;i ++) { sum += (b[e-i+1] - b[s+i-1]) * (b[e-i+1] - b[s+i-1]); } if (sum <= Q) { for (int i = s;i <= e;i ++) c[i] = b[i]; return true; } return false; } signed main () { int T;scanf("%lld",&T); while (T--) { scanf("%lld%lld%lld",&n,&m,&Q); for (int i = 1;i <= n;i ++) scanf("%lld",&a[i]); int p = 1,ans = 0; int st = 1,ed = 1; c[1] = a[1]; while (ed <= n) { if (p == 0) { p = 1; ans ++; st = ed + 1; ed += 1; c[st] = a[st]; } else if (ed + p <= n && check(st,ed,ed+p)) { ed = ed + p; p <<= 1; if (ed == n)break; } else p>>=1; } if( ed == n ) ans ++; printf("%lld\n",ans); } return 0; }
- 贪心
贪就对了
暂不跟新~~~(