算法竞赛模板

一、经典问题

1、Miller-Rabin素数测试

#include<bits/stdc++.h>

using namespace std;
const int MAXN = 100005;
const int p[9] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
typedef long long ll;
typedef long double ld;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
  x = 0; int f = 1;
  char c = getchar();
  for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
  for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
  x *= f;
}
template <typename T> void write(T x) {
  if (x < 0) x = -x, putchar('-');
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
  write(x);
  puts("");
}
ll times(ll a, ll b, ll P) {
  ll tmp = (ld) a * b / P;
  return ((a * b - tmp * P) % P + P) % P;
}
ll power(ll a, ll b, ll P) {
  if (b == 0) return 1;
  ll tmp = power(a, b / 2, P);
  if (b % 2 == 0) return times(tmp, tmp, P);
  else return times(a, times(tmp, tmp, P), P);
}
bool prime(ll n) {
  for (int i = 0; i <= 8; i++) {
      if (p[i] == n) return true;
      else if (p[i] > n) return false;
      ll tmp = power(p[i], n - 1, n), tnp = n - 1;
      if (tmp != 1) return false;
      while (tmp == 1 && tnp % 2 == 0) {
          tnp /= 2;
          tmp = power(p[i], tnp, n);
          if (tmp != 1 && tmp != n - 1) return false;
      }
  }
  return true;
}
int main() {
  ll n;
  while (scanf("%lld", &n) != EOF) {
      if (n == 1) {
          puts("N");
          continue;
      }
      if (prime(n)) puts("Y");
      else puts("N");
  }
  return 0;
}

2、快速幂

int poww(int a, int b) {
    int ans = 1, base = a;
    while (b != 0) {
        if (b & 1 != 0)
            ans *= base;
            base *= base;
            b >>= 1;
    }
    return ans;
}

3、矩阵快速幂求斐波拉契数列

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int mod = 10000;
const int maxn = 35;
int N;
struct Matrix {
    int mat[maxn][maxn];
    int x, y;
    Matrix() {
        memset(mat, 0, sizeof(mat));
        for (int i = 1; i <= maxn - 5; i++) mat[i][i] = 1;
    }
};
inline void mat_mul(Matrix a, Matrix b, Matrix &c) {
    memset(c.mat, 0, sizeof(c.mat));
    c.x = a.x; c.y = b.y;
    for (int i = 1; i <= c.x; i++) {
        for (int j = 1; j <= c.y; j++) {
            for (int k = 1; k <= a.y; k++) {
                c.mat[i][j] += (a.mat[i][k] * b.mat[k][j]) % mod;
                c.mat[i][j] %= mod;
            }
        }
    }
    return ;
}
inline void mat_pow(Matrix &a, int z) {
    Matrix ans, base = a;
    ans.x = a.x; ans.y = a.y;
    while (z) {
        if (z & 1 == 1) mat_mul(ans, base, ans);
        mat_mul(base, base, base);
        z >>= 1;
    }
    a = ans;
}
int main() {
    while (cin >> N) {
        switch (N) {
            case -1: return 0;
            case 0: cout << "0" << endl; continue;
            case 1: cout << "1" << endl; continue;
            case 2: cout << "1" << endl; continue;
        }
        Matrix A, B;
        A.x = 2; A.y = 2;
        A.mat[1][1] = 1; A.mat[1][2] = 1;
        A.mat[2][1] = 1; A.mat[2][2] = 0;
        B.x = 2; B.y = 1;
        B.mat[1][1] = 1; B.mat[2][1] = 1;
        mat_pow(A, N - 1);
        mat_mul(A, B, B);
        cout << B.mat[1][1] << endl;
    }
    return 0;
}

4、大数模拟

/*
    |大数模拟加法|
    |用string模拟|
    |16/11/05ztx, thanks to caojiji|
*/

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;
}
/*
    |大数模拟阶乘|
    |用数组模拟|
    |16/12/02ztx|
*/

#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;
}

5、GCD和LCM

/*
    |辗转相除法|
    |欧几里得算法|
    |求最大公约数|
    |16/11/05ztx|
*/

int gcd(int big, int small)
{
    if (small > big) swap(big, small);
    int temp;
    while (small != 0){ //  辗转相除法
        if (small > big) swap(big, small);
        temp = big % small;
        big = small;
        small = temp;
    }
    return(big);
}
/*
    |辗转相除法|
    |欧几里得算法|
    |求最小公倍数|
    |16/11/05ztx|
*/

int gcd(int big, int small)
{
    if (small > big) swap(big, small);
    int temp;
    while (small != 0){ //  辗转相除法
        if (small > big) swap(big, small);
        temp = big % small;
        big = small;
        small = temp;
    }
    return(big);
}

6、大组合数(卢卡斯定理)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int p = 100010;
LL Power_mod(LL a, LL b, LL p)
{
	LL res = 1;
	while(b!=0)
	{
		if(b&1) res = (res*a)%p;
		a = (a*a)%p;
		b >>= 1;
	}
	return res;
}
LL Comb(LL a,LL b, LL p)
{
	if(a < b) return 0;
	if(a == b) return 1;
	if(b > a-b) b = a-b;
	LL ans = 1, ca = 1, cb = 1;
	for(LL i=0; i<b; ++i)
	{
		ca = (ca*(a-i))%p;
		cb = (cb*(b-i))%p;
	}
	ans = (ca*Power_mod(cb, p-2, p))%p;
	return ans;
}
LL Lucas(int n, int m, int p)
{
	LL ans = 1;
	while(n && m && ans)
	{
		ans = (ans * Comb(n%p, m%p, p))%p;
		n /= p;
		m /= p;
	}
	return ans;
}
int main()
{
	int n, m;
	while(scanf("%d%d%d", &n, &m) !=EOF)
	{
		printf("%lld\n", Lucas(n, m, p));
	}
	return 0;
}

7、约瑟夫环问题

/*
每杀掉一个人,下一个人成为头,相当于把数组向前移动M位。
若已知N-1个人时,胜利者的下标位置位f(N−1,M)f(N−1,M),
则N个人的时候,就是往后移动M为,(因为有可能数组越界,
超过的部分会被接到头上,所以还要模N),既f(N,M)=(f(N−1,M)+M)%n
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
int cir(int n, int m)
{
    int p = 0;
    for(int i=2; i<=n; i++)
    {
        p = (p + m) % i;
    }
    return p + 1;
}
int main()
{
	int n, m;
	while(scanf("%d%d", &n, &m) !=EOF)
	{
		printf("%d\n", cir(n, m));
	}
	return 0;
}

8、博弈论问题

/*
巴什博奕:
只有一堆n个物品,两个人轮流从中取物,规定每次最少取一个,
最多取m个,最后取光者为胜。
*/
#include <iostream>  
using namespace std;  
int main()  
{  
    int n,m;  
    while(cin>>n>>m)  
      if(n%(m+1)==0)  cout<<"后手必胜"<<endl;  
      else cout<<"先手必胜"<<endl;  
    return 0;  
}  

