基本算法设计技术

蛮力法

kmp串匹配问题

kmp一般用于解决:字符串匹配,例如 找出模式串a在主串b中的位置。

强调:
字符串匹配一般有如下两种方式:
1.朴素法

2. kmp算法

一、题目描述

AcWing-4187. 剪花布条

二、分析与代码

分析
该题就是使用kmp算法查找出主串中具有的模式串的数量。首先应该求出模式串的next数组,求next数组具有两种思路代码:

1.对每个字母分别求其前面字符组成串的最大相同前后缀长度,得出相应的next数组值。

//模式串ch下标从1开始
void get_next()
{
    Next[1]=0;          
    if(ch[2]=='\0')     //如果模式串只有一个字符
        return;
    Next[2]=1;
    
    int i,len,j;
    for(i=3;ch[i]!='\0';i++)
    {
        for(len=i-2;len>=1;len--)
        {
            for(j=1;j<=len;j++)
                if(ch[j]!=ch[i+j-len-1])
                    break;
            if(j==len+1)
            {
                Next[i]=j;
                break;
            }
        }
        if(len<1)
            Next[i]=1;
    }
}

注意:该种方法求next数组在比赛中一般都会超时。

2.改进如下:

void get_next()
{
    Next[1]=0;
    if(ch[2]=='\0')     //如果模式串只有一个字符
        return;
    Next[2]=1;
    for(int i=3,k=1;ch[i]!='\0';i++)    //k表示最大相同前后缀长度
    {
        while(k>1&&ch[i-1]!=ch[k])      //如果下标i-1字符与下标k字符不一致,通过next数组回溯
            k=Next[k];
        if(ch[i-1]==ch[k])              //如果下标i-1字符与下标k字符一致,则k值可加1
            k++;
        Next[i]=k;
    }
}

改进后求next数组的时间复杂度大大降低。

具体代码

#include <iostream>

using namespace std;

const int  N=1005;
char str[N];
char ch[N];
int Next[N];

void get_next()
{
    Next[1]=0;
    if(ch[2]=='\0')
        return;
    Next[2]=1;
    //cout<<0<<" "<<1<<" ";
    for(int i=3,k=1;ch[i]!='\0';i++)
    {
        while(k>1&&ch[i-1]!=ch[k])
            k=Next[k];
        if(ch[i-1]==ch[k])
            k++;
        Next[i]=k;
        //cout<<Next[i]<<" ";
    }
    //cout<<endl;
}

int main()
{
    char x;
    int i,j;
    int _count;
    while(1)
    {
        scanf("%c",&x);
        i=1;
        while(x!=' ')
        {
            str[i++]=x;
            scanf("%c",&x);
            if(str[1]=='#'&&x=='\n')
                return 0;
        }
        str[i]='\0';
        
        scanf("%c",&x);
        i=1;
        while(x!='\n')
        {
            ch[i++]=x;
            scanf("%c",&x);
        }
        ch[i]='\0';
        
        get_next();
        
        _count=0;
        i=1;
        while(1)
        {
            j=1;
            while(ch[j]!='\0'&&str[i]!='\0')
            {
                if(str[i]!=ch[j])
                {
                    j=Next[j];
                    if(j==0)
                    {
                        i++;
                        j++;
                    }
                }
                else
                {
                    i++;
                    j++;
                }
            }
            if(ch[j]=='\0')
            {
                _count++;
                //cout<<"i="<<i<<" "<<"j="<<j<<endl;
            }
            if(str[i]=='\0')
                break;
        }
        cout<<_count<<endl;
    }
}

一、题目描述

AcWing-

二、分析与代码

分析

具体代码



分治法

归并排序

一、题目描述

采用归并排序对给定数据排序。

二、分析与代码

分析

采用归并排序即可。

具体代码

#include <iostream>

using namespace std;

const int N=1e6+10;
int cc[N],dd[N];

void Mergesort(int left,int right)
{
    if(left==right)
        return;
        
    int mid;
    mid=(left+right)/2;
    Mergesort(left,mid);
    Mergesort(mid+1,right);
    
    int i=left,j=mid+1,k=left;
    while(i<=mid&&j<=right)
        if(cc[i]<=cc[j])
            dd[k++]=cc[i++];
        else
            dd[k++]=cc[j++];
    while(i<=mid)
        dd[k++]=cc[i++];
    while(j<=right)
        dd[k++]=cc[j++];
    
    for(i=left;i<=right;i++)
        cc[i]=dd[i];
}

