算法基础课一刷

第一遍代码

基础算法

好用的技巧

c++11

-std=c++11

万能头文件

#include<bits/stdc++.h>

基础模板

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

//#defien int long long
#define x first 
#define y second
#define endl '\n'
#define rep(i,a,b) for(int i=(a);i<=b;i++)
#define lep(i,a,b) for(int i=(a);i>=b;i--)
#define mem(a,x) memset(a,x,sizeof a)
#define mep(a,x) memcpy(a,x,sizeof x)

typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
const int N=1e6+10;

int n,m,k;


signed main(){
	
	return 0;
}

关闭同步流

//关了同步流后只能用endl不能用'\n'了, 也要避免scanf和cin混用<cstdio>,<iostream> 
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);

输入输出

输入带空格的字符、字符串

#include<iostream>
#include<cstring>
using namespace std;
int main(){
	//带空格的字符输入 
	char name[20];
	char ch;
	string s;
	cout<<"输入字符:" ;
	ch=cin.get();
	cout<<ch<<'\n';
	fflush(stdin);
	
	cout<<"输入字符串:"; 
	cin.get(name,20);//'\n'还在缓冲区,建议用getline 
	cout<<name<<'\n';
	fflush(stdin);//清空缓冲区  while(getchar()!='\n');
	
	cout<<"输入字符串:"; 
	cin.getline(name,20);
	cout<<name<<'\n';
	fflush(stdin);
	
	cout<<"输入字符串:";
	getline(cin,s);
	cout<<s<<'\n';
	return 0;
}

在这里插入图片描述
gets(char *str)读取一行字符串,并将回车去除丢掉。
scanf(“%[^\n]”,str)相当于gets,只是用完要用getchar清空回车。

输出

在这里插入图片描述
在这里插入图片描述

好用的函数

在这里插入图片描述
在这里插入图片描述

常用变量

在这里插入图片描述

二分

数的范围

#include<iostream>
#include<algorithm>
using namespace std;
const int N=100;

int q[N];

int main(){
	int n,m;
	cin>>n>>m;
	for(int i=0;i<n;i++) cin>>q[i];
	
	while(m--){
		int x;
		cin>>x;
		//求第一个大于(等于)x的数下标 下标从0开始 
		int l=0,r=n-1;
		while(l<r){
			int mid=r+l>>1;//注意mid每次要更新,所有放循环内部
			if(q[mid]>=x) r=mid;
			else l=mid+1;
		}
		if(q[l]!=x) cout<<"-1 -1"<<endl;
		else{
			cout<<l<<' ';
			//求第最后一个小于(等于)x的数下标 
			int l=0,r=n-1;
			while(l<r){
				int mid=l+r+1>>1;
				if(x>=q[mid]) l=mid;
				else r=mid-1;
			}
			cout<<l<<endl;
		}
	}
	

	return 0;
}

数的三次方根

#include<iostream>

using namespace std;

int main(){
	double x;
	cin>>x;
	double l=-100,r=100;
	while(r-l>1e-8){//l和r是浮点数
		double mid=(l+r)/2;//每次都要更新中间值,所有注意要把mid放循环内部 
		if(mid*mid*mid>=x) r=mid;
		else l=mid;
	}
	cout<<l;
}

排序

快速排序

/*
	快速排序 
*/
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1000;
int n,a[N];

void qs(int a[],int l,int r){
	if(l>=r) return;
	int i=l-1,j=r+1,x=a[l+r>>1];
	while(i<j){
		do i++;while(a[i]<x);
		do j--;while(a[j]>x);
		if(i<j) swap(a[i],a[j]);
		
	}
	qs(a,l,j),qs(a,j+1,r);
}

int main(){
	cin>>n;
	for(int i=0;i<n;i++) cin>>a[i];
	qs(a,0,n-1);
	for(int i=0;i<n;i++) cout<<a[i]<<" ";
}

第k个数

#include <iostream>

using namespace std;

const int N = 100010;

int q[N];

int quick_sort(int q[], int l, int r, int k)
{
    if (l >= r) return q[l];

    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x); 
        if (i < j) swap(q[i], q[j]);
    }

    if (j - l + 1 >= k) return quick_sort(q, l, j, k);
    else return quick_sort(q, j + 1, r, k - (j - l + 1));
}

int main()
{
    int n, k;
    scanf("%d%d", &n, &k);

    for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);

    cout << quick_sort(q, 0, n - 1, k) << endl;

    return 0;
}

归并排序

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

const int N=1000;
int q[N],temp[N];

void s(int q[N],int l,int r){
	//递归终止条件
	if(l>=r) return;
	int mid=l+r>>1;
	//先循环,要先让左右两边有序 
	s(q,l,mid),s(q,mid+1,r);
	//然后让该部分总体有序 
	int i=l,j=mid+1,k=0;
	
	while(i<=mid&&j<=r){
		if(q[i]<q[j]) temp[k++]=q[i++];
		else temp[k++]=q[j++];
	}
	
	while(i<=mid) temp[k++]=q[i++];
	while(j<=r) temp[k++]=q[j++];
	//最后记得把排好的数组换回来 
	for(int i=l,j=0;i<=r;i++) q[i]=temp[j++]; 
	
}

int main(){
	
	int n;
	cin>>n;
	for(int i=0;i<n;i++) cin>>q[i];

	s(q,0,n-1);

	for(int i=0;i<n;i++) cout<<q[i]<<" ";

	return 0;
}

逆序对数量

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

const int N=1e6;
int n,q[N],temp[N];
long long res;//开数据要注意
void s(int q[],int l,int r){
	if(l>=r) return;
	
	int mid=l+r>>1,i=l,j=mid+1,k=0;
	s(q,l,mid),s(q,mid+1,r);
	while(i<=mid&&j<=r){
		if(q[i]<=q[j]) temp[k++]=q[i++];//注意这里有等号 
		else{
			res+=mid-i+1;//左边后面的也都会比q[j]大,mid-i+1是从i到mid的个数 
			temp[k++]=q[j++];
		}
	}
	while(i<=mid) temp[k++]=q[i++];
	while(j<=r) temp[k++]=q[j++];
	for(int i=l,j=0;i<=r;i++,j++) q[i]=temp[j];
}

int main(){
	cin>>n;
	for(int i=0;i<n;i++) cin>>q[i];
	s(q,0,n-1);

	cout<<res;
	return 0;
}

高精度

高精度加法

/*
	高精度加法
	1.先将两数用字符串输入,再转换成数字,然后逆序压入数组中
	2.执行加法步骤,注意要明确那个数位数比较多,t表示进位数,本位余数,进位除数
	3.逆序输出 
	当两数过大时,可用压位加法 
*/
#include<iostream>
#include<vector>
using namespace std;

vector<int> add(vector<int> a,vector<int> b){
	if(a.size()<b.size()) return add(b,a);
	int t=0;
	vector<int> c;
	for(int i=0;i<a.size()||t;i++){
		if(i<a.size()) t+=a[i];
		if(i<b.size()) t+=b[i];
		c.push_back(t%10);
		t/=10;
	}
	return c;
}

int main(){
	string a,b;
	vector<int> A,B;
	cin>>a>>b;
	for(int i=a.size()-1;i>=0;i--){
		A.push_back(a[i]-'0');
	}
	for(int i=b.size()-1;i>=0;i--)
		B.push_back(b[i]-'0');
	auto c=add(A,B);
	for(int i=c.size()-1;i>=0;i--) cout<<c[i];
	return 0;
}

压九位(数字比较大时)

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int base=1e9;

vector<int> add(vector<int> a,vector<int> b){
	if(a.size()<b.size()) return add(b,a);
	int t=0;
	vector<int> c;
	for(int i=0;i<a.size()||t;i++){
		if(i<a.size()) t+=a[i];
		if(i<b.size()) t+=b[i];
		c.push_back(t%base);
		t/=base;
	}
	return c;
}

int main(){
	string a,b;
	cin>>a>>b;
	vector<int> A,B;
	for(int i=a.size()-1,j=0,s=0,t=1;i>=0;i--){
		s+=(a[i]-'0')*t;
		j++,t*=10;
		if(j==9||i==0){
			A.push_back(s);
			j=0,s=0,t=1;
		}
	}
	for(int i=b.size()-1,j=0,s=0,t=1;i>=0;i--){
		s+=(b[i]-'0')*t;
		j++,t*=10;
		if(j==9||i==0){
			B.push_back(s);
			j=0,s=0,t=1;
		}
	}
	
	auto c=add(A,B);
	
	cout<<c.back();
	for(int i=c.size()-2;i>=0;i--) printf("%09d",c[i]);
	return 0;
} 

%9d表示占九个字符位置,前面补空格,%-9d表示后面补空格。%09d表示前面补0。

高精度减法

/*
	高精度减法 
	1.先将两数用字符串输入,再转换成数字,然后逆序压入数组中
	2.执行减法步骤,注意要明确那个数比较大cmp函数,t表示借位数0或1,本位正余数
	3.逆序输出 
*/
#include<iostream>
#include<vector>
using namespace std;

bool cmp(vector<int> a,vector<int> b){
	if(a.size()!=b.size()) return a.size()>b.size();
	else{
		for(int i=a.size()-1;i>=0;i--){
			if(a[i]!=b[i])
				return a[i]>b[i];
		}
	}
	return true;
}
vector<int> sul(vector<int> a,vector<int> b){
	int t=0;
	vector<int> c;
	for(int i=0;i<a.size()||t;i++){
		if(i<a.size()) t=a[i]-t;
		if(i<b.size()) t-=b[i];
		c.push_back((t+10)%10);
		if(t<0) t=1;
		else t=0;
	}
	if(c.size()>1&&c.back()==0) c.pop_back();
	return c;
}

int main(){
	string a,b;
	vector<int> A,B;
	cin>>a>>b;
	for(int i=a.size()-1;i>=0;i--){
		A.push_back(a[i]-'0');
	}
	for(int i=b.size()-1;i>=0;i--)
		B.push_back(b[i]-'0');
	vector<int> c;
	if(cmp(A,B)) c=sul(A,B);
	else{
		cout<<'-';
		c=sul(B,A);//注意换位 
	}
	for(int i=c.size()-1;i>=0;i--) cout<<c[i];
	return 0;
}

高精度乘法

/*
	高精度乘法 
	1.先将两数用字符串输入,再转换成数字,然后逆序压入数组中
	2.执行乘法步骤,t表示进位数,本位余数,进位除数 
	3.逆序输出 
*/
#include<iostream>
#include<vector>
using namespace std;

vector<int> mul(vector<int> a,int b){
	int t=0;
	vector<int> c;
	for(int i=0;i<a.size()||t;i++){
		if(i<a.size()) t+=a[i]*b;
		c.push_back(t%10);
		t/=10;
	}
	if(c.size()>1&&c.back()==0) c.pop_back();
	return c;
}

int main(){
	string a;
	int b; 
	vector<int> A;
	cin>>a>>b;
	for(int i=a.size()-1;i>=0;i--){
		A.push_back(a[i]-'0');
	}

	vector<int> c;
	
	c=mul(A,b);
	
	for(int i=c.size()-1;i>=0;i--) cout<<c[i];
	return 0;
}

高精度除法

/*
	高精度除法 
	1.先将两数用字符串输入,再转换成数字,然后逆序压入数组中
	2.执行除法步骤,t表示除位数 
	3.逆序输出 
*/
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

vector<int> div(vector<int> a,int b){
	int t=0;
	vector<int> c;
	for(int i=a.size()-1;i>=0;i--){
		t=t*10+a[i];
		c.push_back(t/b);//此处与前面三个不同 
		t%=b;
	}
	reverse(c.begin(),c.end());
	if(c.size()>1&&c.back()==0) c.pop_back();
	return c;
}

int main(){
	string a;
	int b; 
	vector<int> A;
	cin>>a>>b;
	for(int i=a.size()-1;i>=0;i--){
		A.push_back(a[i]-'0');
	}

	vector<int> c;
	
	c=div(A,b);
	
	for(int i=c.size()-1;i>=0;i--) cout<<c[i];
	return 0;
}

总结:除了除法函数里的步骤循环是从数组后往前,然后完了反转一下reverse,这点不同。其他都是逆序输入逆序输出,然后函数里面数组从前往后。t都是初始化为0

