四点共面
已知四个点,判断是否共面。可以求出三条向量,利用向量积求立方体体积,若体积是0,则四点共面。
求出的三个向量:
d
1
=
(
x
1
,
y
1
,
z
1
)
d_1=(x_1,y_1,z_1)
d1=(x1,y1,z1)
d
2
=
(
x
2
,
y
2
,
z
2
)
d_2=(x_2,y_2,z_2)
d2=(x2,y2,z2)
d
3
=
(
x
3
,
y
3
,
z
3
)
d_3=(x_3,y_3,z_3)
d3=(x3,y3,z3)
V
=
d
1
∗
d
2
∗
d
3
V=d_1*d_2*d_3
V=d1∗d2∗d3
V
=
(
x
1
y
2
z
3
+
x
2
y
3
z
1
+
x
3
y
1
z
2
)
−
(
x
1
y
3
z
2
+
x
2
y
1
z
3
+
x
3
y
2
z
1
)
V=(x_1y_2z_3+x_2y_3z_1+x_3y_1z_2)-(x_1y_3z_2+x_2y_1z_3+x_3y_2z_1)
V=(x1y2z3+x2y3z1+x3y1z2)−(x1y3z2+x2y1z3+x3y2z1)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node{
int x,y,z;
}e[5],d[5];
int main(){
int t;
cin>>t;
while(t--){
for(int i=0;i<4;i++){
cin>>e[i].x>>e[i].y>>e[i].z;
if(i){
d[i].x=e[i-1].x-e[i].x;
d[i].y=e[i-1].y-e[i].y;
d[i].z=e[i-1].z-e[i].z;
}
}
ll d1=d[1].x*d[2].z*d[3].y+d[1].y*d[2].x*d[3].z+d[1].z*d[2].y*d[3].x;
ll d2=d[1].x*d[2].y*d[3].z+d[2].x*d[3].y*d[1].z+d[3].x*d[1].y*d[2].z;
if(d1==d2){
cout<<"Yes\n";
}else{
cout<<"No\n";
}
}
//system("pause");
return 0;
}
好像是因为贴代码的改版了,之前屯的发不了了。
线段相交
线段相交,是给出两两顶点,确定一条线段,判断两线段是否相交。
重点就在于判断线段是否相交,之前的数学思想是两直线是否相交,那么联立方程组,如果不平行,就一定相交。但线段的交点判断可不仅仅是对平行的判断。(之前的圆与三角形题目中也有求线段是否相交的要求)。
走迷宫
数据范围很小,只需要回溯记录每条路径即可。
#include<bits/stdc++.h>
using namespace std;
int dir[4][2]={
{1,0},{-1,0},{0,1},{0,-1}
};
int maps[10][10];
int vis[10][10];
int n,m,t;
int sx,sy;
int fx,fy;
int ans=0;
bool check(int x,int y){
if(x>n||y>m||x<1||y<1||maps[x][y]) return false;
return true;
}
void dfs(int x,int y){
if(x==fx&&y==fy){
ans++;
}
for(int i=0;i<4;i++){
int xx=x+dir[i][0];
int yy=y+dir[i][1];
if(!vis[xx][yy]&&check(xx,yy)){
vis[xx][yy]=1;
dfs(xx,yy);
vis[xx][yy]=0;
}
}
}
int main(){
cin>>n>>m>>t;
cin>>sx>>sy>>fx>>fy;
while(t--){
int x,y;
cin>>x>>y;
maps[x][y]=1;
}
vis[sx][sy]=1;
dfs(sx,sy);
cout<<ans<<endl;
//system("pause");
return 0;
}
迷宫游戏
之前(最短路径)那道题的dijkstra的板子。
用优先队列优化,优先队列用的好,dijkstra的时间复杂度就低。
在网上还找到了一份结构体的,但是结构体的重载很麻烦,不同的编译器可能会报错。用<pair>很方便。
思路就是我们只需要去寻找最短路就好,在路径传递的过程中把选中的点的权值加进去。如果有多条最短路,即有两个点间的两条路径的边权相同,就去比较一下谁的点权大,谁大就加谁的点权。最后维护出来到终点的mon[]数组,记录的就是最短路径下的最大点权。
#include<bits/stdc++.h>
using namespace std;
const int maxn=505;
const int INF=0x3f3f3f3f;
int n,m,s,t;
int d[maxn];
int val[maxn];
int mon[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();
}
void dijkstra(int x){
d[x]=0;
mon[x]=val[x];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >Q;
Q.push(make_pair(d[x],x)); //优先队列自动根据pair中的first从大到小排序(默认), 放入 负距离,则 边短的先弹出
while(!Q.empty()){
int now=Q.top().second; //显然,now 为一个起点
Q.pop();
if(now==t) break;
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;
mon[v]=val[v]+mon[now];
Q.push(make_pair(d[v],v));
}else if(d[v]==d[now]+E[now][j].second){
if(mon[v]<val[v]+mon[now]){
mon[v]=val[v]+mon[now];
Q.push(make_pair(v,d[v]));
}
}
}
}
return;
}
int main(){
cin>>n>>m>>s>>t;
s++;t++;
init();
for(int i=1;i<=n;i++) cin>>val[i];
int u,v,w;
for(int i=0;i<m;i++){
cin>>u>>v>>w;
u++;v++;
E[u].push_back(make_pair(v,w));
E[v].push_back(make_pair(u,w));
}
dijkstra(s);
cout<<d[t]<<" "<<mon[t]<<endl;
//system("pause");
return 0;
}
小明和最短路
思路:在矩阵快速幂中把矩阵乘法改成Floyd,让前行乘后列变成判断两点间的最短路径。每次可以增加一条边,最后得到的结果保证经过了k条边,需要注意的是无解的情况,特判一下即可。
注意51nod的oj定义结构体的全局变量不会初始成0,wa了好几次。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=105;
const int INF=0x3f3f3f3f;
struct Mat{
ll m[maxn][maxn];
};
ll n,m,k;
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]=INF;
}
}
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]=min(b.m[i][j],x.m[i][k]+y.m[k][j]);
}
}
}
return b;
}
Mat qpow(Mat a,ll b){
Mat res;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(i!=j) res.m[i][j]=INF;
else res.m[i][j]=0;
}
}
while(b){
if(b&1) res=mul(res,a);
a=mul(a,a);
b>>=1;
}
return res;
}
int main(){
scanf("%lld%lld%lld",&n,&m,&k);
Mat a;
memset(a.m,INF,sizeof(a.m));
ll u,v,w;
for(int i=0;i<m;i++){
scanf("%lld%lld%lld",&u,&v,&w);
u--;v--;
a.m[u][v]=min(a.m[u][v],w);
a.m[v][u]=a.m[u][v];
}
ll s,e;
scanf("%lld%lld",&s,&e);
Mat ans=qpow(a,k);
ll pp=ans.m[s-1][e-1];
if(pp==INF) pp=-1;
printf("%lld",pp);
//system("pause");
return 0;
}
编辑距离(Edit Distance)
一直不想开dp。
题面:给两个字符串,有三种操作,添加,替换,删除,求把一个串变成另一个串所需要的最小操作数。
首先要求是两个串,一个变成令一个,并没有规定最后变成哪一个。那么对于添加和删除操作,我们可以把删除看成对另一个串的添加’#’,然后让两个串对齐,把添加也看成添加’#’,表示两个串对齐。(对齐是除这些带’#'的字符位置,两个字符串的其它字符要两两相同)。
那么我们可以分情况讨论:
设状态转移方程为
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j],表示字符串a的前i位与字符串b的前j位相同所需要的最小的操作数。
- 若 a [ i − 1 ] = = b [ j − 1 ] ( 字 符 串 的 下 标 从 0 开 始 , 其 实 表 示 的 是 第 i 个 和 第 j 个 ) a[i-1]==b[j-1](字符串的下标从0开始,其实表示的是第i个和第j个) a[i−1]==b[j−1](字符串的下标从0开始,其实表示的是第i个和第j个),说明字符串a的前i位与字符串b的前j位已经相同,当前位的字符也相同,所以所需要进行的操作数只是之前使字符串a的前i位与字符串b的前j位已经相同的操作数,为 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i−1][j−1].
- 若 a [ i − 1 ] ! = b [ j − 1 ] a[i-1]!=b[j-1] a[i−1]!=b[j−1],说明当前位的字符不同,进行的操作数为 d p [ i − 1 ] [ j − 1 ] + 1 dp[i-1][j-1]+1 dp[i−1][j−1]+1.
- 字符串a的前i位已经与字符串b的前j-1位对齐,我们只需要给b在补齐一个’#'即可,进行的操作数为 d p [ i ] [ j − 1 ] + 1 dp[i][j-1]+1 dp[i][j−1]+1.
- 字符串a的前i-1位已经与字符串b的前j位对齐,我们只需要给a在补齐一个’#'即可,进行的操作数为
d
p
[
i
−
1
]
[
j
]
+
1
dp[i-1][j]+1
dp[i−1][j]+1.
得到总状态转移方程: d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j − 1 ] + ( a [ i ] = = b [ j ] ? 0 : 1 ) , d p [ i − 1 ] [ j ] + 1 , d p [ i ] [ j − 1 ] + 1 ) dp[i][j]=min(dp[i-1][j-1]+(a[i]==b[j]?0:1),dp[i-1][j]+1,dp[i][j-1]+1) dp[i][j]=min(dp[i−1][j−1]+(a[i]==b[j]?0:1),dp[i−1][j]+1,dp[i][j−1]+1)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1010;
ll dp[maxn][maxn];
string a,b;
int main(){
cin>>a>>b;
for(int i=0;i<=a.size();i++) dp[i][0]=i;
for(int j=0;j<=b.size();j++) dp[0][j]=j;
for(int i=1;i<=a.size();i++){
for(int j=1;j<=b.size();j++){
int t=0;
if(a[i-1]!=b[j-1]) t=1;
dp[i][j]=min(dp[i-1][j-1]+t,min(dp[i-1][j]+1,dp[i][j-1]+1));
}
}
cout<<dp[a.size()][b.size()]<<endl;
//system("pause");
return 0;
}
莫比乌斯函数
图片来源于51nod评论区
可以根据题意自己列出来。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll miu(ll a){
ll x=a,tmp=a;
int cnt=0,now=0;
for(ll j=2;j*j<=x;j++){
now=0;
if(x%j==0){
while(x%j==0) now++,x/=j;
if(now>1) return 0;
cnt++;
}
}
if(x!=1) cnt++;
return (cnt&1)?-1:1;
}
int main(){
ll n;
cin>>n;
cout<<miu(n)<<endl;
system("pause");
return 0;
}
线段相交
重点写方法感谢大佬的博客
首先介绍两种方法:
- 把线段看成直线,求出两直线交点,然后看交点在不在线段上。若平行或者共线特判。
- 用向量叉积来判断
接下来重点介绍向量叉积的方法。
向量叉积分成两部分:快速排斥实验 与 跨立实验 。
快速排序实验:
先判断x与y坐标的投影是否有重合。投影就算是投到坐标轴上,看两个点的位置。判断一个线段中较大的点的
x
1
x_1
x1是否小于另一个线段中较小的点的
x
2
x_2
x2,若是,则两线段一定没有交点。y同理。
int Compare(Point A,Point B){
if(A.x<B.x||(A.x==B.x&&A.y<B.y)) return -1;
if(A.x==B.x&&A.y==B.y) return 0;
return 1;
}
跨立实验:矢量叉积。
矢量叉积是计算线段与直线相交的核心方法。
设
A
=
(
x
1
,
y
1
)
,
B
=
(
x
2
,
y
2
)
A=(x_1,y_1),B=(x_2,y_2)
A=(x1,y1),B=(x2,y2),则
A
A
Ax
B
=
x
1
y
2
−
x
2
y
1
B=x_1y_2-x_2y_1
B=x1y2−x2y1,得到的结果为AB所在平面的法向量。
double Cross(Vector A,Vector B){
return A.x*B.y-A.y*B.x;
}
性质:
- 若 A A Ax B < 0 B<0 B<0,则A在B的逆时针方向。
- 若 A A Ax B > 0 B>0 B>0,则A在B的顺时针方向。
- 若 A A Ax B = 0 B=0 B=0,则A与B共线,可能同向可能反向。
判断
(
A
−
D
)
×
(
C
−
D
)
⋅
(
B
−
D
)
×
(
C
−
D
)
<
0
(A-D)\times (C-D)·(B-D)\times(C-D)<0
(A−D)×(C−D)⋅(B−D)×(C−D)<0
(
C
−
A
)
×
(
B
−
A
)
⋅
(
D
−
A
)
×
(
B
−
A
)
<
0
(C-A)\times (B-A)·(D-A)\times(B-A)<0
(C−A)×(B−A)⋅(D−A)×(B−A)<0
×
\times
×表示叉乘,· 表示点积,若两个向量叉乘在一侧,时针同顺或同逆;若两个向量叉乘在异侧,时针一顺一逆。由此判断两个线段有没有交点。
特殊情况,若 ( A − D ) × ( C − D ) ⋅ ( B − D ) × ( C − D ) = 0 (A-D)\times (C-D)·(B-D)\times(C-D)=0 (A−D)×(C−D)⋅(B−D)×(C−D)=0,两向量垂直,恰好有一个交点。
int sgn(double x){
if(fabs(x)<eps) return 0;
else return x<0?-1:1;
}
bool Cross_segment(Point a,Point b,Point c,Point d){
double c1=Cross(b-a,c-a),c2=Cross(b-a,d-a);
double d1=Cross(d-c,a-c),d2=Cross(d-c,b-c);
return sgn(c1)*sgn(c2)<0&&sgn(d1)*sgn(d2)<=0||sgn(c1)*sgn(c2)<=0&&sgn(d1)*sgn(d2)<0;
}
全部代码:
#include<bits/stdc++.h>
using namespace std;
const double pi=acos(-1.0);
const double eps=1e-8;
struct Point{
double x,y;
Point(){}
Point(double x,double y):x(x),y(y){}
Point operator-(Point B){return Point(x-B.x,y-B.y);}
};
typedef Point Vector;
int sgn(double x){
if(fabs(x)<eps) return 0;
else return x<0?-1:1;
}
int Compare(Point A,Point B){
if(A.x<B.x||(A.x==B.x&&A.y<B.y)) return -1;
if(A.x==B.x&&A.y==B.y) return 0;
return 1;
}
double Cross(Vector A,Vector B){
return A.x*B.y-A.y*B.x;
}
bool Parallel(Vector A,Vector B){
return sgn(Cross(A,B))==0;
}
bool Cross_segment(Point a,Point b,Point c,Point d){
double c1=Cross(b-a,c-a),c2=Cross(b-a,d-a);
double d1=Cross(d-c,a-c),d2=Cross(d-c,b-c);
return sgn(c1)*sgn(c2)<0&&sgn(d1)*sgn(d2)<=0||sgn(c1)*sgn(c2)<=0&&sgn(d1)*sgn(d2)<0;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
Point a,b,c,d;
cin>>a.x>>a.y>>b.x>>b.y>>c.x>>c.y>>d.x>>d.y;
if(!Compare(a,c)||!Compare(a,d)||!Compare(b,c)||!Compare(b,d)){
cout<<"Yes\n";
continue;
}
if(Parallel(b-a,d-c)){
if(sgn(Cross(b-a,c-a))==0){
if(Compare(a,b)==1) swap(a,b);
if(Compare(c,d)==1) swap(c,d);
if(Compare(c,b)<=0&&Compare(b,d)<=0) cout<<"Yes\n";
else if(Compare(c,a)<=0&&Compare(a,d)<=0) cout<<"Yes\n";
else cout<<"No\n";
}else{
cout<<"No\n";
}
continue;
}
if(Cross_segment(a,b,c,d)) cout<<"Yes\n";
else cout<<"No\n";
}
//system("pause");
return 0;
}