int main()
{
    int i,n;
    
    cin>>n;
    for(i=1;i<=n;i++)
        cin>>cc[i];
        
    Mergesort(1,n);
    
    for(i=1;i<=n;i++)
        cout<<cc[i]<<" ";
}


快速排序

一、题目描述

采用快速排序对给定数据排序。

二、分析与代码

分析

采用快速排序即可。

具体代码

#include <iostream>

using namespace std;

const int N=100010;
int n,a[N];

void Quicksort(int left,int right)
{
    if(left>=right)
        return;
    
    int _count,mid=left+right>>1;
    _count=a[mid];
    
    int i=left-1,j=right+1;
    while(i<j)
    {
        do i++;
        while(a[i]<_count);
        do j--;
        while(a[j]>_count);
        if(i<j)
        {
            int t=a[i];
            a[i]=a[j];
            a[j]=t;
        }
    }
    
    Quicksort(left,j);
    Quicksort(j+1,right);
}

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

最大子段和问题

一、题目描述

AcWing-55. 连续子数组的最大和
二、分析与代码

分析

将该数据段分为两部分,有以下三种情况:
1.最大字段和的所有数据在左半部分
2.最大字段和的所有数据在右半部分
3.最大字段和的所有数据在分界线往左半部分与右半部分延伸

具体代码

AcWing-55. 连续子数组的最大和:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int s=0,l=INT_MIN;
        for(int i=0;i<nums.size();i++)
        {
            if(s<0) s = nums[i];
            else s+=nums[i];
            l=max(s,l);
        }
        return l;
    }
};

通用:

#include <iostream>

using namespace std;

const int N=300010;
int cc[N],dd[N];
int n;

int Maxsum(int left,int right)
{
    if(left==right)
        return cc[left];
        
    int leftsum,rightsum,midsum;
    int mid=(left+right)/2;
    
    leftsum=Maxsum(left,mid);
    rightsum=Maxsum(mid+1,right);
    
    int i,_count=0,sum=0,midleft,midright;
    
    for(i=mid;i>=left;i--)
    {
        sum+=cc[i];
        if(_count<sum)
            _count=sum;
    }
    midleft=_count;
    sum=0;
    _count=0;
    for(i=mid+1;i<=right;i++)
    {
        sum+=cc[i];
        if(_count<sum)
            _count=sum;
    }
    midright=_count;
    midsum=midleft+midright;    
    return max(midsum,max(leftsum,rightsum));
}

int main()
{
    cin>>n;
    int i,j;
    for(i=1;i<=n;i++)
        cin>>cc[i];
    cout<<Maxsum(1,n);
}


棋盘覆盖问题

一、题目描述

棋盘覆盖问题

二、分析与代码

分析

分治法划分棋盘,使划分后的子棋盘的大小相同,并且每个子棋盘均包含一个特殊方格,从而将原问题分解为规模较小的棋盘覆盖问题。k>0时,可将2k×2k的棋盘划分为4个2(k-1)×2(k-1)的子棋盘,这样划分后,由于原棋盘只有一个特殊方格,所以,这4个子棋盘中只有一个子棋盘包含该特殊方格,其余3个子棋盘中没有特殊方格。为了将这3个没有特殊方格的子棋盘转化为特殊棋盘,以便采用递归方法求解,可以用一个L型骨牌覆盖这3个较小棋盘的会合处,从而将原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种划分策略,直至将棋盘分割为1×1的子棋盘。

具体代码

#include <iostream>
#include <math.h>

using namespace std;

const int N=1e6+10;
int cc[N][N];
int t=1;