双指针

最长连续不重复子序列

#include<iostream>
using namespace std;
const int N=1000;
int q[N],a[N];

int main(){
	int n,res=0;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1,j=1;i<=n;i++){
		q[a[i]]++;
		while(j<i&&q[a[i]]>1) q[a[j++]]--;//此处注意不能单纯的j-- 
		res=max(res,i-j+1);
	}
	cout<<res;
	return 0;
} 

离散化(区间和)

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=100010;
typedef pair<int,int> pii;
vector<pii> add,req;
vector<int> alls;
int n,m;
int a[N],s[N];

int find(int x){
	int l=0,r=alls.size()-1;
	while(l<r){
		int mid=l+r>>1;
		if(x<=alls[mid]) r=mid;
		else l=mid+1;
	}
	return l+1;
}
int main(){
	cin>>n;
	for(int i=0;i<n;i++){
		int x,c;
		cin>>x>>c;
		add.push_back({x,c});
		alls.push_back(x);
		
	}
	cin>>m;
	for(int i=0;i<m;i++){
		int l,r;
		cin>>l>>r;
		req.push_back({l,r});
		alls.push_back(l);
		alls.push_back(r);
	}
	for(auto adds:add){
		a[find(adds.first)]+=adds.second;
	}
	sort(alls.begin(),alls.end());//第一个数指针和最后一个数的后一位指针
	alls.erase(unique(alls.begin(),alls.end()),alls.end());
	for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i]; 
	for(auto re:req){
		int l=find(re.first),r=find(re.second);
		cout<<s[r]-s[l-1]<<endl;
	}
	return 0;
}

区间合并

/*
	区间覆盖
	1.一般用pair存区间左右端点,然后放入vector数组中
	2.初始化,将当前的起始和终止位置初始化为最小,然后根据每个区间
	  的左端点从小到大排序
	3.枚举遍历每个区间,当新区间左端点大于目前的ed并且ed不是初始值时,
	  将前面的st和ed区间放入res,然后以新区间的左右端点作为新的st和ed;否则
	  用当前区间的右端点更新ed,比较谁大。
	4.最后有区间放入时,st不等于初始值,要记得把最后一个st和ed放入res
	5.然后将res赋值给segs。 
	   
*/

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int,int> pii;

void merge(vector<pii> &segs){
	vector<pii> res;
	sort(segs.begin(),segs.end()); 
	int st=-2e9,ed=-2e9;
	for(auto seg:segs){
		if(ed<seg.first){
			if(st!=-2e9) res.push_back({st,ed});
			st=seg.first,ed=seg.second;
			
		}
		else ed=max(ed,seg.second);
		
	}
	if(st!=-2e9) res.push_back({st,ed});
	segs=res;
}

int main(){
	int n;
	cin>>n;
	vector<pii> segs;
	for(int i=0;i<n;i++){
		int l,r;
		cin>>l>>r;
		segs.push_back({l,r});
		
	}
	merge(segs);
	
	cout<<segs.size();
	
	return 0;
}

数据结构

常见结构

单链表

#include<iostream>
#include<algorithm>

using namespace std;

const int N=100010;

int h,e[N],ne[N],idx;//一般都将头结点下标初始化为-1,idx表示当前已经用了第几个点了,是下标(指针) 。
//初始化 
void init(){
	idx=0;
	h=-1;
}
//将x插入到头结点 
void addh(int x){
	e[idx]=x,ne[idx]=h,h=idx++;
	
} 
//在k下标节点后面插入x 
void addx(int k,int x){
	e[idx]=x,ne[idx]=ne[k],ne[k]=idx++;
}
//删除k下标节点后面一个数 
void delx(int k){
	ne[k]=ne[ne[k]];
}

双链表

#include<iostream>
#include<algorithm>

using namespace std;

const int N=100010;

int l[N],r[N],idx;
//初始化 
void init(){
	r[0]=1;
	l[1]=0;
	idx=2;
} 
//在下标为k的节点后插入值x 
void addx(int k,int x){
	ne[idx]=x;
	l[idx]=k,r[idx]=r[k];
	l[r[k]]=idx,r[k]=idx++;
}
//删除下标为k的节点
 void del(int k){
 	r[l[k]]=r[k];
 	l[r[k]]=l[k];
 } 

单调栈

不管栈还是队列,tt都是指向最后一个元素(最后一个元素下标)

//单调栈求距离x最近的小于(大于)x的数 
//栈还是队列,tt都是指向最后一个元素 
#include<iostream>
#include<algorithm>

using namespace std;

const int N=100010;

int tt,s[N];

int main(){
	int n;
	cin>>n;
	while(n--){
		int x;
		cin>>x;
		while(tt&&s[tt]>=x) tt--;
		if(!tt) cout<<"-1";
		else cout<<s[tt];
		s[++tt]=x;
	}
	return 0;
}

单调队列(滑动窗口)

//单调队列求窗口内最大(小)值
/*
	n个数n重循环
	1.队列不空,看队头元素有没有超出范围
	2.队列不空,按条件更新队尾元素
	3.队列不空,按条件输出队头元素 
*/ 
#include<iostream>
#include<algorithm>

using namespace std;

const int N=100010;

int hh=0,tt=-1,q[N],a[N];//栈还是队列,tt都是指向最后一个元素 

int main(){
	int n,k;
	cin>>n>>k;
	for(int i=0;i<n;i++) cin>>a[i];
	//求窗口最内大值,队列里面是递减的,只保留价值比a[i]大的 
	int hh=0,tt=-1;
	for(int i=0;i<n;i++){
		if(hh<=tt&&q[hh]<i-k+1) hh++;
		while(hh<=tt&&a[q[tt]]<=a[i]) tt--;
		q[++tt]=i;//包括这个数在内的窗口 
		if(i>=k-1) cout<<a[q[hh]]<<" ";
	} 
	puts("");
	//求窗口内最小值
	hh=0,tt=-1;
	for(int i=0;i<n;i++){
		if(hh<=tt&&q[hh]<i-k+1) hh++;
		while(hh<=tt&&a[q[tt]]>=a[i]) tt--;
		q[++tt]=i;		
		if(i>=k-1) cout<<a[q[hh]]<<" ";
	} 
	
	return 0;
}

kmp字符串

#include<iostream>
using namespace std;
const int N=100010,M=1000010;
int n,m;
int ne[N];//ne[i]=j表示的是p字符串中以i为终点的子串p[1~j]=p[i-j+1,i] 是相等的 
char s[M],p[N];//s是母串,p是子串

int main(){
	cin>>n>>p+1>>m>>s+1;//下标从1开始 
	//求ne数组 
	for(int i=2,j=0;i<=n;i++){
		while(j&&p[i]!=p[j+1]) j=ne[j];
		if(p[i]==p[j+1]) j++;
		ne[i]=j;
	}
	//匹配过程
	for(int i=1,j=0;i<=m;i++){
		while(j&&s[i]!=p[j+1]) j=ne[j];
		if(s[i]==p[j+1]) j++;
		if(j==n){
			cout<<i-n<<" ";
			j=ne[j];
		}
	} 
	
	return 0;
} 

字典树(树状数组)

字符串统计

#include<iostream>

using namespace std;
const int N=100010;
/*
	字典树,树状数组 son存的是本字母的下标,也是下个字母的一维(指向下一个字母),二维是本字母的ascll码, 
	idx下标,一维从1开始

*/
int son[N][26],cnt[N],idx;
char str[N];

void insert(char *str){
	int p=0;
	for(int i=0;str[i];i++){//字符串遍历
		int u=str[i]-'a';
		if(!son[p][u]) son[p][u]=++idx;
		p=son[p][u]; 
	}
	cnt[p]++;
} 

int query(char *str){
	int p=0;
	for(int i=0;str[i];i++){
		int u=str[i]-'a';
		if(!son[p][u]) return 0;
		p=son[p][u];
	}
	return cnt[p];
}

int main(){
	int n;
	cin>>n;
	while(n--){
		char op[2];
		scanf("%s%s",op,str);
		if(*op=='I') insert(str);//第一个字符 
		else printf("%d\n",query(str));
	}
	return 0;
} 

最大异或对

/*
	最大异或对还是利用树状数组和位运算的知识 
*/
#include<iostream>
#include<algorithm>
using namespace std;

const int N=100010,M=3100010;
int n;
int a[N],son[M][2],idx; 

void insert(int x){
	int p=0;
	for(int i=30;i>=0;i--){
		int &s=son[p][x>>i&1];
		if(!s) s=++idx;
		p=s;
	}
}

int search(int x){
	int p=0,res=0;
	for(int i=30;i>=0;i--){
		int s=x>>i&1;
		if(son[p][!s]){
			res+=1<<i;
			p=son[p][!s];
		}
		else p=son[p][s];
	}
	return res;
}
int main(){
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a[i];
		insert(a[i]);
	}
	int res=0;
	for(int i=0;i<n;i++) res=max(res,search(a[i]));
	cout<<res<<'\n';
	return 0;
}

并查集

合并集合

/*
	合并集合 
*/
#include<iostream>
#include<algorithm>
using namespace std;

const int N=100010;
int p[N];
//这个函数本质是找到节点x的祖宗节点,并且路径压缩了,使得路径上的所有点都指向祖宗节点 
int find(int x){
	if(p[x]!=x) p[x]=find(p[x]);//让x的父节点指向父节点的父节点,一直递归,直到p[x]是x的祖宗节点 
	return p[x];
}

int main(){
	int n,m;
	cin>>n>>m;
	for(int i=0;i>n;i++) p[i]=i;//初始化,使每个点父节点都指向自己 
	while(m--){
		char op[2];
		int a,b;
		cin>>op>>a>>b;
		if(*op=='M') p[find(a)]=find(b);//a的父节点指向b,让b成为a的父节点 
		else{
			if(find(a)==find(b)) puts("YES");
			else puts("NO");
		}
	}
	return 0;
}

连通块中点的数量

/*
	连通块中的数量,再开一个数组存储连通块点数,一般是祖宗节点的cnt.并查集加点数维护
	调试小技巧,一般输入输出有问题,先看for循环的输入输出 
*/
#include<iostream>
#include<algorithm>
using namespace std;

const int N=100010;
int p[N],cnt[N];
//这个函数本质是找到节点x的祖宗节点,并且路径压缩了,使得路径上的所有点都指向祖宗节点 
int find(int x){
	if(p[x]!=x) p[x]=find(p[x]);//让x的父节点指向父节点的父节点,一直递归,直到p[x]是x的祖宗节点 
	return p[x];
}

int main(){
	int n,m;
	cin>>n>>m;
	for(int i=0;i<n;i++){//初始化,使每个点父节点都指向自己,并且点数为1 
		p[i]=i;
		cnt[i]=1;
	}
	while(m--){
		string op;//字符串输入操作信号 
		int a,b;
		cin>>op;
		if(op=="C"){
			cin>>a>>b;
			a=find(a),b=find(b);
			if(a!=b){
				p[a]=b;
				cnt[b]+=cnt[a];
			} 
		}
		else if(op=="Q1"){
			cin>>a>>b;
			if(find(a)==find(b)) puts("YES");
			else puts("NO");
		}
		else{
			cin>>a;
			cout<<cnt[find(a)]<<'\n';
		}
	}
	return 0;
}

食物链

/*
	食物链,并查集加距离维护 
	A←B←C←A: A吃B, B吃C, C吃A,若距离%3余数为0同类,1B被根节点吃,2C吃根节点 
*/
#include<iostream>
#include<algorithm>
using namespace std;

const int N=500010;
int p[N],d[N];//d数组存放离父节点距离,路径压缩优化完就是到根节点距离 
//递归调用,找到根节点,完成路径压缩+寻找根节点 
int find(int x){
	if(p[x]!=x){
		int t=find(p[x]);//向上搜索根节点 
		d[x]+=d[p[x]];//到根节点的距离等于到父节点的距离+父节点到根节点的距离 
		p[x]=t;//父节点变根节点 
	}
	return p[x];
}