/*
尼姆博弈:
有任意堆物品,每堆物品的个数是任意的,
双方轮流从中取物品,每一次只能从一堆物品中取部分或全部物品,
最少取一件,取到最后一件物品的人获胜。

结论就是:把每堆物品数全部异或起来,如果得到的值为0,
那么先手必败,否则先手必胜。
*/
#include <cstdio>  
#include <cmath>  
#include <iostream>  
using namespace std;  
int main()  
{  
    int n,ans,temp;  
    while(cin>>n)  
    {  
        temp=0;  
        for(int i=0;i<n;i++)  
        {  
            cin>>ans;  
            temp^=ans;  
        }  
        if(temp==0)  cout<<"后手必胜"<<endl;  
        else cout<<"先手必胜"<<endl;  
    }  
    return 0;  
}  
/*
威佐夫博弈(Wythoff Game):
有两堆各若干的物品,两人轮流从其中一堆取至少一件物品,至多不限,
或从两堆中同时取相同件物品,规定最后取完者胜利。
直接说结论了:若两堆物品的初始值为(x,y),且x<y,则另z=y-x;
记w=(int)[((sqrt(5)+1)/2)*z  ];
若w=x,则先手必败,否则先手必胜。
*/
#include <cstdio>  
#include <cmath>  
#include <iostream>  
using namespace std;  
int main()  
{  
    int n1,n2,temp;  
    while(cin>>n1>>n2)  
    {  
        if(n1>n2)  swap(n1,n2);  
        temp=floor((n2-n1)*(1+sqrt(5.0))/2.0);  
        if(temp==n1) cout<<"后手必胜"<<endl;  
        else cout<<"先手必胜"<<endl;  
    }  
    return 0;  
}  
/*
斐波拉契博弈:
有一堆物品,两人轮流取物品,先手最少取一个,至多无上限,
但不能把物品取完,之后每次取的物品数不能超过上一次取的
物品数的二倍且至少为一件,取走最后一件物品的人获胜。
结论是:先手胜当且仅当n不是斐波那契数(n为物品总数)
*/
#include <iostream>    
#include <string.h>    
#include <stdio.h>    
using namespace std;    
const int N = 55;      
int f[N];     
void Init()    
{    
    f[0] = f[1] = 1;    
    for(int i=2;i<N;i++)    
        f[i] = f[i-1] + f[i-2];    
}      
int main()    
{    
    Init();    
    int n;    
    while(cin>>n)    
    {    
        if(n == 0) break;    
        bool flag = 0;    
        for(int i=0;i<N;i++)    
        {    
            if(f[i] == n)    
            {    
                flag = 1;    
                break;    
            }    
        }    
        if(flag) puts("Second win");    
        else     puts("First win");    
    }    
    return 0;    
}   

9、蒙特卡洛问题(撒点法)

  估计不规则图形的面积。


二、字符串

1、最短编辑距离

/*
1、将a[1…i]转化为b[1…j-1] 
2、将a[1..i-1]转化为b[1..j] 
3、将a[1…i-1]转化为b[1…j-1]

第1种情况,只需要在最后将a[j]加上b[1..i]就可以了,总共就需要k+1次操作。 
第2种情况,只需要在最后将a[i]删除,总共需要k+1个操作。 
第3种情况,只需要在最后将a[i]替换为b[j],总共需要k+1个操作。
但如果a[i]刚好等于b[j],就不用再替换了,那就只需要k个操作。
*/
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int dp[1010][1010], len1, len2;
int main()
{
	string s1, s2;
    while(cin >> s1 >> s2)
    {
        len1 = s1.size();
        len2 = s2.size();
        for(int i = 1; i <= len1; ++i)
            for(int j = 1; j <= len2; ++j)
                dp[i][j]=0;
        int flag;
        for(int i = 0; i <= len1; ++i)    
			dp[i][0] = i;
        for(int i = 0;i <= len2; ++i)    
			dp[0][i] = i;
        for(int i = 1; i <= len1; ++i)
        {
            for(int j = 1; j <= len2; ++j)
            {
                if(s1[i-1] == s2[j-1])
                    flag = 0;
                else
                    flag = 1;
                dp[i][j] = min(min(dp[i-1][j] + 1, dp[i][j-1] + 1), dp[i-1][j-1] + flag);
            }
        }
        cout << dp[len1][len2] << endl;
    }
    return 0;
}

2、字符串匹配之KMP

/*
字符串匹配(连续子串)。给你两个字符串,
寻找其中一个字符串是否包含另一个字符串,
如果包含,返回包含的起始位置。 如下面两个字符串:

char *str = "bacbababadababacambabacaddababacasdsd";
char *ptr = "ababaca";

str有两处包含ptr 
分别在str的下标10,26处包含ptr。
*/
#include <iostream>
#include <string>
#include <vector>
using namespace std;

//部分匹配表
void cal_next(string &str, vector<int> &next)
{
	const int len = str.size();
	next[0] = -1;
	int k = -1;
	int j = 0;
	while (j < len - 1)
	{
		if (k == -1 || str[j] == str[k])
		{
			++k;
			++j;
			next[j] = k;//表示第j个字符有k个匹配(“最大长度值” 整体向右移动一位,然后初始值赋为-1)
		}
		else
			k = next[k];//往前回溯
	}
}

vector<int> KMP(string &str1, string &str2, vector<int> &next)
{
	vector<int> vec;
	cal_next(str2, next);
	int i = 0;//i是str1的下标
	int j = 0;//j是str2的下标
	int str1_size = str1.size();
	int str2_size = str2.size();
	while (i < str1_size && j < str2_size)
	{
		//如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),
		//都令i++,j++. 注意:这里判断顺序不能调换!
		if (j == -1 || str1[i] == str2[j])
		{
			++i;
			++j;
		}
		else
			j = next[j];//当前字符匹配失败,直接从str[j]开始比较,i的位置不变
		if (j == str2_size)//匹配成功
		{
			vec.push_back(i - j);//记录下完全匹配最开始的位置
			j = -1;//重置
		}
	}
	return vec;
}

int main(int argc, char const *argv[])
{
	vector<int> vec(20, 0);
	vector<int> vec_test;
	string str1 = "bacbababadababacambabacaddababacasdsd";
	string str2 = "ababaca";
	vec_test = KMP(str1, str2, vec);
	for (const auto v : vec_test)
		cout << v << endl;
	return 0;
}

3、字符串匹配之字典树

#include <bits/stdc++.h>
using namespace std;
const int ALPHABET_SIZE  = 26;

typedef struct trie_node
{
    int count;
    trie_node *child[ALPHABET_SIZE];//纪录各个子节点
}*trie;

trie create_trie_node()
{
    trie_node* pNode = new trie_node();
    pNode->count = 0;
    for (int i = 0; i < ALPHABET_SIZE; ++i)
    {
        pNode->child[i] = nullptr;
    }
    return pNode;
}

void trie_insert(trie root, string &key)
{
    trie_node* pNode = root;
    for (int i = 0; i < key.size(); ++i)
    {
        //如果子树为空,则创建子树,否则就往下寻找,并将沿路的元素出现的次数累加。
        if (pNode->child[key[i]-'a'] == nullptr)
            pNode->child[key[i]-'a'] = create_trie_node();
        pNode = pNode->child[key[i]-'a'];
        pNode->count += 1;
    }
}

int trie_search_count(trie root, string key)
{
    trie_node* pNode = root;
    for (int i = 0; i < key.size() && pNode != nullptr; ++i)
    {
        pNode = pNode->child[key[i]-'a'];
    }
    if(pNode == nullptr)
        return 0;
    else
        return pNode->count;//搜索所有前缀为key的字符串的个数。
}

int main(int argc, char const *argv[])
{
    vector<string> str = {"cat", "cash", "cap", "app", "apply"};
    trie root = create_trie_node();
    for (int i = 0; i < str.size(); ++i)
    {
        trie_insert(root, str[i]);
    }
    cout << trie_search_count(root, "ca") << endl;
    return 0;
}

4、字符串匹配之AC自动机

#include<iostream>
#include<string.h>
#include<malloc.h>
#include <queue>
using namespace std;

typedef struct node{
    struct node *next[26];  //接收的态
    struct node *par;   //父亲节点
    struct node *fail;  //失败节点
    char inputchar;
    int patterTag;    //是否为可接收态
    int patterNo;   //接收态对应的可接受模式
}*Tree,TreeNode;
char pattern[50][50];

/**
申请新的节点,并进行初始化
*/
TreeNode *getNewNode()
{
    int i;
    TreeNode* tnode=(TreeNode*)malloc(sizeof(TreeNode));
    tnode->fail=NULL;
    tnode->par=NULL;
    tnode->patterTag=0;
    for(i=0;i<26;i++)
        tnode->next[i]=NULL;
    return tnode;
}

/**
将Trie树中,root节点的分支节点,放入队列
*/
int  nodeToQueue(Tree root,queue<Tree> &myqueue)
{
    int i;
    for (i = 0; i < 26; i++)
    {
        if (root->next[i]!=NULL)
            myqueue.push(root->next[i]);
    }
    return 0;
}