void chessBoard(int tr,int tc,int dr,int dc,int size)
{
    if(size==1)
        return;
    int s=size/2,tl=t++;
    
    if(dr<tr+s&&dc<tc+s)                //对左上的处理
        chessBoard(tr,tc,dr,dc,s);
    else
    {
        cc[tr+s-1][tc+s-1]=tl;
        chessBoard(tr,tc,tr+s-1,tc+s-1,s);
    }
    
    if(dr<tr+s&&dc>=tc+s)                //对右上的处理
        chessBoard(tr,tc+s,dr,dc,s);
    else
    {
        cc[tr+s-1][tc+s]=tl;
        chessBoard(tr,tc+s,tr+s-1,tc+s,s);
    }
    
    if(dr>=tr+s&&dc<tc+s)                //对左下的处理
        chessBoard(tr+s,tc,dr,dc,s);
    else
    {
        cc[tr+s][tc+s-1]=tl;
        chessBoard(tr+s,tc,tr+s,tc+s-1,s);
    }
    
    if(dr>=tr+s&&dc>=tc+s)                //对右下的处理
        chessBoard(tr+s,tc+s,dr,dc,s);
    else
    {
        cc[tr+s][tc+s]=tl;
        chessBoard(tr+s,tc+s,tr+s,tc+s,s);
    }
}

int main()
{
    int a,b,c;
    cin>>a>>b>>c;
    c=pow(2,c);
    chessBoard(1,1,a,b,c);
    for(int i=1;i<=c;i++)
    {
        for(int j=1;j<=c;j++)
            cout<<cc[i][j]<<" ";
        cout<<endl;
    }
    return 0;
}


减治法

二分查找

一、题目描述

LeetCode1-704.二分查找
LeetCode2-68.查找插入位置

二、分析与代码

分析
普通二分查找

具体代码
LeetCode1:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left,right;
        left=0;
        right=nums.size()-1;
        while(left<=right)
        {
            int mid=(left+right)/2;
            if(nums[mid]<target)
                left=mid+1;
            if(nums[mid]>target)
                right=mid-1;
            if(nums[mid]==target)
                return mid;
        }
        return -1;
    }
};

LeetCode2:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        //二分查找
        int left=0,right=nums.size()-1;
        while(left<=right)
        {
            int mid=(left+right)/2;
            if(nums[mid]==target)
                return mid;
            if(nums[mid]<target)
                left=mid+1;
            if(nums[mid]>target)
                right=mid-1;
        }
        //nums[left-1]<target<nums[left]
        return left;	
    }
};

二叉查找树

一、题目描述

LeetCode-108.将有序数组转换为二叉搜索树

二、分析与代码

分析
树的根节点的值为nums[x],x为nums.size()/2。即为数组中间数值,左子树与右子树也如此,故自然想到采用递归。

具体代码

//给出定义的结点结构体、一些定义好的类函数
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        if(nums.size()==0)  return nullptr;
        if(nums.size()==1)  return new TreeNode(nums[0]);
        int mid=nums.size()/2;
        TreeNode *root=new TreeNode(nums[mid]);
        vector <int> x1,x2;
        for(int i=0;i<mid;i++)  x1.push_back(nums[i]);
        for(int i=mid+1;i<nums.size();i++)  x2.push_back(nums[i]);
        root->left=sortedArrayToBST(x1);
        root->right=sortedArrayToBST(x2);
        return root;
    }
};

选择问题

一、题目描述

第一行输入数组a的长度n;
第二行输入数组a的内容;
第三行输入k,指选择a数组中第k大的数输出。

二、分析与代码

分析

第一种思路:
将数组进行排序,并输出排序后的第k个数字即可,时间复杂度为O(nlogn)。

第二种思路:
模拟快速排序方法,快速排序每次都将基准数放在了最终位置i,有以下三种情况
i=k,则输出基准数字即可;
i<k,则所求第k个数在i+1到right内;
i>k,则所求第k个数在left到i-1内。

具体代码

#include <iostream>

using namespace std;

int x;

int Quickchance(int a[],int left,int right)
{
    int _count=a[left];
    int i=left,j=right;
    while(i<j)
    {
        while(i<j&&a[j]>=_count)
            j--;
        a[i]=a[j];
        while(i<j&&a[i]<=_count)
            i++;
        a[j]=a[i];
    }
    a[i]=_count;
    if(i<x)
        return Quickchance(a,i+1,right);
    if(i>x)
        return Quickchance(a,left,i-1);
    if(i==x)
        return a[i];
}

int main()
{
    int n;
    cin>>n;
    int a[n+5];
    for(int i=1;i<=n;i++)
        cin>>a[i];
    
    cin>>x;
    int cc=Quickchance(a,1,n);
    cout<<cc;
}

插入排序

一、题目描述

AcWing-697.蒙斯特

二、分析与代码