int main(){
	int n,m,res=0;
	cin>>n>>m;
	for(int i=1;i<=n;i++){//初始化,使每个点父节点都指向自己
		p[i]=i;
	}
	while(m--){
		int op,x,y;
		cin>>op>>x>>y;
		if(x>n||y>n) res++;
		else{
			int px=find(x),py=find(y);
			if(op==1){//x,y是同类的判断。x,y到根节点距离相等
				if(px==py&&(d[x]-d[y])%3) res++;//在同一个集合中,但不是同类关系,错误 
				else if(px!=py){//没在一个集合,加在一个集合中,维持距离是同类 ****
					p[px]=py;
					d[px]=d[y]-d[x];
				}
			}
			else{//判断x吃y,若x吃y,则要x,y在同一个集合中,x到根节点的距离要比y小1 
				if(px==py&&(d[y]-1-d[x])%3) res++;//在同一集合中,但不是x吃y的关系 
				else if(px!=py){
					p[px]=py;
					d[px]=d[y]-d[x]-1;
				}
			} 
		}
				
	}
	cout<<res<<endl; 
	return 0;
}

堆排序

/*
	堆排序 
*/
#include<iostream>
#include<algorithm>
using namespace std;

const int N=500010;
int n,m;
int h[N],cnt;//cnt表示堆中最后一个数的下标,从1开始 

void down(int u){//传堆的下标 
	int t=u;//t为最小数的下标 
	if(u*2<=cnt&&h[u*2]<h[t]) t=u*2;//左节点 
	if(u*2+1<=cnt&&h[u*2+1]<h[t]) t=u*2+1;//右节点 
	if(u!=t){
		swap(h[u],h[t]);
		down(t);
	}
}
int main(){
 	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>h[i];
	cnt=n;
	for(int i=n/2;i>0;i--) down(i);//整理成小根堆 
	while(m--){
		cout<<h[1]<<" ";
		h[1]=h[cnt--];
		down(1);
		
	}
	puts("");
	return 0;
}

模拟堆

/*
	模拟堆。要重写交换函数 
*/
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=500010;
int n,m;//m代表第m个插入的数 
int h[N],ph[N],hp[N],cnt;//cnt表示堆中最后一个数的下标,从1开始
//ph[k]:第k个插入的数在堆中的编号,hp[i]:在堆中编号为i的位置是第几个插入的 

void heap_swap(int a,int b){//a,b表示堆的编号(下标) 
	swap(ph[hp[a]],ph[hp[b]]);
	swap(hp[a],hp[b]);
	swap(h[a],h[b]);
} 

void down(int u){//传堆的下标 
	int t=u;//t为最小数的下标 
	if(u*2<=cnt&&h[u*2]<h[t]) t=u*2;//左节点 
	if(u*2+1<=cnt&&h[u*2+1]<h[t]) t=u*2+1;//右节点 
	if(u!=t){
		heap_swap(u,t);
		down(t);
	}
}
void up(int u){
	while(u/2&&h[u]<h[u/2]){
		heap_swap(u,u/2);
		u=u/2;
	}
}
int main(){
 	cin>>n;
	while(n--){
		char op[5];
		int k,x;
		cin>>op;
		if(!strcmp(op,"I")){//若op定义为string,则可以用op=="I"判断
			cin>>x;
			cnt++;
			m++;
			ph[m]=cnt,hp[cnt]=m;
			h[cnt]=x;
			up(cnt);
		}
		else if(!strcmp(op,"PM")) cout<<h[1]<<'\n';
		else if(!strcmp(op,"DM")){
			heap_swap(1,cnt);
			cnt--;
			down(1);
		}
		else if(!strcmp(op,"D")){
			cin>>k;//删除第k个插入的数
			k=ph[k];//变为下标编号 
			heap_swap(cnt,k);
			cnt--;
			up(k);
			down(k);
		}
		else{
			cin>>k>>x;
			k=ph[k];
			h[k]=x;
			up(k);
			down(k);
		}
	}
	return 0;
}

哈希表

模拟散列表

开放寻址法

/*
	模拟散列表。开放寻址法,用x余数来存放,可以减小范围
*/
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=200003,null=0x3f3f3f3f;//N最好是质数 
 
int h[N];

int find(int x){//找到x的所在位置下标或者x应该存放位置下标 
	int t=(x%N+N)%N;
	while(h[t]!=null&&h[t]!=x){
		t++;
		if(t==N) t=0;
	}
	return t;
}

int main(){
	memset(h,0x3f,sizeof h);
	int n;
	cin>>n;
	while(n--){
		char op[2];
		int x;
		cin>>op>>x;
		if(*op=='I') h[find(x)]=x;
		else{
			if(h[find(x)]==null) puts("NO");
			else puts("YES");
		}
	}
	return 0;
}

拉链法

/*
	模拟散列表。拉链法 
*/
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=100003;//N最好是质数 
 
int h[N],e[N],ne[N],idx;

bool find(int x){//判断链表中是否有x这个数 
	int t=(x%N+N)%N;
	for(int i=h[t];~i;i=ne[i]){
		if(e[i]==x)
			return true;
	}
	return false;
}

void insert(int x){//插入x到相应链表中 
	int t=(x%N+N)%N;
	e[idx]=x,ne[idx]=h[t],h[t]=idx++;//头插法 
} 

int main(){
	memset(h,-1,sizeof h);
	int n;
	cin>>n;
	while(n--){
		char op[2];
		int x;
		cin>>op>>x;
		if(*op=='I') insert(x);
		else{
			if(!find(x)) puts("NO");
			else puts("YES");
		}
	}
	return 0;
}

字符串哈希

/*
	字符串哈希 
*/
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

typedef unsigned long long ull;
const int N=100010,P=131; //p进制或取13331 

int n,m;
char str[N];
ull h[N],p[N];//h[i]表示前i为和,从右往左的前缀和,像一般的数字一样,左边是高位。 
//p[i]表示第i位的权重。p[i]=p^i。p[0]=1。 

ull get(int l,int r){
	return h[r]-h[l-1]*p[r-l+1];
}
int main(){
	cin>>n>>m>>str+1;
	p[0]=1;
	for(int i=1;i<=n;i++){
		h[i]=h[i-1]*P+str[i];//前i个字符转换成的数
		p[i]=p[i-1]*P;//表示P的i次方。
	}
	
	while(m--){//m次询问
		int l1,r1,l2,r2;
		cin>>l1>>r1>>l2>>r2;
		if(get(l1,r1)==get(l2,r2)) puts("YES");
		else puts("NO");
	}
	return 0;
}

搜索和图论

dfs

排序数字

/*
	排列数字 
*/
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

typedef unsigned long long ull;
const int N=10; 

int n;
int path[N],st[N];

void dfs(int u){
	if(u==n){
		for(int i=0;i<n;i++)
			cout<<path[i]<<" ";
		puts("");
	}
	
	for(int i=1;i<=n;i++){
		if(!st[i]){
			path[u]=i;
			st[i]=true;
			dfs(u+1);
			st[i]=false;
		}
	}
	
}

int main(){
	cin>>n;
	dfs(0);
	return 0;
}

n皇后问题

直接考虑

#include <iostream>

using namespace std;

const int N = 10;

int n;
bool row[N], col[N], dg[N * 2], udg[N * 2];
char g[N][N];

void dfs(int x, int y, int s)
{
    if (s > n) return;
    if (y == n) y = 0, x ++ ;

    if (x == n)
    {
        if (s == n)
        {
            for (int i = 0; i < n; i ++ ) puts(g[i]);
            puts("");
        }
        return;
    }

    g[x][y] = '.';
    dfs(x, y + 1, s);

    if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n])
    {
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;
        g[x][y] = 'Q';
        dfs(x, y + 1, s + 1);
        g[x][y] = '.';
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;
    }
}

int main()
{
    cin >> n;

    dfs(0, 0, 0);

    return 0;
}

优化情况

/*
	n皇后问题 
*/
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=10; 

int n;
int p[N][N],col[N],dg[N*2],udg[N*2];
char g[N][N];//char类型 

void dfs(int u,int t){
	if(t>n) return;//剪枝 
	if(u==n){//我们所需要的结果 
		if(t==n){
			for(int i=0;i<n;i++)
				puts(g[i]);//输出一串并自动换行	
		}
		puts("");
		return;
	}
	for(int i=0;i<n;i++){//此层可能的情况,标记后递归,递归取消标记后回溯 
		if(!col[i]&&!dg[i-u+n]&&!udg[i+u]){
			g[u][i]='Q';
			col[i]=dg[i-u+n]=udg[i+u]=true;
			dfs(u+1,t+1);
			col[i]=dg[i-u+n]=udg[i+u]=false;
			g[u][i]='.';
		}
	}
}

int main(){
	cin>>n;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			g[i][j]='.';
	dfs(0,0);
	return 0;
}

总结:
1.dfs的本质是利用递归这种思想(递归内部用栈实现),用for来遍历此层的所有可能的取值情况,然后把已经取值的情况标记,避免下面递归再用此值,再进入下层继续递归,在调用递归结束后取消标记回溯,进入此层的下个可能情况

2.dfs的一般步骤是退出递归的条件,有剪枝和递归到最后一层的下一层可取结果这两种情况。然后就是每层(此层)可取的可能情况枚举(多个用for循环,两个直接枚举了),对于取完一个情况进入下层递归前需要标记这个情况避免影响下面递归,递归执行完后再进行回溯,需要把标记取消

bfs

走迷宫

/*
	走迷宫 
*/
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;

typedef pair<int,int> pii;
const int N=110; 

int n,m;
int g[N][N],d[N][N];

int bfs(){
	queue<pii> q;
	memset(d,-1,sizeof d);
	d[0][0]=0;
	q.push({0,0});
	int dx[4]={-1,0,1,0},dy[4]={0,-1,0,1};
	while(q.size()){
		auto t=q.front();
		q.pop();
		for(int i=0;i<4;i++){
			int x=t.first+dx[i],y=t.second+dy[i];
			if(x>=0&&x<n&&y>=0&&y<n&&g[x][y]==0&&d[x][y]==-1){
				d[x][y]=d[t.first][t.second]+1;
				q.push({x,y});
			}
		}
	} 
	
	return d[n-1][m-1];	
}

int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++){
			cin>>g[i][j];
		}
	cout<<bfs()<<endl;
	return 0;
}

八数码

/*
	八数码
*/
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<unordered_map>
using namespace std;

string state;

int bfs(){
	queue<string> q;
	unordered_map<string,int> d;
	string end="12345678x";
	q.push(state);
	d[state]=0;
	int dx[4]={-1,0,1,0},dy[4]={0,-1,0,1};
	
	while(q.size()){
		auto t=q.front();
		q.pop();
		
		if(t==end) return d[t];
		int di=d[t];
		int k=t.find('x');//字符串查找下标函数 
		int x=k/3,y=k%3;
		
		for(int i=0;i<4;i++){
			int a=x+dx[i],b=y+dy[i];
			if(a>=0&&a<3&&b>=0&&b<3){
				swap(t[a*3+b],t[k]);//交换字符串中下标为a*3+b与k的字符 
				if(!d.count(t)){//map中判断key是否存在 
					d[t]=di+1;//此时的t为交换后的字符串(另一种状态) 
					q.push(t);
				}
				swap(t[a*3+b],t[k]);//交换回来进行下次移动,不影响下次 
			}
		}
	} 
	
	return -1;	
}

int main(){
	char s[2];//这个挺好用
	for(int i=0;i<9;i++){
		cin>>s;
		state+=*s;
	}
	cout<<bfs()<<endl;
	
	return 0;
}

总结:
bfs的本质是利用队列,有最短路的性质。一般步骤是将满足条件的情况入队,有距离数组需要初始化的初始化,然后while队列不空,取队头并将其出队,再将于队头有关的情况或者队头的下一步的所有情况入队,最后返回我们需要的“情况。

树图的搜索

dfs树的重心

/*
	树的重心。用dfs来求 
*/
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=100010,M=N*2;
int n;
int h[N],e[N],ne[M],idx;
int ans=N;//所需要的答案,每个子树个数最大的最小值 
bool st[N];//标记已经走过的点 

void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

