文章目录
- Dinic算法求网络流
- 弗洛伊德算法
- 容斥原理
- 最大三角形面积(凸包+旋转卡壳)
- 大数加法(C++)
- 大数阶乘(C++)
- 常用函数与STL标准模板库
- STL-pair
- STL-vector
- STL-stack
- 优先队列:
- STL-map
- STL-bitset
- STL-algorithm
- 26进制下的大数加法
- 数论模板
- 快速幂模板
- 矩阵快速幂
- 欧拉函数PHI
- 分解质因数法
- 筛法欧拉函数
- 单独求解
- 线性筛
- 凸包模板
- 欧几里得拓展GCD
- 线性方程组(高斯消元)
- 列主元
- 全主元
- 高斯消元(自由变元,一类开关问题,位运算操作)
- 模线性方程(组)
- 公共部分(拓展GCD)
- 模线性方程
- 模线性方程组(互质)
- 模线性方程组(不要求互质)
- 素数相关
- 判断小于maxn的数是不是素数
- 查找出小于等于maxn的素数(生成连续素数表)
- 随机素数测试
- 合数相关
- 合数分解
- 组合数学相关
- 组合数C(n,r)
- 组合数C(a,b)预处理
- 集合划分问题
- 卢卡斯定理(从(1,1)到(n,m)的走法,机器人走方格问题)
- 最大1矩阵
- (全是1的最大子矩阵)
- 约瑟夫环问题
- 函数图像法
- 博弈论
- Bash
- Nim
- SG打表
- SG DFS
- Wythoff
- 周期性方程
- 追赶法解周期性方程
- 阶乘
- 阶乘最后非零位
- N的阶乘的长度
- 排列组合
- 类循环排列
- 全排列
- 不重复排列
- 一般组合
- 全组合
- 不重复组合
- 求逆元
- 拓展欧几里得法
- 欧拉函数法
- 欧拉函数法(求阶乘逆元)
- FWT
- 整数划分
- 整数划分(五边形定理)
- 整数划分(五边形定理拓展)
- A^B约数之和
- A^B约数之和对mod取模
- 莫比乌斯反演
- 莫比乌斯反演公式
- 线性筛法求解
- 单独求解
- Baby-Step Giant-Step
- 自适应simpson积分
- 多项式求根
- 多项式求根(牛顿法)
- 星期问题
- 基姆拉尔森公式:
- 斐波那契额数列
- 矩阵原理单独求解
- 1/n循环节长度
- 矩阵相关
- 矩阵乘法
- 矩阵乘法+判等
- 矩阵快速幂
- 反素数
- 求最小的因子个数为n个正整数
- 求n以内的因子最多的数(不止一个则取最小)
- 容斥
- 母函数
- 数论相关公式
- 素数两种筛法
- 埃拉托斯特尼筛法
- 具有最大素因子的整数
- 欧拉函数
- 阶乘逆元
- Lacus定理模板
- 费马小定理
- KMP算法
- KMP_Pre
- PreKMP
- KMP_Count
- 拓展KMP
- 最短公共祖先
- Karp-Rabin算法
- 字符串匹配
- 字符块匹配
- Manacher最长回文子串
- strstr函数
- 后缀数组
- DA算法
- DC3算法
- 后缀自动机
- 字符串HASH
- BM算法改进的算法:Sunday Algorithm
- Graph图论模板
- 最短路
- Dijkstra 单源最短路 邻接矩阵形式
- Dijkstra 起点Start 结点有权值
- Dijkstra 堆优化
- 单源最短路 SPFA
- Floyd算法 点权 + 路径限制
- 第K短路
- Dijkstra
- A*
- 最小生成树(森林)
- Prim算法
- Kruskal算法
- MST
- 次小生成树
- 曼哈顿最小生成树
- 欧拉路径
- 无向图:
- 有向图:
- 混合图:
- 图的割点、桥和双连通分支的基本概念
- 无向图找桥
- 无向图连通度(割)
- 最大团问题
- DP+DFS
- 最小树形图
- 一般图匹配带花树
- LCA
- DFS+ST在线算法
- Tarjan离线算法
- 倍增法
- 生成树计数
- 计算生成树个数,不取模
- 有向图最小树形图
- 有向图的强连通分量
- Tarjan
涵盖了ACM竞赛的大多数常用算法,算法较多可以直接搜索查询
Dinic算法求网络流
#include <cstdio>
#include <string.h>
#include <queue>
using namespace std;
int const inf = 0x3f3f3f3f;
int const MAX = 205;
int n, m;
int c[MAX][MAX], dep[MAX];//dep[MAX]代表当前层数
int bfs(int s, int t)//重新建图,按层次建图
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep, -1, sizeof(dep));
dep[s] = 0;
q.push(s);
while(!q.empty()){
int u = q.front();
q.pop();
for(int v = 1; v <= m; v++){
if(c[u][v] > 0 && dep[v] == -1){//如果可以到达且还没有访问,可以到达的条件是剩余容量大于0,没有访问的条件是当前层数还未知
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[t] != -1;
}
int dfs(int u, int mi, int t)//查找路径上的最小流量
{
if(u == t)
return mi;
int tmp;
for(int v = 1; v <= m; v++){
if(c[u][v] > 0 && dep[v] == dep[u] + 1 && (tmp = dfs(v, min(mi, c[u][v]), t))){
c[u][v] -= tmp;
c[v][u] += tmp;
return tmp;
}
}
return 0;
}
int dinic()
{
int ans = 0, tmp;
while(bfs(1, m)){
while(1){
tmp = dfs(1, inf, m);
if(tmp == 0)
break;
ans += tmp;
}
}
return ans;
}
int main()
{
while(~scanf("%d %d", &n, &m)){
memset(c, 0, sizeof(c));
int u, v, w;
while(n--){
scanf("%d %d %d", &u, &v, &w);
c[u][v] += w;
}
printf("%d\n", dinic());
}
return 0;
}
弗洛伊德算法
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]);
}
欧拉回路判定,逆序输出路径
输入文件由几个块组成。 每个街区描述一个城镇。 块中的每一行包含三个整数x; Ÿ; z,其中x> 0且y> 0是由街道号z连接的交叉点的数量。 块的末尾由包含x = y = 0的行标记。在输入文件的末尾有一个空块,x = y = 0。
产量
每个块的输出一行包含街道编号序列(序列的单个成员由空格分隔),描述Johnny的往返行程。 如果找不到往返,则相应的输出块包含消息“往返不存在”。
#include<iostream>
#include<cstring>
#include<cstdio>
#define mst(a) memset(a,0,sizeof(a))
using namespace std;
const int maxn=20000+8;
int angleNum[maxn]; //表示顶点度数
int used[maxn]; //判断顶点是否走过
int n,k;
int res[maxn]; //路径数组
int cnt=0;
struct node{ //结构体定义 起点和终点
int a;
int b;
}rng[maxn];
bool is_OK() //此方法用于判定每个顶点的度数
{
for(int i=1;i<=n;i++)
if(angleNum[i]%2) //如果存在一个顶点度数为偶数度,那么就不存在欧拉回路
return false; //仅仅对于有向图而言
return true;
}
void dfs(int x)
{
for(int i=1;i<=k;i++)
{
if(!used[i]&&(rng[i].a==x||rng[i].b==x)) //这里处理有点特殊,我们不确定
{ //走的是每个分支的起点还是终点
used[i]=1;
dfs(rng[i].a+rng[i].b-x);
res[++cnt]=i;
}
}
}
int main()
{
ios::sync_with_stdio(false); //cin提速
cin.tie(0);
int x,y,z;
while(cin>>x>>y&&(x+y))
{
int point=min(x,y); //求出我们的源点
n=max(x,y);
mst(angleNum);
mst(used);
cnt=k=0;
do{
cin>>z;
rng[z].a=x;
rng[z].b=y;
++k;
angleNum[x]++; angleNum[y]++;
n=max(n,max(x,y)); //求出最大的顶点数
}while(cin>>x>>y&&(x+y));
if(!is_OK())
{
cout<<"Round trip does not exist."<<endl;
continue;
}
dfs(point); //dfs深搜欧拉回路
for(int i=cnt;i>=1;i--) //逆序输出路径
{
if(i!=1)
cout<<res[i]<<" ";
else
cout<<res[i]<<endl;
}
}
return 0;
}
容斥原理
//Z城市居住着很多只跳蚤。在Z城市周六生活频道有一个娱乐节目。一只跳蚤将被请上一个高空钢丝的正中央。钢丝很长,可以看作是无限长。节目主持人会给该跳蚤发一张卡片。卡片上写有N+1个自然数。其中最后一个是M,而前N个数都不超过M,卡片上允许有相同的数字。跳蚤每次可以从卡片上任意选择一个自然数S,然后向左,或向右跳S个单位长度。而他最终的任务是跳到距离他左边一个单位长度的地方,并捡起位于那里的礼物。
//比如当N=2,M=18时,持有卡片(10, 15, 18)的跳蚤,就可以完成任务:他可以先向左跳10个单位长度,然后再连向左跳3次,每次15个单位长度,最后再向右连跳3次,每次18个单位长度。而持有卡片(12, 15, 18)的跳蚤,则怎么也不可能跳到距他左边一个单位长度的地方。
//当确定N和M后,显然一共有M^N张不同的卡片。现在的问题是,在这所有的卡片中,有多少张可以完成任务。
//In 2 4 out 12
//先将m质因数分解,然后容斥统计即可
#include<iostream>
#include<cmath>
using namespace std;
const int maxn=100+8;
typedef long long LL;
int num;
int prime[maxn];
LL p[maxn];
LL res,temp;
int n,m;
void divide(int m)
{
num=0;
for(int i=2;i*i<=m;i++)
{
if(m%i==0)
{
prime[++num]=i;
m/=i;
while(m%i==0) m/=i;
}
}
if(m>1) prime[++num]=m;
}
void dfs(int b,int cnt,int c)
{
if(cnt==c)
{
int x=m;
for(int i=1;i<=c;i++)
x/=p[i];
temp+=pow(x,n);
return;
}
for(int i=b;i<=num;i++)
{
p[cnt+1]=prime[i];
dfs(i+1,cnt+1,c);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
while(cin>>n>>m)
{
res=0;
divide(m);
for(int i=1;i<=num;i++)
{
temp=0;
dfs(1,0,i);
if(i&1) res+=temp;
else res-=temp;
}
res=pow(m,n)-res;
cout<<res<<endl;
}
return 0;
}
最大三角形面积(凸包+旋转卡壳)
老师给我们平面上n个点,让我们求出组合的三角形的最大面积
我们可以先将这n个点组成一个凸包,然后通过旋转卡壳求出凸包的最大直径
找出直径对应的两个点,然后通过遍历求出第三个点
这里面用上了叉积的概念,即一条边X另一条边
公式:
三角的面积等于同一个起点出发的两边的叉积*1/2 假设A B是两条边
面积就等于(A+B)/2
3 3 4 2 6 3 7 1.50
6 2 6 3 9 2 0 8 0 6 6 7 7 27.00
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#define mst(a) memset(a,0,sizeof(a))
using namespace std;
const int maxn=50000+8;
struct node{
int x;
int y;
}p[maxn],ch[maxn];
int n;
bool cmp(node a,node b)
{
if(a.x==b.x)
{
return a.y<b.y;
}
return a.x<b.x;
}
double Cross(node s,node a,node b)
{
int x1=a.x-s.x;
int y1=a.y-s.y;
int x2=b.x-s.x;
int y2=b.y-s.y;
return x1*y2-x2*y1;
}
int Andrew()
{
sort(p,p+n,cmp);
int m=0;
for(int i=0;i<n;i++)
{
//下
while(m>1&&Cross(ch[m-2],ch[m-1],p[i])<0) m--;
ch[m++]=p[i];
}
int k=m;
for(int i=n-2;i>=0;i--)
{
//上
while(m>k&&Cross(ch[m-2],ch[m-1],p[i])<0) m--;
ch[m++]=p[i];
}
if(n>1) m--;
return m;
}
int main()
{
while(cin>>n)
{
for(int i=0;i<n;i++)
{
cin>>p[i].x>>p[i].y;
}
int m=Andrew();
double res=0;
for(int i=0;i<m;i++)
{
int q=1;
for(int j=i+1;j<m;j++)
{
while(Cross(ch[i],ch[j],ch[q+1])>Cross(ch[i],ch[j],ch[q]))
q=(q+1)%m;
res=max(res,Cross(ch[i],ch[j],ch[q]));
}
}
printf("%.2lf\n",res/2.0);
}
return 0;
}
大数基本操作(Java)
import java.util.Scanner;
import java.math.BigInteger;
public class Main {
public static void main(String[] args) {
BigInteger[] a = new BigInteger[10100];
a[0] = BigInteger.valueOf(1);
for(int i = 1;i < 10100;i++) {
a[i] = a[i-1].multiply(BigInteger.valueOf(i));
}
Scanner in = new Scanner(System.in);
while (in.hasNextInt()) {
int x = in.nextInt();
System.out.println(a[x]);
}
}
}
大数加法(C++)
string add1(string s1, string s2)
{
if (s1 == "" && s2 == "") return "0";
if (s1 == "") return s2;
if (s2 == "") return s1;
string maxx = s1, minn = s2;
if (s1.length() < s2.length()){
maxx = s2;
minn = s1;
}
int a = maxx.length() - 1, b = minn.length() - 1;
for (int i = b; i >= 0; --i){
maxx[a--] += minn[i] - '0'; // a一直在减 , 额外还要减个'0'
}
for (int i = maxx.length()-1; i > 0;--i){
if (maxx[i] > '9'){
maxx[i] -= 10;//注意这个是减10
maxx[i - 1]++;
}
}
if (maxx[0] > '9'){
maxx[0] -= 10;
maxx = '1' + maxx;
}
return maxx;
}
大数阶乘(C++)
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
const int maxn = 100010;
int num[maxn], len;
/*
在mult函数中,形参部分:len每次调用函数都会发生改变,n表示每次要乘以的数,最终返回的是结果的长度
tip: 阶乘都是先求之前的(n-1)!来求n!
初始化Init函数很重要,不要落下
*/
void Init() {
len = 1;
num[0] = 1;
}
int mult(int num[], int len, int n) {
LL tmp = 0;
for(LL i = 0; i < len; ++i) {
tmp = tmp + num[i] * n; //从最低位开始,等号左边的tmp表示当前位,右边的tmp表示进位(之前进的位)
num[i] = tmp % 10; // 保存在对应的数组位置,即去掉进位后的一位数
tmp = tmp / 10; // 取整用于再次循环,与n和下一个位置的乘积相加
}
while(tmp) { // 之后的进位处理
num[len++] = tmp % 10;
tmp = tmp / 10;
}
return len;
}
int main() {
Init();
int n;
n = 1977; // 求的阶乘数
for(int i = 2; i <= n; ++i) {
len = mult(num, len, i);
}
for(int i = len - 1; i >= 0; --i)
printf("%d",num[i]); // 从最高位依次输出,数据比较多采用printf输出
printf("\n");
return 0;
}
常用函数与STL标准模板库
STL实现Ugly Numbers
#include <iostream>
#include <queue>
/*
* Ugly Numbers
* Ugly numbers are numbers whose only prime factors are 2, 3 or 5.
* 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, ...
*/
typedef std::pair<unsigned long, int> node_type;
int main(int argc, const char * argv[])
{
unsigned long result[1502];
std::priority_queue<node_type, std::vector<node_type>, std::greater<node_type>> Q;
Q.push(std::make_pair(1, 2));
for (int i = 0; i < 1500; i++)
{
node_type node = Q.top();
Q.pop();
switch (node.second)
{
case 2:
Q.push(std::make_pair(node.first * 2, 2));
case 3:
Q.push(std::make_pair(node.first * 3, 3));
case 5:
Q.push(std::make_pair(node.first * 5, 5));
}
result[i] = node.first;
}
int n;
std::cin >> n;
while (n > 0)
{
std::cout << result[n - 1] << '\n';
std::cin >> n;
}
return 0;
}
STL-pair
STL的<utility>头文件中描述了一个看上去非常简单的模版类pair,用来表示一个二元组或元素对,并提供了按照字典序对元素对进行大小比较运算符模版函数。
Example,想要定义一个对象表示一个平面坐标点,则可以:
pair<double, double> p;
cin >> p.first >> p.second;
STL-set的基本操作
s.begin() // 返回指向第一个元素的迭代器
s.clear() // 清除所有元素
s.count() // 返回某个值元素的个数
s.empty() // 如果集合为空,返回true(真)
s.end() // 返回指向最后一个元素之后的迭代器,不是最后一个元素
s.equal_range() // 返回集合中与给定值相等的上下限的两个迭代器
s.erase() // 删除集合中的元素
s.find() // 返回一个指向被查找到元素的迭代器
s.get_allocator() // 返回集合的分配器
s.insert() // 在集合中插入元素
s.lower_bound() // 返回指向大于(或等于)某值的第一个元素的迭代器
s.key_comp() // 返回一个用于元素间值比较的函数
s.max_size() // 返回集合能容纳的元素的最大限值
s.rbegin() // 返回指向集合中最后一个元素的反向迭代器
s.rend() // 返回指向集合中第一个元素的反向迭代器
s.size() // 集合中元素的数目
s.swap() // 交换两个集合变量
s.upper_bound() // 返回大于某个值元素的迭代器
s.value_comp() // 返回一个用于比较元素间的值的函数
多重集合与集合的区别在于集合中不能存在相同元素,而多重集合中可以存在。
multiset s;
multiset ss;
multiset和set的基本操作相似,需要注意的是,集合的count()能返回0(无)或者1(有),而多重集合是有多少个返回多少个
STL-vector
vector<int> s;
// 定义一个空的vector对象,存储的是int类型的元素
vector<int> s(n);
// 定义一个含有n个int元素的vector对象
vector<int> s(first, last);
// 定义一个vector对象,并从由迭代器first和last定义的序列[first, last)中复制初值
Vector的基本操作
s[i] // 直接以下标方式访问容器中的元素
s.front() // 返回首元素
s.back() // 返回尾元素
s.push_back(x) // 向表尾插入元素x
s.size() // 返回表长
s.empty() // 表为空时,返回真,否则返回假
s.pop_back() // 删除表尾元素
s.begin() // 返回指向首元素的随机存取迭代器
s.end() // 返回指向尾元素的下一个位置的随机存取迭代器
s.insert(it, val) // 向迭代器it指向的元素前插入新元素val
s.insert(it, n, val)// 向迭代器it指向的元素前插入n个新元素val
s.insert(it, first, last)
// 将由迭代器first和last所指定的序列[first, last)插入到迭代器it指向的元素前面
s.erase(it) // 删除由迭代器it所指向的元素
s.erase(first, last)// 删除由迭代器first和last所指定的序列[first, last)
s.reserve(n) // 预分配缓冲空间,使存储空间至少可容纳n个元素
s.resize(n) // 改变序列长度,超出的元素将会全部被删除,如果序列需要扩展(原空间小于n),元素默认值将填满扩展出的空间
s.resize(n, val) // 改变序列长度,超出的元素将会全部被删除,如果序列需要扩展(原空间小于n),val将填满扩展出的空间
s.clear() // 删除容器中的所有元素
s.swap(v) // 将s与另一个vector对象进行交换
s.assign(first, last)
// 将序列替换成由迭代器first和last所指定的序列[first, last),[first, last)不能是原序列中的一部分
// 要注意的是,resize操作和clear操作都是对表的有效元素进行的操作,但并不一定会改变缓冲空间的大小
// 另外,vector还有其他的一些操作,如反转、取反等,不再一一列举
// vector上还定义了序列之间的比较操作运算符(>、<、>=、<=、==、!=),可以按照字典序比较两个序列。
STL-stack
Stack的基本操作:
s.push(x); // 入栈
s.pop(); // 出栈
s.top(); // 访问栈顶
s.empty(); // 当栈空时,返回true
s.size(); // 访问栈中元素个数
STL-queue
Queue的基本操作:
q.push(x); // 入队列
q.pop(); // 出队列
q.front(); // 访问队首元素
q.back(); // 访问队尾元素
q.empty(); // 判断队列是否为空
q.size(); // 访问队列中的元素个数
优先队列:
priority_queue<int> q;
priority_queue<pair<int, int> > qq; // 注意在两个尖括号之间一定要留空格,防止误判
priority_queue<int, vector<int>, greater<int> > qqq;// 定义小的先出队列
优先队列的基本操作:
q.empty() // 如果队列为空,则返回true,否则返回false
q.size() // 返回队列中元素的个数
q.pop() // 删除队首元素,但不返回其值
q.top() // 返回具有最高优先级的元素值,但不删除该元素
q.push(item) // 在基于优先级的适当位置插入新元素
#include <iostream>
#include <queue>
using namespace std;
class T
{
public:
int x, y, z;
T(int a, int b, int c) : x(a), y(b), z(c) {}
};
bool operator < (const T &tOne, const T &tTwo)
{
return tOne.z < tTwo.z; // 按照z的顺序来决定tOne和tTwo的顺序
}
int main()
{
priority_queue<T> q;
q.push(T(4, 4, 3));
q.push(T(2, 2, 5));
q.push(T(1, 5, 4));
q.push(T(3, 3, 6));
while (!q.empty())
{
T t = q.top();
q.pop();
cout << t.x << " " << t.y << " " << t.z << '\n';
}
return 0;
}
STL-map
/* 向map中插入元素 */
m[key] = value; // [key]操作是map很有特色的操作,如果在map中存在键值为key的元素对, 则返回该元素对的值域部分,否则将会创建一个键值为key的元素对,值域为默认值。所以可以用该操作向map中插入元素对或修改已经存在的元素对的值域部分。
m.insert(make_pair(key, value)); // 也可以直接调用insert方法插入元素对,insert操作会返回一个pair,当map中没有与key相匹配的键值时,其first是指向插入元素对的迭代器,其second为true;若map中已经存在与key相等的键值时,其first是指向该元素对的迭代器,second为false。
/* 查找元素 */
int i = m[key]; // 要注意的是,当与该键值相匹配的元素对不存在时,会创建键值为key(当另一个元素是整形时,m[key]=0)的元素对。
map<string, int>::iterator it = m.find(key); // 如果map中存在与key相匹配的键值时,find操作将返回指向该元素对的迭代器,否则,返回的迭代器等于map的end()(参见vector中提到的begin()和end()操作)。
/* 删除元素 */
m.erase(key); // 删除与指定key键值相匹配的元素对,并返回被删除的元素的个数。
m.erase(it); // 删除由迭代器it所指定的元素对,并返回指向下一个元素对的迭代器。
/* 其他操作 */
m.size(); // 返回元素个数
m.empty(); // 判断是否为空
m.clear(); // 清空所有元素
二维map应用:
map<string,map<string,int> > mp;
map<string,map<string,int> >::iterator it;
map<string,int>::iterator it2;
for(it=mp.begin();it!=mp.end();it++){
cout<<it->first<<endl;
for(it2=it->second.begin();it2!=it->second.end();it2++){
cout<<" |----"<<it2->first<<"("<<it2->second<<")"<<endl;
}
}
STL-bitset
在 STLSTL 的头文件中 <bitset><bitset> 中定义了模版类 bitsetbitset,用来方便地管理一系列的 bitbit 位的类。bitsetbitset 除了可以访问指定下标的 bitbit 位以外,还可以把它们作为一个整数来进行某些统计。
bitsetbitset 模板类需要一个模版参数,用来明确指定含有多少位
定义bitset对象的示例代码:
const int MAXN = 32;
bitset<MAXN> bt; // bt 包括 MAXN 位,下标 0 ~ MAXN - 1,默认初始化为 0
bitset<MAXN> bt1(0xf); // 0xf 表示十六进制数 f,对应二进制 1111,将 bt1 低 4 位初始化为 1
bitset<MAXN> bt2(012); // 012 表示八进制数 12,对应二进制 1010,即将 bt2 低 4 位初始化为 1010
bitset<MAXN> bt3("1010"); // 将 bt3 低 4 位初始化为 1010
bitset<MAXN> bt4(s, pos, n);// 将 01 字符串 s 的 pos 位开始的 n 位初始化 bt4
bitset的基本操作:
bt.any() // bt 中是否存在置为 1 的二进制位?
bt.none() // bt 中不存在置为 1 的二进制位吗?
bt.count() // bt 中置为 1 的二进制位的个数
bt.size() // bt 中二进制位的个数
bt[pos] // 访问 bt 中在 pos 处的二进制位
bt.test(pos) // bt 中在 pos 处的二进制位是否为 1
bt.set() // 把 bt 中所有二进制位都置为 1
bt.set(pos) // 把 bt 中在 pos 处的二进制位置为 1
bt.reset() // 把 bt 中所有二进制位都置为 0
bt.reset(pos) // 把 bt 中在pos处的二进制位置为0
bt.flip() // 把 bt 中所有二进制位逐位取反
bt.flip(pos) // 把 bt 中在 pos 处的二进制位取反
bt[pos].flip() // 同上
bt.to_ulong() // 用 bt 中同样的二进制位返回一个 unsigned long 值
os << bt // 把 bt 中的位集输出到 os 流
STL-iterator迭代器特别输出
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> s;
s.push_back(1);
s.push_back(2);
s.push_back(3);
copy(s.begin(), s.end(), ostream_iterator<int> (cout, " "));
cout << '\n';
return 0;
}
这段代码中的copy就是STL中定义的一个模版函数,copy(s.begin(), s.end(), ostream_iterator<int>(cout, ” “));的意思是将由s.begin()至s.end()(不含s.end())所指定的序列复制到标准输出流out中,用” “作为每个元素的间隔。也就是说,这句话的作用其实就是将表中的所有内容依次输出
STL-algorithm
min_element/max_element找出容器中的最小/最大值
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> L;
for (int i=0; i<10; i++)
{
L.push_back(i);
}
vector<int>::iterator min_it = min_element(L.begin(), L.end());
vector<int>::iterator max_it = max_element(L.begin(), L.end());
cout << "Min is " << *min_it << endl;
cout << "Max is " << *max_it << endl;
return 0;
}
Copy:
// 将vOne的前三个元素复制到vTwo的中间(覆盖掉原来数据)
copy(vOne.begin(), vOne.begin() + 3, vTwo.begin() + 4);
// 在vTwo内部进行复制,注意参数2表示结束位置,结束位置不参与复制
copy(vTwo.begin() + 4, vTwo.begin() + 7, vTwo.begin() + 2);
输出一个数的2 8 10 16进制(模板)
#include <bitset>
#include <iostream>
using namespace std;
int main()
{
cout << "36的8进制:" << std::oct << 36 << endl;
cout << "36的10进制" << std::dec << 36 << endl;
cout << "36的16进制:" << std::hex << 36 << endl;
cout << "36的2进制: " << bitset<8>(36) << endl;
return 0;
}
10进制与26进制相互转化
#include<iostream>
#include <algorithm>
using namespace std;
int main(){
long long ans=0;
string s;
cin>>s;
int len=s.size();
for(int i=0,j=1;i<s.size();i++,j*=26){
ans+=(int)(s[len-i-1]-65)*j;
}
cout<<ans<<endl; //26进制转十进制
string str="";
//ans=817;
while(ans>0){
int m=ans%26;
if(m==0) m=0;
str+=(char)(m+65);
ans=(ans-m)/26;
}
reverse(str.begin(),str.end()); //反序
cout<<str<<endl; // 十进制转26进制
return 0;
}
26进制下的大数加法
AAAADH BCE DRW UHD D AAAAA
BFL XYZ D
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#define mst(a) memset(a,0,sizeof(a))
using namespace std;
const int maxn=200+10;
char s1[maxn],s2[maxn];
int a[maxn],b[maxn];
char s[maxn];
int main()
{
while(~scanf("%s %s",s1,s2))
{
mst(a);
mst(b);
int len1=strlen(s1);
int len2=strlen(s2);
for(int i=0;i<len1;i++)
{
int x=s1[i]-'A';
a[len1-i-1]=x;
}
for(int i=0;i<len2;i++)
{
int x=s2[i]-'A';
b[len2-i-1]=x;
}
int len=max(len1,len2);
int pre=0;
int k=0;
string res="";
for(int i=0;i<=len;i++)
{
int v=a[i]+b[i]+pre;
pre=0;
if(v>=26)
{
v-=26;
pre=1;
}
if(v!=0)
{
k=i;
}
s[i]=v;
}
for(int i=0;i<=k;i++)
{
res+=s[i]+'A';
}
reverse(res.begin(),res.end());
int n=res.length();
int flag=0;
for(int i=0;i<n;i++)
{
if(res[i]=='A')
{
flag=0;
}
else
{
flag=1;
break;
}
}
if(flag)
cout<<res<<endl;
else
cout<<'A'<<endl;
}
}
数论模板
快速幂模板
long long quick_pow(long long x,long long num)
{
long long res=1;
while(num>0)
{
if(num&1)
{
res=(res*x)%mod;
}
x=(x*x)%mod;
num/=2;
}
return res;
}
矩阵快速幂
#include<bits/stdc++.h>
#define ll long long
#define mod(x) ((x)%MOD)
using namespace std;
const ll MOD = 1e9 + 7;
struct mat{
ll m[3][3];
}a,ans,unit;
void init() {
memset(unit.m,0,sizeof(unit.m));
memset(a.m,0,sizeof(a.m));
unit.m[0][0] = 1;
unit.m[1][1] = 1;
a.m[0][0] = 3;
a.m[0][1] = 1;
a.m[1][0] = 1;
a.m[1][1] = 3;
}
mat operator * (mat m1,mat m2) {
mat t;
ll r;
for(int i = 0;i < 3;i++) {
for(int j = 0;j < 3;j++) {
r = 0;
for(int k = 0;k < 3;k++) {
r = mod(r*1ll + mod(mod(m1.m[i][k])*1ll*mod(m2.m[k][j])));
}
t.m[i][j] = r;
}
}
return t;
}
mat quick_pow(ll x) {
mat t = unit;
while(x) {
if(x & 1) {
t = t*a;
}
a = a*a;
x >>= 1;
}
return t;
}
int main(){
init();
ans = quick_pow(n);
}
欧拉函数PHI
分解质因数法
/*
* 分解质因数法求解,getFactor(n)函数见《合数相关》
*/
int main(int argc, const char * argv[])
{
// ...
getFactors(n);
int ret = n;
for (int i = 0; i < fatCnt; i++)
{
ret = (int)(ret / factor[i][0] * (factor[i][0] - 1));
}
return 0;
}
筛法欧拉函数
const int MAXN = 100;
int phi[MAXN + 2];
int main(int argc, const char * argv[])
{
for (int i = 1; i <= MAXN; i++)
{
phi[i] = i;
}
for (int i = 2; i <= MAXN; i += 2)
{
phi[i] /= 2;
}
for (int i = 3; i <= MAXN; i += 2)
{
if (phi[i] == i)
{
for (int j = i; j <= MAXN; j += i)
{
phi[j] = phi[j] / i * (i - 1);
}
}
}
return 0;
}
单独求解
/*
* 单独求解的本质是公式的应用
*/
unsigned euler(unsigned x)
{
unsigned i, res = x; // unsigned == unsigned int
for (i = 2; i < (int)sqrt(x * 1.0) + 1; i++)
{
if (!(x % i))
{
res = res / i * (i - 1);
while (!(x % i))
{
x /= i; // 保证i一定是素数
}
}
}
if (x > 1)
{
res = res / x * (x - 1);
}
return res;
}
线性筛
/*
* 同时得到欧拉函数和素数表
*/
const int MAXN = 10000000;
bool check[MAXN + 10];
int phi[MAXN + 10];
int prime[MAXN + 10];
int tot; // 素数个数
void phi_and_prime_table(int N)
{
memset(check, false, sizeof(check));
phi[1] = 1;
tot = 0;
for (int i = 2; i <= N; i++)
{
if (!check[i])
{
prime[tot++] = i;
phi[i] = i - 1;
}
for (int j = 0; j < tot; j++)
{
if (i * prime[j] > N)
{
break;
}
check[i * prime[j]] = true;
if (i % prime[j] == 0)
{
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
else
{
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
return ;
}
凸包模板
#include <iostream>
#include <algorithm>
#include <iomanip>
#include<stdio.h>
using namespace std;
const int maxn = 50010;
struct Point {
int x , y;
bool operator < (Point const &rhs) const {
return (x < rhs.x) || (x == rhs.x && y < rhs.y);
}
};
int Cross(Point const &O , Point const &A , Point const &B)
{
int xoa = A.x - O.x;
int xob = B.x - O.x;
int yoa = A.y - O.y;
int yob = B.y - O.y;
return xoa * yob - xob * yoa;
}
int Andrew(Point *p , int n , Point *ch)
{
sort(p , p + n);
int m = 0;
for(int i = 0; i < n; i++)
{ //下凸包
while(m > 1 && Cross(ch[m-2] , ch[m-1] , p[i]) < 0) m--;
ch[m++] = p[i];
}
int k = m;
for(int i = n - 2; i >= 0; i--) { //上凸包
while(m > k && Cross(ch[m-2] , ch[m-1] , p[i]) < 0) m--;
ch[m++] = p[i];
}
if(n > 1) m--;
return m;
}
Point p[maxn] , ch[maxn];
int main()
{
int n;
while(cin >> n)
{
for(int i = 0; i < n; i++) cin >> p[i].x >> p[i].y;
int m = Andrew(p , n , ch); ///求凸包
///旋转卡壳法
int ans = 0;
for(int i = 0; i < m; i++)
{
int q = 1;
for(int j = i + 1; j < m; j++)
{
while(Cross(ch[i],ch[j],ch[q+1]) > Cross(ch[i],ch[j],ch[q]))
q = (q + 1) % m;
ans = max(ans , Cross(ch[i],ch[j],ch[q]));
}
}
//cout << fixed << setprecision(2) << ans / 2.0 << endl;
printf("%.2lf\n",ans/2.0);
}
return 0;
}
欧几里得拓展GCD
扩展欧几里德算法是用来在已知a, b求解一组x,y,使它们满足贝祖等式: ax+by = gcd(a, b) =d(解一定存在,根据数论中的相关定理)。扩展欧几里德常用在求解模线性方程及方程组中。
ll gcd(ll a,ll b) {
return b == 0 ? a : gcd(b, a % b);
}
void exgcd(ll a, ll b, ll &d, ll &x, ll &y) {
if(!b) d=a,x=1,y=0;
else exgcd(b, a % b, d, y, x),y -= x * (a / b);
}
线性方程组(高斯消元)
列主元
/*
* 列主元gauss消去求解a[][] * x[] = b[]
* 返回是否有唯一解,若有解在b[]中
*/
#define fabs(x) ((x) > 0 ? (x) : (-x))
#define eps 1e-10
const int MAXN = 100;
int gaussCpivot(int n, double a[][MAXN], double b[])
{
int i, j, k, row = 0;
double MAXP, temp;
for (k = 0; k < n; k++)
{
for (MAXP = 0, i = k; i < n; i++)
{
if (fabs(a[i][k]) > fabs(MAXP))
{
MAXP = a[row = i][k];
}
}
if (fabs(MAXP) < eps)
{
return 0;
}
if (row != k)
{
for (j = k; j < n; j++)
{
temp = a[k][j];
a[k][j] = a[row][j];
a[row][j] = temp;
temp = b[k];
b[k] = b[row];
b[row] = temp;
}
}
for (j = k + 1; j < n; j++)
{
a[k][j] /= MAXP;
for (i = k + 1; i < n; i++)
{
a[i][j] -= a[i][k] * a[k][j];
}
}
b[k] /= MAXP;
for (i = n - 1; i >= 0; i--)
{
for (j = i + 1; j < n; j++)
{
b[i] -= a[i][j] * b[j];
}
}
}
return 1;
}
全主元
/*
* 全主元gauss消去解a[][] * x[] = b[]
* 返回是否有唯一解,若有解在b[]中
*/
#define fabs(x) ((x) > 0 ? (x) : (-x))
#define eps 1e-10
const int MAXN = 100;
int gaussTpivot(int n, double a[][MAXN], double b[])
{
int i, j, k, row = 0, col = 0, index[MAXN];
double MAXP, temp;
for (i = 0; i < n; i++)
{
index[i] = i;
}
for (k = 0; k < n; k++)
{
for (MAXP = 0, i = k; i < n; i++)
{
for (j = k; j < n; j++)
{
if (fabs(a[i][j] > fabs(MAXP)))
{
MAXP = a[row = i][col = j];
}
}
}
if (fabs(MAXP) < eps)
{
return 0;
}
if (col != k)
{
for (i = 0; i < n; i++)
{
temp = a[i][col];
a[i][col] = a[i][k];
a[i][k] = temp;
}
j = index[col];
index[col] = index[k];
index[k] = j;
}
if (row != k)
{
for (j = k; j < n; j++)
{
temp = a[k][j];
a[k][j] = a[row][j];
a[row][j] = temp;
}
temp = b[k];
b[k] = b[row];
b[row] = temp;
}
for (j = k + 1; j < n; j++)
{
a[k][j] /= MAXP;
for (i = k + 1; i < n; i++)
{
a[i][j] -= a[i][k] * a[k][j];
}
}
b[k] /= MAXP;
for (i = k + 1; i < n; i++)
{
b[i] -= b[k] * a[i][k];
}
}
for (i = n - 1; i >= 0; i--)
{
for (j = i + 1; j < n; j++)
{
b[i] -= a[i][j] * b[j];
}
}
for (k = 0; k < n; k++)
{
a[0][index[k]] = b[k];
}
for (k = 0; k < n; k++)
{
b[k] = a[0][k];
}
return 1;
}
高斯消元(自由变元,一类开关问题,位运算操作)
// 高斯消元法求方程组的解
const int MAXN = 300;
// 有equ个方程,var个变元。增广矩阵行数为equ,列数为var+1,分别为0到var
int equ, var;
int a[MAXN][MAXN]; // 增广矩阵
int x[MAXN]; // 解集
int free_x[MAXN]; // 用来存储自由变元(多解枚举自由变元可以使用)
int free_num; // 自由变元的个数
// 返回值为-1表示无解,为0是唯一解,否则返回自由变元个数
int Gauss()
{
int max_r, col, k;
free_num = 0;
for (k = 0, col = 0; k < equ && col < var; k++, col++)
{
max_r = k;
for (int i = k + 1; i < equ; i++)
{
if (abs(a[i][col]) > abs(a[max_r][col]))
{
max_r = i;
}
}
if (a[max_r][col] == 0)
{
k--;
free_x[free_num++] = col; // 这是自由变元
continue;
}
if (max_r != k)
{
for (int j = col; j < var + 1; j++)
{
swap(a[k][j], a[max_r][j]);
}
}
for (int i = k + 1; i < equ; i++)
{
if (a[i][col] != 0)
{
for (int j = col; j < var + 1; j++)
{
a[i][j] ^= a[k][j];
}
}
}
}
for (int i = k; i < equ; i++)
{
if (a[i][col] != 0)
{
return -1; // 无解
}
}
if (k < var)
{
return var - k; // 自由变元个数
}
// 唯一解,回代
for (int i = var - 1; i >= 0; i--)
{
x[i] = a[i][var];
for (int j = i + 1; j < var; j++)
{
x[i] ^= (a[i][j] && x[j]);
}
}
return 0;
}
模线性方程(组)
公共部分(拓展GCD)
int extgcd(int a, int b, int &x, int &y)
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
int d = extgcd(b, a % b, x, y);
int t = x;
x = y;
y = t - a / b * y;
return d;
}
模线性方程
/*
* 模线性方程 a * x = b (% n)
*/
void modeq(int a, int b, int n)
{
int e, i, d, x, y;
d = extgcd(b, a % b, x, y);
if (b % d > 0)
{
cout << "No answer!\n";
}
else
{
e = (x * (b / d)) % n;
for (i = 0; i < d; i++)
{
cout << i + 1 << "-th ans:" << (e + i * (n / d)) % n << '\n';
}
}
return ;
}
模线性方程组(互质)
/*
* 模线性方程组
* a = B[1](% W[1]); a = B[2](% W[2]); ... a = B[k](% W[k]);
* 其中W,B已知,W[i] > 0且W[i]与W[j]互质,求a(中国剩余定理)
*/
int china(int b[], int w[], int k)
{
int i, d, x, y, m, a = 0, n = 1;
for (i = 0; i < k; i++)
{
n *= w[i]; // 注意不能overflow
}
for (i = 0; i < k; i++)
{
m = n / w[i];
d = extgcd(w[i], m, x, y);
a = (a + y * m * b[i]) % n;
}
if (a > 0)
{
return a;
}
else
{
return (a + n);
}
}
模线性方程组(不要求互质)
typedef long long ll;
const int MAXN = 11;
int n, m;
int a[MAXN], b[MAXN];
int main(int argc, const char * argv[])
{
int T;
cin >> T;
while (T--)
{
cin >> n >> m;
for (int i = 0; i < m; i++)
{
cin >> a[i];
}
for (int i = 0; i < m; i++)
{
cin >> b[i];
}
ll ax = a[0], bx = b[0], x, y;
int flag = 0;
for (int i = 1; i < m; i++)
{
ll d = extgcd(ax, a[i], x, y);
if ((b[i] - bx) % d != 0)
{
flag = 1; // 无整数解
break;
}
ll tmp = a[i] / d;
x = x * (b[i] - bx) / d; // 约分
x = (x % tmp + tmp) % tmp;
bx = bx + ax * x;
ax = ax * tmp; // ax = ax * a[i] / d
}
if (flag == 1 || n < bx)
{
puts("0");
}
else
{
ll ans = (n - bx) / ax + 1;
if (bx == 0)
{
ans--;
}
printf("%lld\n", ans);
}
}
return 0;
}
素数相关
判断小于maxn的数是不是素数
/*
* 素数筛选,判断小于MAXN的数是不是素数
* notprime是一张表,false表示是素数,true表示不是
*/
const int MAXN = 1000010;
bool notprime[MAXN];
void init()
{
memset(notprime, false, sizeof(notprime));
notprime[0] = notprime[1] = true;
for (int i = 2; i < MAXN; i++)
{
if (!notprime[i])
{
if (i > MAXN / i) // 阻止后边i * i溢出(或者i,j用long long)
{
continue;
}
// 直接从i * i开始就可以,小于i倍的已经筛选过了
for (int j = i * i; j < MAXN; j += i)
{
notprime[j] = true;
}
}
}
}
查找出小于等于maxn的素数(生成连续素数表)
/*
* 素数筛选,查找出小于等于MAXN的素数
* prime[0]存素数的个数
*/
const int MAXN = 100000;
int prime[MAXN + 1];
void getPrime()
{
memset(prime, 0, sizeof(prime));
for (int i = 2; i <= MAXN; i++)
{
if (!prime[i])
{
prime[++prime[0]] = i;
}
for (int j = 1; j <= prime[0] && prime[j] <= MAXN / i; j++)
{
prime[prime[j] * i] = 1;
if (i % prime[j] == 0)
{
break;
}
}
}
}
随机素数测试
/*
* 随机素数测试(伪素数原理)
* CALL: bool res = miller(n);
* 快速测试n是否满足素数的“必要”条件,出错概率极低
* 对于任意奇数n > 2和正整数s,算法出错概率≤2^(-s)
*/
int witness(int a, int n)
{
int x, d = 1;
int i = ceil(log(n - 1.0) / log(2.0)) - 1;
for (; i >= 0; i--)
{
x = d;
d = (d * d) % n;
if (d == 1 && x != 1 && x != n - 1)
{
return 1;
}
if (((n - 1) & (1 << i)) > 0)
{
d = (d * a) % n;
}
}
return (d == 1 ? 0 : 1);
}
int miller(int n, int s = 50)
{
if (n == 2) // 质数返回1
{
return 1;
}
if (n % 2 == 0) // 偶数返回0
{
return 0;
}
int j, a;
for (j = 0; j < a; j++)
{
a = rand() * (n - 2) / RAND_MAX + 1;
// rand()只能随机产生[0, RAND_MAX)内的整数
// 而且这个RAND_MAX只有32768直接%n的话是永远
// 也产生不了[RAND_MAX, n)之间的数
if (witness(a, n))
{
return 0;
}
}
return 1;
}
合数相关
合数分解
/*
* 合数的分解需要先进行素数的筛选
* factor[i][0]存放分解的素数
* factor[i][1]存放对应素数出现的次数
* fatCnt存放合数分解出的素数个数(相同的素数只算一次)
*/
const int MAXN = 10000;
int prime[MAXN + 1];
// 获取素数
void getPrime()
{
memset(prime, 0, sizeof(prime));
for (int i = 2; i <= MAXN; i++)
{
if (!prime[i])
{
prime[++prime[0]] = i;
}
for (int j = 1; j <= prime[0] && prime[j] <= MAXN / i; j++)
{
prime[prime[j] * i] = 1;
if (i % prime[j] == 0)
{
break;
}
}
}
return ;
}
long long factor[100][2];
int fatCnt;
// 合数分解
int getFactors(long long x)
{
fatCnt = 0;
long long tmp = x;
for (int i = 1; prime[i] <= tmp / prime[i]; i++)
{
factor[fatCnt][1] = 0;
if (tmp % prime[i] == 0)
{
factor[fatCnt][0] = prime[i];
while (tmp % prime[i] == 0)
{
factor[fatCnt][1]++;
tmp /= prime[i];
}
fatCnt++;
}
}
if (tmp != 1)
{
factor[fatCnt][0] = tmp;
factor[fatCnt++][1] = 1;
}
return fatCnt;
}
组合数学相关
定理
One
{1, 2, … n}的r组合a1, a2, … ar出现在所有r组合中的字典序位置编号, C(n, m)表示n中取m的组合数
index = C(n, r) - C(n - a1, r) - C(n - a2, r - 1) - … - C(n - ar, 1)
Two
k * C(n, k) = n * C(n - 1, k - 1);
C(n, 0) + C(n, 2) + … = C(n, 1) + C(n, 3) + …
1 * C(n, 1) + 2 * C(n, 2) + … + n * C(n, n) = n * 2^(n - 1)
Three · Catalan数
C_n = C(2 * n, n) / (n + 1)
C_n = (4 * n - 2) / (n + 1) * C_n - 1
C_1 = 1
Four · Stirling数 · 1
s(p, k)是将p个物体排成k个非空的循环排列的方法数(或者: 把p个人排成k个非空圆圈的方法数)。
s(p, k) = (p - 1) * s(p - 1, k) + s(p - 1, k - 1);
Five · Stirling数 · 2
S(p, k) = k * S(p - 1, k) + S(p - 1, k - 1).
S(p, 0) = 0, (p >= 1);
S(p, p) = 1, (p >= 0);
且有 S(p, 1) = 1, (p >= 1);
S(p, 2) = 2^(p - 1) - 1, (p >= 2);
S(p, p - 1) = C(p, 2);
Six · Bell数
B_p = S(p, 0) + S(p, 1) + … + S(p, p)
B_p = C(p - 1, 0) * B_0 + C(p - 1, 1) * B_1 + … + C(p - 1, p - 1) * B_(p - 1)
组合数C(n,r)
int com(int n, int r) // return C(n, r)
{
if (n - r > r)
{
r = n - r; // C(n, r) = C(n, n - r)
}
int i, j, s = 1;
for (i = 0, j = 1; i < r; ++i)
{
s *= (n - i);
for (; j <= r && s % j == 0; ++j)
{
s /= j;
}
}
return s;
}
组合数C(a,b)预处理
typedef long long ll;
const ll MOD = 1e9 + 7; // 必须为质数才管用
const ll MAXN = 1e5 + 3;
ll fac[MAXN]; // 阶乘
ll inv[MAXN]; // 阶乘的逆元
ll QPow(ll x, ll n)
{
ll ret = 1;
ll tmp = x % MOD;
while (n)
{
if (n & 1)
{
ret = (ret * tmp) % MOD;
}
tmp = tmp * tmp % MOD;
n >>= 1;
}
return ret;
}
void init()
{
fac[0] = 1;
for (int i = 1; i < MAXN; i++)
{
fac[i] = fac[i - 1] * i % MOD;
}
inv[MAXN - 1] = QPow(fac[MAXN - 1], MOD - 2);
for (int i = MAXN - 2; i >= 0; i--)
{
inv[i] = inv[i + 1] * (i + 1) % MOD;
}
}
ll C(ll a, ll b)
{
if (b > a)
{
return 0;
}
if (b == 0)
{
return 1;
}
return fac[a] * inv[b] % MOD * inv[a - b] % MOD;
}
集合划分问题
/*
* n元集合分划为k类的方案数记为S(n, k),称为第二类Stirling数。
* 如{A,B,C}可以划分{{A}, {B}, {C}}, {{A, B}, {C}}, {{B, C}, {A}}, {{A, C}, {B}}, {{A, B, C}}。
* 即一个集合可以划分为不同集合(1...n个)的种类数
* CALL: compute(N); 每当输入一个n,输出B[n]
*/
const int N = 2001;
int data[N][N], B[N];
void NGetM(int m, int n) // m 个数 n 个集合
{
// data[i][j]: i个数分成j个集合
int min, i, j;
data[0][0] = 1;
for (i = 1; i <= m; i++)
{
data[i][0] = 0;
}
for (i = 0; i <= m; i++)
{
data[i][i + 1] = 0;
}
for (i = 1; i <= m; i++)
{
if (i < n)
{
min = i;
}
else
{
min = n;
}
for (j = 1; j <= min; j++)
{
data[i][j] = (j * data[i - 1][j] + data[i - 1][j - 1]);
}
}
return ;
}
void compute(int m)
{
// b[i]: Bell数
NGetM(m, m);
memset(B, 0, sizeof(B));
int i, j;
for (i=1; i <= m; i++)
{
for (j = 0; j <= i; j++)
{
B[i] += data[i][j];
}
}
return ;
}
卢卡斯定理(从(1,1)到(n,m)的走法,机器人走方格问题)
#define MOD 1000000007
typedef long long LL;
LL quickPower(LL a, LL b)
{
LL ans = 1;
a %= MOD;
while (b)
{
if (b & 1)
{
ans = ans * a % MOD;
}
b >>= 1;
a = a * a % MOD;
}
return ans;
}
LL c(LL n, LL m)
{
if (m > n)
{
return 0;
}
LL ans = 1;
for (int i = 1; i <= m; i++)
{
LL a = (n + i - m) % MOD;
LL b = i % MOD;
ans = ans * (a * quickPower(b, MOD - 2) % MOD) % MOD;
}
return ans;
}
LL lucas(LL n, LL m)
{
if (m == 0)
{
return 1;
}
return c(n % MOD, m % MOD) * lucas(n / MOD, m / MOD) % MOD;
}
int main(int argc, const char * argv[])
{
LL n, m;
while (~scanf("%lld %lld", &n, &m))
{
LL max, min;
max = n + m - 3;
min = m - 1;
printf("%lld\n", lucas(max - 1, min - 1));
}
return 0;
}
Polya计数
/*
* c种颜色的珠子,组成长为s的项链,项链没有方向和起始位置
*/
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int main(int argc, const char * argv[])
{
int c, s;
while (cin >> c >> s)
{
int k;
long long p[64];
p[0] = 1; // power of c
for (k = 0; k < s; k++)
{
p[k + 1] = p[k] * c;
}
// reflection part
long long count = s & 1 ? s * p[s / 2 + 1] : (s / 2) * (p[s / 2] + p[s / 2 + 1]);
// rotation part
for (k = 1 ; k <= s ; k++)
{
count += p[gcd(k, s)];
count /= 2 * s;
}
cout << count << '\n';
}
return 0;
}
最大1矩阵
(全是1的最大子矩阵)
const int N = 1000;
bool a[N][N];
int Run(const int &m, const int &n) // a[1...m][1...n]
{ // O(m*n)
int i, j, k, l, r, max=0;
int col[N];
for (j = 1; j <= n; j++)
{
if (a[1][j] == 0 )
{
col[j] = 0;
}
else
{
for (k = 2; k <= m && a[k][j] == 1; k++);
col[j] = k - 1;
}
}
for (i = 1; i <= m; i++)
{
if (i > 1)
{
for (j = 1; j <= n; j++)
{
if (a[i][j] == 0)
{
col[j] = 0;
}
else
{
if (a[i - 1][j] == 0)
{
for (k = i + 1; k <= m && a[k][j] == 1; k++);
col[j] = k-1;
}
}
}
}
for (j = 1; j <= n; j++)
{
if (col[j] >= i)
{
for (l = j - 1; l > 0 && col[l] >= col[j]; --l);
l++;
for (r = j + 1; r <= n && col[r] >= col[j]; ++r);
r--;
int res = (r - l + 1) * (col[j] - i + 1);
if( res > max )
{
max = res;
}
}
}
}
return max;
}
约瑟夫环问题
/*
* n个人(编号 1...n),先去掉第m个数,然后从m+1个开始报1,
* 报到k的退出,剩下的人继续从1开始报数.求胜利者的编号.
*/
int main(int argc, const char * argv[])
{
int n, k, m;
while (cin >> n >> k >> m, n || k || m)
{
int i, d, s = 0;
for (i = 2; i <= n; i++)
{
s = (s + k) % i;
}
k = k % n;
if (k == 0)
{
k = n;
}
d = (s + 1) + (m - k);
if (d >= 1 && d <= n)
{
cout << d << '\n';
}
else if (d < 1)
{
cout << n + d << '\n';
}
else if (d > n)
{
cout << d % n << '\n';
}
}
return 0;
}
函数图像法
/*
* n 个人数到 k 出列,最后剩下的人编号
*/
unsigned long long n, k;
int main()
{
cin >> n >> k;
long long y = k % 2;
long long x = 2, t = 0;
long long z1 = y, z2 = x;
while (x <= n)
{
z1 = y;
z2 = x;
t = (x - y) / (k - 1);
if (t == 0)
{
t++;
}
y = y + t * k - ((y + t * k) / (x + t)) * (x + t);
x += t;
}
cout << (z1 + (n - z2) * k) % n + 1 << endl;
return 0;
}
博弈论
Bash
#define _MAX 10000
int a[_MAX];
int b[_MAX];
int bash(int N, int K)
{
if (N % (K + 1) == 0)
{
return 2;
}
return 1;
}
int main()
{
int T;
scanf("%d", &T);
for (int i = 0; i < T; i++)
{
scanf("%d%d", a + i, b + i);
}
for (int i = 0; i < T; i++)
{
if (bash(a[i], b[i]) == 1)
{
printf("A\n");
}
else
{
printf("B\n");
}
}
return 0;
}
Nim
int main(int argc, const char * argv[])
{
int N, stone, tag = 0;
scanf("%d", &N);
while (N--)
{
scanf("%d", &stone);
tag ^= stone;
}
//tag为0则为后手赢,否则为先手赢
printf("%c\n", tag == 0 ? 'B' : 'A');
return 0;
}
SG打表
const int MAX_DIG = 64;
// SG打表
// f[]:可以取走的石子个数
// sg[]:0~n的SG函数值
// hash[]:mex{}
int f[MAX_DIG];
int sg[MAX_DIG];
int hash[MAX_DIG];
void getSG(int n)
{
memset(sg, 0, sizeof(sg));
for (int i = 1; i <= n; i++)
{
memset(hash, 0, sizeof(hash));
for (int j = 1; f[j] <= i; j++)
{
hash[sg[i - f[j]]] = 1;
}
for (int j = 0; j <= n; j++) // 求mes{}中未出现的最小的非负整数
{
if (hash[j] == 0)
{
sg[i] = j;
break;
}
}
}
}
SG DFS
const int MAX_DIG = 64;
// DFS
// 注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍
// n是集合s的大小 S[i]是定义的特殊取法规则的数组
int s[MAX_DIG];
int sg[MAX_DIG * 100];
int n;
int SG_dfs(int x)
{
if (sg[x] != -1)
{
return sg[x];
}
bool vis[MAX_DIG];
memset(vis, 0, sizeof(vis));
for (int i = 0; i < n; i++)
{
if (x >= s[i])
{
SG_dfs(x - s[i]);
vis[sg[x - s[i]]] = 1;
}
}
int e;
for (int i = 0; ; i++)
{
if (!vis[i])
{
e = i;
break;
}
}
return sg[x] = e;
}
Wythoff
int main()
{
int t, a, b, m, k;
scanf("%d", &t);
while (t--)
{
scanf("%d%d", &a, &b);
if (a > b)
{
a ^= b;
b ^= a;
a ^= b;
}
m = b - a;
k = (int)(m * (1 + sqrt(5)) / 2.0);
//m = ? * a
//k = m / ?
//?:黄金分割数
//如果a == k,则为后手赢,否则先手赢(奇异局)
printf("%s\n", a == k ? "B" : "A");
}
return 0;
}
周期性方程
追赶法解周期性方程
/*
* 周期性方程定义(n = 5)
* |a_1 b_1 c_1 d_1 e_1| = x_1 --- 1
* |e_2 a_2 b_2 c_2 d_2| = x_2 --- 2
* |d_2 e_2 a_2 b_2 c_2| = x_3 --- 3
* |c_4 d_2 e_2 a_4 b_4| = x_4 --- 4
* |b_5 c_5 d_5 e_5 a_5| = x_5 --- 5
* 输入: a[], b[], c[], x[]
* 输出: 求解结果x在x[]中
*/
const int MAXN = 1000;
int a[MAXN];
int b[MAXN];
int c[MAXN];
int x[MAXN];
void run()
{
c[0] /= b[0];
a[0] /= b[0];
x[0] /= b[0];
for (int i = 1; i < MAXN - 1; i++)
{
double temp = b[i] - a[i] * c[i - 1];
c[i] /= temp;
x[i] = (x[i] - a[i] * x[i - 1]) / temp;
a[i] = -a[i] * a[i - 1] / temp;
}
a[MAXN - 2] = -a[MAXN - 2] - c[MAXN - 2];
for (int i = MAXN - 3; i >= 0; i--)
{
a[i] = -a[i] - c[i] * a[i + 1];
x[i] -= c[i] * x[i + 1];
}
x[MAXN - 1] -= (c[MAXN - 1] * x[0] + a[MAXN - 1] * x[MAXN - 2]);
x[MAXN - 1] /= (c[MAXN - 1] * a[0] + a[MAXN - 1] * a[MAXN - 2] + b[MAXN - 1]);
for (int i = MAXN - 2; i >= 0; i --)
{
x[i] += a[i] * x[MAXN - 1];
}
return ;
}
阶乘
阶乘最后非零位
/*
* 阶乘最后非零位 复杂度O(nlongn)
* 返回改为,n以字符串方式传入
*/
#define MAXN 10000
const int mod[20] = {1, 1, 2, 6, 4, 2, 2, 4, 2, 8, 4, 4, 8, 4, 6, 8, 8, 6, 8, 2};
int lastDigit(char *buf)
{
int len = (int)strlen(buf);
int a[MAXN], i, c, ret = 1;
if (len == 1)
{
return mod[buf[0] - '0'];
}
for (i = 0; i < len; i++)
{
a[i] = buf[len - 1 - i] - '0';
}
for (; len; len -= !a[len - 1])
{
ret = ret * mod[a[1] % 2 * 10 + a[0]] % 5;
for (c = 0, i = len - 1; i >= 0; i--)
{
c = c * 10 + a[i];
a[i] = c / 5;
c %= 5;
}
}
return ret + ret % 2 * 5;
}
N的阶乘的长度
#define PI 3.1415926
int main()
{
int n, a;
while (~scanf(“%d", &n))
{
a = (int)((0.5 * log(2 * PI * n) + n * log(n) - n) / log(10));
printf("%d\n", a + 1);
}
return 0;
}
排列组合
类循环排列
用递归实现多重循环,本递归程序相当于n重循环,每重循环的长度为m的情况,所以输出共有m^n行。
/*
* 输入样例: 3 2
* 输出样例:
* 0 0 0
* 0 0 1
* 0 1 0
* 0 1 1
* 1 0 0
* 1 0 1
* 1 1 0
* 1 1 1
*/
#define MAX_N 10
int n, m; // 相当于n重循环,每重循环长度为m
int rcd[MAX_N]; // 记录每个位置填的数
void loop_permutation(int l)
{
int i;
if (l == n) // 相当于进入了 n 重循环的最内层
{
for (i = 0; i < n; i++)
{
cout << rcd[i];
if (i < n-1)
{
cout << " ";
}
}
cout << "\n";
return ;
}
for (i = 0; i < m; i++) // 每重循环长度为m
{
rcd[l] = i; // 在l位置放i
loop_permutation(l + 1); // 填下一个位置
}
}
int main()
{
while (cin >> n >> m)
{
loop_permutation(0);
}
return 0;
}
全排列
/*
* 对输入的n个数作全排列。
* 输入样例:
* 3
* 1 2 3
* 输出样例:
* 123
* 132
* 213
* 231
* 312
* 321
*/
#define MAX_N 10
int n; // 共n个数
int rcd[MAX_N]; // 记录每个位置填的数
int used[MAX_N]; // 标记数是否用过
int num[MAX_N]; // 存放输入的n个数
void full_permutation(int l)
{
int i;
if (l == n)
{
for (i = 0; i < n; i++)
{
printf("%d", rcd[i]);
if (i < n-1)
{
printf(" ");
}
}
printf("\n");
return ;
}
for (i = 0; i < n; i++) // 枚举所有的数(n个),循环从开始
if (!used[i])
{ // 若num[i]没有使用过, 则标记为已使用
used[i] = 1;
rcd[l] = num[i]; // 在l位置放上该数
full_permutation(l+1); // 填下一个位置
used[i] = 0; // 清标记
}
}
int read_data()
{
int i;
if (scanf("%d", &n) == EOF)
{
return 0;
}
for (i = 0; i < n; i++)
{
scanf("%d", &num[i]);
}
for (i = 0; i < n; i++)
{
used[i] = 0;
}
return 1;
}
int main()
{
while (read_data())
{
full_permutation(0);
}
return 0;
}
程序通过used数组,标记数是否被用过,可以产生全排列,共有n!种。但是, 通过观察会发现,若输入的n个数有重复,那么在输出的n!种排列中,必然存在重复的项
不重复排列
/*
* 输入n个数,输出由这n个数构成的排列,不允许出现重复的项。
* 输入样例:
* 3
* 1 1 2
* 输出样例:
* 1 1 2
* 1 2 1
* 2 1 1
*/
#define MAX_N 10
int n, m; // 共有n个数,其中互不相同的有m个
int rcd[MAX_N]; // 记录每个位置填的数
int used[MAX_N]; // 标记m个数可以使用的次数
int num[MAX_N]; // 存放输入中互不相同的m个数
void unrepeat_permutation(int l)
{
int i;
if (l == n) // 填完了n个数,则输出
{
for (i = 0; i < n; i++)
{
printf("%d", rcd[i]);
if (i < n - 1)
{
printf(" ");
}
}
printf("\n");
return ;
}
for (i = 0; i < m; i++) // 枚举m个本质不同的数
{
if (used[i] > 0) // 若数num[i]还没被用完,则可使用次数减
{
used[i]--;
rcd[l] = num[i]; // 在l位置放上该数
unrepeat_permutation(l+1); // 填下一个位置
used[i]++; // 可使用次数恢复
}
}
}
int read_data()
{
int i, j, val;
if (scanf("%d", &n) == EOF)
{
return 0;
}
m = 0;
for (i = 0; i < n; i++)
{
scanf("%d", &val);
for (j = 0; j < m; j++)
{
if (num[j] == val)
{
used[j]++; break;
}
}
if (j == m)
{
num[m] = val;
used[m++] = 1;
}
}
return 1;
}
int main()
{
while (read_data())
{
unrepeat_permutation(0);
}
return 0;
}
本程序将全排列中的used标记数组改为记录输入中每个本质不同的数出现的次数,并在递归函数中使用。需要注意的是,在输入过程中,应剔除重复的数值。实际上,重复排列的产生是由于同一个位置被多次填入了相同的数,并且这多次填入又是在同一次循环过程中完成的。本方法通过统计每个本质不同的数的个数,使得循环长度由n变为m,这样,i一旦自增,就再也不会指向原先填入过的数了。这种方法剪去了重复项的生成,从而加快了搜索速度,是深度优先搜索中常用的剪枝技巧。
一般组合
/*
* 输入n个数,从中选出m个数可构成集合,输出所有这样的集合。
* 输入样例:
* 4 3
* 1 2 3 4
* 输出样例:
* 1 2 3
* 1 2 4
* 1 3 4
* 2 3 4
*/
#define MAX_N 10
int n, m; // 从n个数中选出m个构成组合
int rcd[MAX_N]; // 记录每个位置填的数
int num[MAX_N]; // 存放输入的n个数
void select_combination(int l, int p)
{
int i;
if (l == m) // 若选出了m个数, 则打印
{
for (i = 0; i < m; i++)
{
printf("%d", rcd[i]);
if (i < m - 1)
{
printf(" ");
}
}
printf("\n");
return ;
}
for (i = p; i < n; i++) // 上个位置填的是num[p-1],本次从num[p]开始试探
{
rcd[l] = num[i]; // 在l位置放上该数
select_combination(l + 1, i + 1); // 填下一个位置
}
}
int read_data()
{
int i;
if (scanf("%d%d", &n, &m) == EOF)
{
return 0;
}
for (i = 0; i < n; i++)
{
scanf("%d", &num[i]);
}
return 1;
}
int main()
{
while (read_data())
{
select_combination(0, 0);
}
return 0;
}
因为在组合生成过程中引入了变量 p,保证了每次填入的数字在num中的下标是递增的,所以不需要使用used进行标记,共C(n, m)种组合。
全组合
/*
* 输入n个数,求这n个数构成的集合的所有子集。
* 输入样例:
* 3
* 1 2 3
* 输出样例:
* 1
* 1 2
* 1 2 3
* 1 3
* 2
* 2 3
* 3
*/
#define MAX_N 10
int n; // 共n个数
int rcd[MAX_N]; // 记录每个位置填的数
int num[MAX_N]; // 存放输入的n个数
void full_combination(int l, int p)
{
int i;
for (i = 0; i < l; i++) // 每次进入递归函数都输出
{
printf("%d", rcd[i]);
if (i < l-1)
{
printf(" ");
}
}
printf("\n");
for (i = p; i < n; i++) // 循环同样从p开始,但结束条件变为i>=n
{
rcd[l] = num[i]; // 在l位置放上该数
full_combination(l + 1, i + 1); // 填下一个位置
}
}
int read_data()
{
int i;
if (scanf("%d", &n) == EOF)
{
return 0;
}
for (i = 0; i < n; i++)
{
scanf("%d", &num[i]);
}
return 1;
}
int main()
{
while (read_data())
{
full_combination(0, 0);
}
return 0;
}
全组合,共2^n种,包含空集和自身。与全排列一样,若输入的n个数有重复, 那么在输出的2^n种组合中,必然存在重复的项。避免重复的方法与不重复排列算法中使用的类似。
不重复组合
/*
* 输入n个数,求这n个数构成的集合的所有子集,不允许输出重复的项。
* 输入样例:
* 3
* 1 1 3
* 输出样例:
* 1
* 1 1
* 1 1 3
* 1 3
* 3
*/
#define MAX_N 10
int n, m; // 输入n个数,其中本质不同的有m个
int rcd[MAX_N]; // 记录每个位置填的数
int used[MAX_N]; // 标记m个数可以使用的次数
int num[MAX_N]; // 存放输入中本质不同的m个数
void unrepeat_combination(int l, int p)
{
int i;
for (i = 0; i < l; i++) // 每次都输出
{
printf("%d", rcd[i]);
if (i < l - 1)
{
printf(" ");
}
}
printf("\n");
for (i = p; i < m; i++) // 循环依旧从p开始,枚举剩下的本质不同的数
{
if (used[i] > 0) // 若还可以用, 则可用次数减
{
used[i]--;
rcd[l] = num[i]; // 在l位置放上该
unrepeat_combination(l+1, i); // 填下一个位置
used[i]++; //可用次数恢复
}
}
}
int read_data()
{
int i, j, val;
if (scanf("%d", &n) == EOF)
{
return 0;
}
m = 0;
for (i = 0; i < n; i++)
{
scanf("%d", &val);
for (j = 0; j < m; j++)
{
if (num[j] == val)
{
used[j]++;
break;
}
}
if (j == m)
{
num[m] = val;
used[m++] = 1;
}
}
return 1;
}
int main()
{
while (read_data())
{
unrepeat_combination(0, 0);
}
return 0;
}
需要注意的是递归调用时,第二个参数是i,不再是全组合中的i+1!
搜索问题中有很多本质上就是排列组合问题,只不过加上了某些剪枝和限制条件。解这类题目的基本算法框架常常是类循环排列、全排列、一般组合或全组 合。而不重复排列与不重复组合则是两种非常有效的剪枝技巧。
求逆元
方程ax≡1(mod p),的解称为a关于模p的逆,当gcd(a,p)==1(即a,p互质)时,方程有唯一解,否则无解。
对于一些题目会要求把结果MOD一个数,通常是一个较大的质数,对于加减乘法通过同余定理可以直接拆开计算,但对于(a/b)%MOD这个式子,是不可以写成(a%MOD/b%MOD)%MOD的,但是可以写为(a*b^-1)%MOD,其中b^-1表示b的逆元。
ll getinv (ll a,ll p) {
ll d, x, y;
exgcd (a, p, d, x, y);
return (x + p) % p == 0 ? p : (x + p) % p;
}
拓展欧几里得法
/*
* 扩展欧几里得法(求ax + by = gcd)
*/
// 返回d = gcd(a, b);和对应于等式ax + by = d中的x、y
long long extendGcd(long long a, long long b, long long &x, long long &y)
{
if (a == 0 && b == 0)
{
return -1; // 无最大公约数
}
if (b == 0)
{
x = 1;
y = 0;
return a;
}
long long d = extendGcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
// 求逆元 ax = 1(mod n)
long long modReverse(long long a, long long n)
{
long long x, y;
long long d = extendGcd(a, n, x, y);
if (d == 1)
{
return (x % n + n) % n;
}
else
{
return -1; // 无逆元
}
}
简洁写法
/*
* 简洁写法I
* 只能求a < m的情况,且a与m互质
* 求ax = 1(mod m)的x值,即逆元(0 < a < m)
*/
long long inv(long long a, long long m)
{
if (a == 1)
{
return 1;
}
return inv(m % a, m) * (m - m / a) % m;
}
欧拉函数法
/*
* 欧拉函数法
* a 和 m 互质
*/
// 快速幂取模
long long powM(long long a, long long b, long long m)
{
long long tmp = 1;
if (b == 0)
{
return 1;
}
if (b == 1)
{
return a % m;
}
tmp = powM(a, b >> 1, m);
tmp = tmp * tmp % m;
if (b & 1)
{
tmp = tmp * a % m;
}
return tmp;
}
long long inv(long long a, long long m)
{
return powM(a, m - 2, m);
}
欧拉函数法(求阶乘逆元)
typedef long long ll;
const ll MOD = 1e9 + 7; // 必须为质数才管用
const ll MAXN = 1e5 + 3;
ll fac[MAXN]; // 阶乘
ll inv[MAXN]; // 阶乘的逆元
ll QPow(ll x, ll n)
{
ll ret = 1;
ll tmp = x % MOD;
while (n)
{
if (n & 1)
{
ret = (ret * tmp) % MOD;
}
tmp = tmp * tmp % MOD;
n >>= 1;
}
return ret;
}
void init()
{
fac[0] = 1;
for (int i = 1; i < MAXN; i++)
{
fac[i] = fac[i - 1] * i % MOD;
}
inv[MAXN - 1] = QPow(fac[MAXN - 1], MOD - 2);
for (int i = MAXN - 2; i >= 0; i--)
{
inv[i] = inv[i + 1] * (i + 1) % MOD;
}
}
FFT
const double PI = acos(-1.0);
// 复数结构体
struct Complex
{
double x, y; // 实部和虚部 x + yi
Complex(double _x = 0.0, double _y = 0.0)
{
x = _x;
y = _y;
}
Complex operator - (const Complex &b) const
{
return Complex(x - b.x, y - b.y);
}
Complex operator + (const Complex &b) const
{
return Complex(x + b.x, y + b.y);
}
Complex operator * (const Complex &b) const
{
return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
}
};
// 进行FFT和IFFT前的反转变换
// 位置i和(i二进制反转后的位置)互换
// len必须去2的幂
void change(Complex y[], int len)
{
int i, j, k;
for (i = 1, j = len / 2; i < len - 1; i++)
{
if (i < j)
{
swap(y[i], y[j]);
}
// 交换护卫小标反转的元素,i < j保证交换一次
// i做正常的+1,j左反转类型的+1,始终保持i和j是反转的
k = len / 2;
while (j >= k)
{
j -= k;
k /= 2;
}
if (j < k)
{
j += k;
}
}
return ;
}
// FFT
// len必须为2 ^ k形式
// on == 1时是DFT,on == -1时是IDFT
void fft(Complex y[], int len, int on)
{
change(y, len);
for (int h = 2; h <= len; h <<= 1)
{
Complex wn(cos(-on * 2 * PI / h), sin(-on * 2 * PI / h));
for (int j = 0; j < len; j += h)
{
Complex w(1, 0);
for (int k = j; k < j + h / 2; k++)
{
Complex u = y[k];
Complex t = w * y[k + h / 2];
y[k] = u + t;
y[k + h / 2] = u - t;
w = w * wn;
}
}
}
if (on == -1)
{
for (int i = 0; i < len; i++)
{
y[i].x /= len;
}
}
}
FWT
/*
* FWT(快速沃尔什变化)-Xor
* MOD:1e9 + 7, INV_2:2关于MOD的逆元
* N:2的整次幂(不够就向上取整)
*/
typedef long long ll;
const int MOD = 1e9 + 7;
const int INV_2 = 5e8 + 4;
inline void FWT(int c[], int N, int tf_utf) // tf_utf 1:tf; 0:utf
{
for (int i = 1; i < N; i <<= 1)
{
int tmp = i << 1;
for (int j = 0; j < N; j += tmp)
{
for (int k = 0; k < i; k++)
{
int x = c[j + k], y = c[j + k + i];
if (tf_utf)
{
c[j + k] = x + y;
if (c[j + k] >= MOD)
{
c[j + k] -= MOD;
}
c[j + k + i] = x - y;
if (c[j + k + i] < 0)
{
c[j + k + i] += MOD;
}
}
else
{
c[j + k] = (ll)(x + y) * INV_2 % MOD;
c[j + k + i] = (ll)(x - y + MOD) * INV_2 % MOD;
}
}
}
}
}
整数划分
整数划分(五边形定理)
P(n) = ∑{P(n - k(3k - 1) / 2 + P(n - k(3k + 1) / 2 | k ≥ 1}
n < 0时,P(n) = 0, n = 0时, P(n) = 1即可
// 划分元素可重复任意次
#define f(x) (((x) * (3 * (x) - 1)) >> 1)
#define g(x) (((x) * (3 * (x) + 1)) >> 1)
const int MAXN = 1e5 + 10;
const int MOD = 1e9 + 7;
int n, ans[MAXN];
int main()
{
scanf("%d", &n);
ans[0] = 1;
for (int i = 1; i <= n; ++i)
{
for (int j = 1; f(j) <= i; ++j)
{
if (j & 1)
{
ans[i] = (ans[i] + ans[i - f(j)]) % MOD;
}
else
{
ans[i] = (ans[i] - ans[i - f(j)] + MOD) % MOD;
}
}
for (int j = 1; g(j) <= i; ++j)
{
if (j & 1)
{
ans[i] = (ans[i] + ans[i - g(j)]) % MOD;
}
else
{
ans[i] = (ans[i] - ans[i - g(j)] + MOD) % MOD;
}
}
}
printf("%d\n", ans[n]);
return 0;
}
整数划分(五边形定理拓展)
F(n, k) = P(n) - 划分元素重复次数≥k次的情况。
// 问一个数n能被拆分成多少种情况
// 且要求拆分元素重复次数不能≥k
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 10;
int ans[MAXN];
// 此函数求ans[]效率比上一个代码段中求ans[]效率高很多
void init()
{
memset(ans, 0, sizeof(ans));
ans[0] = 1;
for (int i = 1; i < MAXN; ++i)
{
ans[i] = 0;
for (int j = 1; ; j++)
{
int tmp = (3 * j - 1) * j / 2;
if (tmp > i)
{
break;
}
int tmp_ = ans[i - tmp];
if (tmp + j <= i)
{
tmp_ = (tmp_ + ans[i - tmp - j]) % MOD;
}
if (j & 1)
{
ans[i] = (ans[i] + tmp_) % MOD;
}
else
{
ans[i] = (ans[i] - tmp_ + MOD) % MOD;
}
}
}
return ;
}
int solve(int n, int k)
{
int res = ans[n];
for (int i = 1; ; i++)
{
int tmp = k * i * (3 * i - 1) / 2;
if (tmp > n)
{
break;
}
int tmp_ = ans[n - tmp];
if (tmp + i * k <= n)
{
tmp_ = (tmp_ + ans[n - tmp - i * k]) % MOD;
}
if (i & 1)
{
res = (res - tmp_ + MOD) % MOD;
}
else
{
res = (res + tmp_) % MOD;
}
}
return res;
}
int main(int argc, const char * argv[])
{
init();
int T, n, k;
cin >> T;
while (T--)
{
cin >> n >> k;
cout << solve(n, k) << '\n';
}
return 0;
}
A^B约数之和
A^B约数之和对mod取模
/*
* 求A^B的约数之和对MOD取模
* 需要素数筛选和合数分解的算法,需要先调用getPrime();
* 参考《合数相关》
* 1+p+p^2+p^3+...+p^n
*/
const int MOD = 1000000;
long long pow_m(long long a, long long n)
{
long long ret = 1;
long long tmp = a % MOD;
while(n)
{
if (n & 1)
{
ret = (ret * tmp) % MOD;
}
tmp = tmp * tmp % MOD;
n >>= 1;
}
return ret;
}
// 计算1+p+p^2+...+p^n
long long sum(long long p, long long n)
{
if (p == 0)
{
return 0;
}
if (n == 0)
{
return 1;
}
if (n & 1)
{
return ((1 + pow_m(p, n / 2 + 1)) % MOD * sum(p, n / 2) % MOD) % MOD;
}
else
{
return ((1 + pow_m(p, n / 2 + 1)) % MOD * sum(p, n / 2 - 1) + pow_m(p, n / 2) % MOD) % MOD;
}
}
// 返回A^B的约数之和%MOD
long long solve(long long A, long long B)
{
getFactors(A);
long long ans = 1;
for (int i = 0; i < fatCnt; i++)
{
ans *= sum(factor[i][0], B * factor[i][1]) % MOD;
ans %= MOD;
}
return ans;
}
莫比乌斯反演
莫比乌斯反演公式
线性筛法求解
/*
* 莫比乌斯反演公式
* 线性筛法求解积性函数(莫比乌斯函数)
*/
const int MAXN = 1000000;
bool check[MAXN + 10];
int prime[MAXN + 10];
int mu[MAXN + 10];
void Moblus()
{
memset(check, false, sizeof(check));
mu[1] = 1;
int tot = 0;
for (int i = 2; i <= MAXN; i++)
{
if (!check[i])
{
prime[tot++] = i;
mu[i] = -1;
}
for (int j = 0; j < tot; j++)
{
if (i * prime[j] > MAXN)
{
break;
}
check[i * prime[j]] = true;
if (i % prime[j] == 0)
{
mu[i * prime[j]] = 0;
break;
}
else
{
mu[i * prime[j]] = -mu[i];
}
}
}
}
单独求解
int MOD(int a, int b)
{
return a - a / b * b;
}
int miu(int n)
{
int cnt, k = 0;
for (int i = 2; i * i <= n; i++)
{
if (MOD(n, i))
{
continue;
}
cnt = 0;
k++;
while (MOD(n, i) == 0)
{
n /= i;
cnt++;
}
if (cnt >= 2)
{
return 0;
}
}
if (n != 1)
{
k++;
}
return MOD(k, 2) ? -1 : 1;
}
Baby-Step Giant-Step
/*
* baby_step giant _step
* a^x = b(mod n) n不要求是素数
* 求解上式0 ≤ x < n的解
*/
#define MOD 76543
int hs[MOD];
int head[MOD];
int _next[MOD];
int id[MOD];
int top;
void insert(int x, int y)
{
int k = x % MOD;
hs[top] = x;
id[top] = y;
_next[top] = head[k];
head[k] = top++;
return ;
}
int find(int x)
{
int k = x % MOD;
for (int i = head[k]; i != -1; i = _next[i])
{
if (hs[i] == x)
{
return id[i];
}
}
return -1;
}
long long BSGS(int a, int b, int n)
{
memset(head, -1, sizeof(head));
top = 1;
if (b == 1)
{
return 0;
}
int m = (int)sqrt(n * 1.0), j;
long long x = 1, p = 1;
for (int i = 0; i < m; i++, p = p * a % n)
{
insert(p * b % n, i);
}
for (long long i = m; ; i++)
{
if ((j = find(x = x * p % n)) != -1)
{
return i - j;
}
if (i > n)
{
break;
}
}
return -1;
}
自适应simpson积分
const double eps = 1e-6; // 积分精度
// 被积函数
double F(double x)
{
double ans;
// 被积函数
// ...
// ans = x * exp(x); // 椭圆为例
return ans;
}
// 三点simpson法,这里要求F是一个全局函数
double simpson(double a, double b)
{
double c = a + (b - a) / 2;
return (F(a) + 4 * F(c) + F(b)) * (b - a) / 6;
}
// 自适应simpson公式(递归过程),已知整个区间[a, b]上的三点simpson指A
double asr(double a, double b, double eps, double A)
{
double c = a + (b - a) / 2;
double L = simpson(a, c), R = simpson(c, b);
if (fabs(L + R - A) <= 15 * eps)
{
return L + R + (L + R - A) / 15.0;
}
return asr(a, c, eps / 2, L) + asr(c, b, eps / 2, R);
}
// 自适应simpson公式(主过程)
double asr(double a, double b, double eps)
{
return asr(a, b, eps, simpson(a, b));
}
int main(int argc, const char * argv[])
{
// std::cout << asr(1, 2, eps) << '\n';
return 0;
}
多项式求根
多项式求根(牛顿法)
/*
* 牛顿法解多项式的根
* 输入:多项式系数c[],多项式度数n,求在[a,b]间的根
* 输出:根 要求保证[a,b]间有根
*/
double fabs(double x)
{
return (x < 0) ? -x : x;
}
double f(int m, double c[], double x)
{
int i;
double p = c[m];
for (i = m; i > 0; i--)
{
p = p * x + c[i - 1];
}
return p;
}
int newton(double x0, double *r, double c[], double cp[], int n, double a, double b, double eps)
{
int MAX_ITERATION = 1000;
int i = 1;
double x1, x2, fp, eps2 = eps / 10.0;
x1 = x0;
while (i < MAX_ITERATION)
{
x2 = f(n, c, x1);
fp = f(n - 1, cp, x1);
if ((fabs(fp) < 0.000000001) && (fabs(x2) > 1.0))
{
return 0;
}
x2 = x1 - x2 / fp;
if (fabs(x1 - x2) < eps2)
{
if (x2 < a || x2 > b)
{
return 0;
}
*r = x2;
return 1;
}
x1 = x2;
i++;
}
return 0;
}
double Polynomial_Root(double c[], int n, double a, double b, double eps)
{
double *cp;
int i;
double root;
cp = (double *)calloc(n, sizeof(double));
for (i = n - 1; i >= 0; i--)
{
cp[i] = (i + 1) * c[i + 1];
}
if (a > b)
{
root = a;
a = b;
b = root;
}
if ((!newton(a, &root, c, cp, n, a, b, eps)) && (!newton(b, &root, c, cp, n, a, b, eps)))
{
newton((a + b) * 0.5, &root, c, cp, n, a, b, eps);
}
free(cp);
if (fabs(root) < eps)
{
return fabs(root);
}
else
return root;
}
星期问题
基姆拉尔森公式:
W = (D + 2 * M + 3 * (M + 1) \ 5 + Y + Y \ 4 - Y \ 100 + Y \ 400) Mod 7
基姆拉尔森公式的计算结果是0,1,2,3,4,5,6 七种可能;
结果的对应关系:
0:星期一
1:星期二
2:星期三
3:星期四
4:星期五
5:星期六
6:星期日
/*
* 已知1752年9月3日是Sunday,并且日期控制在1700年2月28日后
*/
char name[][15] = { "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"};
int main()
{
int d, m, y, a;
printf("Day: ");
scanf("%d", &d);
printf("Month: ");
scanf("%d", &m);
printf("Year: ");
scanf("%d", &y);
// 1月2月当作前一年的13,14月
if (m == 1 || m == 2)
{
m += 12;
y--;
}
// 判断是否在1752年9月3日之前,实际上合并在一起倒更加省事
if ((y < 1752) || (y == 1752 && m < 9) || (y == 1752 && m == 9 && d < 3))
{
// 因为日期控制在1700年2月28日后,所以不用考虑整百年是否是闰年
a = (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 + 5) % 7;
}
else
{
// 这里需要考虑整百年是否是闰年的情况
a = (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7; // 实际上这个可以当做公式背下来
}
printf("it's a %s\n", name[a]);
return 0;
}
斐波那契额数列
矩阵原理单独求解
/*
* 求斐波那契数列第N项,模MOD
*/
#define mod(a, m) ((a) % (m) + (m)) % (m)
const int MOD = 1e9 + 9;
struct MATRIX
{
long long a[2][2];
};
MATRIX a;
long long f[2];
void ANS_Cf(MATRIX a)
{
f[0] = mod(a.a[0][0] + a.a[1][0], MOD);
f[1] = mod(a.a[0][1] + a.a[1][1], MOD);
return ;
}
MATRIX MATRIX_Cf(MATRIX a, MATRIX b)
{
MATRIX ans;
int k;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 2; j++)
{
ans.a[i][j] = 0;
k = 0;
while (k < 2)
{
ans.a[i][j] += a.a[k][i] * b.a[j][k];
ans.a[i][j] = mod(ans.a[i][j], MOD);
++k;
}
}
}
return ans;
}
MATRIX MATRIX_Pow(MATRIX a, long long n)
{
MATRIX ans;
ans.a[0][0] = 1;
ans.a[1][1] = 1;
ans.a[0][1] = 0;
ans.a[1][0] = 0;
while (n)
{
if (n & 1)
{
ans = MATRIX_Cf(ans, a);
}
n = n >> 1;
a = MATRIX_Cf(a, a);
}
return ans;
}
int main()
{
long long n;
while (cin >> n)
{
if (n == 1)
{
cout << '1' << '\n';
continue;
}
a.a[0][0] = a.a[0][1] = a.a[1][0] = 1;
a.a[1][1] = 0;
a = MATRIX_Pow(a, n - 2);
ANS_Cf(a);
cout << f[0] << '\n';
}
return 0;
}
1/n循环节长度
/*
* 求1/i的循环节长度的最大值,i<=n
*/
const int MAXN = 1005;
int res[MAXN]; // 循环节长度
int main()
{
memset(res, 0, sizeof(res));
int i, temp, j, n;
for (temp = 1; temp <= 1000; temp++)
{
i = temp;
while (i % 2 == 0)
{
i /= 2;
}
while (i % 5 == 0)
{
i /= 5;
}
n = 1;
for (j = 1; j <= i; j++)
{
n *= 10;
n %= i;
if (n == 1)
{
res[temp] = j;
break;
}
}
}
int max_re;
while (cin >> n)
{
max_re = 1;
for (i = 1; i <= n; i++)
{
if (res[i] > res[max_re])
{
max_re = i;
}
}
cout << max_re << endl;
}
return 0;
}
矩阵相关
矩阵乘法
/*
* 矩阵乘法 n*n矩阵乘法
*/
#define MAXN 111
#define mod(x) ((x) % MOD)
#define MOD 1000000007
#define LL long long
int n;
struct mat
{
int m[MAXN][MAXN];
};
// 矩阵乘法
mat operator * (mat a, mat &b)
{
mat ret;
memset(ret.m, 0, sizeof(ret.m));
for (int k = 0; k < n; k++)
{
for (int i = 0; i < n; i++)
{
if (a.m[i][k])
{
for (int j = 0; j < n; j++)
{
ret.m[i][j] = mod(ret.m[i][j] + (LL)a.m[i][k] * b.m[k][j]);
}
}
}
}
return ret;
}
矩阵乘法+判等
/*
* AB == C ???
*/
struct Matrix
{
Type mat[MAXN][MAXN];
int n, m;
Matrix()
{
n = m = MAXN;
memset(mat, 0, sizeof(mat));
}
Matrix(const Matrix &a)
{
set_size(a.n, a.m);
memcpy(mat, a.mat, sizeof(a.mat));
}
Matrix & operator = (const Matrix &a)
{
set_size(a.n, a.m);
memcpy(mat, a.mat, sizeof(a.mat));
return *this;
}
void set_size(int row, int column)
{
n = row;
m = column;
}
friend Matrix operator * (const Matrix &a, const Matrix &b)
{
Matrix ret;
ret.set_size(a.n, b.m);
for (int i = 0; i < a.n; ++i)
{
for (int k = 0; k < a.m; ++k)
{
if (a.mat[i][k])
{
for (int j = 0; j < b.m; ++j)
{
if (b.mat[k][j])
{
ret.mat[i][j] = ret.mat[i][j] + a.mat[i][k] * b.mat[k][j];
}
}
}
}
}
return ret;
}
friend bool operator == (const Matrix &a, const Matrix &b)
{
if (a.n != b.n || a.m != b.m)
{
return false;
}
for (int i = 0; i < a.n; ++i)
{
for (int j = 0; j < a.m; ++j)
{
if (a.mat[i][j] != b.mat[i][j])
{
return false;
}
}
}
return true;
}
};
矩阵快速幂
/*
* 矩阵快速幂 n*n矩阵的x次幂
*/
#define MAXN 111
#define mod(x) ((x) % MOD)
#define MOD 1000000007
#define LL long long
int n;
struct mat
{
int m[MAXN][MAXN];
} unit; // 单元矩阵
// 矩阵乘法
mat operator * (mat a, mat &b)
{
mat ret;
memset(ret.m, 0, sizeof(ret.m));
for (int k = 0; k < n; k++)
{
for (int i = 0; i < n; i++)
{
if (a.m[i][k])
{
for (int j = 0; j < n; j++)
{
ret.m[i][j] = mod(ret.m[i][j] + (LL)a.m[i][k] * b.m[k][j]);
}
}
}
}
return ret;
}
void init_unit()
{
for (int i = 0; i < MAXN; i++)
{
unit.m[i][i] = 1;
}
return ;
}
mat pow_mat(mat a, LL n)
{
mat ret = unit;
while (n)
{
if (n & 1)
{
// n--;
ret = ret * a;
}
n >>= 1;
a = a * a;
}
return ret;
}
int main()
{
LL x;
init_unit();
while (cin >> n >> x)
{
mat a;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
cin >> a.m[i][j];
}
}
a = pow_mat(a, x); // a矩阵的x次幂
// 输出矩阵
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (j + 1 == n)
{
cout << a.m[i][j] << endl;
}
else
{
cout << a.m[i][j] << " ";
}
}
}
}
return 0;
}
反素数
求最小的因子个数为n个正整数
typedef unsigned long long ULL;
const ULL INF = ~0ULL;
const int MAXP = 16;
int prime[MAXP] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
int n;
ULL ans;
void dfs(int dept, ULL tmp, int num, int pre) // 深度/当前值/约数个数/上一个数
{
if (num > n)
{
return;
}
if (num == n && ans > tmp)
{
ans = tmp;
}
for (int i = 1; i <= pre; i++)
{
if (ans / prime[dept] < tmp)
{
break;
}
dfs(dept + 1, tmp *= prime[dept], num * (i + 1), i);
}
}
int main()
{
while (cin >> n)
{
ans = INF;
dfs(0, 1, 1, 15);
cout << ans << endl;
}
return 0;
}
求n以内的因子最多的数(不止一个则取最小)
typedef long long ll;
const int MAXP = 16;
const int prime[MAXP] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
ll n, res, ans;
void dfs(ll cur, ll num, int key, ll pre) // 当前值/当前约数数量/当前深度/上一个数
{
if (key >= MAXP)
{
return ;
}
else
{
if (num > ans)
{
res = cur;
ans = num;
}
else if (num == ans) // 如果约数数量相同,则取较小的数
{
res = min(cur, res);
}
ll i;
for ( i = 1; i <= pre; i++)
{
if (cur <= n / prime[key]) // cur*prime[key]<=n
{
cur *= prime[key];
dfs(cur, num * (i + 1), key + 1, i);
}
else
{
break;
}
}
}
}
void solve()
{
res = 1;
ans = 1;
dfs(1, 1, 0, 15);
cout << res << ' ' << ans << endl;
}
int main(int argc, const char * argv[])
{
int T;
cin >> T;
while (T--)
{
cin >> n;
solve();
}
return 0;
}
容斥
const int MAXN = 1111;
int n;
double ans;
double p[MAXN];
void dfs(int x, int tot, double sum) // dfs(1, 0, ?)
{
if (x == n + 1)
{
if (sum == 0.0)
{
return ;
}
if (tot & 1)
{
ans += 1 / sum; // 公式随意变
}
else
{
ans -= 1 / sum;
}
return ;
}
dfs(x + 1, tot, sum);
dfs(x + 1, tot + 1, sum + p[x]);
}
母函数
/*
* 母函数
* c1是保存各项质量砝码可以组合的数目
* c2是中间量,保存每一次的情况
*/
const int MAXN = 1e4 + 10;
int n;
int c1[MAXN];
int c2[MAXN];
int main()
{
while (cin >> n)
{
for (int i = 0; i <= n; ++i)
{
c1[i] = 1;
c2[i] = 0;
}
for (int i = 2; i <= n; ++i)
{
for (int j = 0; j <= n; ++j)
{
for (int k = 0; k + j <= n; k += i)
{
c2[j + k] += c1[j];
}
}
for (int j = 0; j <= n; ++j)
{
c1[j] = c2[j];
c2[j] = 0;
}
}
cout << c1[n] << endl;
}
return 0;
}
数论相关公式
素数两种筛法
bool isp[maxn];
int p[maxn], len;
bool isp[100];
void init() {
int m = (int)sqrt(maxn+0.5);
for(int i = 2;i <= m;i++) {
if(!isp[i]) {
for(int j = i*i;j <= maxn;j += i) {
isp[j] = true;
}
}
}
}
void init() { //推荐这个,较快
isp[0] = isp[1] = true;
for (int i = 2; i < maxn; i++) {
if(!isp[i]) p[++len] = i;
for (int j = 1; j <= len && p[j]*i < maxn; j++) {
isp[i*p[j]] = true;
if (i%p[j] == 0) break;
}
}
}
埃拉托斯特尼筛法
int prime[maxn];
bool is_prime[maxn];
int sieve(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){ // 注意数组大小是n
if(is_prime[i]){
prime[p++] = i;
for(int j = i + i; j <= n; j += i) // 轻剪枝,j必定是i的倍数
is_prime[j] = false;
}
}
return p; // 返回素数个数
}
具有最大素因子的整数
样例:36 38 40 42 out:38
int Prime[maxn];
void IsPrime()
{
Prime[1]=1;
for(int i=2;i<=maxn;i++)
{
if(!Prime[i])
{
Prime[i]=i;
for(int j=2*i;j<=maxn;j+=i)
Prime[j]=i;
}
}
}
欧拉函数
在数论,对正整数n,欧拉函数是小于n的正整数中与n互质的数的数目(φ(1)=1)。
int euler_phi(int n){ //单个值
int m = (int)sqrt(n + 0.5);
int ans = n;
for (int i = 2;i <= m;i++){
if (n%i == 0){ //如果存在素因子
ans = ans/i*(i-1);
while (n%i == 0) n/=i;
}
}
if(n > 1) ans = ans/n*(n-1); //考虑n本身
return ans;
}
void phi_table(int n,int *phi){ //欧拉表
for (int i = 1;i <= n;i++) phi[i] = i;
for(int i = 2;i <= n;i++){
if(phi[i] == i){ //类似于Eratosthenes筛法这里
for(int j = i;j <= n;j+=i){
phi[j] = phi[j]/i*(i-1);
}
}
}
}
阶乘逆元
fac[0] = 1;
for (int i = 1; i <= maxn; i++)
fac[i] = mod(fac[i - 1] * i);
rfac[maxn] = qpow(fac[maxn],MOD - 2);
for (int i = maxn;i > 0; i--)
rfac[i - 1] = mod(rfac[i] * i);
中国剩余定理(CRT拓展)
中国剩余定理给出了以下的一元线性同余方程组:
假设整数m1,m2, ... ,mn两两互质,则对任意的整数:a1,a2, ... ,an,方程组 有解,即x,扩展剩余定理就是m1,m2···mn,这几个数不两两互质的情况
ll CRT(ll M){
ll sum=0,tmp,v;
for (int i=1;i<=cnt;i++){
tmp=M/m[i];
v=getInv(tmp,m[i]);
sum=(sum+tmp*a[i]*v)%M;
}
return sum;
}
/*以下是ECRT*/
bool merge(ll &a1,ll &m1,ll a2,ll m2){
ll c,d,x,a3,m3;
c=a2-a1;d=__gcd(m1,m2);
if (c%d!=0) return false;
c=c/d;m1=m1/d;m2=m2/d;
x=getinv(m1,m2);
x=(x*c)%m2;
x=x*(m1*d)+a1;
m3=m1*m2*d;
a3=(x%m3+m3)%m3;
a1=a3;m1=m3;
return true;
}
ll ECRT(){
ll A=a[1],M=r[1];
for (int i=2;i<=n;i++) //无解返回 -1
if (!merge(A,M,a[i],r[i]))
return -1;
return (A%M+M)%M;
}
Lacus定理模板
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N =1e5;
ll n, m, p, fac[N];
void init()
{
int i;
fac[0] =1;
for(i =1; i <= p; i++)
fac[i] = fac[i-1]*i % p;
}
ll q_pow(ll a, ll b)
{
ll ans =1;
while(b)
{
if(b &1) ans = ans * a % p;
b>>=1;
a = a*a % p;
}
return ans;
}
ll C(ll n, ll m)
{
if(m > n) return 0;
return fac[n]*q_pow(fac[m]*fac[n-m], p-2) % p;
}
ll Lucas(ll n, ll m )
{
if(m ==0) return 1;
else return (C(n%p, m%p)*Lucas(n/p, m/p))%p;
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
scanf("%I64d%I64d%I64d", &n, &m, &p);
init();
printf("%I64d\n", Lucas(n, m));
}
return 0;
}
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
ll a[200005],p,t,n,m;
ll exgcd(ll a,ll b,ll &x,ll &y){//扩展欧几里得
if(b){
ll d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
x=1;y=0;return a;
}
ll inv(ll x,ll mod){//求x在模mod下的逆元
ll anx,any;exgcd(x,mod,anx,any);return (anx+mod)%mod;
}
ll C(ll n,ll m){//求C(n,m)%p n<=10^5 m<=10^5
if(m>n)return 0;
return a[n]*inv(a[m],p)%p*inv(a[n-m],p)%p;
}
ll lucas(ll n,ll m){//lucas定理求 C(n,m)%p n,m可以很大
if(!m)return 1;
return C(n%p,m%p)*lucas(n/p,m/p)%p;
}
void prep(){//预处理n!%p
a[0]=1;
for(ll i=1;i<=p;i++){
a[i]=i*a[i-1]%p;
}
}
void work(){
scanf("%lld%lld%lld",&n,&m,&p);
prep();
printf("%lld\n",lucas(n+m,n));
}
int main(){
scanf("%lld",&t);
while(t--){
work();
}
return 0;
}
费马小定理
String 字符串
编辑距离
编辑距离,又称Levenshtein距离(也叫做Edit Distance),是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 1e3 + 5;
int T, cas = 0;
int n, m;
int dp[N][N];
char s[N], t[N];
int main()
{
while (scanf("%s%s", s, t) != EOF)
{
int n = (int)strlen(s), m = (int)strlen(t);
for (int i = 0; i <= n; i++)
{
dp[i][0] = i;
}
for (int i = 0; i <= m; i++)
{
dp[0][i] = i;
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1;
dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + (s[i - 1] != t[j - 1]));
}
}
printf("%d\n", dp[n][m]);
}
return 0;
}
KMP算法
KMP_Pre
/*
* next[]的含义,x[i - next[i]...i - 1] = x[0...next[i] - 1]
* next[i]为满足x[i - z...i - 1] = x[0...z - 1]的最大z值(就是x的自身匹配)
*/
void KMP_Pre(char x[], int m, int next[])
{
int i, j;
j = next[0] = -1;
i = 0;
while (i < m)
{
while (-1 != j && x[i] != x[j])
{
j = next[j];
}
next[++i] = ++j;
}
return ;
}
PreKMP
/*
* kmpNext[]的意思:next'[i] = next[next[...[next[i]]]]
* (直到next'[i] < 0或者x[next'[i]] != x[i])
* 这样的预处理可以快一些
*/
void preKMP(char x[], int m, int kmpNext[])
{
int i, j;
j = kmpNext[0] = -1;
i = 0;
while (i < m)
{
while (-1 != j && x[i] != x[j])
{
j = kmpNext[j];
}
if (x[++i] == x[++j])
{
kmpNext[i] = kmpNext[j];
}
else
{
kmpNext[i] = j;
}
}
return ;
}
KMP_Count
/*
* 此函数与上述两个函数中的任意一个搭配使用(即调用上述两个函数中的任意一个)
* 返回x在y中出现的次数,可以重叠
*/
int next[10010];
int KMP_Count(char x[], int m, char y[], int n)
{
// x是模式串,y是主串
int i, j;
int ans = 0;
// preKMP(x, m, next);
KMP_Pre(x, m, next);
i = j = 0;
while (i < n)
{
while (-1 != j && y[i] != x[j])
{
j = next[j];
}
i++, j++;
if (j >= m)
{
ans++;
j = next[j];
}
}
return ans;
}
拓展KMP
/*
* 扩展KMP
* next[i]:x[i...m-1]的最长公共前缀
* extend[i]:y[i...n-1]与x[0...m-1]的最长公共前缀
*/
void preEKMP(char x[], int m, int next[])
{
next[0] = m;
int j = 0;
while (j + 1 < m && x[j] == x[j + 1])
{
j++;
}
next[1] = j;
int k = 1;
for (int i = 2; i < m; i++)
{
int p = next[k] + k - 1;
int L = next[i - k];
if (i + L < p + 1)
{
next[i] = L;
}
else
{
j = std::max(0, p - i + 1);
while (i + j < m && x[i + j] == x[j])
{
j++;
}
next[i] = j;
k = i;
}
}
return ;
}
void EKMP(char x[], int m, char y[], int n, int next[], int extend[])
{
preEKMP(x, m, next);
int j = 0;
while (j < n && j < m && x[j] == y[j])
{
j++;
}
extend[0] = j;
int k = 0;
for (int i = 1; i < n; i++)
{
int p = extend[k] + k - 1;
int L = next[i - k];
if (i + L < p + 1)
{
extend[i] = L;
}
else
{
j = std::max(0, p - i + 1);
while (i + j < n && j < m && y[i + j] == x[j])
{
j++;
}
extend[i] = j;
k = i;
}
}
return ;
}
最短公共祖先
将KMP进行略微的改动,依然是查找匹配段,要求要么一个串包含另一个串,要么一个串的前缀等于另一个串的后缀。
/*
* The shortest common superstring of 2 strings S1 and S2 is
* a string S with|the minimum number of characters which
* contains both S1 and S2 as a sequence of consecutive characters.
*/
const int N = 1000010;
char a[2][N];
int fail[N];
inline int max(int a, int b)
{
return (a > b) ? a : b;
}
int kmp(int &i, int &j, char* str, char* pat)
{
int k;
memset(fail, -1, sizeof(fail));
for (i = 1; pat[i]; ++i)
{
for (k = fail[i - 1]; k >= 0 && pat[i] != pat[k + 1]; k = fail[k]);
if (pat[k + 1] == pat[i])
{
fail[i] = k + 1;
}
}
i = j = 0;
while (str[i] && pat[j])
{
if (pat[j] == str[i])
{
i++;
j++;
}
else if (j == 0)
{
i++; // 第一个字符匹配失败,从str下一个字符开始
}
else
{
j = fail[j - 1] + 1;
}
}
if (pat[j])
{
return -1;
}
else
{
return i - j;
}
}
int main(int argc, const char * argv[])
{
int T;
scanf("%d", &T);
while (T--)
{
int i, j, l1 = 0, l2 = 0;
cin >> a[0] >> a[1];
int len1 = (int)strlen(a[0]), len2 = (int)strlen(a[1]), val;
val = kmp(i, j, a[1], a[0]); // a[1]在前
if (val != -1)
{
l1 = len1;
}
else
{
// printf("i:%d, j:%d\n", i, j);
if (i == len2 && j - 1 >= 0 && a[1][len2 - 1] == a[0][j - 1])
{
l1 = j;
}
}
val = kmp(i, j, a[0], a[1]); // a[0]在前
if (val != -1)
{
l2 = len2;
}
else
{
// printf("i:%d, j:%d\n", i, j);
if (i == len1 && j - 1 >= 0 && a[0][len1 - 1] == a[1][j - 1])
{
l2 = j;
}
}
// printf("l1:%d,l2:%d\n",l1,l2);
printf("%d\n", len1 + len2 - max(l1, l2));
}
return 0;
}
Karp-Rabin算法
字符串匹配
/*
* hash(w[0 ... m - 1]) =
* (w[0] * 2 ^ (m - 1) + ... + w[m - 1] * 2 ^ 0) % q;
* hash(w[j + 1 ... j + m]) =
* rehash(y[j], y[j + m], hash(w[j ... j + m - 1]);
* rehash(a, b, h) = ((h - a * 2 ^ (m - 1)) * 2 + b) % q;
* 可以用q = 2 ^ 32简化%运算
*/
#define REHASH(a, b, h) (((h - (a) * b) << 1) + b)
int krmatch(char *x, int m, char *y, int n)
{
//search x in y
int d, hx, hy, i, j;
for (d = i = 1; i < m; i++)
{
d = (d << 1);
}
for (hy = hx = i = 0; i < m; i++)
{
hx = ((hx << 1) + x[i]);
hy = ((hy << 1) + y[i]);
}
for (j = 0; j <= n - m; j++)
{
if (hx == hy && memcmp(x, y + j, m) == 0)
{
return j;
}
hy = REHASH(y[j], y[j + m], hy);
}
return 0; //理论上不会背执行,全部都应该从上一个return返回
}
字符块匹配
/*
* Text: n * m matrix;
* Pattern: x * y matrix;
*/
//#define uint unsigned int // C++中自带
const int A = 1024, B = 128;
const uint E = 27;
char text[A][A];
char patt[B][B];
uint ht, hp;
uint pw[B * B];
uint hor[A];
uint ver[A][A];
int n, m, x, y;
void init()
{
int i, j = B * B;
for (i = 1, pw[0] = 1; i < j; i++)
{
pw[i] = pw[i - 1] * E;
}
return ;
}
void hash()
{
int i, j;
for (i = 0; i < n; i++)
{
for (j = 0, hor[i] = 0; j < y; j++)
{
hor[i] *= pw[x];
hor[i] += text[i][j] - 'a';
}
}
for (j = 0; j < m; j++)
{
for (i = 0, ver[0][j] = 0; i < x; i++)
{
ver[0][j] *= E;
ver[0][j] += text[i][j] - 'a';
}
for (i = 1; i <= n - x; i++)
{
ver[i][j] = (ver[i - 1][j] - (text[i - 1][j] - 'a') * pw[x - 1]) * E + text[i + x - 1][j] - 'a';
}
}
for (j = 0, ht = hp = 0; j < y; j++)
{
for (i = 0; i < x; i++)
{
ht *= E;
ht += text[i][j] - 'a';
hp *= E;
hp += patt[i][j] - 'a';
}
}
return ;
}
void read()
{
int i;
std::cin >> n >> m;
for (i = 0; i < n; i++)
{
std::cin >> text[i];
}
for (i = 0; i < x; i++)
{
std::cin >> patt[i];
}
return ;
}
int solve()
{
if (n == 0 || m == 0 || x == 0 || y == 0)
{
return 0;
}
int i, j, cnt = 0;
uint t;
for (i = 0; i <= n - x; i++)
{
for (j = 0, t = ht; j <= m - y; j++)
{
if (t == hp)
{
cnt++;
}
t = (t - ver[i][j] * pw[y * x - x]) * pw[x] + ver[i][j + y];
}
ht = (ht - hor[i] * pw[x - 1]) * E + hor[i + x];
}
return cnt;
}
int main(int argc, const char * argv[])
{
int T;
init();
for (std::cin >> T; T; T--)
{
read();
hash();
std::cout << solve() << '\n';
}
return 0;
}
Manacher最长回文子串
/*
* 求最长回文子串
*/
const int MAXN = 110010;
char A[MAXN * 2];
int B[MAXN * 2];
void Manacher(char s[], int len)
{
int l = 0;
A[l++] = '$'; //0下标存储为其他字符
A[l++] = '#';
for (int i = 0; i < len; i++)
{
A[l++] = s[i];
A[l++] = '#';
}
A[l] = 0; //空字符
int mx = 0;
int id = 0;
for (int i = 0; i < l; i++)
{
B[i] = mx > i ? std::min(B[2 * id - i], mx - i) : 1;
while (A[i + B[i]] == A[i - B[i]])
{
B[i]++;
}
if (i + B[i] > mx)
{
mx = i + B[i];
id = i;
}
}
return ;
}
/*
* abaaba
* i: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
* A: $ # a # b # a # a # b # a # '\0'
* B: 1 1 2 1 4 1 2 7 2 1 4 1 2 1 //以第i个为中心的回文半径(包括第i个)
*/
char s[MAXN];
int main(int argc, const char * argv[])
{
while (std::cin >> s)
{
int len = (int)strlen(s);
Manacher(s, len);
int ans = 0;
for (int i = 0; i < 2 * len + 2; i++) //两倍长度并且首位插有字符,所以i < 2 * len + 2
{
ans = std::max(ans, B[i] - 1);
}
std::cout << ans << std::endl;
}
return 0;
}
strstr函数
/*
* strstr函数
* 功能:在串中查找指定字符串的第一次出现
* 用法:char *strstr(char *strOne, char *strTwo);
* 据说strstr函数和KMP的算法效率差不多
*/
int main(int argc, const char * argv[])
{
char strOne[] = "Borland International";
char strTwo[] = "nation";
char *ptr;
ptr = strstr(strOne, strTwo);
std::cout << ptr << '\n';
return 0;
}
PS: 输出结果为”national”。
AC自动机
/*
* 求目标串中出现了几个模式串
*/
struct Trie
{
int next[500010][26], fail[500010], end[500010];
int root, L;
int newnode()
{
for (int i = 0; i < 26; i++)
{
next[L][i] = -1;
}
end[L++] = 0;
return L - 1;
}
void init()
{
L = 0;
root = newnode();
}
void insert(char buf[])
{
int len = (int)strlen(buf);
int now = root;
for (int i = 0; i < len; i++)
{
if (next[now][buf[i] - 'a'] == -1)
{
next[now][buf[i] - 'a'] = newnode();
}
now = next[now][buf[i] - 'a'];
}
end[now]++;
}
void build()
{
queue<int>Q;
fail[root] = root;
for (int i = 0; i < 26; i++)
{
if (next[root][i] == -1)
{
next[root][i] = root;
}
else
{
fail[next[root][i]] = root;
Q.push(next[root][i]);
}
}
while (!Q.empty())
{
int now = Q.front();
Q.pop();
for (int i = 0;i < 26;i++)
{
if (next[now][i] == -1)
{
next[now][i] = next[fail[now]][i];
}
else
{
fail[next[now][i]]=next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
}
int query(char buf[])
{
int len = (int)strlen(buf);
int now = root;
int res = 0;
for (int i = 0; i < len; i++)
{
now = next[now][buf[i] - 'a'];
int temp = now;
while (temp != root)
{
res += end[temp];
end[temp] = 0;
temp = fail[temp];
}
}
return res;
}
void debug()
{
for (int i = 0; i < L; i++)
{
printf("id = %3d,fail = %3d,end = %3d,chi = [", i, fail[i], end[i]);
for (int j = 0; j < 26; j++)
{
printf("%2d", next[i][j]);
}
printf("]\n");
}
}
};
char buf[1000010];
Trie ac;
int main()
{
int T;
int n;
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
ac.init();
for (int i = 0; i < n; i++)
{
scanf("%s", buf);
ac.insert(buf);
}
ac.build();
scanf("%s", buf);
printf("%d\n", ac.query(buf));
}
return 0;
}
后缀数组
DA算法
/*
* suffix array
* 倍增算法 O(n*logn)
* 待排序数组长度为n,放在0~n-1中,在最后面补一个0
* da(str, sa, rank, height, n, m);
* 例如:
* n = 8;
* num[] = { 1, 1, 2, 1, 1, 1, 1, 2, $ }; 注意num最后一位为0,其他大于0
* rank[] = { 4, 6, 8, 1, 2, 3, 5, 7, 0 }; rank[0~n-1]为有效值,rank[n]必定为0无效值
* sa[] = { 8, 3, 4, 5, 0, 6, 1, 7, 2 }; sa[1~n]为有效值,sa[0]必定为n是无效值
* height[]= { 0, 0, 3, 2, 3, 1, 2, 0, 1 }; height[2~n]为有效值
* 稍微改动可以求最长公共前缀,需要注意两串起始位置相同的情况
* 另外需要注意的是部分数组需要开两倍空间大小
*/
const int MAXN = 20010;
int t1[MAXN];
int t2[MAXN];
int c[MAXN]; // 求SA数组需要的中间变量,不需要赋值
// 待排序的字符串放在s数组中,从s[0]到s[n-1],长度为n,且最大值小于m,
// 除s[n-1]外的所有s[i]都大于0,r[n-1]=0
// 函数结束以后结果放在sa数组中
bool cmp(int *r, int a, int b, int l)
{
return r[a] == r[b] && r[a + l] == r[b + l];
}
void da(int str[], int sa[], int rank[], int height[], int n, int m)
{
n++;
int i, j, p, *x = t1, *y = t2; // 第一轮基数排序,如果s的最大值很大,可改为快速排序
for (i = 0; i < m; i++)
{
c[i] = 0;
}
for (i = 0; i < n; i++)
{
c[x[i] = str[i]]++;
}
for (i = 1; i < m; i++)
{
c[i] += c[i-1];
}
for (i = n - 1; i >= 0; i--)
{
sa[--c[x[i]]] = i;
}
for (j = 1; j <= n; j <<= 1)
{
p = 0;
// 直接利用sa数组排序第二关键字
for (i = n - j; i < n; i++)
{
y[p++] = i; // 后面的j个数第二关键字为空的最小
}
for (i = 0; i < n; i++)
{
if (sa[i] >= j)
{
y[p++] = sa[i] - j; // 这样数组y保存的就是按照第二关键字排序的结果
}
}
// 基数排序第一关键字
for (i = 0; i < m; i++)
{
c[i] = 0;
}
for (i = 0; i < n; i++)
{
c[x[y[i]]]++;
}
for (i = 1; i < m; i++)
{
c[i] += c[i - 1];
}
for (i = n - 1; i >= 0; i--)
{
sa[--c[x[y[i]]]] = y[i]; // 根据sa和x数组计算新的x数组
}
swap(x, y);
p = 1;
x[sa[0]] = 0;
for (i = 1; i < n; i++)
{
x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
}
if (p >= n)
{
break;
}
m = p; // 下次基数排序的最大值
}
int k = 0;
n--;
for (i = 0; i <= n; i++)
{
rank[sa[i]] = i;
}
for (i = 0; i < n; i++)
{
if (k)
{
k--;
}
j = sa[rank[i] - 1];
while (str[i + k] == str[j + k])
{
k++;
}
height[rank[i]] = k;
}
}
int _rank[MAXN], height[MAXN];
int RMQ[MAXN];
int mm[MAXN];
int best[20][MAXN];
void initRMQ(int n)
{
mm[0] = -1;
for (int i = 1; i <= n; i++)
{
mm[i] = ((i & (i - 1)) == 0) ? mm[i - 1] + 1 : mm[i - 1];
}
for (int i = 1; i <= n; i++)
{
best[0][i] = i;
}
for (int i = 1; i <= mm[n]; i++)
{
for (int j = 1; j + (1 << i) - 1 <= n; j++)
{
int a = best[i - 1][j];
int b = best[i - 1][j + (1 << (i - 1))];
if (RMQ[a] < RMQ[b])
{
best[i][j] = a;
}
else
{
best[i][j] = b;
}
}
}
}
int askRMQ(int a, int b)
{
int t;
t = mm[b - a + 1];
b -= (1 << t) - 1;
a = best[t][a];
b = best[t][b];
return RMQ[a] < RMQ[b] ? a : b;
}
int lcp(int a, int b)
{
a = _rank[a];
b = _rank[b];
if (a > b)
{
swap(a,b);
}
return height[askRMQ(a + 1, b)];
}
char str[MAXN];
int r[MAXN];
int sa[MAXN];
int main()
{
while (scanf("%s", str) == 1)
{
int len = (int)strlen(str);
int n = 2 * len + 1;
for (int i = 0; i < len; i++)
{
r[i] = str[i];
}
for (int i = 0; i < len; i++)
{
r[len + 1 + i] = str[len - 1 - i];
}
r[len] = 1;
r[n] = 0;
da(r, sa, _rank, height, n, 128);
for (int i = 1; i <= n; i++)
{
RMQ[i]=height[i];
}
initRMQ(n);
int ans = 0, st = 0;
int tmp;
for (int i = 0; i < len; i++)
{
tmp = lcp(i, n - i); // 偶对称
if (2 * tmp > ans)
{
ans = 2 * tmp;
st = i - tmp;
}
tmp=lcp(i, n - i - 1); // 奇数对称
if (2 * tmp - 1 > ans)
{
ans = 2 * tmp - 1;
st = i - tmp + 1;
}
}
str[st + ans] = 0;
printf("%s\n", str + st);
}
return 0;
}
DC3算法
da[]和str[]数组都要开大三倍,相关数组也是三倍
/*
* 后缀数组
* DC3算法,复杂度O(n)
* 所有的相关数组都要开三倍
*/
const int MAXN = 2010;
#define F(x) ((x) / 3 + ((x) % 3 == 1 ? 0 : tb))
#define G(x) ((x) < tb ? (x) * 3 + 1 : ((x)-tb) * 3 + 2)
int wa[MAXN * 3], wb[MAXN * 3], wv[MAXN * 3], wss[MAXN * 3];
int c0(int *r, int a, int b)
{
return r[a] == r[b] && r[a + 1] == r[b + 1] && r[a + 2] == r[b + 2];
}
int c12(int k, int *r, int a, int b)
{
if(k == 2)
{
return r[a] < r[b] || (r[a] == r[b] && c12(1, r, a + 1, b + 1));
}
else
{
return r[a] < r[b] || (r[a] == r[b] && wv[a + 1] < wv[b + 1]);
}
}
void sort(int *r, int *a, int *b, int n, int m)
{
int i;
for (i = 0; i < n; i++)
{
wv[i] = r[a[i]];
}
for (i = 0; i < m; i++)
{
wss[i] = 0;
}
for (i = 0; i < n; i++)
{
wss[wv[i]]++;
}
for (i = 1; i < m; i++)
{
wss[i] += wss[i - 1];
}
for (i = n - 1; i >= 0; i--)
{
b[--wss[wv[i]]] = a[i];
}
}
void dc3(int *r, int *sa, int n, int m)
{
int i, j, *rn = r + n;
int *san = sa + n, ta = 0, tb = (n+1)/3, tbc = 0, p;
r[n] = r[n+1] = 0;
for (i = 0; i < n; i++)
{
if (i % 3 != 0)
{
wa[tbc++] = i;
}
}
sort(r + 2, wa, wb, tbc, m);
sort(r + 1, wb, wa, tbc, m);
sort(r, wa, wb, tbc, m);
for (p = 1, rn[F(wb[0])] = 0, i = 1; i < tbc; i++)
{
rn[F(wb[i])] = c0(r, wb[i - 1], wb[i]) ? p - 1 : p++;
}
if (p < tbc)
{
dc3(rn, san, tbc, p);
}
else
{
for (i = 0; i < tbc; i++)
{
san[rn[i]] = i;
}
}
for (i = 0; i < tbc; i++)
{
if (san[i] < tb)
{
wb[ta++] = san[i] * 3;
}
}
if (n % 3 == 1)
{
wb[ta++] = n - 1;
}
sort(r, wb, wa, ta, m);
for (i = 0; i < tbc; i++)
{
wv[wb[i] = G(san[i])] = i;
}
for (i = 0, j = 0, p = 0; i < ta && j < tbc; p++)
{
sa[p] = c12(wb[j] % 3, r, wa[i], wb[j]) ? wa[i++] : wb[j++];
}
for (; i < ta; p++)
{
sa[p] = wa[i++];
}
for (; j < tbc; p++)
{
sa[p] = wb[j++];
}
}
// str和sa也要三倍
void da(int str[], int sa[], int rank[], int height[], int n,int m)
{
for (int i = n; i < n * 3; i++)
{
str[i] = 0;
}
dc3(str, sa, n+1, m);
int i, j, k = 0;
for (i = 0; i <= n; i++)
{
rank[sa[i]] = i;
}
for (i = 0; i < n; i++)
{
if(k)
{
k--;
}
j = sa[rank[i] - 1];
while (str[i + k] == str[j + k])
{
k++;
}
height[rank[i]] = k;
}
}
后缀自动机
const int CHAR = 26;
const int MAXN = 250010;
struct SAM_Node
{
SAM_Node *fa, *next[CHAR];
int len;
int id, pos;
SAM_Node(){}
SAM_Node(int _len)
{
fa = 0;
len = _len;
memset(next, 0, sizeof(next));
}
};
SAM_Node SAM_node[MAXN * 2], *SAM_root, *SAM_last;
int SAM_size;
SAM_Node *newSAM_Node(int len)
{
SAM_node[SAM_size] = SAM_Node(len);
SAM_node[SAM_size].id = SAM_size;
return &SAM_node[SAM_size++];
}
SAM_Node *newSAM_Node(SAM_Node *p)
{
SAM_node[SAM_size] = *p; SAM_node[SAM_size].id = SAM_size;
return &SAM_node[SAM_size++];
}
void SAM_init()
{
SAM_size = 0;
SAM_root = SAM_last = newSAM_Node(0);
SAM_node[0].pos = 0;
}
void SAM_add(int x, int len)
{
SAM_Node *p = SAM_last, *np = newSAM_Node(p->len+1);
np->pos = len;
SAM_last = np;
for (; p && !p->next[x]; p = p->fa)
{
p->next[x] = np;
}
if (!p)
{
np->fa = SAM_root;
return;
}
SAM_Node *q = p->next[x];
if (q->len == p->len + 1)
{
np->fa = q;
return ;
}
SAM_Node *nq = newSAM_Node(q);
nq->len = p->len + 1;
q->fa = nq;
np->fa = nq;
for(;p && p->next[x] == q; p = p->fa)
p->next[x] = nq;
}
void SAM_build(char *s)
{
SAM_init();
int len = (int)strlen(s);
for (int i = 0; i < len; i++)
{
SAM_add(s[i] - 'a', i + 1);
}
}
/*
// 加入串后进行拓扑排序
char str[MAXN];
int topocnt[MAXN];
SAM_Node *topsam[MAXN * 2];
int n = (int)strlen(str);
SAM_build(str);
memset(topocnt, 0, sizeof(topocnt));
for (int i = 0; i < SAM_size; i++)
{
topocnt[SAM_node[i].len]++;
}
for (int i = 1; i <= n; i++)
{
topocnt[i] += topocnt[i-1];
}
for (int i = 0; i < SAM_size; i++)
{
topsam[--topocnt[SAM_node[i].len]] = &SAM_node[i];
}
*/
// 多串的建立:
// 多串的建立,注意SAM_init()的调用
//void SAM_build(char *s)
//{
// int len = (int)strlen(s);
// SAM_last = SAM_root;
// for (int i = 0; i < len; i++)
// {
// if (!SAM_last->next[s[i] - '0'] || !(SAM_last->next[s[i] - '0']->len == i+1))
// {
// SAM_add(s[i] - '0',i+1);
// }
// else
// {
// SAM_last = SAM_last->next[s[i] - '0'];
// }
// }
//}
字符串HASH
/*
* 字符串 Hash
* 注意:mod选择足够大的质数(至少大于字符串个数)
*/
unsigned int hashA(char *url, int mod)
{
unsigned int n = 0;
char *b = (char *)&n;
for (int i = 0; url[i]; i++)
{
b[i % 4] ^= url[i];
}
return n % mod;
}
unsigned int hashB(char *url, int mod)
{
unsigned int h = 0;
unsigned int g;
while (*url)
{
h = (h << 4) + *url++;
g = h & 0xF0000000;
if (g)
{
h ^= (g >> 24);
}
h &= ~g;
}
return h % mod;
}
unsigned int hashC(char *p, int prime = 25013)
{
unsigned int h = 0;
unsigned int g;
for (; *p; p++)
{
h = (h << 4) + *p;
g = h & 0xF0000000;
if (g)
{
h ^= (g >> 24);
h ^= g;
}
}
return h % prime;
}
BM算法改进的算法:Sunday Algorithm
BM算法优于KMP
SUNDAY
算法描述:字符串查找算法中,最著名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore)。两个算法在最坏情 况下均具有线性的查找时间。但是在实用上,KMP算法并不比最简单的c库函数strstr()快多少,而BM算法则往往比KMP算法快上3-5倍。但是BM算法还不是最快的算法,这里介绍一种比BM算法更快一些的查找算法。例如我们要在”substring searching algorithm”查找”search”,刚开始时,把子串与文本左边对齐:
substring searching algorithm search
结果在第二个字符处发现不匹配,于是要把子串往后移动。但是该移动多少呢? 这就是各种算法各显神通的地方了,最简单的做法是移动一个字符位置;KMP是利用已经匹配部分的信息来移动;BM算法是做反向比较,并根据已经匹配的部分来确定移动量。这里要介绍的方法是看紧跟在当前子串之后的那个字符(第一个字符串中的’i’)。显然,不管移动多少,这个字符是肯定要参加下一步的比较的,也就是说,如果下一步匹配到了,这个字符必须在子串内。所以,可以移动子串,使子串中的最右边的这个字符与它对齐。现在子串’search’中并不存在’i’,则说明可以直接跳过一大片,从’i’之后的那个字符开始作下一步的比较,如下:
substring searching algorithm search
比较的结果,第一个字符就不匹配,再看子串后面的那个字符,是’r’,它在子串中出现在倒数第三位,于是把子串向后移动三位,使两个’r’对齐,如下:
substring searching algorithm search
这次匹配成功了!回顾整个过程,我们只移动了两次子串就找到了匹配位置, 是不是很神啊?!可以证明,用这个算法,每一步的移动量都比BM算法要大,所以肯定比BM算法更快。
void SUNDAY(char *text, char *patt)
{
size_t temp[256];
size_t *shift = temp;
size_t i, patt_size = strlen(patt), text_size = strlen(text);
cout << "size : " << patt_size << endl;
for(i = 0; i < 256; i++)
{
*(shift+i) = patt_size + 1;
}
for(i = 0; i < patt_size; i++)
{
*(shift + (unsigned char)(*(patt+i))) = patt_size-i; // shift['s']=6步,shitf['e']=5以此类推
}
size_t limit = text_size - patt_size + 1;
for(i = 0; i < limit; i += shift[text[i + patt_size]])
{
if(text[i] == *patt)
{
char *match_text = text + i + 1;
size_t match_size = 1;
do // 输出所有匹配的位置
{
if(match_size == patt_size)
{
cout << "the NO. is " << i << endl;
}
}
while((*match_text++) == patt[match_size++]);
}
}
cout << endl;
}
int main(void)
{
char text[100] = "substring searching algorithm search";
char patt[10] = "search";
SUNDAY(text, patt);
return 0;
}
Graph图论模板
最短路
Dijkstra 单源最短路 邻接矩阵形式
/*
* 单源最短路径,Dijkstra算法,邻接矩阵形式,复杂度为O(n^2)
* 求出源beg到所有点的最短路径,传入图的顶点数和邻接矩阵cost[][]
* 返回各点的最短路径lowcost[],路径pre[],pre[i]记录beg到i路径上的父节点,pre[beg] = -1
* 可更改路径权类型,但是权值必须为非负,下标0~n-1
*/
const int MAXN = 1010;
const int INF = 0x3f3f3f3f; // 表示无穷
bool vis[MAXN];
int pre[MAXN];
void Dijkstra(int cost[][MAXN], int lowcost[], int n, int beg)
{
for (int i = 0; i < n; i++)
{
lowcost[i] = INF;
vis[i] = false;
pre[i] = -1;
}
lowcost[beg] = 0;
for (int j = 0; j < n; j++)
{
int k = -1;
int min = INF;
for (int i = 0; i < n; i++)
{
if (!vis[i] && lowcost[i] < min)
{
min = lowcost[i];
k = i;
}
}
if (k == -1)
{
break;
}
vis[k] = true;
for (int i = 0; i < n; i++)
{
if (!vis[i] && lowcost[k] + cost[k][i] < lowcost[i])
{
lowcost[i] = lowcost[k] + cost[k][i];
pre[i] = k;
}
}
}
}
Dijkstra 单源最短路 邻接矩阵形式 双路径信息
/*
* 单源最短路径,dijkstra算法,邻接矩阵形式,复杂度为O(n^2)
* 两点间距离存入map[][],两点间花费存入cost[][]
* 求出源st到所有点的最短路径及其对应最小花费
* 返回各点的最短路径lowdis[]以及对应的最小花费lowval[]
* 可更改路径权类型,但是权值必须为非负,下标1~n
*/
const int MAXN = 1010;
const int INF = 0x3f3f3f3f;
int n, m;
int lowdis[MAXN];
int lowval[MAXN];
int visit[MAXN];
int map[MAXN][MAXN];
int cost[MAXN][MAXN];
void dijkstra(int st)
{
int temp = 0;
for (int i = 1; i <= n; i++)
{
lowdis[i] = map[st][i];
lowval[i] = cost[st][i];
}
memset(visit, 0, sizeof(visit));
visit[st] = 1;
for (int i = 1; i < n; i++)
{
int MIN = INF;
for (int j = 1; j <= n; j++)
{
if (!visit[j] && lowdis[j] < MIN)
{
temp = j;
MIN = lowdis[j];
}
}
visit[temp] = 1;
for (int j = 1; j <= n; j++)
{
if (!visit[j] && map[temp][j] < INF)
{
if (lowdis[j] > lowdis[temp] + map[temp][j])
{
lowdis[j] = lowdis[temp] + map[temp][j];
lowval[j] = lowval[temp] + cost[temp][j];
}
else if (lowdis[j] == lowdis[temp] + map[temp][j])
{
if (lowval[j] > lowval[temp] + cost[temp][j])
{
lowval[j] = lowval[temp] + cost[temp][j];
}
}
}
}
}
return ;
}
Dijkstra 起点Start 结点有权值
#define M 505
const int inf = 0x3f3f3f3f;
int num[M]; // 结点权值
int map[M][M]; // 图的临近矩阵
int vis[M]; // 结点是否处理过
int ans[M]; // 最短路径结点权值和
int dis[M]; // 各点最短路径花费
int n, m, Start, End; // n结点数,m边数,Start起点,End终点
void Dij(int v)
{
ans[v] = num[v];
memset(vis, 0, sizeof(vis));
for (int i = 0; i < n; ++i)
{
if (map[v][i] < inf)
{
ans[i] = ans[v] + num[i];
}
dis[i] = map[v][i];
}
dis[v] = 0;
vis[v] = 1;
for (int i = 1; i < n; ++i)
{
int u = 0, min = inf;
for (int j = 0; j < n; ++j)
{
if (!vis[j] && dis[j] < min)
{
min = dis[j];
u = j;
}
}
vis[u] = 1;
for (int k = 0; k < n; ++k)
{
if (!vis[k] && dis[k] > map[u][k] + dis[u])
{
dis[k] = map[u][k] + dis[u];
ans[k] = ans[u] + num[k];
}
}
for (int k = 0; k < n; ++k)
{
if (dis[k] == map[u][k] + dis[u])
{
ans[k] = max(ans[k], ans[u] + num[k]);
}
}
}
printf("%d %d\n", dis[End], ans[End]); // 输出终点最短路径花费、最短路径结点权值和
}
int main()
{
scanf("%d%d%d%d", &n, &m, &Start, &End);
for (int i = 0; i < n; ++i)
{
scanf("%d", &num[i]);
}
memset(vis, 0, sizeof(vis));
memset(map, 0x3f, sizeof(map));
for (int i = 0; i < m; ++i)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
if (map[x][y] > z)
{
map[x][y] = z;
map[y][x] = z;
}
}
Dij(Start);
return 0;
}
Dijkstra 堆优化
/*
* 使用优先队列优化Dijkstra算法
* 复杂度O(ElongE)
* 注意对vector<Edge> E[MAXN]进行初始化后加边
*/
const int INF = 0x3f3f3f3f;
const int MAXN = 1000010;
struct qNode
{
int v;
int c;
qNode(int _v = 0, int _c = 0) : v(_v), c(_c) {}
bool operator < (const qNode &r) const
{
return c > r.c;
}
};
struct Edge
{
int v;
int cost;
Edge(int _v = 0, int _cost = 0) : v(_v), cost(_cost) {}
};
vector<Edge> E[MAXN];
bool vis[MAXN];
int dist[MAXN]; // 最短路距离
void Dijkstra(int n, int start) // 点的编号从1开始
{
memset(vis, false, sizeof(vis));
memset(dist, 0x3f, sizeof(dist));
priority_queue<qNode> que;
while (!que.empty())
{
que.pop();
}
dist[start] = 0;
que.push(qNode(start, 0));
qNode tmp;
while (!que.empty())
{
tmp = que.top();
que.pop();
int u = tmp.v;
if (vis[u])
{
continue;
}
vis[u] = true;
for (int i = 0; i < E[u].size(); i++)
{
int v = E[u][i].v;
int cost = E[u][i].cost;
if (!vis[v] && dist[v] > dist[u] + cost)
{
dist[v] = dist[u] + cost;
que.push(qNode(v, dist[v]));
}
}
}
}
void addEdge(int u, int v, int w)
{
E[u].push_back(Edge(v, w));
}
单源最短路 BellmanFord算法
/*
* 单源最短路BellmanFord算法,复杂度O(VE)
* 可以处理负边权图
* 可以判断是否存在负环回路,返回true,当且仅当图中不包含从源点可达的负权回路
* vector<Edge> E;先E.clear()初始化,然后加入所有边
*/
const int INF = 0x3f3f3f3f;
const int MAXN = 550;
int dist[MAXN];
struct Edge
{
int u;
int v;
int cost;
Edge(int _u = 0, int _v = 0, int _cost = 0) : u(_u), v(_v), cost(_cost){}
};
vector<Edge> E;
bool BellmanFord(int start, int n) // 编号从1开始
{
memset(dist, 0x3f, sizeof(dist));
dist[start] = 0;
for (int i = 1; i < n; i++) // 最多做n - 1次
{
bool flag = false;
for (int j = 0; j < E.size(); j++)
{
int u = E[j].u;
int v = E[j].v;
int cost = E[j].cost;
if (dist[v] > dist[u] + cost)
{
dist[v] = dist[u] + cost;
flag = true;
}
}
if (!flag) // 无负环回路
{
return true;
}
}
for (int j = 0; j < E.size(); j++)
{
if (dist[E[j].v] > dist[E[j].u] + E[j].cost)
{
return false; // 有负环回路
}
}
return true; // 无负环回路
}
单源最短路 SPFA
/*
* 时间复杂度O(kE)
* 队列实现,有时候改成栈实现会更快,较容易修改
*/
const int MAXN = 1010;
const int INF = 0x3f3f3f3f;
struct Edge
{
int v;
int cost;
Edge(int _v = 0, int _cost = 0) : v(_v), cost(_cost) {}
};
vector<Edge> E[MAXN];
void addEdge(int u, int v, int w)
{
E[u].push_back(Edge(v, w));
}
bool vis[MAXN]; // 在队列标志
int cnt[MAXN]; // 每个点的入列队次数
int dist[MAXN];
bool SPFA(int start, int n)
{
memset(vis, false, sizeof(vis));
memset(dist, 0x3f, sizeof(dist));
vis[start] = true;
dist[start] = 0;
queue<int> que;
while (!que.empty())
{
que.pop();
}
que.push(start);
memset(cnt, 0, sizeof(cnt));
cnt[start] = 1;
while (!que.empty())
{
int u = que.front();
que.pop();
vis[u] = false;
for (int i = 0; i < E[u].size(); i++)
{
int v = E[u][i].v;
if (dist[v] > dist[u] + E[u][i].cost)
{
dist[v] = dist[u] + E[u][i].cost;
if (!vis[v])
{
vis[v] = true;
que.push(v);
if (++cnt[v] > n)
{
return false; // cnt[i]为入队列次数,用来判定是否存在负环回路
}
}
}
}
}
return true;
}
Floyd算法 邻接矩阵形式
/*
* Floyd算法,求从任意节点i到任意节点j的最短路径
* cost[][]:初始化为INF(cost[i][i]:初始化为0)
* lowcost[][]:最短路径,path[][]:最短路径(无限制)
*/
const int MAXN = 100;
int cost[MAXN][MAXN];
int lowcost[MAXN][MAXN];
int path[MAXN][MAXN];
void Floyd(int n)
{
memcpy(lowcost, cost, sizeof(cost));
memset(path, -1, sizeof(path));
for (int k = 0; k < n; k++)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (lowcost[i][j] > (lowcost[i][k] + lowcost[k][j]))
{
lowcost[i][j] = lowcost[i][k] + lowcost[k][j];
path[i][j] = k;
}
}
}
}
return ;
}
Floyd算法 点权 + 路径限制
/*
* Floyd算法,求从任意节点i到任意节点j的最短路径
* cost[][]:初始化为INF(cost[i][i]:初始化为0)
* val[]:点权,lowcost[][]:除起点、终点外的点权之和+最短路径
* path[][]:路径限制,要求字典序最小的路径,下标1~N
*/
const int MAXN = 110;
const int INF = 0x1f1f1f1f;
int val[MAXN]; // 点权
int cost[MAXN][MAXN];
int lowcost[MAXN][MAXN];
int path[MAXN][MAXN]; // i~j路径中的第一个结点
void Floyd(int n)
{
memcpy(lowcost, cost, sizeof(cost));
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= n; j++)
{
path[i][j] = j;
}
}
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
int temp = lowcost[i][k] + lowcost[k][j] + val[k];
if (lowcost[i][j] > temp)
{
lowcost[i][j] = temp;
path[i][j] = path[i][k];
}
else if (lowcost[i][j] == temp && path[i][j] > path[i][k])
{
path[i][j] = path[i][k];
}
}
}
}
return ;
}
第K短路
Dijkstra
/*
* Dijkstra变形,可以证明每个点经过的次数为小于等于K,
* 所有Dijkstra的数组dist由一维变为二维,记录经过该点
* 1次、2次......k次的最小值
* 输出dist[n - 1][k]即可
*/
int g[1010][1010];
int n, m, x;
const int INF = 0x3f3f3f3f;
int vis[1010];
int dist[1010][20];
int main(int argc, const char * argv[])
{
while (cin >> n >> m >> x)
{
//初始化
memset(g, 0x3f, sizeof(g));
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
for (int i = 0; i < m; i++)
{
int p, q, r;
cin >> p >> q >> r;
if (r < g[p][q])
{
g[p][q] = r;
}
}
dist[1][0] = 0;
dist[0][0] = INF;
while (1)
{
int k = 0;
for (int i = 1; i <= n; i++)
{
if (vis[i] < x && dist[i][vis[i]] < dist[k][0])
{
k = i;
}
}
if (k == 0)
{
break;
}
if (k == n && vis[n] == x - 1)
{
break;
}
for (int i = 1; i <= n; i++)
{
if (vis[i] < x && dist[k][vis[k]] + g[k][i] < dist[i][x])
{
dist[i][x] = dist[k][vis[k]] + g[k][i];
for (int j = x; j > 0; j--)
{
if (dist[i][j] < dist[i][j - 1])
{
swap(dist[i][j], dist[i][j - 1]);
}
}
}
}
vis[k]++;
}
if (dist[n][x - 1] < INF)
{
cout << dist[n][x - 1] << endl;
}
else
{
cout << -1 << endl;
}
}
return 0;
}
A*
/*
* A* 估价函数 fi为到当前点走过的路经长度,hi为该点到终点的长度
* gi = hi + fi
*/
int n, m, x, ct;
int g[1010][1010];
int gr[1010][1010];
int dist[1010];
int vis[1010];
const int INF = 0x3f3f3f3f;
struct node
{
int id;
int fi;
int gi;
friend bool operator < (node a, node b)
{
if (a.gi == b.gi)
{
return a.fi > b.fi;
}
return a.gi > b.gi;
}
} s[20000010];
int init()
{
memset(dist, 0x3f, sizeof(dist));
for (int i = 0; i <= n; i++)
{
vis[i] = 1;
}
dist[n - 1] = 0;
for (int i = 0; i < n; i++)
{
int k = n;
for (int j = 0; j < n; j++)
{
if (vis[j] && dist[j] < dist[k])
{
k = j;
}
}
if (k == n)
{
break;
}
vis[k] = 0;
for (int j = 0; j < n; j++)
{
if (vis[j] && dist[k] + gr[k][j] < dist[j])
{
dist[j] = dist[k] + gr[k][j];
}
}
}
return 1;
}
int solve()
{
if (dist[0] == INF)
{
return -1;
}
ct = 0;
s[ct].id = 0;
s[ct].fi = 0;
s[ct++].gi = dist[0];
int cnt = 0;
while (ct)
{
int id = s[0].id;
int fi = s[0].fi;
if (id == n - 1)
{
cnt++;
}
if (cnt == x)
{
return fi;
}
pop_heap(s, s + ct);
ct--;
for (int j = 0; j < n; j++)
{
if (g[id][j] < INF)
{
s[ct].id = j;
s[ct].fi = fi + g[id][j];
s[ct].gi = s[ct].fi + dist[j];
ct++;
push_heap(s, s + ct);
}
}
}
return -1;
}
int main()
{
while (cin >> n >> m >> x)
{
memset(g, 0x3f, sizeof(g));
memset(gr, 0x3f, sizeof(gr));
for (int i = 0; i < n; i++)
{
int p, q, r;
cin >> p >> q >> r;
p--;
q--;
g[p][q] = g[p][q] <= r ? g[p][q] : r;
gr[q][p] = gr[q][p] <= r ? gr[q][p] : r;
}
init();
cout << solve() << endl;
}
return 0;
}
最小生成树(森林)
Prim算法
/*
* Prim求MST
* 耗费矩阵cost[][],初始化为INF,标号从0开始,0 ~ n-1
* 返回最小生成树的权值,返回-1表示原图不连通
*/
const int INF = 0x3f3f3f3f;
const int MAXN = 110;
bool vis[MAXN];
int lowc[MAXN];
int cost[MAXN][MAXN];
// 修正cost(添加边)
void updata(int x, int y, int v)
{
cost[x - 1][y - 1] = v;
cost[y - 1][x - 1] = v;
return ;
}
int Prim(int cost[][MAXN], int n) // 0 ~ n - 1
{
int ans = 0;
memset(vis, false, sizeof(vis));
vis[0] = true;
for (int i = 1; i < n; i++)
{
lowc[i] = cost[0][i];
}
for (int i = 1; i < n; i++)
{
int minc = INF;
int p = -1;
for (int j = 0; j < n; j++)
{
if (!vis[j] && minc > lowc[j])
{
minc = lowc[j];
p = j;
}
}
if (minc == INF)
{
return -1; // 原图不连通
}
ans += minc;
vis[p] = true;
for (int j = 0; j < n; j++)
{
if (!vis[j] && lowc[j] > cost[p][j])
{
lowc[j] = cost[p][j];
}
}
}
return ans;
}
Kruskal算法
/*
* Kruskal算法求MST
* 对边操作,并排序
* 切记:初始化赋值问题(tol)
*/
const int MAXN = 110; // 最大点数
const int MAXM = 10000; // 最大边数
int F[MAXN]; // 并查集使用
struct Edge
{
int u; // 起点
int v; // 终点
int w; // 权值
} edge[MAXM]; // 存储边的信息
int tol; // 边数,加边前赋值为0
void addEdge(int u, int v, int w)
{
edge[tol].u = u;
edge[tol].v = v;
edge[tol++].w = w;
return ;
}
bool cmp(Edge a, Edge b)
{
// 排序函数,将边按照权值从小到大排序
return a.w < b.w;
}
int find(int x)
{
if (F[x] == x)
{
return x;
}
else
{
return F[x] = find(F[x]);
}
}
int Kruskal(int n) // 传入点数,返回最小生成树的权值,如果不连通则返回-1
{
for (int i = 0; i <= n; i++)
{
F[i] = i;
}
sort(edge, edge + tol, cmp);
int cnt = 0; // 计算加入的边数
int ans = 0;
for (int i = 0; i < tol; i++)
{
int u = edge[i].u;
int v = edge[i].v;
int w = edge[i].w;
int tOne = find(u);
int tTwo = find(v);
if (tOne != tTwo)
{
ans += w;
F[tOne] = tTwo;
cnt++;
}
if (cnt == n - 1)
{
break;
}
}
if (cnt < n - 1)
{
return -1; // 不连通
}
else
{
return ans;
}
}
MST
/*
* Minimal Steiner Tree
* G(V, E), A是V的一个子集, 求至少包含A中所有点的最小子树.
* 时间复杂度:O(N^3+N*2^A*(2^A+N))
* INIT: d[][]距离矩阵; id[]置为集合A中点的标号;
* CALL: steiner(int n, int a);
* 给4个点对(a1,b1)...(a4,b4),
* 求min(sigma(dist[ai][bi])),其中重复的路段只能算一次.
* 这题要找出一个Steiner森林, 最后要对森林中树的个数进行枚举
*/
#define typec int // type of cost
const typec inf = 0x3f3f3f3f; // max of cost
const typec V = 10010;
const typec A = 10;
int vis[V], id[A]; // id[]: A中点的标号
typec d[V][V], dp[1 << A][V]; // dp[i][v]: 点v到点集i的最短距离
void steiner(int n, int a)
{
int i, j, k, mx, mk = 0, top = (1 << a);
for (k = 0; k < n; k++)
{
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (d[i][j] > d[i][k] + d[k][j])
{
d[i][j] = d[i][k] + d[k][j];
}
}
}
}
for (i = 0; i < a; i++)
{
// vertex: 0 ~ n-1
for (j = 0; j < n; j++)
{
dp[1 << i][j] = d[j][id[i]];
}
}
for (i = 1; i < top; i++)
{
if (0 == (i & (i - 1)))
{
continue;
}
memset(vis, 0, sizeof(vis));
for (k = 0; k < n; k++) // init
{
for (dp[i][k] = inf, j = 1; j < i; j++)
{
if ((i | j) == i && dp[i][k] > dp[j][k] + dp[i - j][k])
{
dp[i][k] = dp[j][k] + dp[i - j][k];
}
}
}
for (j = 0; mx = inf, j < n; j++)
{
// update
for (k = 0; k < n; k++)
{
if (dp[i][k] <= mx && 0 == vis[k])
{
mx = dp[i][mk = k];
}
}
for (k = 0, vis[mk] = 1; k < n; k++)
{
if (dp[i][mk] > dp[i][k] + d[k][mk])
{
dp[i][mk] = dp[i][k] + d[k][mk];
}
}
}
}
return ;
}
int main(int argc, const char * argv[])
{
int n, a = 8;
int b, z, i, j, k, x = 0, y;
// TODO: read data;
steiner(n, a);
// enum to find the result
for (i = 0, b = inf; z = 0, i < 256; b > z ? b = z : b, i++)
{
for (j = 0; y = 0, j < 4; z += !!y * dp[y][x], j++)
{
for (k = 0; k < 8; k += 2)
{
if ((i >> k & 3) == j)
{
y += 3 << k, x = id[k];
}
}
}
}
// TODO: cout << b << endl;
return 0;
}
次小生成树
O(V^2)
次小生成树可由最小生成树转换一条边得到
只要充分利用上述结论,既得v^2的算法。具体如下:
step1. 先用Prim求出最小生成树T,在Prim的同时,用一个矩阵MAX[u][v]记录在T中连结任意两点u,v的唯一的路中权值最大的那条边的权值。(注意这里),这是很容易做到的,因为Prim是每次增加一个结点s,而已经标好了的结点集合为w,则w中所有的结点到s的路中最大权值的边就是当前加入的这条边,用时O(V^2);
step2.枚举所有不在T中的边u_v,加入边u_v替换权为MAX[u][v]的边,不断更新最小值,即次小生成树,用时O(E),故总用时O(V^2)
/*
* 求最小生成树时,用数组MAX[i][j]表示i到j的最大边权
* 求完后,直接枚举所有不在MST中的边,替换掉最大边权的边,更新答案
* 点的编号从0开始
*/
const int MAXN = 110;
const int INF = 0x3f3f3f3f;
bool vis[MAXN];
int lowc[MAXN];
int pre[MAXN];
int MAX[MAXN][MAXN];
bool used[MAXN][MAXN];
int Prim(int cost[][MAXN], int n)
{
int ans = 0;
memset(vis, false, sizeof(vis));
memset(MAX, 0, sizeof(MAX));
memset(used, false, sizeof(used));
vis[0] = true;
pre[0] = -1;
lowc[0] = 0;
for (int i = 1; i < n; i++)
{
lowc[i] = cost[0][i];
pre[i] = 0;
}
for (int i = 1; i < n; i++)
{
int minc = INF;
int p = -1;
for (int j = 0; j < n; j++)
{
if (!vis[j] && minc > lowc[j])
{
minc = lowc[j];
p = j;
}
}
if (minc == INF)
{
return -1;
}
ans += minc;
vis[p] = true;
used[p][pre[p]] = used[pre[p]][p] = true;
for (int j = 0; j < n; j++)
{
if (vis[j])
{
MAX[j][p] = MAX[p][j] = max(MAX[j][pre[p]], lowc[p]);
}
if (!vis[j] && lowc[j] > cost[p][j])
{
lowc[j] = cost[p][j];
pre[j] = p;
}
}
}
return ans;
}
曼哈顿最小生成树
曼哈顿距离:简单说,他指两点之间的横纵坐标的差的绝对值之和。
题意:查找平面上的点的曼哈顿距离最小生成树的第n-k小边的长度,点数在100000以内。
解析: 对于曼哈顿距离的最小生成树,朴素算法需要建立n^(n - 1)条边进行kruskal算法处理,这样子做一定会TLE的。所以需要做特殊的优化,将边数优化为4 * n条。
这里的优化涉及到一个与曼哈顿相关的性质:以任一一个点为端点,将平面分为八块,每块占45度角,那么在生成树的最优解中,每个块与这个点至多有一条边,即一个点最多分别向八个方向最近的点连接一条边,一条边两个点共用,所以最后边数为4 * n。
#include <iostream>
#include <algorithm>
const int MAXN = 100010;
const int INF = 0x3f3f3f3f;
struct Point
{
int x;
int y;
int id;
}poi[MAXN];
bool cmp(Point a, Point b)
{
if (a.x != b.x)
{
return a.x < b.x;
}
else
{
return a.y < b.y;
}
}
//树状数组,找y - x大于当前的,但是y + x最小的
struct BIT
{
int minVal;
int pos;
void init()
{
minVal = INF;
pos = -1;
}
}bit[MAXN];
//所有有效边
struct Edge
{
int u;
int v;
int d;
}edge[MAXN << 2];
bool cmpEdge(Edge a, Edge b)
{
return a.d < b.d;
}
int tot;
int n;
int F[MAXN];
int find(int x)
{
if (F[x] == -1)
{
return x;
}
else
{
return F[x] = find(F[x]);
}
}
void addEdge(int u, int v, int d)
{
edge[tot].u = u;
edge[tot].v = v;
edge[tot++].d = d;
return ;
}
int lowbit(int x)
{
return x & (-x); //???
}
//更新bit
void update(int i, int val, int pos)
{
while (i > 0)
{
if (val < bit[i].minVal)
{
bit[i].minVal = val;
bit[i].pos = pos;
}
i -= lowbit(i);
}
return ;
}
//查询[i, m]的最小值位置
int ask(int i, int m)
{
int minVal = INF;
int pos = -1;
while (i <= m)
{
if (bit[i].minVal < minVal)
{
minVal = bit[i].minVal;
pos = bit[i].pos;
}
i += lowbit(i);
}
return pos;
}
int dist(Point a, Point b)
{
return abs(a.x - b.x) + abs(a.y - b.y);
}
void ManhattanMinimumSpanningTree(int n, Point p[])
{
int a[MAXN], b[MAXN];
tot = 0;
for (int dir = 0; dir < 4; dir++)
{
//变换4种坐标
if (dir == 1 || dir == 3)
{
for (int i = 0; i < n; i++)
{
std::swap(p[i].x, p[i].y);
}
}
else if (dir == 2)
{
for (int i = 0; i < n; i++)
{
p[i].x = -p[i].x;
}
}
std::sort(p, p + n, cmp);
for (int i = 0; i < n; i++)
{
a[i] = b[i] = p[i].y - p[i].x;
}
std::sort(b, b + n);
int m = (int)(std::unique(b, b + n) - b);
for (int i = 1; i <= m; i++)
{
bit[i].init();
}
for (int i = n - 1; i >= 0; i--)
{
int pos = (int)(std::lower_bound(b, b + m, a[i]) - b + 1);
int ans = ask(pos, m);
if (ans != -1)
{
addEdge(p[i].id, p[ans].id, dist(p[i], p[ans]));
}
update(pos, p[i].x + p[i].y, i);
}
}
return ;
}
int solve(int k)
{
ManhattanMinimumSpanningTree(n, poi);
memset(F, -1, sizeof(F));
std::sort(edge, edge + tot, cmpEdge);
for (int i = 0; i < tot; i++)
{
int u = edge[i].u;
int v = edge[i].v;
int tOne = find(u);
int tTwo = find(v);
if (tOne != tTwo)
{
F[tOne] = tTwo;
k--;
if (k == 0)
{
return edge[i].d;
}
}
}
return -1;
}
int main(int argc, const char * argv[])
{
//freopen("in.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
int k;
while ((std::cin >> n >> k) && n)
{
for (int i = 0; i < n; i++)
{
std::cin >> poi[i].x >> poi[i].y;
poi[i].id = i;
}
std::cout << solve(n - k) << std::endl;
}
return 0;
}
欧拉路径
无向图:
连通(不考虑度为0的点),每个顶点度数都为偶数。
/*
* SGU 101
*/
struct Edge
{
int to;
int next;
int index;
int dir;
bool flag;
} edge[220];
int head[10]; //前驱
int tot;
void init()
{
memset(head, -1, sizeof((head)));
tot = 0;
}
void addEdge(int u, int v, int index)
{
edge[tot].to = v;
edge[tot].next = head[u];
edge[tot].index = index;
edge[tot].dir = 0;
edge[tot].flag = false;
head[u] = tot++;
edge[tot].to = u;
edge[tot].next = head[v];
edge[tot].index = index;
edge[tot].dir = 1;
edge[tot].flag = false;
head[v] = tot++;
return ;
}
int du[10];
std::vector<int>ans;
void dfs(int u)
{
for (int i = head[u]; i != -1; i = edge[i].next)
{
if (!edge[i].flag)
{
edge[i].flag = true;
edge[i ^ 1].flag = true;
dfs(edge[i].to);
ans.push_back(i); //容器尾部插入i
}
}
return ;
}
int main()
{
//freopen("in.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
int n;
while (std::cin >> n)
{
init();
int u, v;
memset(du, 0, sizeof(du));
for (int i = 1; i <= n; i++)
{
std::cin >> u >> v;
addEdge(u, v, i);
du[u]++;
du[v]++;
}
int s = -1;
int cnt = 0;
for (int i = 0; i <= 6; i++)
{
if (du[i] & 1)
{
cnt++;
s = i;
}
if (du[i] > 0 && s == -1)
{
s = i;
}
}
if (cnt != 0 && cnt != 2)
{
std::cout << "No solution" << '\n';
continue;
}
ans.clear();
dfs(s);
if (ans.size() != n)
{
std::cout << "No solution" << '\n';
continue;
}
for (int i = 0; i < ans.size(); i++)
{
printf("%d ", edge[ans[i]].index);
if (edge[ans[i]].dir == 0)
{
std::cout << "-" << '\n';
}
else
{
std::cout << "+" << '\n';
}
}
}
return 0;
}
有向图:
基图连通(把边当成无向边,同样不考虑度为0的点),每个顶点出度等于入度。
混合图:
既有无向边也有有向边,首先是基图连通(不考虑度为0的点),然后需要借助网络流判定。
首先给原图中的每条无向边随便指定一个方向(称为初始定向),将原图改为有向图G’,然后的任务就是改变G’中某些条边的方向(当然是无向边转化来的,愿混合图中的有向边不能动)使其满足每个点的入度等于出度。
设D[i]为G’中(点i的出度-点i的入度),即可发现,在改变G’中边的方向的过程中,任何点的D值的奇偶性都不会发生改变(设将边<i, j>改为<j, i>,则i入度加1出度减1,j入度减1出度佳1,两者之差加2或者减2,奇偶性不变),而最终要求的是每个点的入度等于出度,即每个点的D值都为0,是偶数,姑可得:若初始定向得到的G’中任一个点D值是奇数,那么原图中一定不存在欧拉环。
若初始D值都是偶数,则将G’改装成网络:设立源点S和汇点T,对于每个D[i] > 0的点i,连边<S, i>,容量为D[i]/2;对于每个D[j] < 0的点j,连边<j, T>,容量为-D[j]/2;G’中的每条边在网络中仍保留,容量为i(表示该边最多只能被改变一次方向)。求这个网络的最大流,若S引出的所有边均满流,则原混合图是欧拉图,将网络中所有流量为1的中间边(就是不与S或T关联的边)在G’中改变方向,形成的新图G”一定是有向欧拉图;若S引出的边中有的没有满流,则原混合图不是欧拉图。
欧拉路径
每条边只经过一次,不要求回到起点
无向图:
连通(不考虑度为0的点),每个顶点度数都为偶数或者仅有两个点的度数为奇数。
/*
* O(E)
* INIT:adj[][]置为图的邻接表;cnt[a]为a点的邻接点数
* CALL:alpath(0); 注意:不要有自向边
*/
const int V = 10000;
int adj[V][V];
int idx[V][V];
int cnt[V];
int stk[V];
int top = 0;
int path(int v)
{
for (int w; cnt[v] > 0; v = w)
{
stk[top++] = v;
w = adj[v][--cnt[v]];
adj[w][idx[w][v]] = adj[w][--cnt[w]];
//处理的是无向图——边是双向边,删除v->w后,还要处理删除w->v
}
return v;
}
void elpath(int b, int n)
{
int i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j < cnt[i]; j++)
{
idx[i][adj[i][j]] = j;
}
}
std::cout << b;
for (top = 0; path(b) == b && top != 0; )
{
b = stk[--top];
std::cout << '-' << b;
}
std::cout << std::endl;
}
有向图:
基图连通(把边当成无向边,同样不考虑度为0的点),每个顶点出度等于入度或者有且仅有一个点的出度比入度多1,有且仅有一个点的出度比入度少1,其余的出度等于入度。
/*
* POJ 2337
* 给出n个小写字母组成的单词,要求将n个单词连接起来。使得前一个单词的最后一个字母和
* 后一个单词的第一个字母相同。输出字典序最小解
*/
struct Edge
{
int to;
int next;
int index;
bool flag;
}edge[2010];
int head[30];
int tot;
void init()
{
tot = 0;
memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int index)
{
edge[tot].to = v;
edge[tot].next = head[u];
edge[tot].index = index;
edge[tot].flag = false;
head[u] = tot++;
return ;
}
std::string str[1010];
int in[30];
int out[30];
int cnt;
int ans[1010];
void dfs(int u)
{
for (int i = head[u]; i != -1; i = edge[i].next)
{
if (!edge[i].flag)
{
edge[i].flag = true;
dfs(edge[i].to);
ans[cnt++] = edge[i].index;
}
}
return ;
}
int main(int argc, const char * argv[])
{
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
int T, n;
std::cin >> T;
while (T--)
{
std::cin >> n;
for (int i = 0; i < n; i++)
{
std::cin >> str[i];
}
std::sort(str, str + n); //要输出字典序最小的解,先按照字典序排序
init();
memset(in, 0, sizeof(in));
memset(out, 0, sizeof(out));
int start = 100;
for (int i = n - 1; i >= 0; i--) //字典序大的先加入
{
int u = str[i][0] - 'a';
int v = str[i][str[i].length() - 1] - 'a';
addEdge(u, v, i);
out[u]++;
in[v]++;
if (n < start)
{
start = u;
}
if (v < start)
{
start = v;
}
}
int ccOne = 0;
int ccTwo = 0;
for (int i = 0; i < 26; i++)
{
if (out[i] - in[i] == 1)
{
ccOne++;
start = 1; //如果有一个出度比入度大1的点,就从这个点出发,否则从最小的点出发
}
else if (out[i] - in[i] == -1)
{
ccTwo++;
}
else if (out[i] - in[i] != 0)
{
ccOne = 3;
}
}
if (!((ccOne == 0 && ccTwo == 0) || (ccOne == 1 && ccTwo == 1)))
{
std::cout << "***" << '\n';
continue;
}
cnt = 0;
dfs(start);
if (cnt != n) //判断是否连通
{
std::cout << "***" << '\n';
continue;
}
for (int i = cnt - 1; i >= 0; i--)
{
std::cout << str[ans[i]];
if (i > 0)
{
std::cout << '.';
}
else
{
std::cout << '\n';
}
}
}
return 0;
}
混合图:
如果存在欧拉回路,一定存在欧拉路径,否则如果有且仅有两个点的(出度-入度)是奇数,那么给这两个点加边,判断是否存在欧拉回路,如果存在就一定存在欧拉路径。
/*
* POJ 1637
* 本题保证了连通,故不需要判断连通,否则要判断连通
*/
const int MAXN = 210;
const int MAXM = 20100; //最大流ISAP部分
const int INF = 0x3f3f3f3f;
struct Edge
{
int to;
int next;
int cap;
int flow;
}edge[MAXM];
int tol;
int head[MAXN];
int gap[MAXN];
int dep[MAXN];
int pre[MAXN];
int cur[MAXN];
void init()
{
tol = 0;
memset(head, -1, sizeof(head));
return ;
}
void addEdge(int u, int v, int w, int rw = 0)
{
edge[tol].to = v;
edge[tol].cap = w;
edge[tol].next = head[u];
edge[tol].flow = 0;
head[u] = tol++;
edge[tol].to = u;
edge[tol].cap = rw;
edge[tol].next = head[v];
edge[tol].flow = 0;
head[v] = tol++;
return ;
}
int sap(int start, int end, int N)
{
memset(gap, 0, sizeof(gap));
memset(dep, 0, sizeof(dep));
memcpy(cur, head, sizeof(head));
int u = start;
pre[u] = -1;
gap[0] = N;
int ans = 0;
while (dep[start] < N)
{
if (u == end)
{
int MIN = INF;
for (int i = pre[u]; i != -1; i = pre[edge[i ^ 1].to])
{
if (MIN > edge[i].cap - edge[i].flow)
{
MIN = edge[i].cap - edge[i].flow;
}
}
for (int i = pre[u]; i != -1; i = pre[edge[i ^ 1].to])
{
edge[i].flow += MIN;
edge[i ^ 1].flow -= MIN;
}
u = start;
ans += MIN;
continue;
}
bool flag = false;
int v = 0;
for (int i = cur[u]; i != -1; i = edge[i].next)
{
v = edge[i].to;
if (edge[i].cap - edge[i].flow && dep[v] + 1 == dep[u])
{
flag = true;
cur[u] = pre[v] = i;
break;
}
}
if (flag)
{
u = v;
continue;
}
int MIN = N;
for (int i = head[u]; i != -1; i = edge[i].next)
{
if (edge[i].cap - edge[i].flow && dep[edge[i].to] < MIN)
{
MIN = dep[edge[i].to];
cur[u] = i;
}
}
gap[dep[u]]--;
if (!gap[dep[u]])
{
return ans;
}
dep[u] = MIN + 1;
gap[dep[u]]++;
if (u != start)
{
u = edge[pre[u] ^ 1].to;
}
}
return ans;
}
//the end of 最大流部分
int in[MAXN];
int out[MAXN];
int main()
{
//freopen("in.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
int T;
int n, m;
std::cin >> T;
while (T--)
{
std::cin >> n >> m;
init();
int u, v, w;
memset(in, 0, sizeof(in));
memset(out, 0, sizeof(out));
while (m--)
{
std::cin >> u >> v >> w;
out[u]++;
in[v]++;
if (w == 0)
{
addEdge(u, v, 1); //双向
}
}
bool flag = true;
for (int i = 1; i <= n; i++)
{
if (out[i] - in[i] > 0)
{
addEdge(0, i, (out[i] - in[i]) / 2);
}
else if (in[i] - out[i] > 0)
{
addEdge(i, n + 1, (in[i] - out[i]) / 2);
}
if ((out[i] - in[i]) & 1)
{
flag = false;
}
}
if (!flag)
{
std::cout << "impossible" << '\n';
continue;
}
sap(0, n + 1, n + 2);
for (int i = head[0]; i != -1; i = edge[i].next)
{
if (edge[i].cap > 0 && edge[i].cap > edge[i].flow)
{
flag = false;
break;
}
}
if (flag)
{
std::cout << "possible" << '\n';
}
else
{
std::cout << "impossible" << '\n';
}
}
return 0;
}
DAG的深度优先搜索标记
/*
* DAG(有向无环图)的深度优先搜索标记
* INIT:edge[][]邻接矩阵;pre[], post[], tag全置0
* CALL:dfsTag(i, n); pre/post:开始/结束时间
*/
const int V = 1010;
int edge[V][V];
int pre[V];
int post[V];
int tag;
void dfsTag(int cur, int n)
{
//vertex:0 ~ n - 1
pre[cur] = ++tag;
for (int i = 0; i < n; i++)
{
if (edge[cur][i])
{
if (0 == pre[i])
{
std::cout << "Three Edge!" << '\n';
dfsTag(i, n);
}
else
{
if (0 == post[i])
{
std::cout << "Back Edge!" << '\n';
}
else if (pre[i] > pre[cur])
{
std::cout << "Down Edge!" << '\n';
}
else
{
std::cout << "Cross Edge!" << '\n';
}
}
}
}
post[cur] = ++tag;
return ;
}
图的割点、桥和双连通分支的基本概念
点连通度与边连通度
在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。一个图的点连通度的定义为,最小割点集合中的顶点数。 类似的,如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。一个图的边连通度的定义为,最小割边集合中的边数。
双连通图、割点与桥
如果一个无向连通图的点连通度大于1,则称该图是点双连通的(point biconnected),简称双连通或重连通。一个图有割点,当且仅当这个图的点连通度为1,则割点集合的唯一元素被称为割点(cut point),又叫关节 点(articulation point)。如果一个无向连通图的边连通度大于1,则称该图是边双连通的(edge biconnected),简称双连通或重连通。一个图有桥,当且仅当这个图的边连通度为 1,则割边集合的唯一元素被称为桥(bridge),又叫关节边 (articulation edge)。
可以看出,点双连通与边双连通都可以简称为双连通,它们之间是有着某种联系的,下文中提到的双连通, 均既可指点双连通,又可指边双连通。
双连通分支
在图G的所有子图G’中,如果G’是双连通的,则称G’为双连通子图。如果一个双连通子图G’它不是任何一个双连通子图的真子集,则G’为极大双连通子图。双连通分支(biconnected component),或重连通分支, 就是图的极大双连通子图。特殊的,点双连通分支又叫做块。
求割点与桥
该算法是R.Tarjan发明的。对图深度优先搜索,定义DFS(u)为u在搜索树(以下简称为树)中被遍历到的次序号。定义Low(u)为u或u的子树中能通过非父子边追溯到的最早的节点,即DFS序号最小的节点。根据定义,则有:Low(u)=Min{DFS(u)DFS(v)(u,v)为后向边(返祖边)等价于DFS(v) < DFS(u)且v不为u的父亲节点Low(v)(u,v)为树枝边(父子边)}一个顶点u是割点,当且仅当满足(1)或(2)(1)u为树根,且u有多于一个子树。(2)u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得 DFS(u) <= Low(v)。一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u) < Low(v)。
求双连通分支
下面要分开讨论点双连通分支与边双连通分支的求法。
对于点双连通分支,实际上在求割点的过程中就能顺便把每个点双连通分支求出。建立一个栈,存储当前双连通分支,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满 足DFS(u) <= Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。对于边双连通分支,求法更为简单。只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。
构造双连通图
一个有桥的连通图,如何把它通过加边变成边双连通图?
方法为首先求出所有的桥,然后删除这些桥边, 剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf + 1) / 2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf + 1) / 2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf + 1) / 2 次,把所有点收缩到了一起。
无向图找桥
/*
* 无向图找桥
* INIT: edge[][]邻接矩阵;vis[],pre[],ans[],bridge置0;
* CALL: dfs(0, -1, 1, n);
*/
const int V = 1010;
int bridge; //桥
int edge[V][V];
int ans[V];
int pre[V];
int vis[V];
void dfs(int cur, int father, int dep, int n)
{
//vertex: 0 ~ n - 1
if (bridge)
{
return ;
}
vis[cur] = 1;
pre[cur] = ans[cur] = dep;
for (int i = 0; i < n; i++)
{
if (edge[cur][i])
{
if (i != father && 1 == vis[i])
{
if (pre[i] < ans[cur])
{
ans[cur] = pre[i]; //back edge
}
}
if (0 == vis[i]) //tree edge
{
dfs(i, cur, dep + 1, n);
if (bridge)
{
return ;
}
if (ans[i] < ans[cur])
{
ans[cur] = ans[i];
}
if (ans[i] > pre[cur])
{
bridge = 1;
return ;
}
}
}
}
vis[cur] = 2;
return ;
}
无向图连通度(割)
/*
* INIT: edge[][]邻接矩阵;vis[],pre[],anc[],deg[]置为0;
* CALL: dfs(0, -1, 1, n);
* k = deg[0], deg[i] + 1(i = 1...n - 1)为删除该节点后得到的连通图个数
* 注意: 0作为根比较特殊
*/
const int V = 1010;
int edge[V][V];
int anc[V];
int pre[V];
int vis[V];
int deg[V];
void dfs(int cur, int father, int dep, int n)
{
//vertex:0 ~ n - 1
int cnt = 0;
vis[cur] = 1;
pre[cur] = anc[cur] = dep;
for (int i = 0; i < n; i++)
{
if (edge[cur][i])
{
if (i != father && 1 == vis[i])
{
if (pre[i] < anc[cur])
{
anc[cur] = pre[i]; //back edge
}
}
if (0 == vis[i]) //tree edge
{
dfs(i, cur, dep + 1, n);
cnt++; //分支个数
if (anc[i] < anc[cur])
{
anc[cur] = anc[i];
}
if ((cur == 0 && cnt > 1) || (cnt != 0 && anc[i] >= pre[cur]))
{
deg[cur]++; //link degree of a vertex
}
}
}
}
vis[cur] = 2;
return ;
}
最大团问题
DP+DFS
/*
* INIT: g[][]邻接矩阵
* CALL: res = clique(n);
*/
const int V = 10010;
int g[V][V];
int dp[V];
int stk[V][V];
int mx;
int dfs(int n, int ns, int dep)
{
if (0 == ns)
{
if (dep > mx)
{
mx = dep;
}
return 1;
}
int i, j, k, p, cnt;
for (i = 0; i < ns; i++)
{
k = stk[dep][i];
cnt = 0;
if (dep + n - k <= mx)
{
return 0;
}
if (dep + dp[k] <= mx)
{
return 0;
}
for (j = i + 1; j < ns; j++)
{
p = stk[dep][j];
if (g[k][p])
{
stk[dep + 1][cnt++] = p;
}
}
dfs(n, cnt, dep + 1);
}
return 1;
}
int clique(int n)
{
int i, j, ns;
for (mx = 0, i = n - 1; i >= 0; i--) // vertex: 0 ~ n-1
{
for (ns = 0, j = i + 1; j < n; j++)
{
if (g[i][j])
{
stk[1][ns++] = j;
}
}
dfs(n, ns, 1);
dp[i] = mx;
}
return mx;
}
最小树形图
/*
* 最小树形图
* int型
* 复杂度O(NM)
* 点从0开始
*/
const int INF = 0x3f3f3f3f;
const int MAXN = 1010;
const int MAXM = 1000010;
struct Edge
{
int u, v, cost;
};
Edge edge[MAXM];
int pre[MAXN], id[MAXN], visit[MAXN], in[MAXN];
int zhuliu(int root, int n, int m)
{
int res = 0, v;
while (1)
{
memset(in, 0x3f, sizeof(in));
for (int i = 0; i < m; i++)
{
if (edge[i].u != edge[i].v && edge[i].cost < in[edge[i].v])
{
pre[edge[i].v] = edge[i].u;
in[edge[i].v] = edge[i].cost;
}
}
for (int i = 0; i < n; i++)
{
if (i != root && in[i] == INF)
{
return -1; // 不存在最小树形图
}
}
int tn = 0;
memset(id, -1, sizeof(id));
memset(visit, -1, sizeof(visit));
in[root] = 0;
for (int i = 0; i < n; i++)
{
res += in[i];
v = i;
while (visit[v] != i && id[v] == -1 && v != root)
{
visit[v] = i;
v = pre[v];
}
if (v != root && id[v] == -1)
{
for (int u = pre[v]; u != v ; u = pre[u])
{
id[u] = tn;
}
id[v] = tn++;
}
}
if (tn == 0)
{
break; // 没有有向环
}
for (int i = 0; i < n; i++)
{
if (id[i] == -1)
{
id[i] = tn++;
}
}
for (int i = 0; i < m; i++)
{
v = edge[i].v;
edge[i].u = id[edge[i].u];
edge[i].v = id[edge[i].v];
if (edge[i].u != edge[i].v)
{
edge[i].cost -= in[v];
}
}
n = tn;
root = id[root];
}
return res;
}
一般图匹配带花树
const int maxn = 300;
int N;
bool G[maxn][maxn];
int match[maxn];
bool InQueue[maxn], InPath[maxn], InBlossom[maxn];
int head, tail;
int Queue[maxn];
int start, finish;
int NewBase;
int father[maxn], Base[maxn];
int Count;
void CreateGraph()
{
int u, v;
memset(G, 0, sizeof(G));
scanf("%d", &N);
while (scanf("%d%d",&u,&v) != EOF)
{
G[u][v] = G[v][u] = 1;
}
}
void Push(int u)
{
Queue[tail++] = u;
InQueue[u] = 1;
}
int Pop()
{
int res = Queue[head++];
return res;
}
int FindCommonAncestor (int u, int v)
{
memset(InPath, 0, sizeof(InPath));
while (true)
{
u = Base[u];
InPath[u] = 1;
if (u == start)
{
break;
}
u = father[match[u]];
}
while (true)
{
v = Base[v];
if (InPath[v])
{
break;
}
v = father[match[v]];
}
return v;
}
void ResetTrace(int u)
{
int v;
while (Base[u] != NewBase)
{
v = match[u];
InBlossom[Base[u]] = InBlossom[Base[v]] = 1;
u = father[v];
if (Base[u] != NewBase)
{
father[u] = v;
}
}
}
void BlossomContract(int u, int v)
{
NewBase = FindCommonAncestor(u, v);
memset(InBlossom, 0, sizeof(InBlossom));
ResetTrace(u);
ResetTrace(v);
if (Base[u] != NewBase)
{
father[u]=v;
}
if (Base[v] != NewBase)
{
father[v]=u;
}
for (int tu=1; tu <= N; tu++)
{
if (InBlossom[Base[tu]])
{
Base[tu] = NewBase;
if (!InQueue[tu])
{
Push(tu);
}
}
}
}
void FindAugmentingPath()
{
memset(InQueue, 0, sizeof(InQueue));
memset(father, 0, sizeof(father));
for (int i = 1; i <= N; i++)
{
Base[i] = i;
}
head = tail = 1;
Push(start);
finish = 0;
while (head < tail)
{
int u = Pop();
for (int v = 1; v <= N; v++)
{
if (G[u][v] && (Base[u] != Base[v]) && match[u] != v)
{
if ((v == start) || ((match[v] > 0) && father[match[v]] > 0))
{
BlossomContract(u, v);
}
else if (father[v] == 0)
{
father[v] = u;
if (match[v] > 0)
{
Push(match[v]);
}
else
{
finish = v;
return ;
}
}
}
}
}
}
void AugmentPath()
{
int u, v, w;
u = finish;
while (u > 0)
{
v = father[u];
w = match[v];
match[v] = u;
match[u] = v;
u = w;
}
}
void Edmonds()
{
memset(match, 0, sizeof(match));
for (int u = 1; u <= N; u++)
{
if (match[u] == 0)
{
start = u;
FindAugmentingPath();
if (finish > 0)
{
AugmentPath();
}
}
}
}
void PrintMatch()
{
Count = 0;
for (int u = 1; u <= N; u++)
{
if (match[u] > 0)
{
Count++;
}
}
printf("%d\n", Count);
for (int u = 1; u <= N; u++)
{
if (u < match[u])
{
printf("%d %d\n", u, match[u]);
}
}
}
int main()
{
CreateGraph();
Edmonds(); // 进行匹配
PrintMatch(); // 输出匹配
return 0;
}
LCA
DFS+ST在线算法
const int MAXN = 10010;
int rmq[2 * MAXN]; // rmq数组,就是欧拉序列对应的深度序列
struct ST
{
int mm[2 * MAXN];
int dp[2 * MAXN][20]; // 最小值对应的下标
void init(int n)
{
mm[0] = -1;
for (int i = 1; i <= n; i++)
{
mm[i] = ((i & (i - 1)) == 0) ? mm[i - 1] + 1 : mm[i - 1];
dp[i][0] = i;
}
for (int j = 1; j <= mm[n]; j++)
{
for (int i = 1; i + (1 << j) - 1 <= n; i++)
{
dp[i][j] = rmq[dp[i][j - 1]] < rmq[dp[i + (1 << (j - 1))][j - 1]] ? dp[i][j - 1] : dp[i + (1 << (j - 1))][j - 1];
}
}
}
int query(int a,int b) // 查询[a,b]之间最小值的下标
{
if (a > b)
{
swap(a, b);
}
int k = mm[b - a + 1];
return rmq[dp[a][k]] <= rmq[dp[b - (1 << k) + 1][k]] ? dp[a][k] : dp[b - (1 << k) + 1][k];
}
};
// 边的结构体定义
struct Edge
{
int to, next;
};
Edge edge[MAXN * 2];
int tot, head[MAXN];
int F[MAXN * 2]; // 欧拉序列,就是dfs遍历的顺序,长度为2*n-1,下标从1开始
int P[MAXN]; // P[i]表示点i在F中第一次出现的位置
int cnt;
ST st;
void init()
{
tot = 0;
memset(head, -1, sizeof(head));
}
void addedge(int u, int v) // 加边,无向边需要加两次
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void dfs(int u, int pre, int dep)
{
F[++cnt] = u;
rmq[cnt] = dep;
P[u] = cnt;
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (v == pre)
{
continue;
}
dfs(v, u, dep + 1);
F[++cnt] = u;
rmq[cnt] = dep;
}
}
void LCA_init(int root, int node_num) // 查询LCA前的初始化
{
cnt = 0;
dfs(root, root, 0);
st.init(2 * node_num - 1);
}
int query_lca(int u, int v) // 查询u,v的lca编号
{
return F[st.query(P[u], P[v])];
}
bool flag[MAXN];
int main()
{
int T;
int N;
int u, v;
scanf("%d", &T);
while(T--)
{
scanf("%d", &N);
init();
memset(flag, false, sizeof(flag));
for (int i = 1; i < N; i++)
{
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
flag[v] = true;
}
int root;
for (int i = 1; i <= N; i++)
{
if (!flag[i])
{
root = i;
break;
}
}
LCA_init(root, N);
scanf("%d%d", &u, &v);
printf("%d\n", query_lca(u, v));
}
return 0;
}
Tarjan离线算法
/*
* 给出一颗有向树,Q个查询
* 输出查询结果中每个点出现次数
* 复杂度O(n + Q);
*/
const int MAXN = 1010;
const int MAXQ = 500010; // 查询数的最大值
// 并查集部分
int F[MAXN]; // 需要初始化为-1
int find(int x)
{
if (F[x] == -1)
{
return x;
}
return F[x] = find(F[x]);
}
void bing(int u, int v)
{
int t1 = find(u);
int t2 = find(v);
if (t1 != t2)
{
F[t1] = t2;
}
}
bool vis[MAXN]; // 访问标记
int ancestor[MAXN]; // 祖先
struct Edge
{
int to, next;
} edge[MAXN * 2];
int head[MAXN],tot;
void addedge(int u, int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
struct Query
{
int q, next;
int index; // 查询编号
} query[MAXQ * 2];
int answer[MAXQ]; // 存储最后的查询结果,下标0~Q-1
int h[MAXQ];
int tt;
int Q;
void add_query(int u, int v, int index)
{
query[tt].q = v;
query[tt].next = h[u];
query[tt].index = index;
h[u] = tt++;
query[tt].q = u;
query[tt].next = h[v];
query[tt].index = index;
h[v] = tt++;
}
void init()
{
tot = 0;
memset(head, -1, sizeof(head));
tt = 0;
memset(h, -1, sizeof(h));
memset(vis, false, sizeof(vis));
memset(F, -1, sizeof(F));
memset(ancestor, 0, sizeof(ancestor));
}
void LCA(int u)
{
ancestor[u] = u;
vis[u] = true;
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (vis[v])
{
continue;
}
LCA(v);
bing(u, v);
ancestor[find(u)] = u;
}
for (int i = h[u]; i != -1; i = query[i].next)
{
int v = query[i].q;
if (vis[v])
{
answer[query[i].index] = ancestor[find(v)];
}
}
}
bool flag[MAXN];
int Count_num[MAXN];
int main()
{
int n;
int u, v, k;
while (scanf("%d", &n) == 1)
{
init();
memset(flag, false, sizeof(flag));
for (int i = 1; i <= n; i++)
{
scanf("%d:(%d)", &u, &k);
while (k--)
{
scanf("%d", &v);
flag[v] = true;
addedge(u,v);
addedge(v,u);
}
}
scanf("%d", &Q);
for (int i = 0; i < Q; i++)
{
char ch;
cin >> ch;
scanf("%d %d)", &u, &v);
add_query(u, v, i);
}
int root;
for (int i = 1; i <= n; i++)
{
if (!flag[i])
{
root = i;
break;
}
}
LCA(root);
memset(Count_num, 0, sizeof(Count_num));
for (int i = 0; i < Q; i++)
{
Count_num[answer[i]]++;
}
for (int i = 1; i <= n; i++)
{
if (Count_num[i] > 0)
{
printf("%d:%d\n", i, Count_num[i]);
}
}
}
return 0;
}
倍增法
/*
* LCA在线算法(倍增法)
*/
const int MAXN = 10010;
const int DEG = 20;
struct Edge
{
int to, next;
} edge[MAXN * 2];
int head[MAXN], tot;
void addedge(int u, int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void init()
{
tot = 0;
memset(head, -1, sizeof(head));
}
int fa[MAXN][DEG]; // fa[i][j]表示结点i的第2^j个祖先
int deg[MAXN]; // 深度数组
void BFS(int root)
{
queue<int>que;
deg[root] = 0;
fa[root][0] = root;
que.push(root);
while (!que.empty())
{
int tmp = que.front();
que.pop();
for (int i = 1; i < DEG; i++)
{
fa[tmp][i] = fa[fa[tmp][i - 1]][i - 1];
}
for (int i = head[tmp]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (v == fa[tmp][0])
{
continue;
}
deg[v] = deg[tmp] + 1;
fa[v][0] = tmp;
que.push(v);
}
}
}
int LCA(int u, int v)
{
if (deg[u] > deg[v])
{
swap(u, v);
}
int hu = deg[u], hv = deg[v];
int tu = u, tv = v;
for (int det = hv-hu, i = 0; det ; det >>= 1, i++)
{
if (det & 1)
{
tv = fa[tv][i];
}
}
if (tu == tv)
{
return tu;
}
for (int i = DEG - 1; i >= 0; i--)
{
if (fa[tu][i] == fa[tv][i])
{
continue;
}
tu = fa[tu][i];
tv = fa[tv][i];
}
return fa[tu][0];
}
bool flag[MAXN];
int main()
{
int T;
int n;
int u, v;
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
init();
memset(flag, false, sizeof(flag));
for (int i = 1; i < n; i++)
{
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
flag[v] = true;
}
int root;
for (int i = 1; i <= n; i++)
{
if (!flag[i])
{
root = i;
break;
}
}
BFS(root);
scanf("%d%d", &u, &v);
printf("%d\n", LCA(u, v));
}
return 0;
}
生成树计数
Matrix-Tree 定理(Kirchhoff 矩阵-树定理)
1、G 的度数矩阵 D[G]是一个 n*n 的矩阵,并且满足:当 i≠j 时,dij=0;当 i=j 时,dij 等于 vi 的度数。
2、G 的邻接矩阵 A[G]也是一个 n*n 的矩阵, 并且满足:如果 vi、vj 之间有边直接相连,则 aij=1,否则
为 0。
我们定义 G 的 Kirchhoff 矩阵(也称为拉普拉斯算子)C[G]为 C[G]=D[G]-A[G],则 Matrix-Tree 定理可以
描述为:G 的所有不同的生成树的个数等于其 Kirchhoff 矩阵 C[G]任何一个 n-1 阶主子式的行列式的绝对
值。所谓 n-1 阶主子式,就是对于 r(1≤r≤n),将 C[G]的第 r 行、第 r 列同时去掉后得到的新矩阵,用 Cr[G]
表示。
求生成树计数部分代码,计数对10007取模
// 求生成树计数部分代码,计数对10007取模
const int MOD = 10007;
int INV[MOD];
// 求ax = 1(mod m)的x值,就是逆元(0<a<m)
long long inv(long long a, long long m)
{
if (a == 1)
{
return 1;
}
return inv(m % a, m) * (m - m / a) % m;
}
struct Matrix
{
int mat[330][330];
void init()
{
memset(mat, 0, sizeof(mat));
}
int det(int n) // 求行列式的值模上MOD,需要使用逆元
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
mat[i][j] = (mat[i][j] % MOD + MOD) % MOD;
}
}
int res = 1;
for (int i = 0; i < n; i++)
{
for (int j = i; j < n; j++)
{
if (mat[j][i] != 0)
{
for (int k = i; k < n; k++)
{
swap(mat[i][k], mat[j][k]);
}
if (i != j)
{
res = (-res + MOD) % MOD;
}
break;
}
}
if (mat[i][i] == 0)
{
res = -1; // 不存在(也就是行列式值为0)
break;
}
for (int j = i + 1; j < n; j++)
{
//int mut = (mat[j][i]*INV[mat[i][i]])%MOD;//打表逆元
int mut = (mat[j][i] * inv(mat[i][i], MOD)) % MOD;
for (int k = i; k < n; k++)
{
mat[j][k] = (mat[j][k] - (mat[i][k] * mut) % MOD + MOD) % MOD;
}
}
res = (res * mat[i][i]) % MOD;
}
return res;
}
};
int main()
{
Matrix ret;
ret.init();
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (i != j && g[i][j])
{
ret.mat[i][j] = -1;
ret.mat[i][i]++;
}
}
}
printf("%d\n", ret.det(n - 1));
return 0;
}
计算生成树个数,不取模
const double eps = 1e-8;
const int MAXN = 110;
int sgn(double x)
{
if (fabs(x) < eps)
{
return 0;
}
if (x < 0)
{
return -1;
}
else
{
return 1;
}
}
double b[MAXN][MAXN];
double det(double a[][MAXN], int n)
{
int i, j, k, sign = 0;
double ret = 1;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
b[i][j] = a[i][j];
}
}
for (i = 0; i < n; i++)
{
if (sgn(b[i][i]) == 0)
{
for (j = i + 1; j < n; j++)
{
if (sgn(b[j][i]) != 0)
{
break;
}
}
if (j == n)
{
return 0;
}
for (k = i; k < n; k++)
{
swap(b[i][k], b[j][k]);
}
sign++;
}
ret *= b[i][i];
for (k = i + 1; k < n; k++)
{
b[i][k] /= b[i][i];
}
for (j = i+1; j < n; j++)
{
for (k = i+1; k < n; k++)
{
b[j][k] -= b[j][i] * b[i][k];
}
}
}
if (sign & 1)
{
ret = -ret;
}
return ret;
}
double a[MAXN][MAXN];
int g[MAXN][MAXN];
int main()
{
int T;
int n, m;
int u, v;
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &n, &m);
memset(g, 0, sizeof(g));
while (m--)
{
scanf("%d%d", &u, &v);
u--;
v--;
g[u][v] = g[v][u] = 1;
}
memset(a, 0, sizeof(a));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (i != j && g[i][j])
{
a[i][i]++;
a[i][j] = -1;
}
}
}
double ans = det(a, n - 1);
printf("%.0lf\n", ans);
}
return 0;
}
有向图最小树形图
/*
* 有向图最小树形图
* INIT: eg置为边表;res置为0;cp[i]置为i;
* CALL: dirTree(root, nv, ne); res是结果
*/
#define typec int // type of res
const typec V = 1010;
const typec E = 10010;
const typec inf = 0x3f3f3f3f; // max of res
typec res, dis[V];
int to[V], cp[V], tag[V];
struct Edge
{
int u, v;
typec c;
} eg[E];
int iroot(int i)
{
if (cp[i] == i)
{
return i;
}
return cp[i] = iroot(cp[i]);
}
int dirTree(int root, int nv, int ne) // root:树根
{
// vertex:0~n-1
int i, j, k, circle = 0;
memset(tag, -1, sizeof(tag));
memset(to, -1, sizeof(to));
for (i = 0; i < nv; i++)
{
dis[i] = inf;
}
for (j = 0; j < ne; j++)
{
i = iroot(eg[j].u);
k = iroot(eg[j].v);
if (k != i && dis[k] > eg[j].c)
{
dis[k] = eg[j].c;
to[k] = i;
}
}
to[root] = -1;
dis[root] = 0;
tag[root] = root;
for (i = 0; i < nv; i++)
{
if (cp[i] == i && -1 == tag[i])
{
j = i;
for (; j != -1 && tag[j] == -1; j = to[j])
{
tag[j] = i;
if (j == -1)
{
return 0;
}
if (tag[j] == i)
{
circle = 1;
tag[j] = -2;
for (k = to[j]; k != j; k = to[k])
{
tag[k] = -2;
}
}
}
}
}
if (circle)
{
for (j = 0; j < ne; j++)
{
i = iroot(eg[j].u);
k = iroot(eg[j].v);
if (k != i && tag[k] == -2)
{
eg[j].c -= dis[k];
}
}
for (i = 0; i < nv; i++)
{
if (tag[i] == -2)
{
res += dis[i];
tag[i] = 0;
for (j = to[i]; j != i; j = to[j])
{
res += dis[j];
cp[j] = i;
tag[j] = 0;
}
}
}
if (0 == dirTree(root, nv, ne))
{
return 0;
}
}
else
{
for (i = 0; i < nv; i++)
{
if (cp[i] == i)
{
res += dis[i];
}
}
}
return 1; // 若返回0代表原图不连通
}
有向图的强连通分量
Tarjan
/*
* Tarjan算法
* 复杂度O(N+M)
*/
const int MAXN = 20010; // 点数
const int MAXM = 50010; // 边数
struct Edge
{
int to, next;
}edge[MAXM];
int head[MAXN], tot;
int Low[MAXN], DFN[MAXN], Stack[MAXN], Belong[MAXN]; // Belong数组的值是1~scc
int Index, top;
int scc; // 强连通分量的个数
bool Instack[MAXN];
int num[MAXN]; // 各个强连通分量包含点的个数,数组编号1~scc
// num数组不一定需要,结合实际情况
void addedge(int u, int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
return ;
}
void Tarjan(int u)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
for (int i = head[u]; i != -1; i = edge[i].next)
{
v = edge[i].to;
if (!DFN[v])
{
Tarjan(v);
if (Low[u] > Low[v])
{
Low[u] = Low[v];
}
}
else if (Instack[v] && Low[u] > DFN[v])
{
Low[u] = DFN[v];
}
}
if (Low[u] == DFN[u])
{
scc++;
do
{
v = Stack[--top];
Instack[v] = false;
Belong[v] = scc; num[scc]++;
}
while (v != u);
}
return ;
}
void solve(int N)
{
memset(DFN, 0, sizeof(DFN));
memset(Instack, false, sizeof(Instack));
memset(num, 0, sizeof(num));
Index = scc = top = 0;
for (int i = 1; i <= N; i++)
{
if (!DFN[i])
{
Tarjan(i);
}
}
return ;
}
void init()
{
tot = 0;
memset(head, -1, sizeof(head));
return ;
}
Kosaraju
/*
* 复杂度O(N+M)
*/
const int MAXN = 20010;
const int MAXM = 50010;
struct Edge
{
int to, next;
} edge1[MAXM], edge2[MAXM]; // edge1是原图G,edge2是逆图GT
int head1[MAXN], head2[MAXN];
bool mark1[MAXN], mark2[MAXN];
int tot1, tot2;
int cnt1, cnt2;
int st[MAXN]; // 对原图进行dfs,点的结束时间从小到大排序
int Belong[MAXN]; // 每个点属于哪个连通分量(0~cnt2-1)
int num; // 中间变量,用来数某个连通分量中点的个数
int setNum[MAXN]; // 强连通分量中点的个数,编号0~cnt2-1
void addedge(int u, int v)
{
edge1[tot1].to = v;
edge1[tot1].next = head1[u];
head1[u] = tot1++;
edge2[tot2].to = u;
edge2[tot2].next = head2[v];
head2[v] = tot2++;
return ;
}
void DFS1(int u)
{
mark1[u] = true;
for (int i = head1[u]; i != -1; i = edge1[i].next)
{
if(!mark1[edge1[i].to])
{
DFS1(edge1[i].to);
}
}
st[cnt1++] = u;
return ;
}
void DFS2(int u)
{
mark2[u] = true;
num++;
Belong[u] = cnt2;
for (int i = head2[u]; i != -1; i = edge2[i].next)
{
if(!mark2[edge2[i].to])
{
DFS2(edge2[i].to);
}
}
return ;
}
void solve(int n) // 点的编号从1开始
{
memset(mark1, false, sizeof(mark1));
memset(mark2, false, sizeof(mark2));
cnt1 = cnt2 = 0;
for (int i = 1; i <= n; i++)
{
if (!mark1[i])
{
DFS1(i);
}
}
for (int i = cnt1 - 1; i >= 0; i--)
{
if (!mark2[st[i]])
{
num = 0;
DFS2(st[i]);
setNum[cnt2++] = num;
}
}
return ;
}
双连通分支
点双连通分支
去掉桥,其余的连通分支就是边双连通分支了。一个有桥的连通图要变成边双连通图的话,把双连通子图 收缩为一个点,形成一颗树。需要加的边为(leaf+1)/2 (leaf 为叶子结点个数)
给定一个连通的无向图 G,至少要添加几条边,才能使其变为双连通图。
const int MAXN = 5010; // 点数
const int MAXM = 20010; // 边数,因为是无向图,所以这个值要*2
struct Edge
{
int to, next;
bool cut; // 是否是桥标记
}edge[MAXM];
int head[MAXN], tot;
int Low[MAXN], DFN[MAXN], Stack[MAXN], Belong[MAXN]; //Belong数组的值是1~block
int Index,top;
int block; // 边双连通块数
bool Instack[MAXN];
int bridge; // 桥的数目
void addedge(int u, int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
edge[tot].cut=false;
head[u] = tot++;
return ;
}
void Tarjan(int u, int pre)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
for (int i = head[u]; i != -1; i = edge[i].next)
{
v = edge[i].to;
if (v == pre)
{
continue;
}
if (!DFN[v])
{
Tarjan(v, u);
if (Low[u] > Low[v])
{
Low[u] = Low[v];
}
if (Low[v] > DFN[u])
{
bridge++;
edge[i].cut = true;
edge[i^1].cut = true;
}
}
else if (Instack[v] && Low[u] > DFN[v])
{
Low[u] = DFN[v];
}
}
if (Low[u] == DFN[u])
{
block++;
do
{
v = Stack[–top]; Instack[v] = false;
Belong[v] = block;
}
while (v != u);
}
return ;
}
void init()
{
tot = 0;
memset(head, -1, sizeof(head));
return ;
}
int du[MAXN]; // 缩点后形成树,每个点的度数
void solve(int n)
{
memset(DFN, 0, sizeof(DFN));
memset(Instack, false, sizeof(Instack));
Index = top = block = 0;
Tarjan(1,0);
int ans = 0;
memset(du, 0, sizeof(du));
for (int i = 1; i <= n; i++)
{
for (int j = head[i]; j != -1; j = edge[j].next)
{
if (edge[j].cut)
{
du[Belong[i]]++;
}
}
}
for (int i = 1; i <= block; i++)
{
if(du[i]==1)
{
ans++;
}
}
// 找叶子结点的个数ans,构造边双连通图需要加边(ans+1)/2
printf("%d\n", (ans + 1) / 2);
}
int main()
{
int n, m;
int u, v;
while (scanf("%d%d", &n, &m) == 2)
{
init();
while (m–)
{
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
solve(n);
}
return 0;
}
边双连通分支
对于点双连通分支,实际上在求割点的过程中就能顺便把每个点双连通分支求出。建立一个栈,存储 当前双连通分支,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足 DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v), 取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。
奇圈,二分图判断的染色法,求点双连通分支
/*
- POJ 2942 Knights of the Round Table
- 亚瑟王要在圆桌上召开骑士会议,为了不引发骑士之间的冲突,
- 并且能够让会议的议题有令人满意的结果,每次开会前都必须对出席会议的骑士有如下要求:
- 1、 相互憎恨的两个骑士不能坐在直接相邻的2个位置;
- 2、 出席会议的骑士数必须是奇数,这是为了让投票表决议题时都能有结果。
- 注意:1、所给出的憎恨关系一定是双向的,不存在单向憎恨关系。
- 2、由于是圆桌会议,则每个出席的骑士身边必定刚好有2个骑士。
- 即每个骑士的座位两边都必定各有一个骑士。
- 3、一个骑士无法开会,就是说至少有3个骑士才可能开会。
- 首先根据给出的互相憎恨的图中得到补图。
- 然后就相当于找出不能形成奇圈的点。
- 利用下面两个定理:
- (1)如果一个双连通分量内的某些顶点在一个奇圈中(即双连通分量含有奇圈), 那么这个双连通分量的其他顶点也在某个奇圈中;
- (2)如果一个双连通分量含有奇圈,则他必定不是一个二分图。反过来也成立,这是一个充要条件。
- 所以本题的做法,就是对补图求点双连通分量。然后对于求得的点双连通分量,使用染色法判断是不是二分图,不是二分图,这个双连通分量的点是可以存在的
*/
const int MAXN = 1010;
const int MAXM = 2000010;
struct Edge
{
int to, next;
} edge[MAXM];
int head[MAXN], tot;
int Low[MAXN], DFN[MAXN], Stack[MAXN], Belong[MAXN];
int Index,top;
int block; // 点双连通分量的个数
bool Instack[MAXN];
bool can[MAXN];
bool ok[MAXN]; // 标记
int tmp[MAXN]; // 暂时存储双连通分量中的点
int cc; // tmp的计数
int color[MAXN];// 染色
void addedge(int u, int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
return ;
}
bool dfs(int u, int col) // 染色判断二分图
{
color[u] = col;
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (!ok[v])
{
continue;
}
if (color[v] != -1)
{
if (color[v]==col)
{
return false;
}
continue;
}
if (!dfs(v,!col))
{
return false;
}
}
return true;
}
void Tarjan(int u, int pre)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
for (int i = head[u]; i != -1; i = edge[i].next)
{
v = edge[i].to;
if (v == pre)
{
continue;
}
if (!DFN[v])
{
Tarjan(v, u);
if (Low[u] > Low[v])
{
Low[u] = Low[v];
}
if (Low[v] >= DFN[u])
{
block++;
int vn;
cc = 0;
memset(ok, false, sizeof(ok));
do
{
vn = Stack[–top];
Belong[vn] = block;
Instack[vn] = false;
ok[vn] = true;
tmp[cc++] = vn;
}
while (vn!=v);
ok[u] = 1;
memset(color, -1, sizeof(color));
if (!dfs(u,0))
{
can[u] = true;
while (cc–)
{
can[tmp[cc]] = true;
}
}
}
}
else if (Instack[v] && Low[u] > DFN[v])
{
Low[u] = DFN[v];
}
}
}
void solve(int n)
{
memset(DFN, 0, sizeof(DFN));
memset(Instack, false, sizeof(Instack));
Index = block = top = 0;
memset(can, false, sizeof(can));
for (int i = 1; i <= n; i++)
{
if (!DFN[i])
{
Tarjan(i, -1);
}
}
int ans = n;
for (int i = 1; i <= n; i++)
{
if(can[i])
{
ans–;
}
}
printf("%d\n", ans);
}
void init()
{
tot = 0;
memset(head, -1, sizeof(head));
}
int g[MAXN][MAXN];
int main()
{
int n, m;
int u, v;
while (scanf("%d%d", &n, &m) == 2)
{
if (n == 0 && m == 0)
{
break;
}
init();
memset(g, 0, sizeof(g));
while (m–)
{
scanf("%d%d", &u, &v);
g[u][v] = g[v][u] = 1;
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if(i != j && g[i][j] == 0)
{
addedge(i, j);
}
}
}
solve(n);
}
return 0;
}
弦图判断
/*
- 弦图判断
- INIT: g[][]置为邻接矩阵;
- CALL: mcs(n); peo(n);
- 第一步: 给节点编号 mcs(n)
- 设已编号的节点集合为A, 未编号的节点集合为B
- 开始时A为空, B包含所有节点.
- for num=n-1 downto 0 do {
-
在B中找节点x, 使与x相邻的在A集合中的节点数最多,
-
将x编号为num,并从B移入A。
- }
- 第二部:检查peo(n)
- for num=0 to n-1 do {
-
对编号为num的点x,设所有编号>num且与x相邻的点集为C
-
在C中找出编号最小的节点y,
-
若C中存在x != y,使得y与x之间无边,则此图不是弦图。
- }
- 检查完毕, 则此图是弦图.
*/
const int V = 10010;
int g[V][V], order[V], inv[V], tag[V];
void mcs(int n)
{
int i, j, k;
memset(tag, 0, sizeof(tag));
memset(order, -1, sizeof(order));
for (i = n - 1; i >= 0; i–)
{ // vertex:0~n-1
for (j = 0; order[j] >= 0; j++);
for (k = j + 1; k < n; k++)
{
if (order[k] < 0 && tag[k] > tag[j])
{
j = k;
}
}
order[j] = i, inv[i] = j;
for (k = 0; k < n; k++)
{
if (g[j][k])
{
tag[k]++;
}
}
}
return ;
}
int peo(int n)
{
int i, j, k, w, min;
for (i = n - 2; i >= 0; i–)
{
j = inv[i], w = -1, min = n;
for (k = 0; k < n; k++)
{
if (g[j][k] && order[k] > order[j] && order[k] < min)
{
min = order[k], w=k;
}
}
if (w < 0)
{
continue;
}
for (k = 0; k < n; k++)
{
if (g[j][k] && order[k] > order[w] && !g[k][w])
{
return 0; // no
}
}
}
return 1; // yes
}
弦图的PERFECT ELIMINATION点排列
/*
- INIT: g[][]置为邻接矩阵;
- CALL: cardinality(n);tag[i]为排列中第i个点的标号;
*/
const int V = 10010;
int tag[V], g[V][V], deg[V], vis[V];
void cardinality(int n)
{
int i, j, k;
memset(deg, 0, sizeof(deg));
memset(vis, 0, sizeof(vis));
for (i = n - 1; i >= 0; i–)
{
for (j = 0, k = -1; j < n; j++)
{
if (0 == vis[j])
{
if (k == -1 || deg[j] > deg[k])
{
k = j;
}
}
}
vis[k] = 1, tag[i] = k;
for (j = 0; j<n; j++)
{
if (0 == vis[j] && g[k][j])
{
deg[j]++;
}
}
}
return ;
}
稳定婚姻问题
/*
- 稳定婚姻问题O(n^2)
*/
const int N = 1001;
struct People
{
bool state;
int opp, tag;
int list[N]; // man使用
int priority[N]; // woman使用,有必要的话可以和list合并,以节省空间
void Init()
{
state = tag = 0;
}
} man[N], woman[N];
struct R
{
int opp;
int own;
} requst[N];
int n;
void Input();
void Output();
void stableMatching();
int main()
{
Input();
stableMatching();
Output();
return 0;
}
void Input()
{
scanf("%d\n", &n);
int i, j, ch;
for (i = 0; i < n; ++i)
{
man[i].Init();
for(j = 0; j < n; ++j)
{ // 按照man的意愿递减排序
scanf("%d", &ch);
man[i].list[j] = ch - 1;
}
}
for (i = 0; i < n; ++i)
{
woman[i].Init();
for (j = 0; j < n; ++j)
{ // 按照woman的意愿递减排序,但是,存储方法与man不同
scanf("%d", &ch);
woman[i].priority[ch - 1] = j;
}
}
return ;
}
void stableMatching()
{
int k;
for (k = 0; k < n; ++k)
{
int i, id = 0;
for (i = 0; i < n; ++i)
{
if (man[i].state == 0)
{
requst[id].opp = man[i].list[man[i].tag];
requst[id].own = i;
man[i].tag += 1;
++id;
}
}
if (id == 0)
{
break;
}
for (i = 0; i < id; i++)
{
if (woman[requst[i].opp].state == 0)
{
woman[requst[i].opp].opp = requst[i].opp;
woman[requst[i].opp].state = 1;
man[requst[i].own].state = 1;
man[requst[i].own].opp = requst[i].opp;
}
else
{
if (woman[requst[i].opp].priority[woman[requst[i].opp].opp] >woman[requst[i].opp].priority[requst[i].own])
{
man[woman[requst[i].opp].opp].state = 0;
woman[requst[i].opp].opp = requst[i].own;
man[requst[i].own].state = 1;
man[requst[i].own].opp = requst[i].opp;
}
}
}
}
return ;
}
void Output()
{
for (int i = 0; i < n; i++)
{
printf("%d\n", man[i].opp + 1);
}
return ;
}
拓扑排序
/*
- 拓扑排序
- INIT:edge[][]置为图的邻接矩阵;cnt[0…i…n-1]:顶点i的入度。
*/
const int MAXV = 1010;
int edge[MAXV][MAXV];
int cnt[MAXV];
void TopoOrder(int n)
{
int i, top = -1;
for (i = 0; i < n; ++i)
{
if (cnt[i] == 0)
{ // 下标模拟堆栈
cnt[i] = top;
top = i;
}
}
for (i = 0; i < n; i++)
{
if (top == -1)
{
printf(“存在回路\n”);
return ;
}
else
{
int j = top;
top = cnt[top];
printf("%d", j);
for (int k = 0; k < n; k++)
{
if (edge[j][k] && (–cnt[k]) == 0)
{
cnt[k] = top;
top = k;
}
}
}
}
}
无向图连通分支
/*
- 无向图连通分支(dfs/bfs邻接阵)
- DFS / BFS / 并查集
- 返回分支数,id返回1.分支数的值
- 传入图的大小n和邻接阵mat,不相邻点边权0
*/
#define MAXN 100
void search(int n, int mat[][MAXN], int* dfn, int* low, int now, int& cnt, int& tag, int* id, int* st, int& sp)
{
int i, j;
dfn[st[sp++]=now] = low[now] = ++cnt;
for (i = 0; i < n; i++)
{
if (mat[now][i])
{
if (!dfn[i])
{
search(n, mat, dfn, low, i, cnt, tag, id, st, sp);
if (low[i] < low[now])
{
low[now]=low[i];
}
}
else if (dfn[i] < dfn[now])
{
for (j = 0; j < sp && st[j] != i; j++)
{
if (j < cnt && dfn[i] < low[now])
{
low[now] = dfn[i];
}
}
}
}
}
if (low[now] == dfn[now])
{
for (tag++; st[sp] != now; id[st[–sp]] = tag);
}
}
int find_components(int n, int mat[][MAXN], int* id)
{
int ret = 0, i, cnt, sp, st[MAXN], dfn[MAXN], low[MAXN];
for (i = 0; i < n; dfn[i++] = 0);
for (sp = cnt = i = 0; i < n; i++)
{
if (!dfn[i])
{
search(n, mat, dfn, low, i, cnt, ret, id, st, sp);
}
}
return ret;
}
有向图强连通分支
/*
- 有向图强连通分支(dfs/bfs邻接阵)O(n^2)
- 返回分支数,id返回1…分支数的值
- 传入图的大小n和邻接阵mat,不相邻点边权0
/
#define MAXN 100
int find_components(int n, int mat[][MAXN], int id)
{
int ret = 0, a[MAXN], b[MAXN], c[MAXN], d[MAXN], i, j, k, t;
for (k = 0; k < n; id[k++] = 0);
for (k = 0; k < n; k++)
{
if (!id[k])
{
for (i = 0; i < n; i++)
{
a[i] = b[i] = c[i] = d[i] = 0;
}
a[k] = b[k] = 1;
for (t = 1; t;)
{
for (t = i = 0; i < n; i++)
{
if (a[i] && !c[i])
{
for (c[i] = t = 1, j = 0; j < n; j++)
{
if (mat[i][j] && !a[j])
{
a[j] = 1;
}
}
}
if (b[i] && !d[i])
{
for (d[i] = t = 1, j = 0; j < n; j++)
{
if (mat[j][i] && !b[j])
{
b[j] = 1;
}
}
}
}
}
for (ret++, i = 0; i < n; i++)
{
if (a[i] & b[i])
{
id[i] = ret;
}
}
}
}
return ret;
}
有向图最小点基
/*
- 有向图最小点基(邻接阵)O(n^2)
- 点基B满足:对于任意一个顶点Vj,一定存在B中的一个Vi,使得Vi是Vj的前代。
- 返回点基大小和点基 传入图的大小n和邻接阵mat,不相邻点边权0 需要调用强连通分支
- find_components(n, mat, id);参考《有向图强连通分支》
/
#define MAXN 100
int base_vertex(int n, int mat[][MAXN], int sets)
{
int ret=0, id[MAXN], v[MAXN], i, j;
j = find_components(n, mat, id);
for (i = 0; i < j; v[i++] = 1);
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (id[i] != id[j] && mat[i][j])
{
v[id[j] - 1] = 0;
}
}
}
for (i = 0; i < n; i++)
{
if (v[id[i] - 1])
{
v[id[sets[ret++] = i] - 1] = 0;
}
}
return ret;
}