/**
建立trie树
*/
Tree buildingTree(int n)
{
    int i,j;
    Tree root=getNewNode();
    Tree tmp1=NULL,tmp2=NULL;
    for(i=0;i<n;i++)
    {
        tmp1=root;
        for(j=0;j<strlen(pattern[i]);j++)   ///对每个模式进行处理
        {
            if(tmp1->next[pattern[i][j]-'a']==NULL) ///是否已经有分支,Trie共用节点
            {
                tmp2=getNewNode();
                tmp2->inputchar=pattern[i][j];
                tmp2->par=tmp1;
                tmp1->next[pattern[i][j]-'a']=tmp2;
                tmp1=tmp2;
            }
            else
                tmp1=tmp1->next[pattern[i][j]-'a'];
        }
        tmp1->patterTag=1;
        tmp1->patterNo=i;
    }
    return root;
}

/**
建立失败指针
*/
int buildingFailPath(Tree root)
{
    int i;
    char inputchar;
    queue<Tree> myqueue;
    root->fail=root;
    for(i=0;i<26;i++)   ///对root下面的第二层进行特殊处理
    {
        if (root->next[i]!=NULL)
        {
            nodeToQueue(root->next[i],myqueue);
            root->next[i]->fail=root;
        }
    }

    Tree tmp=NULL,par=NULL;
    while(!myqueue.empty())
    {
        tmp=myqueue.front();
        myqueue.pop();
        nodeToQueue(tmp,myqueue);

        inputchar=tmp->inputchar;
        par=tmp->par;

        while(true)
        {
            if(par->fail->next[inputchar-'a']!=NULL)
            {
                tmp->fail=par->fail->next[inputchar-'a'];
                break;
            }
            else
            {
                if(par->fail==root)
                {
                    tmp->fail=root;
                    break;
                }
                else
                    par=par->fail->par;
            }
        }
    }
    return 0;
}

/**
进行多模式搜索,即搜寻AC自动机
*/
int searchAC(Tree root,char* str,int len)
{
    TreeNode *tmp=root;
    int i=0;
    while(i < len)
    {
        int pos=str[i]-'a';
        if (tmp->next[pos]!=NULL)
        {
            tmp=tmp->next[pos];
            if(tmp->patterTag==1)    ///如果为接收态
            {
                cout<<i-strlen(pattern[tmp->patterNo])+1<<'\t'<<tmp->patterNo<<'\t'<<pattern[tmp->patterNo]<<endl;
            }
            i++;
        }
        else
        {
            if(tmp==root)
                i++;
            else
            {
                tmp=tmp->fail;
                if(tmp->patterTag==1)    //如果为接收态
                    cout<<i-strlen(pattern[tmp->patterNo])+1<<'\t'<<tmp->patterNo<<'\t'<<pattern[tmp->patterNo]<<endl;
            }
        }
    }
    while(tmp!=root)
    {
        tmp=tmp->fail;
        if(tmp->patterTag==1)
            cout<<i-strlen(pattern[tmp->patterNo])+1<<'\t'<<tmp->patterNo<<'\t'<<pattern[tmp->patterNo]<<endl;
    }
    return 0;
}

/**
释放内存,DFS
*/
int destory(Tree tree)
{
    if(tree==NULL)
        return 0;
    queue<Tree> myqueue;
    TreeNode *tmp=NULL;

    myqueue.push(tree);
    tree=NULL;
    while(!myqueue.empty())
    {
        tmp=myqueue.front();
        myqueue.pop();

        for (int i = 0; i < 26; i++)
        {
            if(tmp->next[i]!=NULL)
                myqueue.push(tmp->next[i]);
        }
        free(tmp);
    }
    return 0;
}

int main()
{
    char a[50];
    scanf("%s", a); //目标串 
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++i)
    	scanf("%s", pattern[i]); // 模式串 
    Tree root=buildingTree(); ///建立Trie树
    buildingFailPath(root); ///添加失败转移
    cout<<"待匹配字符串:"<<a<<endl;
    cout<<"模式"<<pattern[0]<<" "<<pattern[1]<<" "<<pattern[2]<<" "<<pattern[3]<<" "<<endl<<endl;
    cout<<"匹配结果如下:"<<endl<<"位置\t"<<"编号\t"<<"模式"<<endl;
    searchAC(root,a,strlen(a)); ///搜索
    destory(root);  ///释放动态申请内存
    return 0;
}

5、最长回文子串之Manacher

/*
manacher算法(民间称马拉车算法)是用来找字符串中的最长回文子串的。
*/
#include <iostream>
#include <string>
#include <vector>
#include <tuple>
#include <algorithm>
using namespace std;

string getstr(string &s)
{
	int k = 0;
	const int len = s.size();
	string str;
	// 输入字符串的长度小于100
	str.resize(201);
	str[k++] = '$'; //防止数组越界
	for (int i = 0; i < len; ++i)
	{
		str[k++] = '#';//添加一些字符,使得字符串的长度总是奇数
		str[k++] = s[i];
	}
	str[k++] = '#';
	//删除多余字符
	str.erase(remove(str.begin(), str.end(), '\0'), str.end());
	return str;
}

tuple<int, string> Manacher(string &s)
{
	string str_new = getstr(s);//获取新的字符串
	int len = str_new.size();
	vector<int> p;
	p.resize(len);
	int max_len = -1;  //设置初始的最长回文长度
	int id = 0;
	int mx = 0;
	int resLen = 0, resCenter = 0;
	for (int i = 1; i < len; ++i)
	{
		if (i < mx)
		/*
		*1、i关于中心 id 对称的点 j 的已知子串长度(左边对称点 j=2id-i 的最长回文子串长度已知):
		*
		*因为 i 和 j 是关于 id 的对称点,而 id 子串(以 id 为中心的最长回文子串,下同)
		*肯定是左右对称且包含了 i 子串(前提),那么 i 子串必定与 j 子串对称,所以可以直接
		*继承已经计算过的 j 的最长子串长度 P[j] ,即 P[i]=P[j]=P[2id-i]
		*
		*2、右边界到 i 点的距离(无论超过边界还是正好处于边界,都取最小值 mx-i ):
		*
		* 在这种情况下, i 子串并不完全包含在 id 子串中,但是容易看出的是, i子串至少会有 
		* mx-i 的长度(正好达到边界),即 P[i]>=mx-i
		*/
			p[i] = min(p[2 * id - i], mx - i);
		else
			p[i] = 1;
		//当前位置的最长回文子串超过mx了,所以向后遍历
		while (str_new[i - p[i]] == str_new[i + p[i]])
			p[i]++;
		// 我们每走一步 i,都要和 mx 比较,我们希望 mx 尽可能的远,这样才能更有机会执行 
		// if (i < mx)这句代码,从而提高效率
		if (mx < i + p[i])
		{
			mx = i + p[i];
			id = i;
		}
		// 记录最长回文子串
		if (resLen < p[i]) 
        {
            resLen = p[i];
            resCenter = i;
        }
		//最长回文子串就是p[i] - 1
		max_len = max(max_len, p[i] - 1);
	}
	return make_tuple(max_len, s.substr((resCenter - resLen) / 2, resLen - 1));
}

int main(int argc, char const *argv[])
{
	string str;
	getline(cin, str);
	int a;
    string b;
    // 返回最长回文子串的长度和子串
 	tie(a, b) = Manacher(str); 
	cout << a << " " << b << endl;
	system("pause");
	return 0;
}

6、回文子串的个数

/*
如果首尾两字符不相同,则:
该子序列的回文子序列个数 = 
去掉首字符的字符串的回文子序列个数 + 
去掉尾字符的字符串的回文子序列个数 - 
去掉首尾字符的字符串的回文子序列个数。 
如果首尾两字符相同,在上式的基础上,还要加上当首尾字符相等时
新增的回文子序列个数,即:
去掉首尾字符的字符串的回文子序列个数 + 1,
这里的1指的是仅仅由首尾两个字符所构成的回文子序列。
*/
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;

const int MOD = 100007;

int len, d[1010][1010];
string s;

void dp()
{
    for(int i = 1; i < len; i++)
    {
        for(int j = 0; j < (len - i); j++)
        {
            if(s[j] == s[j+i])
                d[j][i+j] = (d[j][i+j-1] + d[j+1][i+j] + 1) % MOD;
            else
                d[j][i+j] = (d[j][i+j-1] + d[j+1][i+j] - d[j+1][i+j-1] + MOD) % MOD;
        }
    }
}
int main()
{
    while (getline(cin, s))
    {
        memset(d, 0, sizeof(d));
        len = s.size();
        for(int i = 0; i < len; i++) 
			d[i][i] = 1;
        dp();
        printf("%d\n", d[0][len-1] % MOD);
    }
    return 0;
}