int dfs(int u){//求以u为根的所有子树的节点个数,包括根节点 
	int res=0,sum=1;//res求子树个数最大值,sum是包括根节点的总节点数 
	st[u]=true;//判断那个是重心,每个点都遍历到 
	
	for(int i=h[u];~i;i=ne[i]){//求res和sum 
		int j=e[i];
		if(!st[j]){
			int s=dfs(j);//s是以j为根节点的总节点个数 
			res=max(res,s);//求最大的子树节点数 
			sum+=s;
		}
	}
	res=max(res,n-sum);//求最大 
	ans=min(ans,res);//答案是res的最小值 
	return sum;
}

int main(){
	cin>>n;
	memset(h,-1,sizeof h);
	for(int i=0;i<n-1;i++){//树的边数是点数-1 
		int a,b;
		cin>>a>>b;
		add(a,b),add(b,a);//树无向边 
	}
	dfs(1);
	cout<<ans;
	return 0;
}

图中点的层次

/*
	图中点的层次。bfs来求 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=100010,M=N*2;
//在上面定义的一些数组记得初始化 
int n,m;
int h[N],e[N],ne[M],idx;
int d[N];//存储每个点的层次,d[i]表示点i所在层次 

void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

int bfs(){ 
	memset(d,-1,sizeof d);
	d[1]=0;
	
	queue<int> q;
	q.push(1);
	
	while(q.size()){
		auto t=q.front();
		q.pop();
		 
		for(int i=h[t];~i;i=ne[i]){
			int j=e[i];
			if(d[j]==-1){
				d[j]=d[t]+1;
				q.push(j);
			}
		}
	}
	
	return d[n];
}

int main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);
	
	for(int i=0;i<m;i++){
		int a,b;
		cin>>a>>b;
		add(a,b);
	}
	cout<<bfs()<<endl;
	return 0;
}

拓扑排序

/*
	拓扑排序。用bfs来求,手写队列 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=100010,M=N*2;
//在上面定义的一些数组记得初始化 
int n,m;
int h[N],e[N],ne[M],idx;
int d[N];//存储每个点的入度 
int q[N],hh,tt=-1;//手写队列
 
void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

bool topsort(){
	
	for(int i=1;i<=n;i++){
		if(!d[i])
			q[++tt]=i;
	}
	
	while(hh<=tt){
		auto t=q[hh++];
		for(int i=h[t];~i;i=ne[i]){
			int j=e[i];
			if(--d[j]==0) 
				q[++tt]=j;
		}
	}
	
	if(tt<n-1) return false;
	else return true;
}

int main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);
	
	for(int i=0;i<m;i++){
		int a,b;
		cin>>a>>b;
		add(a,b);//a→b 
		d[b]++;//b点入度加1 
		
	}
	if(!topsort()) puts("-1");
	else{
		for(int i=0;i<n;i++)
			cout<<q[i]<<" ";
		puts("");
	}
	return 0;
}

最短路算法

单源最短路

朴素版dijkstra
/*
	朴素版dijkstra。用领接矩阵来表示
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=510;
//在上面定义的一些数组记得初始化 
int n,m;
int g[N][N];//领接矩阵来存储图 
int d[N];//存储每个点到源点的距离 
bool st[N];//判断点是否在s集合中。s集合表示已经确定了最短路的点集 

int dj(){
	//初始化 
	memset(d,0x3f,sizeof d);
	d[1]=0;
	//将所有点都放入s集合中,每次找到里源点最近的点,然后用此点来更新其他点 
	for(int i=0;i<n;i++){
		int t=-1;
		for(int j=1;j<=n;j++){
			if(!st[j]&&(d[j]<d[t]||t==-1)){
				t=j;
			}
		}
		st[t]=true;
		for(int j=1;j<=n;j++){
			if(d[j]>d[t]+g[t][j])
				d[j]=d[t]+g[t][j];
		}
	}
	if(d[n]==0x3f3f3f3f) return -1;
	return d[n];
}

int main(){
	cin>>n>>m;
	memset(g,0x3f,sizeof g);
	
	while(m--){
		int a,b,c;
		cin>>a>>b>>c;
		g[a][b]=min(g[a][b],c);//重边用最短的路径 
	}
	
	cout<<dj()<<'\n';
	return 0;
}
堆优化版dijsktra
/*
	堆优化版dijkstra。用小根堆优化,直接取每次距离源点最近的点 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

typedef pair<int,int> pii;
const int N=1e6+10;

int n,m;
int h[N],e[N],ne[N],w[N],idx;//邻接表存储 
int d[N];//每个点到源点距离 
bool st[N];

void add(int a,int b,int c){
	e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int dj(){
	//初始化 
	memset(d,0x3f,sizeof d);
	d[1]=0;
	
	priority_queue<pii,vector<pii>,greater<pii> > q;
	q.push({0,1});
	
	while(q.size()){
		auto t=q.top();
		q.pop();
		
		int di=t.first,v=t.second;
		if(st[v]) continue;
		st[v]=true;
		
		for(int i=h[v];~i;i=ne[i]){
			int j=e[i];
			if(d[j]>d[v]+w[i]){//注意是w[i]而不是w[j],i是下标,j是点 
				d[j]=d[v]+w[i];
				if(!st[j])
					q.push({d[j],j});
			}
		}
	}
	if(d[n]==0x3f3f3f3f) return -1;
	return d[n];
}

int main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);
	
	while(m--){
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);//有向图 
	}
	
	cout<<dj()<<'\n';
	return 0;
}
bellman-ford算法(可存在负权边)
/*
	有边数限制的最短路,bellman-ford算法 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=510,M=10010;

struct Edge{
	int a,b,c;
}e[M];

int n,m,k;//点数,边数,限制边数 
int d[N];
int last[N];//上层限制边数条件下,各点到源点距离 

void bellman_ford(){
	
	memset(d,0x3f,sizeof d);
	d[1]=0;
	
	for(int i=0;i<k;i++){//若无边数限制则i<n 
		memcpy(last,d,sizeof d);//用memset转换无效,上层边数限制 
		for(int j=0;j<m;j++){ 
			int a=e[j].a,b=e[j].b,c=e[j].c;
			if(d[b]>last[a]+c){
				d[b]=last[a]+c;
				
			}
		}	
	}
	
}
int main(){
	cin>>n>>m>>k;
	
	for(int i=0;i<m;i++){
		int a,b,c;
		cin>>a>>b>>c;
		e[i]={a,b,c};
	}
	
	bellman_ford();
	if(d[n]>0x3f3f3f3f/2) puts("impossible");//存在负权可能会使INF变小 
	else cout<<d[n];
	return 0;
}
spfa算法(可存在负权边)

spfs是bellman-ford算法用队列优化的算法,每次松弛不用每各点都判断更新,只有某个点的前驱节点更新才会导致改点更新,st数组来表示被更新的点,入队true,出队false,可逆。
1.求最短路

/*
	spfa算法求最短路,用邻接表存储,用队列来优化 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=10010;

int n,m;//点数,边数
int h[N],e[N],ne[N],w[N],idx;//邻接表第一反应要记得初始化h数组 
int d[N];
bool st[N];//表示被更新的点,而不是判断是否在s集合中 

void add(int a,int b,int c){
	e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}

void spfa(){
	memset(d,0x3f,sizeof d);
	d[1]=0;
	
	queue<int> q;
	q.push(1);
	st[1]=true;
	
	while(q.size()){
		auto t=q.front();
		q.pop();
		st[t]=false;
		for(int i=h[t];~i;i=ne[i]){
			int j=e[i];
			if(d[j]>d[t]+w[i]){
				d[j]=d[t]+w[i];
				if(!st[j]){
					q.push(j);
					st[j]=true;
				}		
			}
			
		}
	}
}
int main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);//不要忘了初始化,不然没有输出程序一直运行。 
	
	for(int i=0;i<m;i++){
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c); 
	}
	
	spfa();
	if(d[n]==0x3f3f3f3f) puts("impossible");//存在负权可能会使INF变小 
	else cout<<d[n]<<'\n';
	return 0;
}

2.求是否存在负环

/*
	spfa算法求是否有负权回路,用邻接表存储,用队列来优化
	要多一个cnt数组来存储到达每个点的边数,若边数大于n-1则说明有负权回路
	然后全部点也要先入队 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=10010;

int n,m;//点数,边数
int h[N],e[N],ne[N],w[N],idx;//邻接表第一反应要记得初始化h数组 
int d[N];
int cnt[N];//表示到达i点所经过的边数 
bool st[N];//表示被更新的点,而不是判断是否在s集合中 

void add(int a,int b,int c){
	e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}

bool spfa(){
	memset(d,0x3f,sizeof d);
	d[1]=0;
	
	queue<int> q;
	for(int i=i;i<=n;i++){//将所有的点全部入队 
		q.push(i);
		st[i]=true;
	}
	
	while(q.size()){
		auto t=q.front();
		q.pop();
		st[t]=false;
		for(int i=h[t];~i;i=ne[i]){
			int j=e[i];
			if(d[j]>d[t]+w[i]){
				d[j]=d[t]+w[i];
				cnt[j]=cnt[t]+1;
				if(cnt[j]>n-1) return true;
				if(!st[j]){
					q.push(j);
					st[j]=true;
				}		
			}
			
		}
	}
	return false;
}
int main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);//不要忘了初始化,不然没有输出程序一直运行。 
	
	for(int i=0;i<m;i++){
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c); 
	}
	
	if(spfa()) puts("YES");//存在负权可能会使INF变小 
	else puts("NO");
	return 0;
}

多源最短路

Floyd算法
/*
	Floyd算法求多源回路问题。点的取值范围三个for循环 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=210,inf=1e9;

int n,m;//点数,边数
int d[N][N];//d[x][y]表示点x到点y的距离 

void floyd(){
	for(int k=1;k<=n;k++)//点的取值范围 
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int Q;
	cin>>n>>m>>Q;
	//初始化距离 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(i==j) d[i][j]=0;
			else d[i][j]=inf;
			
	for(int i=0;i<m;i++){
		int a,b,c;
		cin>>a>>b>>c;
		d[a][b]=min(d[a][b],c);
	}
	
	floyd();
	while(Q--){
		int a,b;
		cin>>a>>b;
		int t=d[a][b];
		if(t>inf/2) puts("impossible");
		else cout<<t<<endl;
	}
	
	return 0;
}

总结:距离数组记得初始化,图的表示方式也要记得初始化。
然后h数组没有初始化,输完数据不管怎么按都没有反应,也不会结束,程序会一直执行。切记要初始化为-1。

最小生成树

prim

/*
	prim算法求最小生成树的权值 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=210,inf=0x3f3f3f3f;

int n,m; //点边 
int g[N][N];//领接矩阵存储 
int d[N];//每个点到s的距离 
bool st[N];//判断是否是已经加入s的点 

int prim(){
	memset(d,0x3f,sizeof d);
	d[1]=0;
	int res=0;//最小生成树的权值 
	for(int i=0;i<n;i++){
		int t=-1;
		for(int j=1;j<=n;j++){
			if(!st[j]&&(t==-1||d[t]>d[j])){
				t=j;
			}
		}
		if(i) res+=d[t];//先累加在更新
		if(i&&d[t]==inf) return inf;
		st[t]=true;
		
		for(int j=1;j<=n;j++) d[j]=min(d[j],g[t][j]);
	}
	return res;
}

int main(){
	cin>>n>>m;
	memset(g,0x3f,sizeof g);
	while(m--){
		int a,b,c;
		cin>>a>>b>>c;
		g[a][b]=min(g[a][b],c); 
	}
	int t=prim();
	if(t==inf) puts("impossible");
	else cout<<t;
	return 0;
}

kruskal

/*
	kruskal算法求最小生成树的权值 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=100010,M=200010,inf=0x3f3f3f3f;

int n,m; //点边 
int p[N];//并查集 

struct Edge{
	int a,b,w;
	bool operator<(const Edge&W) const{
		return w<W.w;
	}
}e[M];

int find(int x){
	if(p[x]!=x) p[x]=find(p[x]);
	return p[x];
}

int kruskal(){
	sort(e,e+m);
	int cnt=0;//积累的边数 
	int res=0;//权值 
	for(int i=0;i<m;i++){
		int a=e[i].a,b=e[i].b,w=e[i].w;
		if(find(a)!=find(b)){
			p[find(a)]=find(b);
			cnt++;
			res+=w;
		}
	}
	if(cnt<n-1) return inf;
	else return res; 
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		p[i]=i;
	}
	for(int i=0;i<m;i++){
		int a,b,w;
		cin>>a>>b>>w;
		e[i]={a,b,w};
	}
	int t=kruskal();
	if(t==inf) puts("impossible");
	else cout<<t;
	return 0;
}

二分图

染色法判断二分图

/*
	染色法判断二分图,用dfs染色 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=100010,M=200010;

int n,m; //点边 
int h[N],e[M],ne[M],idx;
int color[N];//标记每个点的颜色 

void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

bool dfs(int u,int c){//将u点染成c颜色,并将与u点有关的也染成相应颜色 
	color[u]=c;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(!color[j]){
			if(!dfs(j,3-c)) return false;
		}else if(color[j]==c){//与u相连的点颜色和u染成一样不行 
			return false;
		}
	}
	return true; 
} 

int main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);
	while(m--){
		int a,b;
		cin>>a>>b;
		add(a,b),add(b,a);
	}
	bool flag=true;
	for(int i=1;i<=n;i++){
		if(!color[i]){
			if(!dfs(i,1)){
				flag=false;
				break;
			}
		}
	}
	if(flag) puts("YES");
	else puts("NO");
	return 0;
}

二分图的最大匹配

/*
	染色法判断二分图,用dfs染色 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=510,M=100010;

int n1,n2,m,res; //点边 
int h[N],e[M],ne[M],idx;
int match[N];//妹子是否与左边匹配了,匹配了的话就是左边的数 
bool st[N];//判断左边对妹子是否考虑了 

void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

bool find(int x){//左边的点x尝试匹配右边的妹子 
	for(int i=h[x];~i;i=ne[i]){
		int j=e[i];//j点是右边的 
		if(!st[j]){
			st[j]=true;
			if(!match[j]||find(match[j])){
				match[j]=x;
				return true;
			}
		}
	}
	return false;
} 

int main(){
	cin>>n1>>n2>>m;
	memset(h,-1,sizeof h);
	while(m--){
		int a,b;
		cin>>a>>b;
		add(a,b);
	} 
	for(int i=1;i<=n1;i++){
		memset(st,false,sizeof st);//每个左边都要初始化一下st数组 
		if(find(i)) res++;
	}
	cout<<res;
	return 0;
}

数学知识

质数

试除法判断质数

/*
	试除法判断质数。质数大于1,除了1只有本身这两个约数 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

bool is_prime(int x){
	if(x<2) return false;
	for(int i=2;i<=x/i;i++){
		if(x%i==0) 
			return false;
	}
	return true;
} 

int main(){
	int n;
	cin>>n;
	while(n--){
		int x;
		cin>>x;
		if(is_prime(x)) puts("YES");
		else puts("NO");
	}
	return 0;
}

分解质因数

/*
	分解质因数 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

void divide(int x){
	for(int i=2;i<=x/i;i++){
		if(x%i==0){
			int res=0;
			while(x%i==0){
				res++;
				x/=i;
			}
			cout<<i<<" "<<res<<endl;
		}
	}
	if(x>1) cout<<x<<" "<<"1"<<endl;
}

int main(){
	int n;
	cin>>n;
	while(n--){
		int x;
		cin>>x;
		divide(x);
	}
	return 0;
}

质数筛

/*
	优化的质数筛
	若i是primes[j]倍数,则s=primes[j+1]*i也是primes[j]倍数,
	则s就不是其最小公因数标记的。例如12是i=6时被标记,而不是i=4时就被标记 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=10000;
int primes[N],cnt;
bool st[N];

void get_primes(int x){
	for(int i=2;i<=x;i++){
		if(!st[i]) primes[cnt++]=i;
		for(int j=0;primes[j]<=x/i;j++){
			st[primes[j]*i]=true;//每个非质数都由其最小公因数(质数)标记
			if(i%primes[j]==0)  
				break;
		}
	}
}

int main(){
	int n;
	cin>>n;
	get_primes(n);
	
	for(int i=0;i<cnt;i++) cout<<primes[i]<<" ";
	
	return 0;
}

约数

试除法求约数

/*
	试除法求约数 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=10000;
int di[N],cnt;

void get(int x){
	for(int i=1;i<=x/i;i++){
		if(x%i==0){
			di[cnt++]=i;
			if(x/i!=i)
				di[cnt++]=x/i;
		}
	}
}

int main(){
	int n;
	cin>>n;
	get(n);
	sort(di,di+cnt);
	for(int i=0;i<cnt;i++) 
		cout<<di[i]<<" ";
	return 0;
}

约数个数

/*
	约数的个数,首先分解质因数,如何根据每个质因数的个数来求约数的个数 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=10000;

int get(int x){
	int res=0,ans=1;
	for(int i=2;i<=x/i;i++){
		res=0;
		if(x%i==0){
			while(x%i==0){
				x/=i;
				res++;
			}
			ans=(res+1)*ans;
		}
	}
	if(x>1) ans=ans*2;
	return ans;
}

int main(){
	int n;
	cin>>n;
	int s=get(n);
	cout<<s;
	return 0;
}

用数据结果unordered_map<int,int> 来将质因数和其指数记录;

/*
	约数的个数,首先分解质因数,如何根据每个质因数的个数来求约数的个数 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int N=110,mod=1e9;

int main(){
	int n;
	cin>>n;
	unordered_map<int,int> primes;
	while(n--){
		int x;
		cin>>x;
		for(int i=2;i<=x/i;i++){
			if(x%i==0){
				while(x%i==0){
					x/=i;
					primes[i]++;
				}
			}
		}
		if(x>1) primes[x]++;
	}
	int res=1;
	for(auto prime:primes) res=res*(prime.second+1)%mod;
	cout<<res;
	return 0;
}

约数之和

/*
	求n个数的乘积的约数之和 
	约数之和,首先分解质因数,然后根据每个质因数和其指数来求和 
	18=3^2*2^1;
	质因数时3,2;指数分别是2,1;
	约数之和=(3^0+3^1+3^2)*(2^0+2^1);
	3^0+3^1+3^2=(1+3(1+3)); 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int N=110,mod=1e9;

int main(){
	int n;
	cin>>n;
	unordered_map<int,int> primes;
	while(n--){
		int x;
		cin>>x;
		for(int i=2;i<=x/i;i++){
			if(x%i==0){
				while(x%i==0){
					x/=i;
					primes[i]++;
				}
			}
		}
		if(x>1) primes[x]++;
	}
	ll res=1;
	for(auto prime:primes){
		int a=prime.first,b=prime.second;
		ll t=1;
		while(b--) t=(1+a*t)%mod;//用公式可以要用费马小定理
		res=res*t%mod;
	}
	cout<<res<<endl;
	return 0;
}

最大共约数

/*
	求最大公约数 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int N=110,mod=1e9;

int gcd(int a,int b){
	return b?gcd(b,a%b):a;
}

int main(){
	int n;
	cin>>n;
	while(n--){
		int a,b;
		cin>>a>>b;
		int t=gcd(a,b);//gcd(a,b)=gcd(b,a%b)=......=gcd(x,0)=x;求最大公约数 
		cout<<t<<endl;
	}
	return 0;
}

欧拉函数

求欧拉函数

/*
	欧拉函数:数n,求1~n内与n互质的数的个数,互质是指两个数只有一个公约数1;
	质数n的欧拉函数是n-1;因为在1~n内与n互质的数有n-1个
	
	公式与n的公因数有关 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int N=110,mod=1e9;

int phi(int x){
	int res=x;//公式 
	for(int i=2;i<=x/i;i++){
		if(x%i==0){
			res=res*(i-1)/i;//公式 
			while(x%i==0) x/=i;
		}
	}
	if(x>1) res=res*(x-1)/x;
	return res;
}

int main(){
	int n;
	cin>>n;
	while(n--){
		int x;
		cin>>x;
		cout<<phi(x)<<endl;
	}
	return 0;
}

质数筛求多个数的欧拉函数之和

用质数筛的同时求1~n的里面的所有数的欧拉函数,注意1的欧拉函数是1,要先写出来

/*
	欧拉函数:数n,求1~n内与n互质的数的个数,互质是指两个数只有一个公约数1;
	质数n的欧拉函数是n-1;因为在1~n内与n互质的数有n-1个
	
	公式与n的公因数有关 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int N=1000010,mod=1e9;
int primes[N],eul[N],cnt;
bool st[N];

void get_eulors(int x){
	eul[1]=1;
	for(int i=2;i<=x;i++){
		if(!st[i]){
			primes[cnt++]=i;
			eul[i]=i-1;
		}
		for(int j=0;primes[j]<=x/i;j++){
			int s=i*primes[j];
			st[s]=true;
			if(i%primes[j]==0){
				eul[s]=primes[j]*eul[i];
				break;
			}
			else{
				eul[s]=(primes[j]-1)*eul[i];
			}
		}
	}
}

int main(){
	int n;
	cin>>n;
	get_eulors(n);
	int res=0;
	for(int i=1;i<=n;i++) res+=eul[i]; 
	cout<<res<<endl;
	return 0;
}

快速幂

快速幂模板

/*
	快速幂模板 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int N=1000010,mod=1e9;

int qmi(int a,int b,int p){
	int res=1%p;
	while(b){
		if(b&1) res=res*a%p;
		a=a*(ll)a%p;
		b>>=1;
	}
	return res;
}

int main(){
	int n;
	cin>>n;
	while(n--){
		int a,b,p;
		cin>>a>>b>>p;//求a^b%p
		cout<<qmi(a,b,p)<<endl;
	}
	return 0;
}

快速幂求逆元(p为质数时)

/*
	快速幂求逆元a*a^-1==1(%p)。当a,p互质时,存在逆元,那么a,p的最大公约数是1=gcd(a,p) 
	当p是质数时,逆元a^-1是a^p-2;
	当p不是质数时,可用扩展欧几里得算法,a*x+p*y=1=exgcd(a,p,x,y)可求出x 
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int N=1000010,mod=1e9;

ll qmi(int a,int b,int p){
	ll res=1%p;
	while(b){
		if(b&1) res=(ll)res*a%p;
		a=a*(ll)a%p;
		b>>=1;
	}
	return res;
}

int main(){
	int n;
	cin>>n;
	while(n--){
		int a,p;
		cin>>a>>p;//求a%p的乘法逆元,a^-1*a==1(%p) 求a^-1. 
		if(a%p)
			cout<<qmi(a,p-2,p)<<endl;
		else//a,p不互质则逆元不存在 
			puts("impossible");
	}
	return 0;
}

扩展欧几里得算法

模板

/*
	扩展欧几里得算法模板 
	快速幂求逆元a*a^-1==1(%p)。当a,p互质时,存在逆元,那么a,p的最大公约数是1=gcd(a,p) 
	当p是质数时,逆元a^-1是a^p-2;
	当p不是质数时,可用扩展欧几里得算法,a*x+p*y=1=exgcd(a,p,x,y)可求出x,逆元为(x%p+p)%p
*/
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int N=1000010,mod=1e9;

int exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1,y=0;
		return a;
	}
	int x1,y1;
	int d=exgcd(b,a%b,x1,y1);
	x=y1;
	y=x1-(a/b)*y1;
	return d;
}

int main(){
	int n;
	cin>>n;
	while(n--){
		int a,b;
		cin>>a>>b;
		int x,y;
		int d=exgcd(a,b,x,y);//求a*x+b*y=gcd(a,b)一对x,y;结果不唯一,d是最大公约数即gcd(a,b)
		cout<<x<<" "<<y<<endl; 
	}
	return 0;
}

线性同余方程

#include<iostream>
using namespace std;
typedef long long ll;

ll exgcd(int a,int b,int &x,int &y){
    if(!b){
        x=1;
        y=0;
        return a;
    }
    int d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}

int main(){
    int n;
    cin>>n;
    while(n--){
        int a,b,m,x,y;
        cin>>a>>b>>m;
        int d=exgcd(a,m,x,y);
        if(b%d) cout<<"impossible"<<endl;//只有当b为gcd(a,m)的倍数时才有解。
        else cout<<(long long)x*b/d%m<<endl;
    }
}

中国剩余定理(表达整数的奇怪方式)

表达整数的奇怪方式

#include <cstdio>
#include <iostream>
using namespace std;
typedef long long LL;
int n;
LL exgcd(LL a, LL b, LL &x, LL &y){
    if(b == 0){
        x = 1, y = 0;
        return a;
    }

    LL d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}
