非精写版-51nod基础训练(4)

四点共面

已知四个点,判断是否共面。可以求出三条向量,利用向量积求立方体体积,若体积是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=d1d2d3 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位相同所需要的最小的操作数。

  1. a [ i − 1 ] = = b [ j − 1 ] ( 字 符 串 的 下 标 从 0 开 始 , 其 实 表 示 的 是 第 i 个 和 第 j 个 ) a[i-1]==b[j-1](字符串的下标从0开始,其实表示的是第i个和第j个) a[i1]==b[j1](0,ij),说明字符串a的前i位与字符串b的前j位已经相同,当前位的字符也相同,所以所需要进行的操作数只是之前使字符串a的前i位与字符串b的前j位已经相同的操作数,为 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1].
  2. a [ i − 1 ] ! = b [ j − 1 ] a[i-1]!=b[j-1] a[i1]!=b[j1],说明当前位的字符不同,进行的操作数为 d p [ i − 1 ] [ j − 1 ] + 1 dp[i-1][j-1]+1 dp[i1][j1]+1.
  3. 字符串a的前i位已经与字符串b的前j-1位对齐,我们只需要给b在补齐一个’#'即可,进行的操作数为 d p [ i ] [ j − 1 ] + 1 dp[i][j-1]+1 dp[i][j1]+1.
  4. 字符串a的前i-1位已经与字符串b的前j位对齐,我们只需要给a在补齐一个’#'即可,进行的操作数为 d p [ i − 1 ] [ j ] + 1 dp[i-1][j]+1 dp[i1][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[i1][j1]+(a[i]==b[j]?0:1),dp[i1][j]+1,dp[i][j1]+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评论区**
图片来源于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=x1y2x2y1,得到的结果为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 (AD)×(CD)(BD)×(CD)<0 ( C − A ) × ( B − A ) ⋅ ( D − A ) × ( B − A ) < 0 (C-A)\times (B-A)·(D-A)\times(B-A)<0 (CA)×(BA)(DA)×(BA)<0 × \times ×表示叉乘,· 表示点积,若两个向量叉乘在一侧,时针同顺或同逆;若两个向量叉乘在异侧,时针一顺一逆。由此判断两个线段有没有交点。
在这里插入图片描述

特殊情况,若 ( A − D ) × ( C − D ) ⋅ ( B − D ) × ( C − D ) = 0 (A-D)\times (C-D)·(B-D)\times(C-D)=0 (AD)×(CD)(BD)×(CD)=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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值