模板
模板:
矩阵快速幂:
//矩阵快速幂模板 #include <map> #include <queue> #include <string> #include<iostream> #include<stdio.h> #include<string.h> #include <algorithm> #include <math.h> typedef long long ll; using namespace std; typedef pair<ll,ll> pii; #define mem(a,x) memset(a,x,sizeof(a)) #define debug(x) cout << #x << ": " << x << endl; #define rep(i,n) for(int i=0;i<(n);++i) #define repi(i,a,b) for(int i=int(a);i<=(b);++i) #define repr(i,b,a) for(int i=int(b);i>=(a);--i) const int maxn=2e5+1010; #define inf 0x3f3f3f3f #define sf scanf #define pf printf const int mod=1e9+7; const int MOD=10007; inline int read() { int x=0; bool t=false; char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')t=true,ch=getchar(); while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar(); return t?-x:x; } /*priority_queue<ll , vector<ll> , greater<ll> > mn;//上 小根堆 小到大 priority_queue<ll , vector<ll> , less<ll> > mx;//下 大根堆 大到小 map<ll,ll>mp;*/ ll n,m,t,l,r,p; struct mat{ ll a[110][110];//矩阵的大小 }; mat in,out;//in输入的矩阵,out输出的矩阵 mat mull(mat x, mat y){ mat c; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) c.a[i][j]=0; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) c.a[i][j]=c.a[i][j]%mod+x.a[i][k]*y.a[k][j]%mod; return c; } mat pow(mat x,ll y){ mat ans; for(int i=1;i<=n;i++)//初始化成单位矩阵 就是对角线唯一,其余为零 ans.a[i][i]=1; while(y){ if(y&1) ans=mull(ans,x); y>>=1; x=mull(x,x); } return ans; } int main(){ sf("%lld%lld",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) sf("%lld",&in.a[i][j]); out=pow(in,m); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(j!=n) printf("%lld ",out.a[i][j]); else printf("%lld\n",out.a[i][j]); return 0; }
最短路:
floyed:
#include <map> #include <queue> #include <string> #include<iostream> #include<stdio.h> #include<string.h> #include <algorithm> #include <math.h> typedef long long ll; typedef unsigned long long ull; using namespace std; typedef pair<ll,ll> pii; #define mem(a,x) memset(a,x,sizeof(a)) #define debug(x) cout << #x << ": " << x << endl; #define rep(i,n) for(int i=0;i<(n);++i) #define repi(i,a,b) for(int i=int(a);i<=(b);++i) #define repr(i,b,a) for(int i=int(b);i>=(a);--i) const int maxn=2e5+1010; #define inf 0x3f3f3f3f #define sf scanf #define pf printf const int mod=998244353; const int MOD=10007; inline int read() { int x=0; bool t=false; char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')t=true,ch=getchar(); while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar(); return t?-x:x; } /*priority_queue<ll , vector<ll> , greater<ll> > mn;//上 小根堆 小到大 priority_queue<ll , vector<ll> , less<ll> > mx;//下 大根堆 大到小 map<ll,ll>mp;*/ ll n,m,t,l,r,p; ll sum,ans,res,cnt,flag,maxx,minn; bool isprime[maxn]; ll a[maxn],b[maxn],c[maxn]; ll dis[maxn],vis[maxn]; ll dp[1010][1010]; string str,s; #define read read() //k是中间结点,i是起点,j是终点 void floyed(ll dp[][1010]){ for(int k=0;k<n;k++){ for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ if(dp[i][j]>dp[i][k]+dp[k][j]){ dp[i][j]=dp[i][k]+dp[k][j]; } } } } } int main(){ sf("%lld%lld",&n,&m); for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ dp[i][j]=inf; } dp[i][i]=0; //如果需要判断又没没有回路把这个删掉。 } for(int i=0;i<m;i++){ sf("%d%d%d",&x,&y,&z); dp[x][y]=dp[y][x]=z; } floyed(dp); //可以判断是否存在负环 for(int i=0;i<n;i++){ if(mp[i][i]<0){ flag=true; } } if(flag) cout<<"存在负环"<<endl; else cout<<"不存在负环"<<endl; // cout<<mp[0][n-1]<<endl; return 0; }
二分图:
二分图定义:简单的理解就是两个点集,其中在内部没有连接的边。
其充要条件是:图 G 中至少存在两个点,且图中所有回路的长度均为偶数。
完全二分图:其中一个点集中与另一点集中的点一定有相对应的边连接,也就是没有孤立的点。
二分图中匹配的概念:
在边集中,任意两条边都没有公共顶点。
二分图中最大匹配的概念:
在匹配的条件下,使得边集中边最多。
二分图中的完全匹配概念:
简单来说,对于一个二分图,左点集中的每一个点都与右点集的一个点匹配,或者右点集中的每一个点都与左点集的一个点匹配。
二分图中的完美匹配概念:
对于一个二分图,左点集与右点集的点数相同,若存在一个匹配,包含左点集、右点集的所有顶点,则称为完美匹配。
简单来说,对于一个二分图,左点集中的每一个点都与右点集的一个点匹配,并且右点集中的每一个点都与左点集的一个点匹配。
二分图的判定:
int n;//节点数 vector<int> G[N];//G[i]表示i节点邻接的点 int color[N];//color[i]=0,1,2 表i节点不涂颜色、涂白色、涂黑***ool bipartite(int u)//判断无向图是否可二分 { for(int i=0;i<G[u].size();i++)//枚举结点 { int v=G[u][i];//相邻节点 if(color[v]==color[u])//若两结点颜色相同,无法二分 return false; if(!color[v])//若结点没涂颜色 { color[v]=3-color[u];//改变结点颜色,使v的颜色与u的颜色对立 if(!bipartite(v))//若点v不满足二分图话,则无法二分 return false; } } return true; }
二分图-匈牙利算法:
交替路:
从一个未匹配点出发经过未匹配边->未匹配点->未匹配边....形成的路径。
未匹配点,边的概念是在二分图匹配中如果匹配的两个点叫匹配点,其边就是匹配边,未匹配的点就是未匹配点,其边就是未匹配边。
定义:设 M 为二分图 G 已匹配边的集合,若 P 是图 G 中一条连通两个未匹配点的路径(起点在 X/Y 部,终点在 Y/X 部),且属 M 的边(匹配边)与不属 M 的边(非匹配边)在 P 上交替出现,则称 P 为相对 M 的一条增广路径。
由于增广路的第一条边是没有参与匹配的,第二条边参与了匹配,...,最后一条边没有参与匹配,并且起点和终点还没有被选择过,显然 P 有奇数条边。
其中这样理解第一条边不参加匹配,第二条边参加匹配,因为你要建增广路,方式是交替路的方式,所以说如果第一条边认为是匹配边的话第一天边就不能连起来,就像下面的9->4如果任务他是匹配边,这个边就建不上,之后如果认为4->8为未匹配边的话,4这个点已经是匹配点了,不能再连边了,所以之间把他认为是匹配边,儿8还是未匹配点,这样才能继续之后的建图。
简单来说,从一个未匹配点出发,走交替路,若途径另一未匹配点(除起点外),则这条交替路称为增广路。
如下图,左图中的一条增广路如右图所示,图中的匹配点均用红色标出
增广路的性质:
1,增广路的长度一定是一定是奇数,起点跟终点一定是分别属于两个点集。均未匹配。保证不成环。
2,未匹配边比匹配边多1,因为是未->已->未->......->已->未。
3,P 经过取反操作可以得到一个更大的匹配 M’。
4,M 为 G 的最大匹配当且仅当不存在相对于 M 的增广路径。3,4不是特别懂先放这吧
增广路定理:
由于增广路中间的匹配节点不存在其他相连的匹配边,因此交换增广路中的匹配边与非匹配边不会破坏匹配的性质。
由增广路性质可知,只要把增广路中的匹配边和非匹配边交换,交换后,图中的匹配边数目比原来多了 1 条。
故而,可以通过不停地找增广路来增加匹配中的匹配边和匹配点,找不到增广路时,即达到最大匹配,这就是增广路定理。
其实说简单点就是扩展路线。使得最长
匈牙利树
匈牙利树一般由 DFS 构造(类似于 DFS 树),其是从一个未匹配点出发进行 DFS(必须走交替路),直到不能再扩展为止。(达到最长路径)
如下图,通过左侧的二分图,进行 DFS 可以得到右侧的树,但这棵树存在一叶结点为非匹配点(7号),而匈牙利树要求所有叶结点均为匹配点,故这棵树不是匈牙利树。
但若原图中不含 7 号结点,那么从 2 号结点出发就会得到一棵匈牙利树,如下图
前导概念:
K-正则图:各顶点的度都是K的无向简单图。
最大匹配数:最大匹配的匹配边数
最大独立集数:选取最多的点集,使得点集中任意两点不相连。
最小点覆盖数:选取最少的点集,使得任意一条边至少有一个端点在点集中。
最大独立集数可以与最小点覆盖数比较理解
性质
最大匹配数=最小覆盖数
最大独立集数=顶点数-最大匹配数
最大匹配数/最小覆盖数
DFS:代码短容易理解 路线多时时间复杂度要高。
int n,m; bool vis[N];//判断是否在交替路 vector<int> G[N];//存边 int link[N];//存连接点 bool dfs(ll x){ for(int i=0;i<G[x].size();i++){ int j=G[x][i]; if(!vis[j]){ vis[j]=1; if(link[j]==-1||dfs(link[j])){//如果是未匹配点,说明交替路是增广路 link[j]=x;//交换路径 return 1; } } } return 0; //不存在增广路,返回失败 } int print(){ int ans=0; for(int i=1;i<=n;i++){//从左侧开始每个结点找一次增广路 memset(vis,0,sizeof vis); if(dfs(i)) ans++;//找到一条增广路 } printf("%lld\n",ans); } int main (){ while(~scanf("%lld%lld",&n,&m)){ memset(link,-1,sizeof link);//全部初始化为未匹配点 for(int i=0;i<N;i++)//每次清空 G[i].clear(); while(m--){ int l,r; scanf("%lld%lld",&l,&r); G[l].push_back(r);//建图 无向图 G[r].push_back(l); } print();//输出最大匹配数 } return 0; }
BFS版本
int n,m;//左边点数,右边点数 int vis[N];//vis[i]表示是否在交替路中 int link[N];//存连接点 int pre[N];//存前驱结点 vector<int> G[N];//存边 queue<int> Q; int hungarian(){ memset(vis,-1,sizeof(vis)); memset(pre,-1,sizeof(pre)); memset(link,-1,sizeof(link)); int ans=0;//记录最大匹配数 for(int i=1;i<=n;i++){ if(link[i]==-1){//若点未匹配 pre[i]=-1;//没有前驱 while(!Q.empty())//清空队列 Q.pop(); Q.push(i); bool flag=false; while(!Q.empty() && !flag){ int x=Q.front(); for(int j=0;j<G[x].size();j++){//对x的每个邻接点 if(!flag)//如果falg为真,则说明找到一未匹配点,不必继续下去 break; int y=G[x][j]; if(vis[y]!=i){//不在交替路中 vis[y]=i;//存入交替路 Q.push(link[y]);//交换路径 if(link[y]>=0)//在已匹配点中 pre[link[y]]=x; else {//找到未匹配点,交替路变增广路 flag=true; int d=x; int e=y; while(d!=-1){//找到一个未匹配点,无法构成匈牙利树,让所有点不断的往回更新,重选下一个 int temp=link[d]; link[d]=e; link[e]=d; d=pre[d]; e=temp; } } } } Q.pop(); } if(link[i]!=-1)//统计最大匹配数 ans++; } } return ans; } int main(){ while(scanf("%d%d",&n,&m)!=EOF){ while(m--){ int x,y; scanf("%d%d",&x,&y); G[x].push_back(y); G[y].push_back(x); } printf("%d\n", hungarian());//输出最大匹配数 } return 0; }
最大独立集
int n,m; bool vis[N]; int link[N]; bool G[N][N]; bool dfs(int x){ for(int y=1;y<=m;y++){ if(G[x][y]&&!vis[y]){ vis[y]=true; if(link[y]==-1 || dfs(link[y])){ link[y]=x; return true; } } } return false; } int hungarian(){ int ans=0; for(int i=1;i<=n;i++){ memset(vis,false,sizeof(vis)); if(dfs(i)) ans++; } return ans; } int main(){ while(scanf("%d%d",&n,&m)!=EOF&&(n+m)){ memset(link,-1,sizeof(link)); memset(G,true,sizeof(G)); while(m--){ int x,y; scanf("%d%d",&x,&y); G[x][y]=false;//不满足条件则连一条边 } int mate=hungarian();//最大匹配数 int res=n-mate;//最大独立集 printf("%d\n",res); } return 0; }
与最短路中的dj建图思路有些相似。
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 1010,M = 1e5+10; int n1,n2,m; 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) { for(int i = h[x]; i!=-1; i=ne[i]) // 遍历 x 男孩喜欢所有的女孩 { int j = e[i]; if(!st[j]) // 如果st[j] = true 就不考虑这个女孩 { st[j] = true; // 标记 j 这个女孩,作用是如果这个女孩有男朋友,我们去找她男朋友有没有可能和别的女孩在一起时,就不需要考虑他现对象 j 了 if(match[j] == 0 || find(match[j]))// 女孩还没有对象或女孩的男朋友可以和其他喜欢的女生在一起 { match[j] = x; return true; } } } return false; } int main() { int p; while(~scanf("%d",&p)){ if(p==0) return 0; else cin>>n1>>n2; memset(h,-1,sizeof(h)); memset(e,0,sizeof e); memset(ne,0,sizeof(ne)); memset(match,0,sizeof match); for(int i=1;i<=p;i++) { int a, b; cin>>a>>b; add(a,b); } int res = 0; for(int i=1; i<=n1; i++) { memset(st,false,sizeof(st)); if(find(i)) res++; } cout << res<<endl; } }
回文子序列
马拉车算法(吴老师的分析)
• 对于p[i],如果i<mx,设j是i关于id对称点,如图所示,则基于以下三
种情况,可以求出p[i]的值:
这一点的话细心分析!就可以理解了
• (1)以j为中心的回文串有一部分在以id为中心的回文串之外。因为mx
是以id为中心的最长回文的右边界,所以以i为中心的回文串不可能会
有字符在以id为中心的回文串之外;否则mx就不是以id为中心的最长回
文的右边界。所以,在这种情况下,p[i]=mx–i。
• (2)以j为中心的回文串全部在以id为中心的回文串的内部,则
p[i]=p[j],而且p[i]不可能再增加。
• (3)以j为中心的回文串的左端正好与以id为中心的回文串的左端重合。
则p[i]=p[j]或p[i]=mx–i,并且p[i]还有可能会继续增加,即while
(s_new[i-p[i]]==s_new[i+p[i]]) p[i]++;
• 所以,if (i < mx) p[i] = min(p[2 * id - i], mx- i);其中2*id - i为i关于id的
对称点,即上面的j点,而p[j]表示以j为中心的最长回文半径,因此可
以利用p[j]来加快求解p[i]。
#include<stdio.h> #include<iostream> #include<string.h> #include<algorithm> #define M 1000010 using namespace std; char str[M],str_new[2*M]; //原串和辅助串 int p[2*M],len; //p[i]表示以第 i 个字符为中心的最长回文的半径;字符串长度 len void init() { //构造辅助串 len=strlen(str); //计算字符串长度 str_new[0]='@'; //辅助串的首字符 str_new[1]='#'; //辅助串的间隔字符 for(int i=0; i<len; i++) { //逐个字符地构造辅助串 str_new[i*2+2]=str[i]; str_new[i*2+3]='#'; } str_new[len*2+2]='$'; //辅助串的尾字符 } void Manacher() { //计算和输出最长回文的半径 memset(p,0,sizeof(p)); //p[i]表示以第 i 个字符为中心的最长回文的半径,所有最长回文的半径初始化为 0 int mx=0,di,ans=0; //以 id 为中心的最长回文的右边界为 mx,即 mx=id+p[id],mx 和最长回文的长度 ans 初始化为 0 for(int i=1; i<len*2+2; i++) { //枚举每一个可能的中心字符 if(mx>i) //根据 i 位置在 mx 位置的左侧还是右侧,调整以最长回文的半径的最长回文半径的初始值 p[i]=min(mx-i,p[di*2-i]); else p[i]=1; for(; str_new[i-p[i]]==str_new[i+p[i]]; p[i]++); //以 i 位置为中心计算最长回文半径 p[i] if(p[i]+i>mx) //若以 i 为中心的右边界大于 mx,则中心 id 调整为 i,重新计算右边界 mx mx=p[i]+i,di=i; ans=max(ans,p[i]); //调整最长回文的半径 } printf("%d\n",--ans); //输出最长回文的半径 } int main() { int t=1; //测试用例编号初始化 while(~scanf("%s",str)) { //反复输入字符串,直至输入"END"为止 if(!strcmp(str,"END")) break; printf("Case %d: ",t++); //输出测试用例编号 init(); //构造辅助串 Manacher(); //计算和输出最长回文的半径 } }
hash 循环节
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; char s[1001000];/// 输入字符串 int mod=10009;//模 int len,k=131;///s的长度为len ll now; ll hash[1001000];//hash[i] 存储以第i个字符为尾的前缀的散列值 ll cal(int x,ll y)//计算和返回 y^x%mod 的结果值 { ll re=1;//结果值初始化 while(x)//分析次幂x的没一个二进制位 { if(x&1) re=(re*y)%mod;//若当前位为1,则累乘当前位的权并取模 x>>=1;y=(y*y)%mod; //次幂x右移一位 ,计算该位的权后并取模 } return re; //返回结果值 } bool check(int x) //若所有长度为x的相邻子串对应的散列函数值相等 ,则返回 true 负责返回false { ll cc=cal(x,(ll)k); //计算 k^x%mod for(int i=(x<<1);i<=len;i+=x) //搜索 字符i(2*x<i<len).若任意长度i的子串S_i-x+1...i的散列值不等于长度为x的前缀的散列值,则返回false,否则返回true { if((hash[i]-(hash[i-x]*cc)%mod+mod)%mod!=hash[x]) { return false; } } return true; } int main() { while(1) { scanf("%s",s+1); len=strlen(s+1); if(len==1 && s[1]=='.') { return 0; } for(int i=1;i<=len;i++)/// 计算所有前缀的散列值 { hash[i]=(hash[i-1]*k+s[i])%mod; } for(int i=1;i<=len;i++) /// 枚举可能的子串长度 { if(len%i==0 && check(i)) //若s能过划分出长度i的子串且所有相邻子串的 { printf("%d\n",len/i); break; } } } }
哈希模板
#include <cstdio> #include <iostream> #include <string> #include <algorithm> #include <cstring> #define LL long long using namespace std; const LL base1=131; const LL base2=131; const LL mod1=1e9+7; const LL mod2=1e9+9; char ss[10][10010]; struct node{ LL hash1,hash2; }a[10011]; LL make_hash1(char s[]) { LL ans=0; for(int i=0;i<strlen(s);i++) ans=(ans*base1+(LL)(s[i]))%mod1; return ans; } LL make_hash2(char s[]) { LL ans=0; for(int i=0;i<strlen(s);i++) ans=(ans*base2+(LL)(s[i]))%mod2; return ans; } int main() { int n; scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%s",ss[i]),a[i].hash1=make_hash1(ss[i]),a[i].hash2=make_hash2(ss[i]); cout<<"***********"<<endl; for(int i=1;i<=n;i++) { cout<<ss[i]<<" "<<a[i].hash1<<" "<<a[i].hash2<<endl; } return 0; }//求子串哈希值hash=((hash[r]−hash[l−1]∗mod^(r−l+1))%mod+mod)%mod
KMP模板
#include<bits/stdc++.h> using namespace std; const int Maxn=1000005; char s1[Maxn],s2[Maxn]; int l1,l2,fail[Maxn]; void Fail(){//构造fail数组 for(int i=2,j=0;i<=l2;++i){ while(j&&s2[j+1]!=s2[i])j=fail[j]; if(s2[j+1]==s2[i])++j; fail[i]=j; } } void Match(){ for(int i=1,j=0;i<=l1;++i){ while(j&&s2[j+1]!=s1[i])j=fail[j]; if(s2[j+1]==s1[i])++j; if(j==l2){ //找到一个串,自行维护 j=fail[j]; } } } int main(){ scanf("%s%s",s1+1,s2+1); l1=strlen(s1+1); l2=strlen(s2+1); Fail(); Match(); return 0; }
凸包
#include <map> #include <queue> #include <string> #include<iostream> #include<stdio.h> #include<string.h> #include <algorithm> #include <math.h> typedef long long ll; typedef unsigned long long ull; using namespace std; typedef pair<ll,ll> pii; #define mem(a,x) memset(a,x,sizeof(a)) #define debug(x) cout << #x << ": " << x << endl; #define rep(i,n) for(int i=0;i<(n);++i) #define repi(i,a,b) for(int i=int(a);i<=(b);++i) #define repr(i,b,a) for(int i=int(b);i>=(a);--i) const int maxn=2e5+1010; const double pi=acos(-1.0); #define inf 0x3f3f3f3f #define sf scanf #define pf printf const int mod=998244353; const int MOD=10007; struct point { double x,y; point(){ } point(int a,int b){ x=a;y=b; } point operator - (const point b)const { return point(x-b.x,y-b.y); } double operator * (const point b)const { return x*b.x+y*b.y; } }point1; point p[maxn]; int top=1,n,m,res[maxn]; bool cmp(point a,point b){ if(a.x!=b.x) return a.x<b.x; return a.y<b.y; } double dis(point a,point b){///求两点的距离 return sqrt((a-b)*(a-b)); } bool multi(point a,point b,point c){ return (b.x - a.x)*(c.y - a.y) >= (c.x - a.x)*(b.y - a.y); } void Graham(int n){ int i,len; sort(p,p+n,cmp); if(n==0) return; res[0]=0; if(n==1) return; res[1]=1; if(n==2) return; res[2]=2; for(int i=2;i<n;i++){ while(top&&multi(p[i],p[res[top]],p[res[top-1]])) top--; res[++top]=i; } len=top; res[++top]=n-2; for(int i=n-3;i>=0;i--){ while(top!=len&&multi(p[i],p[res[top]],p[res[top-1]])) top--; res[++top]=i; } } int main(){ cin>>n; for(int i=0;i<n;i++) sf("%lf%lf",&p[i].x,&p[i].y); Graham(n); double sum=0.0; for(int i=0;i<top-1;i++){ sum+=fabs(p[res[i]].x*p[res[i+1]].y-p[res[i]].y*p[res[i+1]].x); } sum+=fabs(p[res[top-1]].x*p[res[0]].y-p[res[top-1]].y*p[res[0]].x); sum=sum/2; pf("%d\n",int(sum/50)); return 0; }
素数筛
int pri[N+9>>1],now; bool vis[N+9]; void init(){ for(int i=2;i<=N;i++){ if(!vis[i])pri[++now]=i; for(int j=1;j<=now&&pri[j]*i<=N;j++){ vis[pri[j]*i]=1; if(i%pri[j]==0)break; } } }