三、数据结构与常规算法

1、并查集

/*
并查集主要是用来快速判断两个点是否属于同一个集合,以及判断图的连通性。
*/
#include <iostream>  
using namespace std;  
  
int  pre[1050];  // pre[i] = j 表示i的"上级"是j 
bool t[1050];  
  
int Find(int x) // 查找x的"上级" 
{  
    int r = x;  
    while (r != pre[r])   //如果r的上级不是r自己
        r = pre[r];    // r就接着找他的上级,直到找到老大为止。
    int i = x, j;  
    while(i != r)  // 路径压缩
    {
        j = pre[i];  
        pre[i] = r;  
        i = j;  
    }
    return r;
}
  
void Union(int x, int y)
{  
    int fx = Find(x),fy = Find(y); // x的老大是fx,y的老大是fy 
    if(fx != fy)
    {
        pre[fy] = fx; // 让fx成为fy的上级 
    }
}

int main()
{  
    int N, i;
    for(i = 1; i <= N; ++i) //初始化
        pre[i] = i;
    /* 数据处理 */
    return 0; 
}

/*demo:
一共有4个点,2条路。下面两行告诉你,1、3之间有条路,4、3之间有条路。
那么整幅图就被分成了1-3-4和2两部分。只要再加一条路,把2和其他任意一个点连起来,
畅通工程就实现了,那么这个这组数据的输出结果就是1。好了,现在编程实现这个功能吧,
城镇有几百个,路有不知道多少条,而且可能有回路。 这可如何是好?
*/
#include <iostream>  
#include <vector>
using namespace std;
#pragma warning(disable:4996)
int  pre[10005];  // pre[i] = j表示i的"上级"是j 

int find(int x)
{
	int r = x;
	while (pre[r] != r)
		r = pre[r];
	int i = x;
	int j;
	while (i != r)
	{
		j = pre[i];
		pre[i] = r;
		i = j;
	}
	return r;
}

int main()
{
	int n, m, p1, p2, i, total, f1, f2;
	while (scanf("%d", &n))  //读入n,如果n为0,结束
	{   
		//刚开始的时候,有n个城镇,一条路都没有 //那么要修n-1条路才能把它们连起来
		total = n - 1;
		//每个点互相独立,自成一个集合,从1编号到n //所以每个点的上级都是自己
		for (i = 1;i <= n;i++) { pre[i] = i; }   //共有m条路
		scanf("%d", &m); 
		while (m--)
		{ 
			//下面这段代码,其实就是join函数,只是稍作改动以适应题目要求
			//每读入一条路,看它的端点p1,p2是否已经在一个连通分支里了
			scanf("%d %d", &p1, &p2);
			f1 = find(p1);
			f2 = find(p2);
			//如果是不连通的,那么把这两个分支连起来
			//分支的总数就减少了1,还需建的路也就减了1
			if (f1 != f2)
			{
				pre[f2] = f1;
				total--;
			}
			//如果两点已经连通了,那么这条路只是在图上增加了一个环 //对连通性没有任何影响,无视掉
		}
		//最后输出还要修的路条数
		printf("%d\n", total);
	}
	system("pause");
	return 0;
}

2、线段树

/*
*(1)维护区间和,使得区间求和在O(log(n))的复杂度下完成;
*(2)维护区间最小值,使得查询区间最小值在O(log(n))的复杂度下完成;
*(3)维护区间最大值;
*(4)更新序列中某个值,也能使得树维护区间的最大,最小,和区间和;
*(5)基于区间和的应用,最小值得应用,例如序列的动态更新查询。
*/
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <string>
#include <queue>
#include <vector>
#include <map>
#pragma warning(disable:4996)

using namespace std;
typedef long long LL;
const int maxn = 1e5;

struct Tree 
{
	int l, r;
	LL sum, lazy, maxnum, minnum;
}tree[maxn * 4 + 5];
LL A[maxn + 5];
int n, Q;

void Build(int L, int R, int x) 
{
	tree[x].l = L, tree[x].r = R, tree[x].lazy = 0;
	if (L == R) 
	{
		tree[x].sum = A[L];
		tree[x].maxnum = A[L];
		tree[x].minnum = A[L];
		return;
	}
	int mid = (L + R) / 2;
	Build(L, mid, x * 2);
	Build(mid + 1, R, x * 2 + 1);
	tree[x].sum = tree[x * 2].sum + tree[x * 2 + 1].sum;
	tree[x].maxnum = max(tree[x * 2].maxnum, tree[x * 2 + 1].maxnum);
	tree[x].minnum = min(tree[x * 2].minnum, tree[x * 2 + 1].minnum);
}

void PushDown(int x) 
{
	if (tree[x].lazy)
	{
		tree[x * 2].lazy += tree[x].lazy;
		tree[x * 2 + 1].lazy += tree[x].lazy;
		tree[x * 2].sum += tree[x].lazy * (LL)(tree[x * 2].r - tree[x * 2].l + 1);
		tree[x * 2 + 1].sum += tree[x].lazy * (LL)(tree[x * 2 + 1].r - tree[x * 2 + 1].l + 1);
		tree[x * 2].maxnum += tree[x].lazy * (LL)(tree[x * 2].r - tree[x * 2].l + 1);
		tree[x * 2 + 1].maxnum += tree[x].lazy * (LL)(tree[x * 2 + 1].r - tree[x * 2 + 1].l + 1);
		tree[x * 2].minnum += tree[x].lazy * (LL)(tree[x * 2].r - tree[x * 2].l + 1);
		tree[x * 2 + 1].minnum += tree[x].lazy * (LL)(tree[x * 2 + 1].r - tree[x * 2 + 1].l + 1);
		tree[x].lazy = 0LL;
	}
}

// 区间更新
void Update(int L, int R, LL add, int x) 
{
	if (L <= tree[x].l && tree[x].r <= R) 
	{
		tree[x].sum += add * (LL)(tree[x].r - tree[x].l + 1);
		tree[x].maxnum += add * (LL)(tree[x].r - tree[x].l + 1);
		tree[x].minnum += add * (LL)(tree[x].r - tree[x].l + 1);
		tree[x].lazy += (LL)add;
		return;
	}
	PushDown(x);
	int mid = (tree[x].l + tree[x].r) / 2;
	if (L <= mid)Update(L, R, add, x * 2);
	if (R > mid)Update(L, R, add, x * 2 + 1);
	tree[x].sum = tree[x * 2].sum + tree[x * 2 + 1].sum;
	tree[x].maxnum = max(tree[x * 2].maxnum, tree[x * 2 + 1].maxnum);
	tree[x].minnum = min(tree[x * 2].minnum, tree[x * 2 + 1].minnum);
}

// 查询区间和
LL QuerySum(int L, int R, int x)
{
	if (L <= tree[x].l && tree[x].r <= R) return tree[x].sum;
	PushDown(x);
	int mid = (tree[x].l + tree[x].r) / 2;
	LL res = 0LL;
	if (L <= mid) res += QuerySum(L, R, x * 2);
	if (R > mid) res += QuerySum(L, R, x * 2 + 1);
	return res;
}

// 查询最大值
LL QueryMax(int L, int R, int x) 
{
	if (L <= tree[x].l && tree[x].r <= R) return tree[x].maxnum;
	PushDown(x);
	int mid = (tree[x].l + tree[x].r) / 2;
	LL res = 0LL;
	if (L <= mid) res = max(res, QueryMax(L, R, x * 2));
	if (R > mid) res = max(res, QueryMax(L, R, x * 2 + 1));
	return res;
}

// 查询最小值
LL QueryMin(int L, int R, int x)
{
	if (L <= tree[x].l && tree[x].r <= R) return tree[x].minnum;
	PushDown(x);
	int mid = (tree[x].l + tree[x].r) / 2;
	LL res = 999999LL;
	if (L <= mid) res = min(res, QueryMin(L, R, x * 2));
	if (R > mid) res = min(res, QueryMin(L, R, x * 2 + 1));
	return res;
}

