ACM 算法模板

文章目录


涵盖了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
基姆拉尔森公式的计算结果是0123456 七种可能; 
结果的对应关系: 
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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值