LL inline mod(LL a, LL b){
    return ((a % b) + b) % b;
}
int main(){
    scanf("%d", &n);
    LL a1, m1;
    scanf("%lld%lld", &a1, &m1);
    for(int i = 1; i < n; i++){
        LL a2, m2, k1, k2;
        scanf("%lld%lld", &a2, &m2);
        LL d = exgcd(a1, -a2, k1, k2);
        if((m2 - m1) % d){ puts("-1"); return 0; }
        k1 = mod(k1 * (m2 - m1) / d, abs(a2 / d));
        m1 = k1 * a1 + m1;
        a1 = abs(a1 / d * a2);
    }
    printf("%lld\n", m1);
    return 0;
}

高斯消元

解线性方程组

/*
	高斯消元四步
	1.找到绝对值最大的一行
	2.将该行换到目前最顶端并将其首位变为1 
	3.用该行将下面的行首位消为0
	4.判断解条件 
*/
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=110;
const double eps=1e-8;
int n;//在再main函数中定义n是不一样的 
double a[N][N];//不能开得很大,一般不超过10^8 

int gauss(){
	int c,r;
	for(c=0,r=0;c<n;c++){
		int t=r;//找此列中绝对值最大的一行
		for(int i=r;i<n;i++) 
			if(fabs(a[i][c])>fabs(a[t][c]))
				t=i;
				
		if(fabs(a[t][c])<eps) continue;//若为零,则进入下次循环 
		
		for(int i=c;i<=n;i++) swap(a[t][i],a[r][i]);//将绝对值最大的一行换到当前第一行r 
		for(int i=n;i>=c;i--) a[r][i]/=a[r][c];//将当前行的首位变成1 
		for(int i=r+1;i<n;i++)//用当前行将下面所有的列消成0
			if(fabs(a[i][c])>eps)
				for(int j=n;j>=c;j--)
					a[i][j]-=a[r][j]*a[i][c];
		
		r++;
	}
	if(r<n){
		for(int i=r;i<n;i++)
			if(fabs(a[i][n])>eps)
				return 2;//无解 
		return 1;//无穷多解 
	}
	for(int i=n-1;i>=0;i--)//唯一解 
		for(int j=i+1;j<n;j++)
			a[i][n]-=a[i][j]*a[j][n];
			
	return 0;
} 

int main(){
	
	cin>>n;
	for(int i=0;i<n;i++)
		for(int j=0;j<n+1;j++)
			cin>>a[i][j];
	
	int t=gauss();
		
	if(t==2) puts("No solution");
	else if(t==1) puts("Infinite group solutions");
	else{
		for(int i=0;i<n;i++){
			if(fabs(a[i][n])<eps) a[i][n]=0;//去掉输出 -0.00 的情况 
			printf("%.2lf\n",a[i][n]);
		}
	}	
	return 0;
}

求组合数

一般四种情况

1.询问次数很多——直接用C[a][b]=C[a-1][b]+C[a-1][b-1]公式处理
2.询问次数比较多,a,b比较大——预处理fact[i],infact[i];用快速幂处理逆元;
C[a][b]=fact[a]*infact[b]*infact[a-b]
3.a,b很大——用Lucas定理。C[a][b]=C[a%p][b%p]*C[a/p][b/p](mod p),直接用快速幂处理C[a%p][b%p]。
4.高精度——用高精度乘法,分解质因数,求阶乘中质因数的个数(指数)
1.

/*
	组合数1。询问较多时 
	初始化直接公式求C[a][b]
*/ 
#include<iostream>
#include<algorithm>
using namespace std;

const int N=2020,mod=1e9+7;
int c[N][N];

void init(){
	for(int i=0;i<N;i++)
		for(int j=0;j<=i;j++)//j不能大于i 
			if(j==0) c[i][j]=1;
			else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
			
}

int main(){
	int n;
	cin>>n;
	init();
	while(n--){
		int a,b;
		cin>>a>>b;
		cout<<c[a][b]<<endl;
	}
}
/*
	组合数2。
	快速幂处理逆元infact数组 
*/ 
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=100010,mod=1e9+7;
int fact[N],infact[N];

int qmi(int a,int b,int p){
	int res=1%p;
	while(b){
		if(b&1) res=res*(ll)a%p;
		a=a*(ll)a%p;
		b>>=1;
	}
	return res;
}

int main(){
	fact[0]=infact[0]=1;
	for(int i=1;i<N;i++){
		fact[i]=(ll)fact[i-1]*i%mod;
		infact[i]=(ll)infact[i-1]*qmi(i,mod-2,mod)%mod;//注意扩展ll再mod,不然数据会爆 
	}
	int n;
	cin>>n;
	while(n--){
		int a,b;
		cin>>a>>b;
		cout<<(ll)fact[a]%mod*infact[b]%mod*infact[a-b]%mod<<endl;
	}
}
/*
	组合数3。强制转换防止数据爆了
	1.快速幂
	2.求C[a][b]
	3.lucas定理
*/ 
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;

int qmi(int a,int b,int p){
	int res=1%p;
	while(b){
		if(b&1) res=res*(ll)a%p;
		a=a*(ll)a%p;
		b>>=1;
	}
	return res;
}

int C(int a,int b,int p){
	if(b>a) return 0;
	int res=1;
	for(int i=1,j=a;i<=b;i++,j--){
		res=(ll)res*j%p;
		res=(ll)res*qmi(i,p-2,p)%p;
	}
	return res;
}

int lucas(ll a,ll b,int p){
	if(a<p&&b<p) return C(a,b,p);
	else return (ll)C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;
}

int main(){
	int n;
	cin>>n;
	while(n--){
		ll a,b;
		int p;
		cin>>a>>b>>p;
		cout<<lucas(a,b,p)<<endl;
	} 
}
/*
	组合数4。
	1.质数筛
	2.求阶乘中因数的指数(个数)
	3.高精度加法 
*/ 
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

const int N=5010;
int primes[N],cnt; 
bool st[N];
int sum[N];//存储公因数还剩多少 

void get_primes(int n){//求1~n中所有的质数 
	for(int i=2;i<=n;i++){
		if(!st[i]) primes[cnt++]=i;
		for(int j=0;primes[j]<=n/i;j++){
			st[i*primes[j]]=true;
			if(i%primes[j]==0)
				break;
		}
	}
}
int get(int n,int p){//求阶乘n!中p的个数 
	int res=0;//局部变量注意初始化 
	while(n){
		res+=n/p;
		n/=p;
	}
	return res;
}

vector<int> mul(vector<int> res,int b){
	int t=0;
	vector<int> c;
	for(int i=0;i<res.size();i++){
		t=res[i]*b+t;
		c.push_back(t%10);
		t/=10;
	}
	while(t){
		c.push_back(t%10);
		t/=10;
	}
	return c;
}

int main(){
	int a,b;
	cin>>a>>b;
	get_primes(a);
	
	for(int i=0;i<cnt;i++){
		int p=primes[i];
		sum[i]=get(a,p)-get(b,p)-get(a-b,p);
	}
	vector<int> res;
	res.push_back(1);
	for(int i=0;i<cnt;i++){
		while(sum[i]){
			res=mul(res,primes[i]);
			sum[i]--;
		}
	}

	for(int i=res.size()-1;i>=0;i--){
		cout<<res[i];
	}
	return 0;
}

组合数的应用(卡特兰数)

卡特兰数cat(n)=C[2n][n]-C[2n][n-1]=C[2n][n]/n+1.

满足条件的01序列
在这里插入图片描述

/*
	组合数的应用卡特兰数:满足条件的01序列
	cat(n)=C[2n][n]/n+1 
*/ 
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int N=100010,mod=1e9+7;

int qmi(int a,int b,int p){
	int res=1%p;
	while(b){
		if(b&1) res=(ll)res*a%p;
		a=(ll)a*a%p;
		b>>=1;
	}
	return res;
}

int main(){
	int n;
	cin>>n;
	int a=n*2,b=n;
	int res=1;
	//求C[a][b] 
	for(int i=1,j=a;i<=b;i++,j--){
		res=(ll)res*j%mod;
		res=(ll)res*qmi(i,mod-2,mod)%mod;
	}
	res=(ll)res*qmi(n+1,mod-2,mod)%mod;
	cout<<res;
}

容斥原理

/*
	能被整除的数
	问题:1~n中至少被m个质数其中的一个整除的整数有多少个 
	二进制枚举和容斥原理 
*/ 
#include<iostream>
#include<algorithm>

using namespace std;
typedef long long ll;
const int N=20;
int p[N]; 

int main(){
	int n,m;
	cin>>n>>m;
	for(int i=0;i<m;i++){
		cin>>p[i];
	}
	int res=0;
	for(int i=0;i<1<<m;i++){
		int t=1,s=0;
		for(int j=0;j<m;j++){
			if(i>>j&1){
				if((ll)t*p[j]>n){
					t=-1;
					break;
				}
				t*=p[j];
				s++;
			}
		}
		if(t!=-1){
			if(s%2) res+=n/t;// n/t表示1~n中能被t整除的个数,奇加偶减 
			else res-=n/t;
		}
	}
}

博弈论

Nim游戏(尼姆博弈)

/*
	Nim游戏 
*/ 
#include<iostream>
#include<algorithm>

using namespace std;
const int N=100010;


int main(){
	int n,res=0;
	cin>>n;
	while(n--){
		int x;
		cin>>x;
		res^=x;
	}
	if(res) puts("Yes");
	else puts("No");
	return 0;
}

台阶-Nim游戏(尼姆的应用)

/*
	台阶-Nim游戏 
*/ 
#include<iostream>
#include<algorithm>

using namespace std;
const int N=100010;


int main(){
	int n,res=0;
	cin>>n;
	for(int i=1;i<=n;i++){
		int x;
		cin>>x;
		if(x%2) res^=x;
	}
	if(res) puts("Yes");
	else puts("No");
	return 0;
}

集合-Nim游戏(seg函数将问题转换为尼姆博弈)

拆分-Nim游戏

日期问题

回文日期

传送门
在这里插入图片描述
在这里插入图片描述

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>

using namespace std;
int months[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
bool check(int date) //判断日期是否合法
{
    int year = date / 10000;
    int month = date % 10000 / 100;
    int day = date % 100;
    if(!day || month < 1 || month > 12 ) return false;
    if(month != 2 && day >months[month]) return false;
    if(month == 2)
    {
        if((year%4==0&&year%100!=0)||(year%400==0)) //闰年特判
        {
            if(day > 29) return false;
        }
        else 
        {
            if(day > 28) return false;
        }
    }
    return true;
}
bool check1(string s)  //判断是否是回文日期
{
    int len = s.size();
    for(int i = 0, j = len - 1; i < j ; i++,j--)  //双指针
    {
        if(s[i] != s[j]) return false;
    }
    return true;
}
bool check2(string s)  //判断是否是ABABBABA 型的回文日期
{
    if(check1(s))  //首先该日期要满足回文格式
    {
       if(s[0]!=s[2] || s[1]!= s[3] || s[0] == s[1]) return false;
       return true;
    }
}
int main()
{
    int date,flag=0;
    cin>>date;
    for(int i = date + 1; ;i++)
    {
        if(check(i))
        {
            string s = to_string(i);
            if(check1(s)&&!flag)   //输出回文日期
            {
                cout<<i<<endl;
                flag = 1;  //标记一下,避免多次输出
            }
            if(check2(s))  //输出ABABBABA 型的回文日期
            {
                cout<<i<<endl;
                return 0;
            }
        }
    }
    return 0;
}

日期问题模板

const int months[]={0,31,28,31,30,31,30,31,31,30,31,30,31};

int is_leap(int year){
	if(year%4=0 && year%100 || year%400=0)
		return 1;
	return false; 
}

int get_days(int y,int m){
	if(m==2) return 28+is_leap(y);
	return months[m];
}

动态规划

背包问题

01背包

/*
	01背包
	f[i][j]表示在前i个物品中选总体积不超过j的所有方案数中价值最大
	一维优化第i层是由i-1层递推过来的 
*/ 
#include<iostream>
#include<algorithm>

using namespace std;
const int N=10010;
int f[N];
int n,m;

int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		int v,w;
		cin>>v>>w;
		for(int j=m;j>=v;j--){
			f[j]=max(f[j],f[j-v]+w);
		}
	}
	cout<<f[m];
	return 0;
}