int main() 
{
	scanf("%d%d", &n, &Q);
	for (int i = 1; i <= n; i++)scanf("%lld", &A[i]);
	Build(1, n, 1);
	for (int i = 1; i <= Q; i++) 
	{
		char c; int l, r;
		scanf(" %c%d%d", &c, &l, &r);
		if (c == 'Q') // 查询操作
			printf("%lld %lld %lld\n", QuerySum(l, r, 1), QueryMax(l, r, 1), QueryMin(l, r, 1));
		else  // 更新操作
		{
			LL b; scanf("%lld", &b);
			Update(l, r, b, 1);
		}
	}
	system("pause");
	return 0;
}

3、树状数组

/*
主要用来求区间和,即:有一个数组a,下标从0到n-1,现在给你w次修改,q次查询,修改的话是修改数组中某一个元素的值;查询的话是查询数组中任意一个区间的和。
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <vector>
#pragma warning(disable:4996)
using namespace std;
int c[100005],a[100005], n;

// 获取最低位1的位置
int lowbit(int i)
{
	return i & (-i);
	/*
	-i 代表i的负数 计算机中负数使用对应的正数的补码来表示
	例如 : i=6(0110) 此时 k = 1
	-i=-6=(1001+1)=(1010)
	i&(-i)=(0010)=2=2^1
	C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i];
	C[i]=A[i-lowbit(i)+1]+A[i-lowbit(i)+2]+......A[i];
	*/
}

// 查询
int query(int i)//求区间[1,i]所有元素的和
{
	int res = 0;
	while (i > 0) 
	{
		res += c[i];//从右往左区间求和
		i -= lowbit(i);
	}
	return res;
}

// 单点更新
void update(int i, int val)//更新单节点的值
{
	while (i <= n) 
	{
		c[i] += val;
		i += lowbit(i);//由叶子节点向上更新a数组
	}
	//可以发现 更新过程是查询过程的逆过程
	//由叶子结点向上更新C[]数组
}

int main()
{
	scanf("%d", &n); // 输入n个数
	for (int i = 1; i <= n; i++) 
	{
		scanf("%d", &a[i]);
	}
	for (int i = 1; i <= n; i++)
	{
		int tmp = lowbit(i);
		for (int k = i - tmp + 1; k <= i; ++k)
			c[i] += a[k]; // 获取c数组
	}
	int m;
	for (scanf("%d", &m); m--; ) // 有m个操作
	{
		char l;
		scanf("%c", &l);  
		if (l == 'Q') // 是查询操作
		{
			int i, j;
			scanf("%d %d", &i, &j);
			cout << query(j) - query(i-1) << endl; // 查询区间和
		} 
		if (l == 'U') // 是更新操作
		{
			int i, val;
			scanf("%d %d", &i, &val);
			update(i, val); // 更新操作,单点增加一个数
		}
	}
	system("pause");
	return 0;
}

4、最长上升子序列之LIS

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 10001
using namespace std;
 
int f[N];
int main()
{
    int n;
    while(scanf("%d",&n) != EOF)
    {
        int c=0;
        for(int i = 1; i <= n; ++i)
        {
            int t;
            scanf("%d", &t);
            if(i == 1) 
            	f[++c] = t;
            else
            {
                if(t > f[c]) 
                	f[++c] = t;
                else
                {
                    int pos = lower_bound(f+1, f+c, t) - f;//二分找到数组中比t大的第一个元素的的地址。
                    f[pos] = t;
                }
            }
        }
        printf("%d\n",c);
    }
    return 0;
}

5、最长公共子序列之LCS

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int c[5001][5001];//函数内不能申请太大的数组,所以放在全局存储区
int lcs(const string &str1, const string &str2)
{
	int s1 = str1.size();
	int s2 = str2.size();
	//初始状态
	for (int i = 0; i <= s1; ++i)
		c[i][0] = 0;
	for (int j = 0; j <= s1; ++j)
		c[0][j] = 0;
	//状态转移方程
	for (int i = 1; i <= s1; ++i)
	{
		for (int j = 1; j <= s2; ++j)
		{
			if (str1[i - 1] == str2[j - 1])
			{
				c[i][j] = c[i - 1][j - 1] + 1;
			}
			else
			{
				c[i][j] = max(c[i - 1][j], c[i][j - 1]);
			}
		}
	}
	return c[s1][s2];
}

int main(int argc, char const *argv[])
{
	string s1, s2;
	getline(cin, s1);
	getline(cin, s2);
	cout << lcs(s1, s2);
	return 0;
}

6、最近公共祖先之LCA

/*
LCA问题(Least Common Ancestors,最近公共祖先问题),是指给定一棵有根树T,给出若干个查询LCA(u, v)
(通常查询数量较大),每次求树T中两个顶点u和v的最近公共祖先,即找一个节点,同时是u和v的祖先,
并且深度尽可能大(尽可能远离树根)。

Tarjan算法是离线算法,基于DFS(深度优先搜索)和并查集:

1、任选一个点为根节点,从根节点开始。
2、遍历该点u所有子节点v,并标记这些子节点v已被访问过。
3、若是v还有子节点,返回2,否则下一步。
4、合并v到u上。
5、寻找与当前点u有询问关系的点v。
6、若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。

*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <vector>
#pragma warning(disable:4996)
using namespace std;
const int MAX = 1000;
int indegree[MAX]; // 记录每个节点的入度
int f[MAX]; // 记录每个节点的上级
int vis[MAX]; // 记录节点是否被访问
int frequency[MAX]; // 记录下某个节点作为LCA出现的次数
vector<int> adj[MAX]; // 记录边的信息
vector<int> que[MAX]; // 查询矩阵
vector<vector<int>> res; // 存储结果

// 初始化
void init(int n)
{
	memset(frequency, 0, sizeof(frequency));
	memset(vis, 0, sizeof(vis));
	memset(indegree, 0, sizeof(indegree));
	for (int i = 1; i <= n; ++i)
	{
		adj[i].clear();
		que[i].clear();
		f[i] = i; // 并查集初始化
	}
}

// 并查集的查询操作
int find(int k)
{
	int r = k;
	while (f[r] != r)
		r = f[r];
	int i = k, j;
	while (f[i] != r)
	{
		j = f[i];
		f[i] = r;
		i = j;
	}
	return r;
}

// Tarjan算法求解LCA
void dfs(int u)
{
	int len = adj[u].size();
	for (int j = 0; j < len; ++j)
	{
		int v = adj[u][j];
		dfs(v); // 访问子节点
		f[v] = u; // 让父节点成为子节点的上级(合并操作)
	}
	vis[u] = 1; // 标记为已访问
	len = que[u].size(); // 询问所有和u有询问关系的e(可能会有多个查询)
	for (int j = 0; j < len; ++j)
	{
		int e = que[u][j];
		if (vis[e]) // 如果e被访问过了
		{
			int ans = find(e); // 则u,e的最近公共祖先为find(e)
			frequency[ans]++; // 记录下该节点作为LCA的次数
			res.push_back({ u, e, ans }); // 记录LCA结果
		}
	}
}

int main()
{
	int n, i, t, m, a, b;
	while (scanf("%d %d", &n, &m) != EOF) // 输入n个节点,m条边
	{
		init(n);
		for (i = 0; i < m; ++i)
		{
			scanf("%d %d", &a, &b);
			indegree[b]++;
			adj[a].push_back(b);
		}
		scanf("%d", &t); // 有t个查询, 记录查询关系
		while (t--)
		{
			scanf("%d %d", &a, &b);
			que[a].push_back(b);
			que[b].push_back(a);
		}
		for (i = 1; i <= n; ++i)
		{
			if (indegree[i] == 0) // 从入度为0的节点开始搜索
			{
				dfs(i);
				break;
			}
		}
		for (i = 1; i <= n; ++i)
		{
			if (frequency[i] > 0)
				printf("%d : %d\n", i, frequency[i]);
		}
		for (auto r : res)
			cout << r[0] << " " << r[1] << " : " << r[2] << endl;
 	}
	return 0;
}

// 完全二叉树LCA:
#include <bits/stdc++.h>
using namespace std;
int main()
{
    int x, y;
    while (cin >> x >> y)
    {
        vector<int> vecx;
        set<int> sety;
        while (x)
        {
            vecx.push_back(x);
            x /= 2;
        }
        while (y)
        {
            sety.insert(y);
            y /= 2;
        }
        for (auto v : vecx)
        {
            if (sety.find(v) != sety.end())
            {
                cout << v << endl;
                break;
            }
        }
    }
    return 0;
}

7、背包问题及动态规划详解

#include <iostream>
#include <cstdio>
#include <algorithm>
const int MAXN = 101;
const int SIZE = 50001;

int dp[SIZE];
int volume[MAXN], value[MAXN], c[MAXN];
int n, v;           //  总物品数,背包容量

//  01背包
void ZeroOnepark(int val, int vol)
{
    for (int j = v ; j >= vol; j--)
    {
        dp[j] = max(dp[j], dp[j - vol] + val);
    }
}

//  完全背包
void Completepark(int val, int vol)
{
    for (int j = vol; j <= v; j++)
    {
        dp[j] = max(dp[j], dp[j - vol] + val);
    }
}

//  多重背包
void Multiplepark(int val, int vol, int amount)
{
    if (vol * amount >= v)
    {
        Completepark(val, vol);
    }
    else
    {
        int k = 1;
        while (k < amount)
        {
            ZeroOnepark(k * val, k * vol);
            amount -= k;
            k <<= 1;
        }
        if (amount > 0)
        {
            ZeroOnepark(amount * val, amount * vol);
        }
    }
}

int main()
{
    while (cin >> n >> v)
    {
        for (int i = 1 ; i <= n ; i++)
        {
            cin >> volume[i] >> value[i] >> c[i];      //   费用,价值,数量
        }
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; i++)
        {
            Multiplepark(value[i], volume[i], c[i]);
        }
        cout << dp[v] << endl;
    }
    return 0;
}

四、图论

1、图的广搜

/*
题目描述:
有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的
(上下左右四个方向)黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。
输入描述:
输入包含多组数据。

每组数据第一行是两个整数 m 和 n(1≤m, n≤20)。紧接着 m 行,每行包括 n 个字符。
每个字符表示一块瓷砖的颜色,规则如下:

1. “.”:黑色的瓷砖;
2. “#”:白色的瓷砖;
3. “@”:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
输出描述:
对应每组数据,输出总共能够到达多少块黑色的瓷砖。

示例1
输入

9 6
....#.
.....#
......
......
......
......
......
#@...#
.#..#.

输出
45
*/
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <algorithm>
using namespace std;