分析
该题很明显就是使用插入排序的方法,计算排序过程中需要进行几次插入。
插入排序有以下两种方法:
1、直接插入排序
2、二分插入排序:利用二分查找找到数据的插入位置,再将数据插入。
注意:二分插入排序中寻找插入位置过程可参考本文减治法中的二分查找

两种方式时间复杂度都是O(n^2),但二分插入排序消耗的时间低于直接插入排序。
具体代码

//https://ac.nowcoder.com/acm/contest/13924#question
#include <iostream>

using namespace std;

const int N=105;
string name[N];
int _count;

void InsertSort1(int n)
{
    for(int i=2;i<=n;i++)
    {
        int j;
        name[0]=name[i];    //哨兵;
        for(j=i-1;name[j]>name[0]&&j>=1;j--)
            name[j+1]=name[j];
        if(j!=i-1)
            _count++;
        name[j+1]=name[0];
    }
}

int FindInsert(int i)
{
    int left=1,right=i-1;
    while(left<=right)
    {
        int mid=(left+right)/2;
        if(name[mid]==name[0])
        {
            while(name[mid++]==name[0]);
                return mid;
        }
        if(name[mid]<name[0])
            left=mid+1;
        if(name[mid]>name[0])
            right=mid-1;
    }
    return left;
}

void InsertSort2(int n)
{
    for(int i=2;i<=n;i++)
    {
        name[0]=name[i];
        int x=FindInsert(i);
        if(x!=i)
            _count++;
        for(int j=i-1;j>=x;j--)
            name[j+1]=name[j];
        name[x]=name[0];
    }
}

int main()
{
    int T;
    cin>>T;
    
    for(int j=1;j<=T;j++)
    {
        _count=0;
        
        int n;
        cin>>n;
        getchar();
        
        for(int i=1;i<=n;i++)
            getline(cin,name[i]);
        
        //直接插入
        //InsertSort1(n);
        
        //二分插入
        InsertSort2(n);
        
        cout<<"Case #"<<j<<": "<<_count<<endl;
    }
}

堆排序

一、题目描述

输入一个长度为 n的整数数列,从小到大输出前 m小的数。

输入格式:
第一行包含整数 n和 m。
第二行包含 n个整数,表示整数数列。

输出格式:
共一行,包含 m个整数,表示整数数列中前 m小的数。

数据范围:
1≤m≤n≤105
1≤数列中元素≤109

输入样例:
5 3
4 5 1 3 2

输出样例:
1 2 3

二、分析与代码

分析
堆排序思路,采用小堆排序,求出前m小的数并输出。

具体代码

#include <iostream>

using namespace std;

const int N=100010;
int a[N];

void SiftHeap(int i,int n)
{
    int j=i;
    while(j<=n/2)	//j为分支结点
    {
        j=2*i;
        if(j<n&&a[j]>a[j+1])    
            j++;
            
        if(a[i]<a[j])
            break;
           
        int temp;
        temp=a[i];
        a[i]=a[j];
        a[j]=temp;
        
        i=j;
    }
}

void HeapSort(int n)
{
    for(int i=n/2;i>=1;i--)		//构造堆,从后往前对每一个分支结点进行调整
        SiftHeap(i,n);
    
    for(int i=2;i<=n;i++)		//每次将堆头与堆尾元素交换后,对对头进行调整
    {
        int temp;
        temp=a[1];
        a[1]=a[n-i+2];
        a[n-i+2]=temp;
        
        SiftHeap(1,n-i+1);
    }
}

int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)
        cin>>a[i];

    HeapSort(n);
    
    for(int i=n;i>=n-m+1;i--)
        cout<<a[i]<<" ";
}

一、题目描述

AcWing-

二、分析与代码

分析

具体代码



动态规划法

提示:动态规划法中的动规数组的维数取决于比较对象的个数,如两个字符串对象进行内容匹配,则动规数组的维数为2。

最长递增子序列问题

一、题目描述

LeetCode-300.最长递增子序列问题

二、分析与代码

分析
设flength[i]为序列x1-xi的最长子序列长度,可证在求flength[i]时可假设子序列必须包括xi,此假设不影响整个序列的最长子序列长度。

初始状态:flength[0]=1;

则对于1<=j<i
有: i=1或不存在xj<xi时,flength[I]=1
否则:对于所有xj<xi时,flength[I]=max(flength[j]+1)