完全背包问题

/*
	完全背包问题
	一维优化从小到大 
*/ 
#include<iostream>
#include<algorithm>

using namespace std;
const int N=10010;
int f[N];
int n,m;

int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		int v,w;
		cin>>v>>w;
		for(int j=v;j<=m;j++){
			f[j]=max(f[j],f[j-v]+w);
		}
	}
	cout<<f[m];
	return 0;
}

多重背包

1.朴素版

/*
	多重背包朴素版 
*/ 
#include<iostream>
#include<algorithm>

using namespace std;
const int N=10010;
int f[N] ;
int n,m;

int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		int v,w,s;
		cin>>v>>w>>s;
		for(int j=m;j>=v;j--){
			for(int k=0;k<=s&&k*v<=j;k++)
				f[j]=max(f[j],f[j-k*v]+k*w);	
		}
	}
	cout<<f[m];
	return 0;
}

2.二进制优化

/*
	多重背包二进制优化 
*/ 
#include<iostream>
#include<algorithm>

using namespace std;
const int N=10010;
int f[N];
int n,m;
int v[N],w[N],cnt;

int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		int v1,w1,s;
		cin>>v1>>w1>>s;
		for(int i=1;i<=s;i*=2){
			v[cnt]=i*v1;
			w[cnt]=i*w1;
			s-=i;
			cnt++;
		}
		if(s){
			v[cnt]=s*v1;
			w[cnt]=s*w1;
			cnt++;
		}
	}
	
	n=cnt;//看成有cnt件物品,每件物品体积和价值分别是v[i],w[i]
	 
	for(int i=0;i<cnt;i++){
		for(int j=m;j>=v[i];j--)
			f[j]=max(f[j],f[j-v[i]]+w[i]);	
	}
	
	cout<<f[m];
	return 0;
}

3.队列优化