int main(int argc, char const *argv[])
{
	int m, n;
	while (cin >> m >> n)
	{	
		cin.get();
		vector<vector<char>> grid;
		for (int i = 0; i < m; ++i)
		{
			vector<char> vec;
			char x;
			for (int j = 0; j < n; ++j)
			{
				cin >> x;
				vec.push_back(x);
			}
			grid.push_back(vec);
		}
		// 标记某坐标是否被访问
		vector<vector<int>> visited;
		visited.resize(m, vector<int>(n, 0));
		// 设置方向坐标
		int dir[4][2] = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };
		// 用队列记录下坐标
		queue<pair<int, int>> que;
		int cnt = 0;
		for (int i = 0; i < m; ++i)
		{
			for (int j = 0; j < n; ++j)
			{
				if (grid[i][j] == '@')
				{
					que.push(make_pair(i, j));
					visited[i][j] = 1;
					cnt++;
				}
				while (!que.empty())
				{
					// 取队头元素
					auto tmp = que.front();
					int x = tmp.first;
					int y = tmp.second;
					for (auto d : dir)
					{
						int nx = x + d[0];
						int ny = y + d[1];
						// 判断是否到边界
						if (nx >= 0 && nx < m && ny >= 0 && ny < n
							&& grid[nx][ny] == '.' && !visited[nx][ny])
						{
							cnt++; // 记录下访问过了多少个坐标
							que.push(make_pair(nx, ny));
							visited[nx][ny] = 1;
						}
					}
					que.pop();
				}
			}
		}
		cout << cnt << endl;
	}
	return 0;
}

2、图的深搜

#include <iostream>
#include <vector>
#include <stack>
#include <queue>
using namespace std;

bool visited1[100] = {false};

void dfs(int current, int m, vector<vector<int>> data)
{
	visited1[current] = true;
	cout << current << " ";
	for (int i = 0; i < m; ++i)
		if (data[i][0] == current && visited1[data[i][1]] == false)
			dfs(data[i][1], m, data);
}

int main(int argc, char const *argv[])
{
	int m, n;
	cin >> m >> n;
	vector<vector<int>> data;
	for (int i = 0; i < m; ++i)
	{
		vector<int> vec;
		int x;
		for (int j = 0; j < n; ++j)
		{
			cin >> x;
			vec.push_back(x);
		}
		data.push_back(vec);
	}
	dfs(1, m, data);
	cout << endl;
	system("pause");
	return 0;
}

3、最小生成树之克鲁斯卡尔

#include <bits/stdc++.h>
using namespace std;

struct edge//纪录边的信息
{
	int start;
	int end;
	int weight;
};

//克鲁斯卡尔算法
void Kruskal(int data[10][3])
{
	int data2[10][2] = {0};
	multiset<int> st1;
	vector<edge> vec;//存储所有边的信息
	edge temp;
	for (int j = 0; j < 10; ++j)//初始化所有边的信息
	{
		temp.start = data[j][0];
		temp.end = data[j][1];
		temp.weight = data[j][2];
		vec.push_back(temp);
	}
	//按权值排序
	sort(vec.begin(), vec.end(), [](const edge &e1, const edge &e2) -> bool
	{ 
		return e1.weight < e2.weight ? true : false ;
	});
	for (int j = 0; j < 10; ++j)
	{
		//st1存放的是已经加入的边用于判断是否会形成环(如果起点和终点都在st1中则加入该边会形成环)
		if (st1.find(vec[j].start) != st1.end() && st1.find(vec[j].end) != st1.end())
			continue;
		else
		{
			data2[j][0] = vec[j].start;//data2存放的是组成最小生成树的边
			data2[j][1] = vec[j].end;
			st1.insert(vec[j].start);
			st1.insert(vec[j].end);
		}
	}
	for (int j = 0; j < 10; ++j)
	{
		if (data2[j][0] != 0)
			cout << data2[j][0] << " " << data2[j][1] << endl;
	}
}

int main(int argc, char const *argv[])
{
	int data[10][3] = //所有边的信息
	{
		{1,2,6},{1,6,12},
        {1,5,10},{2,3,3},
        {2,4,5},{2,6,8},
        {3,4,7},{4,6,11},
		{4,5,9},{5,6,16}
	};
	Kruskal(data);
	return 0;
}

4、最短路之迪杰斯特拉和弗洛伊德

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int inf = 999999;//不连通的点之间的距离设为无穷大
long long int e[10000][10000];
int dis[10000];//最短距离数组
int book[10000];//记录下哪些点被选中

//计算单点到全部顶点的距离
int Dijkstra(int &n, int &m, int &s, vector<vector<int>> &data, int &t)
{
	//初始化任意两点之间的距离数组
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= n; ++j)
		{
			if (i == j)
				e[i][j] = 0;
			else
				e[i][j] = inf;
		}
	}
	//把权值加入到任意两点之间的距离数组中
	for (int i = 1; i <= m; ++i)
	{
		e[data[i - 1][0]][data[i - 1][1]] = data[i - 1][2];
	}
	for (int i = 1; i <= n; ++i)
	{
		if (i != s)
		{
			dis[i] = e[s][i];//记录源点到其余所有点的最短路径
			book[i] = 0;//记录哪些点被选取了
		}
	}
	int u, min;
	for (int i = 1; i <= n - 1; ++i)
	{
		min = inf;
		for (int j = 1; j <= n; ++j)
		{
			if (book[j] == 0 && dis[j] < min)//找到源点离还没有被选取的点中的最近顶点
			{
				min = dis[j];
				u = j;//记录下最近顶点的位置
			}
		}
		book[u] = 1;
		/*
		*例如存在一条从u到v的边,那么可以通过将边u->v添加到尾部来拓展一条从源点到v的路径,
		*这条路径的长度是dis[u]+e[u][v]。如果这个值比目前已知的dis[v]的值要小,
		*我们可以用新值来替代当前dis[v]中的值。
		*/
		for (int v = 1; v <= n; ++v)
		{
			if (e[u][v] < inf)
			{
				if (dis[v] > dis[u] + e[u][v])
					dis[v] = dis[u] + e[u][v];//松弛
			}
		}
	}
	return dis[t];
}