具体代码
在LeetCode上的:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
    const int N=2510;
    int flength[N];
    
    //求出最长递增子序列长度
    memset(flength,0,sizeof flength);
    flength[0]=1;
    for(int i=1;i<nums.size();i++)
        for(int j=0;j<i;j++)
        {
            while(nums[j]>=nums[i]&&j<i)
                j++;
            if(j<i)
                flength[i]=max(flength[i],flength[j]+1);
            else
                flength[i]=max(1,flength[i]);   
        }
    int l1=0;
    for(int i=0;i<=nums.size();i++)
        l1=max(l1,flength[i]);
    return l1;
    }
};

完整代码:
题目:给定一个长度为 N的数列,求数值严格单调递增的子序列的长度最长是多少。

#include <iostream>
#include <cstring>

using namespace std;

const int N=1005;
int a[N],flength[N];

void increaseOrder(int n)
{
    memset(flength,0,sizeof flength);
    flength[1]=1;
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            while(a[j]>=a[i]&&j<i)
                j++;
            if(j<i)
                flength[i]=max(flength[i],flength[j]+1);
            else
                flength[i]=max(1,flength[i]);   
        }
    }
}

int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    
    //求出最长递增子序列长度
    increaseOrder(n);
    int l1=0;
    for(int i=1;i<=n;i++)
        l1=max(l1,flength[i]);
    cout<<l1;
    
    return 0;
}

最长公共子序列问题

一、题目描述

AcWing-897.最长公共子序列
LeetCode-1143.最长公共子序列
LeetCode-583.两个字符串的删除操作
注意:字符串中的子序列并不要求连续。

二、分析与代码

分析
设length[i][j]代表a字符串前i个与b字符串前j个的最长公共子序列长度

初始化length[i][0]=length[0][i]=0;

当ai=bj时,length[i][j]=length[i-1][j-1]+1;
当ai!=bj时,length[i][j]=max(length[i-1][j],length[i][j-1])

两个字符串的删除操作只需在找到两字符串的最长公共子序列长度后,return length1-len[length1][length2]*2+length2即可。

具体代码
Acwing:

#include <iostream>

using namespace std;

const int N=1005;
char a[N],b[N];
int n,m,length[N][N];   //length[i][j]代表a字符串前i个与b字符串前j个的最长公共子序列长度

void commonOrder()
{
    for(int i=1;i<=n;i++)       //初始化
    {
        length[0][i]=0;
        length[i][0]=0;
    }
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(a[i]==b[j])
                length[i][j]=length[i-1][j-1]+1;
            else
                length[i][j]=max(length[i-1][j],length[i][j-1]);
}

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];
    
    commonOrder();
    
    cout<<length[n][m];
    return 0;
}

LeetCode-1143:

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
    const int N=1005;
    int n,length[N][N];   //length[i][j]代表a字符串前i个与b字符串前j个的最长公共子序列长度
    
    n=max(text1.size(),text2.size());

    for(int i=1;i<=n;i++)     //初始化
    {
        length[0][i]=0;
        length[i][0]=0;
    }
    
    for(int i=0;i<text1.size();i++)
        for(int j=0;j<text2.size();j++)
            if(text1[i]==text2[j])
                length[i+1][j+1]=length[i][j]+1;
            else
                length[i+1][j+1]=max(length[i][j+1],length[i+1][j]);

    return length[text1.size()][text2.size()];
    }
};

LeetCode-583:

class Solution {
public:
    int minDistance(string word1, string word2) {
        int length1=word1.length(),length2=word2.length();
        int n=max(length1,length2)+1;
        int len[n][n];
        for(int i=0;i<n;i++)
        {
            len[0][i]=0;
            len[i][0]=0;
        }
        for(int i=1;i<=length1;i++)
            for(int j=1;j<=length2;j++)
                if(word1[i-1]==word2[j-1])
                    len[i][j]=len[i-1][j-1]+1;
                else
                    len[i][j]=max(len[i-1][j],len[i][j-1]);
        return length1-len[length1][length2]*2+length2;
    }
};

0/1背包问题

一、题目描述

AcWing-2.0/1背包问题

二、分析与代码

分析
设f[I][j]为前i个物品在允许容量为j时能装入的最大价值

初始化:

for(int i=0;i<=m;i++)
        f[0][i]=0;
for(int i=0;i<=n;i++)
        f[i][0]=0;

另:求具体f[i][j]分为当允许容量大于weigh[I]小于weigh[I] 两种情况:

if(j>=weigh[i])
    f[i][j]=max(f[i-1][j],f[i-1][j-weigh[i]]+value[i]);
else
    f[i][j]=f[i-1][j];

具体代码

#include <iostream>

using namespace std;

const int N=1005;
int weigh[N],value[N];

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>weigh[i];
        cin>>value[i];
    }
    
    int f[n+1][m+1];        //初始化
    for(int i=0;i<=m;i++)
        f[0][i]=0;
    for(int i=0;i<=n;i++)
        f[i][0]=0;
        
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(j>=weigh[i])
                f[i][j]=max(f[i-1][j],f[i-1][j-weigh[i]]+value[i]);
            else
                f[i][j]=f[i-1][j];
        }
    }
    
    cout<<f[n][m];
}

注意:变量能作为局部变量申请就作为局部变量申请,否则可能引起内存混乱造成变量值被莫名其妙改变。

多段图的最短路径问题(单源最短路径)

最著名的就是dijkstra算法

一、题目描述

AcWing-849.Dijkstra求最短路|

二、分析与代码

分析
此题为单源最短路径题目,按照dijkstra算法思路即可求解。

具体代码

#include <iostream>
#include <cstring>

using namespace std;

const int N=505,MAX=10005;  
int dijk[N],arc[N][N];      //dijk数组代表1到1-n各个顶点的最小值,arc代表i,j的边长
bool tr[N]={false};

int main()
{
    memset(dijk,0x3f,sizeof dijk);  //初始化为无穷大
    memset(arc,0x3f,sizeof arc);    //初始化为无穷大
    
    int n,m;
    cin>>n>>m;
    
    for(int i=1;i<=m;i++)
    {
        int x,y,p;
        cin>>x>>y>>p;
        arc[x][y]=min(arc[x][y],p); //保留重复边的最小边
    }
    
    dijk[1]=0;
    
    for(int k=0;k<n;k++)
    {
        int t=-1;
        
        for(int i=1;i<=n;i++)       //每次都找还未确定的边中的最小边作为下一中间结点
            if(!tr[i]&&(t==-1||dijk[i]<dijk[t]))//!tr[i]代表结点i未确定
                t=i;
                
        tr[t]=true;                 //将其设为确定
        
        for(int i=1;i<=n;i++)       //将t节点作为中间节点后对所有节点进行一次更新
            dijk[i]=min(dijk[i],dijk[t]+arc[t][i]);
    }
    
    if(dijk[n]>=MAX)    //无路径到达n
        cout<<-1;
    else
        cout<<dijk[n];
}

多源点最短路径问题(多源最短路径)

一、题目描述
给定一个 n个点, m条边的有向图,图中可能存在重边和自环,边权可能为负数。
再给定 k个询问,每个询问包含两个整数 x和 y,表示查询从点 x到点 y的最短距离,如果路径不存在,则输出 impossible。
数据保证图中不存在负权回路。

输入格式:
第一行包含三个整数 n,m,k。
接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
接下来 k 行,每行包含两个整数 x,y,表示询问点 x 到点 y 的最短距离。

输出格式:
共 k行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 impossible。

数据范围:
1≤n≤200
1≤k≤n2
1≤m≤20000
图中涉及边长绝对值均不超过 10000。

输入样例:
3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3

输出样例:
impossible
1

二、分析与代码

分析
此题为多源最短路径题目,按照Floyd算法思路即可求解。

具体代码

#include <iostream>
#include <cstring>

using namespace std;

const int N=205,M=20010;
int dijk[N][N];
int n;

void _Floyd()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                dijk[i][j]=min(dijk[i][j],dijk[i][k]+dijk[k][j]);
}

int main()
{
    memset(dijk,0x3f,sizeof dijk);
    for(int i=1;i<N;i++)
        dijk[i][i]=0;
    
    int m,k;
    cin>>n>>m>>k;
    
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        dijk[x][y]=min(dijk[x][y],z);
    }
    
    _Floyd();
    
    for(int i=1;i<=k;i++)
    {
        int x,y;
        cin>>x>>y;
        if(dijk[x][y]>=0x3f3f3f3f/2)
            cout<<"impossible"<<endl;
        else
            cout<<dijk[x][y]<<endl;;
    }
}

近似串匹配问题

一、题目描述

