无向图最小生成树
Kruskal算法,基于边选择的方法,适合稠密图。模板:
#include<bits/stdc++.h>
using namespace std;
const int maxn=50050;
struct node{
int fr,to,w;
}e[maxn];
bool cmp(const node &a,const node &b){
return a.w<b.w;
}
int father[maxn];
int find(int x){
if(father[x]==x) return x;
else father[x]=find(father[x]);
return father[x];
}
void add(int a,int b){
father[find(a)]=find(b);
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&e[i].fr,&e[i].to,&e[i].w);
}
sort(e+1,e+1+m,cmp);
for(int i=1;i<=n;i++){
father[i]=i;
}
int ans=0;
for(int i=1,j=1;j<n;i++){
if(find(e[i].fr)==find(e[i].to)) continue;
else{
ans+=e[i].w;
add(e[i].fr,e[i].to);
j++;
}
}
printf("%d",ans);
//system("pause");
return 0;
}
Prim算法,基于点选择的方法,适合稀疏图。模板:
(本题并不太适用,但也可以AC)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1010;
const double eps=1e-6;
const int INF=0x3f3f3f3f;
typedef long long ll;
int g[maxn][maxn];
int mst[maxn][maxn];
int lowcost[maxn];
int n,m;
int prim(int x){
int sum=0;
for(int i=1;i<=n;i++){
lowcost[i]=g[x][i];
}
lowcost[x]=-1;
for(int i=1;i<=n-1;i++){
int mind=INF,minid=0;
for(int j=1;j<=n;j++){
if(lowcost[j]<mind&&lowcost[j]!=-1){
mind=lowcost[j];
minid=j;
}
}
sum+=mind;
lowcost[minid]=-1;
for(int j=1;j<=n;j++){
if(lowcost[j]>g[minid][j]){
lowcost[j]=g[minid][j];
}
}
}
return sum;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
g[i][j]=INF;
}
}
int u,v,w;
for(int i=0;i<m;i++){
scanf("%d%d%d",&u,&v,&w);
g[u][v]=g[v][u]=w;
}
int sum=prim(1);
printf("%d\n",sum);
//system("pause");
return 0;
}
威佐夫游戏V2
考虑精度问题的威佐夫游戏。
我们在前文中知道:
第
一
个
值
=
差
值
∗
(
s
q
r
t
(
5
)
+
1
)
/
2
第一个值 = 差值 *(sqrt(5)+1)/2
第一个值=差值∗(sqrt(5)+1)/2
而
(
s
q
r
t
(
5
)
+
1
)
/
2
(sqrt(5)+1)/2
(sqrt(5)+1)/2在保留精度时保留不到27位小数,那最后算出来的值因为精度原因造成与n不相等,发生错误。
应手动模拟大数乘法,把18位拆成两个9位储存。
模拟两位数乘以三位数的手写运算。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m;
ll tmp[3]={618033988,749894848,204586834};
ll mod=1000000000;
int main(){
int t;
cin>>t;
while(t--){
cin>>m>>n;
if(m<n) swap(n,m);
ll c=m-n;
ll ta=c/mod,tb=c%mod;
ll tp=tb*tmp[2];
tp=ta*tmp[2]+tb*tmp[1]+tp/mod;
tp=ta*tmp[1]+tb*tmp[0]+tp/mod;
tp=c+ta*tmp[0]+tp/mod;
if(tp==n) cout<<"B"<<endl;
else cout<<"A"<<endl;
}
//system("pause");
return 0;
}
已经开始不按照顺序写题解了。
既希望快点完成整个零级题目,又怕囫囵吞枣,知识都是一知半解。
乘法逆元
参考文章1
参考文章2
题目要求:
k
∗
m
%
n
=
1
k*m\%n=1
k∗m%n=1,求k的最小正值。
根据公式推导
a
∗
x
%
n
=
1
%
n
,
a
=
m
,
x
=
k
a*x\%n=1\%n,a=m,x=k
a∗x%n=1%n,a=m,x=k我们要求的k就是最小正数解x的值。
a
∗
x
+
b
∗
y
=
1
,
b
=
n
a*x+b*y=1,b=n
a∗x+b∗y=1,b=n意思为在模n相等的情况下,其实就是相差了y个n。
最终根据扩展欧几里得求出x的值,由于我们需要正整数的解,如果是负数还需要再进一步处理:
x
=
(
x
%
n
+
n
)
%
n
x=(x\%n+n)\%n
x=(x%n+n)%n
证明完毕。
后面还有一些知识点:
- 求逆元用扩展欧几里得
- 若模数是质数可以直接用费马小定理,若不是质数用欧拉函数。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int exgcd(int a,int b,int &x,int &y){
int d=a;
if(b!=0){
d=exgcd(b,a%b,y,x);
y-=(a/b)*x;
}else{
x=1,y=0;
}
return d;
}
int moi(int a,int m){
int x,y;
exgcd(a,m,x,y);
return (m+x%m)%m;
}
int main(){
int n,m;
cin>>n>>m;
cout<<moi(n,m)<<endl;
//system("pause");
return 0;
}
斐波那契数列的第N项(矩阵快速幂)
[
f
(
n
)
f
(
n
−
1
)
]
=
[
1
1
1
0
]
[
f
(
n
−
1
)
f
(
n
−
2
)
]
\left[ \begin{matrix} f(n) \\ f(n-1) \\ \end{matrix} \right] = \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \\ \end{matrix} \right] \left[ \begin{matrix} f(n-1) \\ f(n-2) \\ \end{matrix} \right]
[f(n)f(n−1)]=[1110][f(n−1)f(n−2)]
[
f
(
n
)
f
(
n
−
1
)
]
=
[
1
1
1
0
]
[
1
1
1
0
]
[
f
(
n
−
2
)
f
(
n
−
3
)
]
\left[ \begin{matrix} f(n) \\ f(n-1) \\ \end{matrix} \right] = \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \\ \end{matrix} \right] \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \\ \end{matrix} \right] \left[ \begin{matrix} f(n-2) \\ f(n-3) \\ \end{matrix} \right]
[f(n)f(n−1)]=[1110][1110][f(n−2)f(n−3)]
[
f
(
n
)
f
(
n
−
1
)
]
=
[
1
1
1
0
]
n
−
1
[
f
(
2
)
f
(
1
)
]
\left[ \begin{matrix} f(n) \\ f(n-1) \\ \end{matrix} \right] = \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \\ \end{matrix} \right] ^{n-1} \left[ \begin{matrix} f(2) \\ f(1) \\ \end{matrix} \right]
[f(n)f(n−1)]=[1110]n−1[f(2)f(1)]根据公式我们可以知道,要求斐波那契数列的第n项,就对矩阵做n-1次乘法运算,故使用矩阵快速幂。
啊啊啊,题目给的是1e9+9,一直模的是1e9+7,调了半个小时。之前矩阵乘法的板子,又试了一遍,无问题。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2;
const int mod=1e9+9;
typedef long long ll;
struct Mat{
ll m[maxn][maxn];
};
ll n,m;
Mat mul(Mat x,Mat y){
Mat b;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
b.m[i][j]=0;
}
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
for(int k=0;k<n;k++){
b.m[i][j]=(b.m[i][j]%mod+(x.m[i][k]*y.m[k][j])%mod)%mod;
}
}
}
return b;
}
Mat qpow(Mat a,ll b){
Mat res=a;
if(b<0) return res;
while(b){
if(b&1) res=mul(res,a);
a=mul(a,a);
b>>=1;
}
return res;
}
int main(){
Mat a,c,t;
t.m[0][0]=1;
t.m[1][0]=0;
t.m[0][1]=0;
t.m[1][1]=1;
ll idx;
cin>>idx;
n=2;m=2;
a.m[0][0]=1;
a.m[0][1]=1;
a.m[1][0]=1;
a.m[1][1]=0;
c=qpow(a,idx-2);
c=mul(c,t);
cout<<c.m[0][0]<<endl;
//system("pause");
return 0;
}
全排列
要先排一下字典序呀。然后用C++自带的全排列函数即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char s[10];
int main(){
scanf("%s",s);
int len=strlen(s);
sort(s,s+len);
printf("%s\n",s);
while(next_permutation(s,s+len)){
printf("%s\n",s);
}
//system("pause");
return 0;
}
圆与三角形
要求是给你圆的圆心和半径,给你三角形的三个顶点,问是否相交。
首先很容易想到,求三角形顶点和圆的圆心之间的距离,如果小于半径,则说明该顶点在圆内。
- 如果三个顶点都在圆内,则一定不相交。
- 如果有的顶点在圆外,有的顶点在圆内,则一定相交。
- 如果恰好有顶点到圆心之间的距离为半径,则该点在圆上。
- 如果三个点都在圆外,那就要分情况讨论了,可能相交,也可能不相交。(在判断的时候记得去找线段到圆心的距离,而非直线到圆心的距离,若线段到圆心的距离大于半径,就算延长线的距离到圆心的距离小于半径,那也不属于三角形)
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
double xc,yc,R;
double xa,xb,xt,ya,yb,yt;
double dis(double x,double xx,double y,double yy){
return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy));
}
bool Dis(double x1,double y1,double x2,double y2){
ll a,b,c,dis1,dis2,an1,an2;
if(x1==x2){
a=1;b=0;c=-x1;
}else if(y1==y2){
a=0;b=1;c=-y1;
}else{
a=y1-y2;
b=x2-x1;
c=x1*y2-x2*y1;
}
dis1=a*xc+b*yc+c;
dis1*=dis1;
dis2=(a*a+b*b)*R;
if(dis1>dis2){
return 0;
}
an1=(xc-x1)*(x2-x1)+(yc-y1)*(y2-y1);
an2=(xc-x2)*(x1-x2)+(yc-y2)*(y1-y2);
if(an1>0&&an2>0) return true;
return false;
}
int main(){
int t;
cin>>t;
while(t--){
scanf("%lf%lf%lf",&xc,&yc,&R);
scanf("%lf%lf",&xa,&ya);
scanf("%lf%lf",&xb,&yb);
scanf("%lf%lf",&xt,&yt);
int d1,d2,d3;
R=R*R;
d1=(xc-xa)*(xc-xa)+(yc-ya)*(yc-ya);
d2=(xc-xb)*(xc-xb)+(yc-yb)*(yc-yb);
d3=(xc-xt)*(xc-xt)+(yc-yt)*(yc-yt);
if(d1>R&&d2>R&&d3>R){
int dis1=Dis(xa,ya,xb,yb);
int dis2=Dis(xa,ya,xt,yt);
int dis3=Dis(xb,yb,xt,yt);
if(dis1||dis2||dis3) printf("Yes\n");
else printf("No\n");
}
else if(d1<R&&d2<R&&d3<R) printf("No\n");
else printf("Yes\n");
}
//system("pause");
return 0;
}
最短路径(dijkstra优先队列优化板子)
#include<bits/stdc++.h>
using namespace std;
const int maxn=505;
const int INF=0x3f3f3f3f;
int n,m;
int d[maxn];
vector<pair<int,int> >E[maxn];
void init(){
for(int i=0;i<maxn;i++) d[i]=INF;
for(int i=0;i<maxn;i++) E[i].clear();
}
int main(){
while(cin>>n>>m){
init();
int a,b,x;
for(int i=0;i<m;i++){
cin>>a>>b>>x;
E[a].push_back(make_pair(b,x));
E[b].push_back(make_pair(a,x));
}
int s,t;
cin>>s>>t;
d[s]=0;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >Q;
Q.push(make_pair(d[s],s)); //优先队列自动根据pair中的first从大到小排序(默认), 放入 负距离,则 边短的先弹出
while(!Q.empty()){
int now=Q.top().second; //显然,now 为一个起点
Q.pop();
for(int j=0;j<E[now].size();j++){
int v=E[now][j].first; //显然,v 为 now 可以到达的一个终点
if(d[v]>d[now]+E[now][j].second){ //如果 已知的v 到 s 的距离 大于 now这个点到 s 的距离 + now 这个点到 v 的距离, 更新一下, 并放入优先队列
d[v]=d[now]+E[now][j].second;
Q.push(make_pair(d[v],v));
}
}
}
if(d[t]==INF) printf("-1\n");
else printf("%d\n",d[t]);
}
//system("pause");
return 0;
}
生成树
题面意思:有一个无向图,告诉你要选哪些边,通过操作(每次可以让某个边的边权加一)使选择的这些边得到的最小生成树成立。
思路:只可以增加边权的话一定是在生成树的数边上增加边权的(别令造一些边出来)。我们考虑一条非树边,它的端点在生成树中一定是连通的,那么我们考虑这个环,如果这条非树边一定不再生成树里面,那么它在这个环里面的权值一定是最大的,这里环里面的最大的边可以有很多条,我们只需要求出每一条树的路径上的最大权值边即可,考虑lca求解,最后枚举非树边,计算差值加入答案。
叶子节点的路径
对每个节点的子节点,按照节点编号从小到大排序,这样在遍历树的过程中,路径的字典序是从小到大的。 遍历过程中,用一个数组保存保存从根到当前节点路径中的所有节点。遍历到叶子节点时直接输出即可。
定一个动态数组往里存,该点的子节点都有哪些(对子节点排序,从小到大,满足题目中要求的字典序),然后深搜,一条条搜出来,输出结果。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=2021;
vector<int> g[maxn];
int n;
int q[maxn];
void dfs(int u,int dep){
q[dep]=u;
if(g[u].size()==0){
for(int i=1;i<=dep;i++){
cout<<q[i]<<" ";
}
cout<<endl;
return ;
}
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
dfs(v,dep+1);
}
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
g[a].push_back(b);
}
for(int i=1;i<=n;i++){
sort(g[i].begin(),g[i].end());
}
dfs(1,1);
//system("pause");
return 0;
}
凌波微步
建图共 8个点,Me, Home ,传送门1起点,传送门1终点,传送门2起点,传送门2终点,传送门3起点,传送门3终点。传送门起点与终点的边权为 10,其他点之间的边权为两点间的曼哈顿距离,即 |Xv−Xu|+|Yv−Yu|。 建图后跑最短路即可。
还是看了一眼别人的代码,有细节没想到(应该也可以想到,懒了)
一共四对起点终点,每对里面是一组坐标。进去深搜,从起点开始,当k==2时表示传送门全使了一遍。(0,1,2)思考为什么必须要用传送门,要是传送门的距离不如我走过去的曼哈顿距离,那就不用,不用的代码就是那句很突兀的dfs(k+1,x,y,sum);
这样回溯的话表示没有选择传送门,且k增加了。
补充:如果是正常的最短路,是点与点连接,现在是坐标与坐标连接,就没办法写到二维数组中,可以写到四维数组中储存边权,但是坐标点的范围是1e9,过于大,内存一定会爆掉。可以把坐标标号,然后离散化,只记录当前几个标号点间的边权,一共八个点。但是计算曼哈顿路径的时候比较麻烦(也不麻烦)。吐血调试一个小时,代码写在下面了,是因为Floyd写错了,(k,i,j),k一直写在了第三层,应该写在第一层。写在第三层最后状态转移不回最初的起点到终点。
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
typedef long long ll;
struct node{
ll x,y;
ll a,b;
}me,home,Node[4];
ll ans=INF;
int vis[4];
ll dis(ll x,ll y,ll a,ll b){
return abs(x-a)+abs(y-b);
}
void dfs(int k,ll x,ll y,ll sum){
if(k==2){
ans=min(ans,sum+dis(x,y,home.x,home.y));
return ;
}
dfs(k+1,x,y,sum);
for(int i=0;i<3;i++){
if(!vis[i]){
vis[i]=1;
dfs(k+1,Node[i].x,Node[i].y,sum+dis(x,y,Node[i].a,Node[i].b)+10);
dfs(k+1,Node[i].a,Node[i].b,sum+dis(x,y,Node[i].x,Node[i].y)+10);
vis[i]=0;
}
}
}
int main(){
cin>>me.x>>me.y>>home.x>>home.y;
for(int i=0;i<3;i++){
cin>>Node[i].x>>Node[i].y>>Node[i].a>>Node[i].b;
}
dfs(0,me.x,me.y,0);
cout<<ans<<endl;
//system("pause");
return 0;
}
AC题解版:
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
typedef long long ll;
struct node{
ll x,y;
ll a,b;
}me,home,Node[4];
ll ans=INF;
int vis[4];
ll dis(ll x,ll y,ll a,ll b){
return abs(x-a)+abs(y-b);
}
void dfs(int k,ll x,ll y,ll sum){
if(k==2){
ans=min(ans,sum+dis(x,y,home.x,home.y));
return ;
}
dfs(k+1,x,y,sum);
for(int i=0;i<3;i++){
if(!vis[i]){
vis[i]=1;
dfs(k+1,Node[i].x,Node[i].y,sum+dis(x,y,Node[i].a,Node[i].b)+10);
dfs(k+1,Node[i].a,Node[i].b,sum+dis(x,y,Node[i].x,Node[i].y)+10);
vis[i]=0;
}
}
}
int main(){
cin>>me.x>>me.y>>home.x>>home.y;
for(int i=0;i<3;i++){
cin>>Node[i].x>>Node[i].y>>Node[i].a>>Node[i].b;
}
dfs(0,me.x,me.y,0);
cout<<ans<<endl;
system("pause");
return 0;
}