文章目录
- 一、经典问题
- 1、Miller-Rabin素数测试
- 2、快速幂
- 3、矩阵快速幂求斐波拉契数列
- 4、大数模拟
- 5、GCD和LCM
- 6、大组合数(卢卡斯定理)
- 7、约瑟夫环问题
- 8、博弈论问题
- 9、蒙特卡洛问题(撒点法)
- 二、字符串
- 1、最短编辑距离
- 2、字符串匹配之KMP
- 3、字符串匹配之字典树
- 4、字符串匹配之AC自动机
- 5、最长回文子串之Manacher
- 6、回文子串的个数
- 三、数据结构与常规算法
- 1、并查集
- 2、线段树
- 3、树状数组
- 4、最长上升子序列之LIS
- 5、最长公共子序列之LCS
- 6、最近公共祖先之LCA
- 7、背包问题及动态规划详解
- 四、图论
- 1、图的广搜
- 2、图的深搜
- 3、最小生成树之克鲁斯卡尔
- 4、最短路之迪杰斯特拉和弗洛伊德
- 5、二分图判断之染色法
- 6、二分图的最大匹配算法
- 五、计算几何
- 1、向量的基本用法
- 2、求三角形外心
- 3、判断线段相交
- 4、凸包
- 5、求多边形面积
一、经典问题
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;
}