给定两字符串,求出两字符串的最小差别数,即求出一字符串通过多少次操作可修改为另一字符串。操作类型有:修改,删除,增加。

例如:happy与hsppay,如下:
h a p p y
h s p p a y

通过两次操作可将hsppay修改为happy:
s改为a;将a删除。

则最小差别数为2。

二、分析与代码

分析

申请整型数组k[100][100],字符数组a[100],b[100]。
k[i][j]代表a[1-i]与b[1-j]的最小差别数。

初始化: 当一字符串长度为0时,另一字符串长度等于两字符串的最小差别数。

for(int i=1;i<=n;i++)       //初始化
        k[i][0]=i;
    for(int i=1;i<=m;i++)
        k[0][i]=i;

具体代码

#include <iostream>

using namespace std;

int k[100][100];            //k[i][j]代表a[1-i]与b[1-j]的最小差别数
char a[100],b[100];

void ASM(int n,int m)
{
    for(int i=1;i<=n;i++)       //初始化
        k[i][0]=i;
    for(int i=1;i<=m;i++)
        k[0][i]=i;
    
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(a[i]==b[j])		
                k[i][j]=min(k[i-1][j-1],min(k[i-1][j]+1,k[i][j-1]+1));
            else
                k[i][j]=min(k[i-1][j-1]+1,min(k[i-1][j]+1,k[i][j-1]+1));
            
        }
    }
}

int main()
{
    int n,m;
    
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    
    cin>>m;
    for(int j=1;j<=m;j++)
        cin>>b[j];
        
    ASM(n,m);
    
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            cout<<k[i][j]<<" ";
        cout<<endl;
    }
    
    cout<<k[n][m];
}

一、题目描述

AcWing-

二、分析与代码

分析

具体代码



回溯法

皇后问题

一、题目描述

AcWing-3472.八皇后
LeetCode-52.N皇后

二、分析与代码

分析
设queer[i]代表第i行的皇后所在列数,初始化:

for(int i=1;i<=n;i++)
        queer[i]=0;

将皇后的位置依次向右移动,直到该皇后满足要求。若该皇后移出了界,则将该皇后移到初始位置,即queer[i]=0;再回溯到上一个皇后。

具体代码

AcWing-3472.八皇后:

#include <iostream>

using namespace std;

int x[95][10],queer[10];        //x[i][10]记录八皇后问题的第i个解
int _count=1;

int Place(int k)                //判断皇后是否满足要求
{
    for(int i=1;i<k;i++)
        if(queer[i]==queer[k]||abs(k-i)==abs(queer[k]-queer[i]))
            return 0;
    return 1;
}

void _Queer()                   
{
    int k;
    
    if(_count==1)
        k=1;
    else
        k=8;
    while(k>=1)                 //k回溯到0,说明无解
    {
        queer[k]++;
        while(queer[k]<=8&&Place(k)==0) //未出界但不满足要求
            queer[k]++;
        
        if(queer[k]<=8&&k==8)           //满足要求
        {
            for(int i=1;i<=8;i++)
                x[_count][i]=queer[i];
            return;
        }
        if(queer[k]<=8&&k<8)            //还有皇后未分配位置
            k++;
        else                            //皇后超界,回溯
        {
            queer[k]=0;
            k--;
        }
    }
}

int main()
{
    for(int i=1;i<=8;i++)
        queer[i]=0;
        
    for(int i=1;i<=92;i++)
    {
        _Queer();
        _count++;
    }
    
    int n;
    cin>>n;
    while(n--)
    {
        int c;
        cin>>c;
        for(int i=1;i<=8;i++)
            cout<<x[c][i];
        cout<<endl;
    }
}

LeetCode-52.N皇后:

class Solution {
public:
    int totalNQueens(int n) {
        int x[1000][10],queer[10];        //x[i][10]记录八皇后问题的第i个解,设最多有1000个解
        int _count=1;

        for(int i=1;i<=n;i++)
        queer[i]=0;
        
        for(int i=1;i<=1000;i++)
        {
            int k;
    
            if(_count==1)
                k=1;
            else
                k=n;
            while(k>=1)                 //k回溯到0,说明无解
            {
                queer[k]++;
                int place;
                for(int i=1;i<k;i++)
                    if(queer[i]==queer[k]||abs(k-i)==abs(queer[k]-queer[i]))
                    {
                        place=0;
                        break;
                    }   
                    else
                        place=1;
                while(queer[k]<=n&&place==0)
                {
                    queer[k]++;
                    for(int i=1;i<k;i++)
                        if(queer[i]==queer[k]||abs(k-i)==abs(queer[k]-queer[i]))
                        {
                            place=0;
                            break;
                        }
                        else
                            place=1;
                }

                if(queer[k]<=n&&k==n)
                {
                    for(int i=1;i<=n;i++)
                        x[_count][i]=queer[i];
                    break;
                }
                if(queer[k]<=n&&k<n)
                    k++;
                else
                {
                    queer[k]=0;
                    k--;
                }
            }
            if(k>=1)
                _count++;
            else
                break;
        }
        return _count-1;
    }
};

一、题目描述

AcWing-

二、分析与代码

分析

具体代码



贪心法

最小生成树问题

一、题目描述

给定一个 n个点 m条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。

二、分析与代码

分析
求最小生成树有两种方法:
1、prim算法
2、kruskal算法

具体代码

prim:直接使用贪心法即可

#include <iostream>
#include <cstring>

using namespace std;

//typedef pair<int,int> PII;
const int N=505,MAX=0x3f3f3f3f;
int a[N][N],p[N];
bool k[N]={false};  //记录点是否确认
//vector <PII> p[N];
int _count=0;

int findmin(int n)
{
    int x=0x3f3f3f3f,len;
    for(int i=1;i<=n;i++)
    {
        if(p[i]<x&&!k[i])
        {
            len=i;
            x=p[i];
        }
    }
    
    if(x>=MAX/2)            //无解
        return -1;
    return len;
}

void _new(int x,int n)
{
    for(int i=1;i<=n;i++)
    {
        if(k[i])
            continue;
        p[i]=min(p[i],a[x][i]);
    }
}

void _prim(int n)
{
    for(int i=1;i<=n;i++)       //初始化
        p[i]=a[1][i];
        
    k[1]=true;  //点1确认
    
    //再确认n-1次
    for(int i=1;i<n;i++)
    {
        int x=findmin(n);
        if(x==-1)
        {
            cout<<"impossible";
            return;
        }
        k[x]=true;
        _count+=p[x];
        _new(x,n);
    }
    cout<<_count;
    return;
}

int main()
{
    memset(a,0x3f,sizeof a);
    int n,m;
    cin>>n>>m;
    
    //重边取最短值
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        a[x][y]=min(a[x][y],z);
        a[y][x]=a[x][y];
    }
    //防止自回路
    for(int i=1;i<=n;i++)
        a[i][i]=0;
    
    _prim(n);
}

kruskal:该算法需要用到并查集将两树合一

#include <iostream>
#include <algorithm>

using namespace std;

const int N=1e5+5,MAX=0x3f3f3f3f;
int _count=0;

typedef struct
{
    int value=MAX;
    int l;
    int r;
    int kk=-1;      //用于并查集
}Edge;

Edge edge[2*N];

int _find(int x)
{
    while(edge[x].kk>=0)
        x=edge[x].kk;
    return x;
}

void _union(int x,int y)            //_union需要优化,尽量减少并查集的高度,否则会超时
{
    if(edge[x].kk>edge[y].kk)
    {
        edge[y].kk+=edge[x].kk;
        edge[x].kk=y;
    }
    else
    {
        edge[x].kk+=edge[y].kk;
        edge[y].kk=x;
    }
}

void _kruskal(int n,int m)
{
    int c=0;
    for(int i=1;i<=m;i++)
    {
        int x=_find(edge[i].l);
        int y=_find(edge[i].r);
        if(x==y)
            continue;
        c++;
        _count+=edge[i].value;
        _union(x,y);
    }
    
    if(c==n-1)
        cout<<_count;
    else
        cout<<"impossible";
    return;
}

int cmp(Edge a,Edge b)
{
    return  a.value< b.value;
}

int main()
{
    int n,m;
    cin>>n>>m;
    
    for(int i=1;i<=m;i++)
    {
        int xy,z;
        cin>>x>>y>>z;
        edge[i].l=x;
        edge[i].r=y;
        edge[i].value=z;
    }
    
    sort(edge+1,edge+m+1,cmp);
    
    _kruskal(n,m);
}

一、题目描述

AcWing-

二、分析与代码

分析

具体代码



  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值