//计算两两顶点之间的最短路径
void Floyd(int &n, int &m, vector<vector<int>> &data)
{
	//初始化任意两点之间的距离数组
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= n; ++j)
		{
			if (i == j)
				e[i][j] = 0;
			else
				e[i][j] = inf;
		}
	}
	//把权值加入到任意两点之间的距离数组中
	for (int i = 1; i <= m; ++i)
	{
		e[data[i - 1][0]][data[i - 1][1]] = data[i - 1][2];
	}
	/*
	*最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点
	*进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。
	*/
	for (int k = 1; k <= n; ++k)
		for (int i = 1; i <= n; ++i)
			for (int j = 1; j <= n; ++j)
				if (e[i][j] > e[i][k] + e[k][j])
					e[i][j] = e[i][k] + e[k][j];
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= n; ++j)
			cout << e[i][j] << " ";
		cout << endl;
	}
}

int main(int argc, char const *argv[])
{
	int n, m, s, t;
	cin >> n >> m >> s >> t;//输入顶点数和边数,以及起止位置
	vector<vector<int>> Path_Cost;
	for (int i = 0; i < m; ++i)
	{
		vector<int> vec;
		int x;
		for (int j = 0; j <= 2; ++j)
		{
			cin >> x;
			vec.push_back(x);
		}
		Path_Cost.push_back(vec);
	}
	cout << Dijkstra(n, m, s, Path_Cost, t) << endl;
	Floyd(n, m, Path_Cost);
	system("pause");
	return 0;
}

5、二分图判断之染色法

#include <queue>  
#include <cstring>  
#include <iostream>  
using namespace std;

const int N = 510;
int color[N], graph[N][N];

//0为白色,1为黑色   
bool bfs(int s, int n) 
{
	queue<int> q;
	q.push(s);
	color[s] = 1;
	while (!q.empty()) 
	{
		int from = q.front();
		q.pop();
		for (int i = 1; i <= n; i++) 
		{
			if (graph[from][i] && color[i] == -1) 
			{
				q.push(i);
				color[i] = !color[from];//染成不同的颜色   
			}
			if (graph[from][i] && color[from] == color[i])//颜色有相同,则不是二分图   
				return false;
		}
	}
	return true;
}

int main() 
{
	int n, m, a, b, i;
	memset(color, -1, sizeof(color));
	cin >> n >> m;
	for (i = 0; i < m; i++) 
	{
		cin >> a >> b;
		graph[a][b] = graph[b][a] = 1;
	}
	bool flag = false;
	for (i = 1; i <= n; i++)
	{
		if (color[i] == -1 && !bfs(i, n))
		{
			//遍历各个连通分支   
			flag = true;
			break;
		}
	}
	if (flag)
		cout << "NO" << endl;
	else
		cout << "YES" << endl;
	return 0;
}

6、二分图的最大匹配算法

/*
一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配,
其中任意两条边都没有公共顶点。
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#pragma warning(disable:4996)
using namespace std;
/*
*  初始化:g[][]两边顶点的划分情况
*  建立g[i][j]表示i->j的有向边就可以了,是左边向右边的匹配
*  g没有边相连则初始化为0
*  uN是匹配左边的顶点数,vN是匹配右边的顶点数
*  调用:res=hungary();输出最大匹配数
*  优点:适用于稠密图,DFS找增广路,实现简洁易于理解
*  时间复杂度:O(VE)
*/
// 顶点编号从1开始
const int MAXN = 510;
int uN, vN;         //  u,v的数目,使用前面必须赋值
int g[MAXN][MAXN];  //  邻接矩阵
int linker[MAXN];
bool used[MAXN];
bool dfs(int u)
{
	for (int v = 1; v <= vN; v++)
	{
		if (g[u][v] && !used[v])
		{
			used[v] = true;
			if (linker[v] == -1 || dfs(linker[v]))
			{
				linker[v] = u;
				return true;
			}
		}
	}
	return false;
}

int hungary()
{
	int res = 0;
	memset(linker, -1, sizeof(linker));
	for (int u = 1; u <= uN; u++)
	{
		memset(used, false, sizeof(used));
		if (dfs(u))
		{
			res++;
		}
	}
	return res;
}

int main()
{
	int u, v, m; // m表示输入的边数 
	scanf("%d %d %d", &uN, &vN, &m);
	for (int i = 1; i <= m; ++i)
	{
		scanf("%d %d", &u, &v);
		g[u][v] = 1;
	}
	printf("%d", hungary());
	return 0;
}

/*demo:
地鼠(产自北美的一种地鼠)刚刚逃过了犬的危险,有面对一个新的天敌。
有n个地鼠,有m个地洞,地鼠和地洞都有坐标(x,y)。
现在 一只鹰要来抓地鼠了。如果地鼠在s秒内无法到达地洞就会被吃掉。
一个地洞只能容一只地鼠。地鼠跑的速度为v,
下来地鼠家族正在设计一个逃跑策略使得被吃掉的地鼠最少。
【输入格式】
多组数据。
每组数据第一行为四个整数(均小于100), n, m, s,  v. 
下来n行表示n个地鼠的坐标。
再下来m行表示m个地洞的坐标。
【输出格式】
输出被吃掉的地鼠的数目。
【样例输入】
2 2 5 10
1.0 1.0
2.0 2.0
100.0 100.0
20.0 20.0
【样例输出】
1
*/

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#pragma warning(disable:4996)
using namespace std;

const int MAXN = 510;
int uN, vN;         
int g[MAXN][MAXN]; 
int linker[MAXN];
float input1[MAXN][MAXN];
float input2[MAXN][MAXN];
bool used[MAXN];
bool dfs(int u)
{
	for (int v = 1; v <= vN; v++)
	{
		if (g[u][v] && !used[v])
		{
			used[v] = true;
			if (linker[v] == -1 || dfs(linker[v]))
			{
				linker[v] = u;
				return true;
			}
		}
	}
	return false;
}

int hungary()
{
	int res = 0;
	memset(linker, -1, sizeof(linker));
	for (int u = 1; u <= uN; u++)
	{
		memset(used, false, sizeof(used));
		if (dfs(u))
		{
			res++;
		}
	}
	return res;
}

int main()
{
	int s, v; 
	scanf("%d %d %d %d", &uN, &vN, &s, &v);
	for (int i = 1; i <= uN; ++i)
	{	
		float a, b;
		scanf("%f %f", &a, &b);
		input1[i][0] = a;
		input1[i][1] = b;
	}
	for (int i = 1; i <= vN; ++i)
	{
		float a, b;
		scanf("%f %f", &a, &b);
		input2[i][0] = a;
		input2[i][1] = b;
	}
	for (int i = 1; i <= uN; ++i)
	{
		for (int j = 1; j <= vN; ++j)
		{
			// 判断老鼠能到达的有哪些洞,能到达则连一条边
			if (pow((input1[i][0] - input2[i][0]), 2) + 
				pow((input1[i][1] - input2[i][1]), 2) < pow(s * v, 2))
			{
				g[i][j] = 1;
			}
		}
	}
	printf("%d", hungary());
	system("pause");
	return 0;
}

五、计算几何

1、向量的基本用法

struct node {  
    double x; // 横坐标  
    double y; // 纵坐标  
};  

typedef node Vector;

Vector operator + (Vector A, Vector B) { return Vector(A.x + B.x, A.y + B.y); }  
Vector operator - (Point A, Point B) { return Vector(A.x - B.y, A.y - B.y); }  
Vector operator * (Vector A, double p) { return Vector(A.x*p, A.y*p); }  
Vector operator / (Vector A, double p) { return Vector(A.x / p, A.y*p); }  

double Dot(Vector A, Vector B) { return A.x*B.x + A.y*B.y; } // 向量点乘  
double Length(Vector A) { return sqrt(Dot(A, A)); }  // 向量模长  
double Angle(Vector A, Vector B) { return acos(Dot(A, B) / Length(A) / Length(B)); }  // 向量之间夹角  

double Cross(Vector A, Vector B) { // 叉积计算 公式  
    return A.x*B.y - A.y*B.x;  
}  

Vector Rotate(Vector A, double rad) // 向量旋转 公式  {  
    return Vector(A.x*cos(rad) - A.y*sin(rad), A.x*sin(rad) + A.y*cos(rad));  
}  