/*
	多重背包队列优化 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;
int f[N],g[N];
int n,m,hh,tt=-1;
int q[N];

int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		int v,w,s;
		cin>>v>>w>>s;
		memcpy(g,f,sizeof g);
		for(int j=0;j<v;j++){
			hh=0,tt=-1;
			for(int k=j;k<=m;k+=v){
				if(hh<=tt&&q[hh]<k-s*v) hh++;//处理队头,包括k在内长度为s 
				while(hh<=tt&&g[q[tt]]+(k-q[tt])/v*w<=g[k]) tt--;//处理队尾
				if(hh<=tt) f[k]=max(g[k],g[q[hh]]+(k-q[hh])/v*w);
				q[++tt]=k;
			}
		}
	}
	cout<<f[m];
	return 0;
}

分组背包

/*
	分组背包问题 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;
int f[N];
int v[N][N],w[N][N],s[N];
int n,m;

int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		cin>>s[i];
		for(int j=0;j<s[i];j++){
			cin>>v[i][j]>>w[i][j];
			for(int k=m;k>=v[i][j];k--){
				f[k]=max(f[k],f[k-v[i][j]]+w[i][j]);
			}
		}	
	}
	cout<<f[m];
	return 0;
}

线性dp

数字三角形

/*
	数字三角形
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;
int f[N][N],a[N][N];
int n,m;

int main(){
	cin>>n;
	for(int i=0;i<=n;i++)
		for(int j=0;j<=i+1;j++)
			f[i][j]=-0x3f3f3f3f;
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			cin>>a[i][j];
			
	f[1][1]=a[1][1];
	for(int i=2;i<=n;i++)
		for(int j=1;j<=i;j++)
			f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];
			
	int res=0;
	for(int i=1;i<=n;i++) res=max(res,f[n][i]);	
	cout<<res;	
	return 0;
}

子序列

最长上升子序列

1.朴素版

/*
	最长上升子序列朴素版 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;
int f[N],a[N];
int n,m;

int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++){
		f[i]=1;
		for(int j=1;j<i;j++){
			if(a[i]>a[j])
				f[i]=max(f[i],f[j]+1);
		}
	}
	int res=0;
	for(int i=1;i<=n;i++) res=max(res,f[i]);
	cout<<res;
	return 0;
}
/*
	最长上升子序列优化版
	直接用一个数组将目前的上升子序列存储起来,
	然后用二分找第一个比当前数大的数,并用当前数将其替换 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;
int q[N],a[N];//用q数组存放已有上升子序列的值 
int n,m,len;

int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++){
		int l=0,r=len;
		while(l<r){
			int mid=l+r+1>>1;
			if(a[i]>q[mid]) l=mid;//找小于a[i]的最后一个数下标 
			else r=mid-1; 
		} 
		len=max(len,r+1);
		q[r+1]=a[i];
	}
	cout<<len;
	
	return 0;
}
最长公共子序列
/*
	最长公共子序列
	f[i][j]表示a中前i个字母,b中前j个字母的所有方案中子序列最长的值 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;
int f[N][N];
char a[N],b[N]; 
int n,m;

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=m;i++) cin>>b[i];
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			f[i][j]=max(f[i-1][j],f[i][j-1]);
			if(a[i]=a[j])
				f[i][j]=max(f[i][j],f[i-1][j-1]+1);
		}
	}
	cout<<f[n][m];
	
	return 0;
}
最长公共上升子序列

1.朴素版

/*
	最长公共上升子序列朴素版 
	f[i][j]表示a中前i个数,b中前j个数并且以b[j]结尾的所有方案中公共上升子序列最长的值 
	分为两种情况
	1.子序列不包含a[i] 则f[i][j]=max(f[i][j],f[i-1][j]) 其实就是f[i][j]=f[i-1][j]
	2.包含a[i] (即a[i]==b[j]),子序列倒数第二个数是b序列中1~j-1中的某一个或者是为空。 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;
int f[N][N];
int a[N],b[N]; 
int n;

int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			f[i][j]=max(f[i-1][j],f[i][j]);//不包括a[i] 
			
			if(a[i]==b[j]){
				f[i][j]=max(f[i][j],1);//倒数第二个数不存在 
				for(int k=1;k<j;k++){
					if(b[k]<b[j]){//倒数第二个数为b[k]时 
						f[i][j]=max(f[i][j],f[i-1][k]+1);
					}
				}
			}
		}
	}
	int res=0
	for(int i=1;i<=n;i++) res=max(res,f[n][i]);
	cout<<res;
	return 0;
}
/*
	最长公共上升子序列优化版 
	f[i][j]表示a中前i个数,b中前j个数并且以b[j]结尾的所有方案中公共上升子序列最长的值 
	分为两种情况
	1.子序列不包含a[i] 则f[i][j]=max(f[i][j],f[i-1][j]) 其实就是f[i][j]=f[i-1][j]
	2.包含a[i] (即a[i]==b[j]),子序列倒数第二个数是b序列中1~j-1中的某一个或者是为空。 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;
int f[N][N];
int a[N],b[N]; 
int n;

int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			int maxv=0;//维护maxv是满足b[k]<a[i]的情况下,最大的f[i-1][k] 
			f[i][j]=f[i-1][j];//不包括a[i] 
			if(b[j]==a[i]) f[i][j]=max(f[i][j],maxv+1);//倒数第二个数不存在时,为1 
			if(b[j]<a[i]) maxv=max(maxv,f[i-1][j]);//若存在倒数第二个数,则a[i]==b[j]
		}
	}
	int res=0
	for(int i=1;i<=n;i++) res=max(res,f[n][i]);
	cout<<res;
	return 0;
}

编辑距离

最短编辑距离
/*
	最短编辑距离
	f[i][j]表示将a[1~i]变成b[1~j]的操作方式的最小值。对象是a数组
	f[i][j]是有a数组增加b[j],减a[i],改a[i]这三种情况转变过来的
	1.a数组要后面要增加b[j]得到f[i][j],则前提是要a[1~i]已经变成了b[1~j-1],
	  所以f[i][j]=f[i][j-1]+1(增加的操作) 
	2.a数组要减a[i]得到f[i][j]的前提是a[1~i-1]已经变成了b[1~j],f[i][j]=f[i-1][j]+1
	3.若a[i]==b[j],则f[i][j]=f[i-1][j-1],若a[i]!=b[j],则f[i][j]=f[i-1][j-1]+1;
	  即f[i][j]=min( f[i][j], f[i-1][j-1]+(a[i]!=b[j]) )
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;
int f[N][N];
char a[N],b[N]; 
int n,m;

int main(){
	cin>>n>>a+1;
	cin>>m>>b+1;
	for(int i=0;i<=n;i++) f[i][0]=i;
	for(int i=0;i<=m;i++) f[0][i]=i;
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
			f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j]));
		}
		
	cout<<f[n][m];
	return 0;
}
编辑距离应用
/*
	编辑距离
	f[i][j]表示将a[1~i]变成b[1~j]的操作方式的最小值。对象是a数组
	f[i][j]是有a数组增加b[j],减a[i],改a[i]这三种情况转变过来的
	1.a数组要后面要增加b[j]得到f[i][j],则前提是要a[1~i]已经变成了b[1~j-1],
	  所以f[i][j]=f[i][j-1]+1(增加的操作) 
	2.a数组要减a[i]得到f[i][j]的前提是a[1~i-1]已经变成了b[1~j],f[i][j]=f[i-1][j]+1
	3.若a[i]==b[j],则f[i][j]=f[i-1][j-1],若a[i]!=b[j],则f[i][j]=f[i-1][j-1]+1;
	  即f[i][j]=min( f[i][j], f[i-1][j-1]+(a[i]!=b[j]) )
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;
int f[N][N];
char a[N][N];//给定的n个字符串 
int n,m;

int edit(char a[],char b[]){
	int la=strlen(a+1),lb=strlen(b+1);
	//注意初始化 
	for(int i=0;i<=la;i++) f[i][0]=i;
	for(int i=0;i<=lb;i++) f[0][i]=i;
	
	for(int i=1;i<=la;i++)
		for(int j=1;j<=lb;j++){
			f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
			f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j])); 
		}
	return f[la][lb];
}

int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++) cin>>a[i]+1;
	while(m--){
		char b[N];
		int limit;
		cin>>b+1>>limit;
		int res=0;
		for(int i=0;i<n;i++){
			if(edit(a[i],b)<=limit) res++;
		}
		cout<<res<<endl;
	}
	
	return 0;
}

区间dp

/*
	区间dp,石子合并,求合并一堆石子的最小能量(f数组注意初始化很大),只能合并相邻的两堆石子
	1.先遍历区间大小2<=len<=n
	2.然后遍历左端点,右端点是循环的判断条件.l=1,r=i+len-1;
	3.再遍历此区间由那两个区间合并过来的,相邻的端点k,满足l<=k<r; 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;
int f[N][N];
int a[N],s[N];
int n;

int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
	
	for(int len=2;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int l=i,r=i+len-1;
			f[l][r]=1e8;//注意初始化 
			for(int k=l;k<r;k++){
				f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
			}
		}
	}
	cout<<f[1][n]<<endl;
	return 0;
}

计数类dp

/*
	整数划分。n=n1+n2+n3+....+nk.其中n1>=n2>=n3>=....>=nk.求整数n有多少种划分的方法
	从1~n种取数,使得其之和为n
	转换为完全背包问题 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010,mod=1e9+7;
int f[N];
int n;

int main(){
	cin>>n;
	f[0]=1;
	for(int i=1;i<=n;i++){//i表示体积 
		for(int j=i;j<=n;j++)
			f[j]=(f[j]+f[j-i])%mod;//这里的f是前i个物品中总体积为j的总方案数(数量) 
	}
	cout<<f[n];
	return 0;
}

数位统计类dp

/*
	数位统计类dp,计数问题
	求a到b直接所有数字中0~9出现的次数[a,b] 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=10010;

int dgt(int n){//求n有多少位 
	int res=0;
	while(n){
		res++;
		n/=10;
	}
	return res;
}

int count(int n,int i){//求1~n所有的数中i的个数之和 
	int res=0,d=dgt(n);//d是整数n的位数 
	for(int j=1;j<=d;j++){//从右往左第j位上数字i出现的次数 
		int p=pow(10,j-1),l=n/p/10,r=n%p,dj=n/p%10;//p位第j位右边的权值,l位左边的数,人为右边的数,dj为第j位上的数字 
		//当左边的数小于整数n第j位左边的数时 
		if(i) res+=l*p;//不是求0的次数时 0~l 共l+1 
		if(!i&&l) res+=(l-1)*p;//求0的次数并且左边不为零 1~l 共l 
		//当左边数等于时,要看第j位上的数和i的情况 
		if(dj>i&&(i||l)) res+=p;//i和l不能同时为零 
		if(dj==i) res+=r+1;//0~r共r+1 
	} 
	return res;
}

int main(){
	int a,b;
	while(cin>>a>>b,a||b){
		if(a>b) swap(a,b);
		for(int i=0;i<=9;i++){
			cout<<count(b,i)-count(a-1,i)<<' ';
		}
		cout<<endl;
	}
	return 0;
}

状态压缩dp (二进制表示状态)

蒙德里安的梦想

/*
	状态压缩dp
	蒙德里安的梦想
	f[i][j]表示已经将前i-1列摆好,且从i-1列伸到第i列的状态为j的方案数 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N=12,M=1<<N;
long long f[N][M];
bool st[M];
int n,m;

int main(){
	while(cin>>n>>m,n||m){
		//预处理状态是否满足要求 
		for(int i=0;i<1<<n;i++){
			int cnt=0;
			st[i]=true;
			for(int j=0;j<n;j++){
				if(i>>j&1){
					if(cnt&1){
						st[i]=false;
						break;
					}
					cnt=0;
				}
				else cnt++;
			}
			if(cnt&1) st[i]=false;
			
		}
		
		memset(f,0,sizeof f);
		f[0][0]=1;//列数从0开始 
		for(int i=1;i<=m;i++){
			for(int j=0;j<1<<n;j++)
				for(int k=0;k<1<<n;k++)
					if((j&k)==0&&st[j|k])
						f[i][j]+=f[i-1][k];
		}
		
		cout<<f[m][0]<<endl;
	}
	return 0;
}

优化版去除无效状态

/*
	状态压缩dp
	蒙德里安的梦想优化版 
	f[i][j]表示已经将前i-1列摆好,且从i-1列伸到第i列的状态为j的方案数 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;
const int N=12,M=1<<N;
long long f[N][M];
bool st[M];
int n,m;
vector<int> s[M];

int main(){
	while(cin>>n>>m,n||m){
		//预处理状态是否满足要求 
		for(int i=0;i<1<<n;i++){
			int cnt=0;
			bool is=true; 
			for(int j=0;j<n;j++){
				if(i>>j&1){
					if(cnt&1){
						is=false;
						break;
					}
					cnt=0;//注意重新初始化 
				}
				else cnt++;
			}
			if(cnt&1) is=false;
			st[i]=is;
		}
		
		for(int i=0;i<1<<n;i++){
			s[i].clear();//特别要注意初始化清空,不然会影响下面的数据 
			for(int j=0;j<1<<n;j++){
				if((i&j)==0&&st[i|j])
					s[i].push_back(j);
			}
		}
			
		
		memset(f,0,sizeof f);
		f[0][0]=1;//列数从0开始 
		for(int i=1;i<=m;i++){
			for(int j=0;j<1<<n;j++)
				for(auto k:s[j]){
					f[i][j]+=f[i-1][k];
				}
		}
		
		cout<<f[m][0]<<endl;
	}
	return 0;
}

最短Hamilton路径

/*
	状态压缩dp
	最短Hamilton路径:0~n-1个点,求从0到n-1每个点都不重不漏的经过了的最短路径
	属性最小时注意f数组开始要很大,方案数是注意有个是1. 
	f[i][j]中i表示状态,j表示从0走到了j(终点)。因此i的第j个位置是1(从0开始) 
	状态转移方程,看第二点是那个 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;
const int N=20,M=1<<N;
int f[M][N];//注意第一个是M表示状态,如果反了则没有输出 
int w[N][N];//邻接矩阵存储图 

int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
		 	cin>>w[i][j];
	memset(f,0x3f,sizeof f);
	f[1][0]=0;

	for(int i=0;i<1<<n;i++)
		for(int j=0;j<n;j++){
			if(i>>j&1){
				for(int k=0;k<n;k++){
					if(i>>k&1){
						f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j]);
					}
				}
			}
		} 
		
	cout<<f[(1<<n)-1][n-1];	
	return 0;
}

树形dp

/*
	树形dp(dfs的思想来遍历树) 
	没有上司的舞会 
	状态表示有两种情况:
	f[u][0]所有以u为根的子树中选择,并且不选u这个点的方案 属性max 
	f[u][1]所有以u为根的子树中选择,并且选u这个点的方案 
	状态转移:
	当u不选时,其子节点可选可不选
	当u选时,其子节点不选 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;
const int N=6010;
int n;
int h[N],e[N],ne[N],idx,happy[N];
int f[N][2];
bool isf[N];//找父节点用 

void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u){
	f[u][1]=happy[u];
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		dfs(j);//先把子树的f解决 
		f[u][1]+=f[j][0];
		f[u][0]+=max(f[j][1],f[j][0]);
	}
} 
 
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>happy[i];
	memset(h,-1,sizeof h);
	for(int i=0;i<n-1;i++){
		int a,b;
		cin>>a>>b;
		add(b,a);
		isf[a]=true;
	}
	int root=1;
	while(isf[root]) root++;
	dfs(root);
	int res=0;
	res=max(f[root][0],f[root][1]);
	cout<<res;
	return 0;
}

记忆化搜索

/*
	记忆化搜索(dfs的思想遍历) 
	滑雪 
	状态表示:
	f[i][j]表示滑到[i,j]位置的所有路径中最长的滑雪轨迹(经过的点数) 
	状态转移:
	怎么到f[i][j]这个位置的,倒数第二个位置向上下左右滑行到此点 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;
const int N=6010;
int n,m;
int f[N][N],g[N][N];
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};


int dp(int x,int y){//求滑到[x,y]位置最长的路径,即求f[i][j] 

	int &v=f[x][y];
	if(v!=-1) return v;//前面滑到过,直接返回最长路径
	
	v=1;//没滑到过,当前本身一个点,然后推前一个满足条件的点,一共四种情况,用for循环枚举
	for(int i=0;i<4;i++){
		int a=x+dx[i],b=y+dy[i];
		if(a>=1&&a<=n&&b>=1&&b<=m&&g[a][b]<g[x][y])//从[a,b]滑到[x,y]
			v=max(v,dp(a,b)+1); 
	}

	return v; 
} 
 
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>g[i][j];
			
	memset(f,-1,sizeof f);//然后sizeof后面空间弄错了,也会出现bug,答案为零 
	//如果输出f数组全为零,则要反应过来memset函数还没有生效,检查这里面的错误
	int res=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			res=max(res,dp(i,j));

	cout<<res<<endl;
	return 0;
}

贪心

区间问题

区间选点

/*
	区间选点
	选尽可能少的点,使得每个区间至少有一个点 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;
const int N=100010;
int n;
struct Range{
	int l,r;
	bool operator<(const Range &w) const{//按区间的右端点排序 
		return r<w.r;
	}
}range[N];
 
int main(){
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>range[i].l>>range[i].r;
		
	sort(range,range+n);
	int res=0;ed=-2e9;
	for(int i=0;i<n;i++)
		if(range[i].l>ed){
			res++;
			ed=range[i].r;
		}
	cout<<res;
	return 0;
}

最大不相交区间数量

代码同上题一样

/*
	最大不相交区间数量
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;
const int N=100010;
int n;
struct Range{
	int l,r;
	bool operator<(const Range &w) const{//按区间的右端点排序 
		return r<w.r;
	}
}range[N];
 
int main(){
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>range[i].l>>range[i].r;
		
	sort(range,range+n);
	int res=0;ed=-2e9;
	for(int i=0;i<n;i++)
		if(range[i].l>ed){
			res++;
			ed=range[i].r;
		}
	cout<<res;
	return 0;
}

区间分组

用小根堆找最左的右端点

/*
	区间分组
	将区间分为若干个组,使得每组内部区间两两之间没有交集 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;
const int N=100010;
int n;
struct Range{
	int l,r;
	bool operator<(const Range &w) const{//按区间的右端点排序 
		return l<w.l;
	}
}range[N];
 
int main(){
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>range[i].l>>range[i].r;
		
	sort(range,range+n);
	priority_queue<int,vector<int>,greater<int> > h;
	for(int i=0;i<n;i++)
	{
		auto r=range[i];
		if(h.empty()||r.l<=h.top()) h.push(r.r);//加入小根堆中,重新开一个组
		else{
			h.pop();
			h.push(r.r);
		} 
	}
	cout<<h.size();
	return 0;
}

区间覆盖

双指针求最右边的右端点

/*
	区间覆盖
	给定n个区间,和一个被覆盖区间[s,t],求需要几个区间才能将其覆盖 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;
const int N=100010;
int n;
struct Range{
	int l,r;
	bool operator<(const Range &w) const{//按区间的右端点排序 
		return l<w.l;
	}
}range[N];
 
int main(){
	int st,ed;
	cin>>st>>ed>>n;
	for(int i=0;i<n;i++)
		cin>>range[i].l>>range[i].r;
		
	sort(range,range+n);
	bool suc=false;
	int res=0;
	for(int i=0;i<n;i++){
		int j=i,r=-2e9;
		while(j<n&&st>=range[j].l){
			j++;
			r=max(r,range[j].r);
		}
		if(r<st){
			res=-1;
			break;
		}
		res++;
		if(r>=ed){
			suc=true;
			break;
		}
		st=r;//st变成最右边的端点 
		i=j-1;
	} 
	if(!suc) res=-1;
	cout<<res;
	return 0;
}

Huffman树(合并果子)

小根堆的应用

/*
	huffman树 
	合并果子 
	n堆果子,每堆ai个,合并两堆消耗的能量为两堆果子重量,从小到大合并 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;

int main(){
	int n,a;
	cin>>n;
	priority_queue<int ,vector<int>,greater<int> > h;
	for(int i=0;i<n;i++){
		cin>>a;
		h.push(a);
	}
	int res=0;
	while(h.size()>1){
		int a=h.top();
		h.pop();
		int b=h.top();
		h.pop();
		res+=a+b;
		h.push(a+b);
	}
	cout<<res;
	return 0;
}

排序不等式(排队打水)

/*
	排序不等式
	排队打水
	求总体等待时间最短 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;
const int N=100000;
int a[N];

int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++) cin>>a[i];
	sort(a,a+n);
	int res=0;
	for(int i=0;i<n;i++){
		res+=a[i]*(n-i-1);//第一个打水人的时间,后面n-1个人,每人要等一次。 
	}
	cout<<res;
	return 0;
}

绝对值不等式(货仓选址)

/*
	绝对值不等式
	货仓选址:选一个点到每个点的距离之和最小(从小往大中间的点) 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;
const int N=100000;
int a[N];

int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++) cin>>a[i];
	sort(a,a+n);
	int res=0;
	for(int i=0;i<n;i++){
		res+=abs(a[i]-a[n/2]); 
	}
	cout<<res;
	return 0;
}

推公式(耍杂技的牛)

/*
	推公式
	耍杂技的牛:垂直堆叠,求每只牛上面所有牛的重量-此牛的强壮程度的最大值(危险指数)最小 
*/ 
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;
typedef pair<int,int> pii;
const int N=100000;
pii a[N];

int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		int s,w;
		cin>>w>>s;//重量和强壮程度 
		a[i]={s+w,w};
	}
	sort(a,a+n);
	int res=-2e9,sum=0;
	for(int i=0;i<n;i++){
		int s=a[i].first-a[i].second,w=a[i].second;
		res=max(res,sum-s);
		sum+=w; 
	}
	cout<<res;
	return 0;
}
  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值