Point getLineIntersection(Point P, Vector v, Point Q, Vector w) { // 两直线交点t1 t2计算公式   
    Vector u = P - Q;   
    double t = Cross(w, u) / Cross(v, w);  // 求得是横坐标  
    return P + v*t;  // 返回一个点  
}  

2、求三角形外心

struct Ponit{  
    double x; // 横坐标  
    double y; // 纵坐标  
};  
Point circumcenter(const Point &a, const Point &b, 
				const Point &c) { //返回三角形的外心        
    Point ret;  
    double a1 = b.x - a.x, b1 = b.y - a.y, c1 = (a1*a1 + b1*b1) / 2;  
    double a2 = c.x - a.x, b2 = c.y - a.y, c2 = (a2*a2 + b2*b2) / 2;  
    double d = a1*b2 - a2*b1;  
    ret.x = a.x + (c1*b2 - c2*b1) / d;  
    ret.y = a.y + (a1*c2 - a2*c1) / d;  
    return ret;  
}  

3、判断线段相交

node P[35][105];     

double Cross_Prouct(node A,node B,node C) 
{     
	//  计算BA叉乘CA     
    return (B.x-A.x)*(C.y-A.y)-(B.y-A.y)*(C.x-A.x);      
}      
bool Intersect(node A,node B,node C,node D)  
{  
	//  通过叉乘判断线段是否相交;           
    if(min(A.x,B.x)<=max(C.x,D.x)&&    //  快速排斥实验;      
       min(C.x,D.x)<=max(A.x,B.x)&&      
       min(A.y,B.y)<=max(C.y,D.y)&&      
       min(C.y,D.y)<=max(A.y,B.y)&&      
       Cross_Prouct(A,B,C)*Cross_Prouct(A,B,D)<0&& //  跨立实验;      
       Cross_Prouct(C,D,A)*Cross_Prouct(C,D,B)<0) //  叉乘异号表示在两侧;      
       return true;      
    else return false;      
}    

4、凸包

/*
核心思想:
1、根据坐标某一维(y)排序,选择最下面的一个点;
2、根据极角对所有点排序;
3、将p0,p1,p2入栈,然后新的点和栈顶点作叉积,判断新的点是否在栈顶点的左边(叉积为负),
如果不是左转,则将栈顶点出栈,新的点压栈。
*/
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#define EPS 1e-8
#pragma warning(disable:4996)
using namespace std;
const int maxn = 500006;
struct point 
{
	double x, y;
};
point p[maxn], convex[maxn];
bool cmp(const point p1, const point p2)//比较函数,不用极角,用坐标(x, y); 
{
	return ((p1.y == p2.y && p1.x < p2.x) || p1.y < p2.y);//找最左下角的点为最小点
}
int sgn(double x)
{
	if (fabs(x) < EPS)
		return 0;
	return x < 0 ? -1 : 1;
}
double x_multi(point p1, point p2, point p3)
{
	return ((p3.x - p1.x) * (p2.y - p1.y) - (p2.x - p1.x) * (p3.y - p1.y));
}
double get_distance(point p1, point p2)
{
	return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
double dist2(const point p1, const point p2)//距离的平方
{
	return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y);
}
double Max(double a, double b)
{
	return a > b ? a : b;
}
//求凸包, 原来点的集合存在p中, 凸包中的点集合存在convex中, len为凸包中点的个数,n是原来点集的个数
void convex_hull(point *p, point *convex, int n, int &len)
{
	sort(p, p + n, cmp);
	int top = 1;
	convex[0] = p[0];
	convex[1] = p[1];
	//找出凸包的下半部分凸壳 
	for (int i = 2; i < n; i++)
	{
		while (top > 0 && sgn(x_multi(convex[top - 1], convex[top], p[i])) <= 0)//大于0为逆时针,小于0为顺时针
			top--;
		convex[++top] = p[i];
	}
	int tmp = top;
	//找出凸包的上半部分,因为我的比较函数是写的y优先的,所以上下部分,当然也可以以x优先排序,这时候就是左右部分了 
	for (int i = n - 2; i >= 0; i--)
	{
		while (top > tmp && sgn(x_multi(convex[top - 1], convex[top], p[i])) <= 0)//大于0为逆时针,小于0为顺时针
			top--;
		convex[++top] = p[i];//存放凸包中的点
	}
	len = top;
}
//旋转卡壳, 返回最远点对的距离的平方
double rotating_calipers(point *convex, int n)
{
	double ans = 0;
	int q = 1;
	convex[n] = convex[0];
	for (int p = 0; p < n; p++)
	{
		while (x_multi(convex[p], convex[p + 1], convex[q + 1]) > x_multi(convex[p], convex[p + 1], convex[q]))
			q = (q + 1) % n;
		ans = Max(ans, Max(dist2(convex[p], convex[q]), dist2(convex[p + 1], convex[q + 1])));//ans为距离的平方
	}
	return ans;
}

// 两点间距离
double dist(point &a, point &b)
{
	return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

int main()
{
	int n, len;
	while (~scanf("%d", &n))
	{
		for (int i = 0; i < n; i++)
			scanf("%lf %lf", &p[i].x, &p[i].y);
		convex_hull(p, convex, n, len);
		double dis = 0.0;
		for (int i = 0; i < len; i++)
		{
			printf("%.0f, %.0f\n", convex[i].x, convex[i].y);
			if (i != len - 1)
				dis += dist(convex[i], convex[i + 1]);
			else
				dis += dist(convex[i], convex[0]);
		}
		printf("周长:%.2f\n", dis);
		printf("直径:%.2f\n", sqrt(rotating_calipers(convex, len)));
	}
	return 0;
}

5、求多边形面积

node G[maxn];  
int n;  

double Cross(node a, node b) 
{ 
	// 叉积计算  
    return a.x*b.y - a.y*b.x;  
}  

int main()  
{  
    while (scanf("%d", &n) != EOF && n) 
    {  
        for (int i = 0; i < n; i++)   
            scanf("%lf %lf", &G[i].x, &G[i].y);  
        double sum = 0;  
        G[n].x = G[0].x;  
        G[n].y = G[0].y;  
        for (int i = 0; i < n; i++) 
        { 
                sum += Cross(G[i], G[i + 1]);  
        } 
        sum = sum / 2.0;  
        printf("%.1f\n", sum);  
    } 
    system("pause");  
    return 0;  
}
  • 6
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Python算法竞赛模板是指在Python编程语言中,为了提高代码效率和简洁性,在算法竞赛中常用的一种代码结构和技巧的总结和应用。 一般来说,Python算法竞赛模板包含以下内容: 1. 导入必要的模块和库:在算法竞赛中,常常需要使用到一些常用的模块和库,比如math、collections等。在模板中,首先需要导入这些模块和库,以便后续的代码编写和使用。 2. 读入输入和处理:算法竞赛通常会给出一些输入数据,比如数组、矩阵、图等。在模板中,需要根据题目要求进行输入读取和处理。这部分代码主要负责将输入数据保存在变量中,并进行预处理,以便后续的算法和逻辑处理。 3. 算法和逻辑处理:这部分是整个模板的核心部分,主要是根据题目要求设计算法和逻辑处理的代码。在算法竞赛中,常用的算法包括贪心、动态规划、深度优先搜索、广度优先搜索等。根据具体的题目要求,选择合适的算法进行实现和应用。 4. 输出结果:在算法竞赛中,常常需要输出计算结果。在模板中,需要编写输出代码,将计算得到的结果输出到标准输出或文件中。 5. 主函数和调用:为了能够方便地测试和调用代码,在模板中通常要定义一个主函数,并在主函数中调用前面编写的函数和代码。主函数通常用来读取输入、调用处理和计算的函数,并输出结果。 Python算法竞赛模板的好处在于能够提高代码的复用性和可维护性。通过事先总结和编写模板,可以减少在比赛过程中的代码重复和错误,提高编写效率和代码质量。同时,模板可以帮助选手更好地理解和应用常用的算法和数据结构,提升解题能力。 当然,Python算法竞赛模板只是一种常见的代码结构和技巧总结,具体的应用还需根据不同的比赛和题目要求进行调整和优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~青萍之末~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值