c++算法題:綜合
拯救公主
百鍊OJ, 題目鏈接
思路,典型的bfs問題,但需要處理的條件較多,此種題適合使用結構體來做,注意熟練使用結構體編程。
先貼上代碼,以後再多寫幾遍,這裏用到了狀態壓縮的方法記錄寶石的數量。
#include <iostream>
#include <cstring>
#include <queue>
#include <map>
using namespace std;
#define MAX 201
char a[MAX][MAX];
struct Node
{
int x, y; //座標。
int num = 0; //存儲寶石數目。
int deep = 0; //搜索深度。
Node() {
}
Node(int xx, int yy, int gg, int dd) : x(xx), y(yy), num(gg), deep(dd) {
}
};
int dir[4][2] = {{0, -1},{-1, 0},{0, 1},{1, 0}};
int visited[MAX][MAX][1 << 5 - 1]; //寶石數目最大11111
int r, c, k, doorCount;
Node doors[11];
int bit1Count(int value) {
unsigned int count = 0;
while (value > 0) { // until all bits are zero
if ((value & 1) == 1) // check lower bit
count++;
value >>= 1; // shift bits, removing lower bit
}
return count;
}
void printQueue(std::queue<Node> q) {
while (!q.empty()) {
Node node = q.front();
q.pop();
cout << "(" << node.x << ", " << node.y << ", "
<< bit1Count(node.num) << ", " << node.deep << ") ";
}
cout << endl << endl;
}
int getTarget(Node* node, Node* endNode) {
return (node->x == endNode->x && node->y == endNode->y && bit1Count(node->num) >= k);
}
int bfs(Node* startNode, Node* endNode) {
if (startNode == NULL || endNode == NULL) return -1;
memset(visited, 0, sizeof(visited));
visited[startNode->x][startNode->y][0] = 1; //置起點已經訪問
queue<Node> q;
q.push(*startNode); //起點入隊
while (!q.empty()) {
Node node = q.front(); //取隊首元素,出隊
q.pop();
for (int i = 0; i < 4; i++) { // 遍歷四個方向
int newX = node.x + dir[i][0];
int newY = node.y + dir[i][1];
if (newX < 0 || newX >= r || newY < 0 || newY >= c) continue; // 越界則下一個
if (a[newX][newY] == '#' || visited[newX][newY][node.num]==1) continue; //碰到牆或者已經訪問過,則下一個
//不是牆壁並且此節點沒訪問過
visited[newX][newY][node.num] = 1;
// 當前路可走
Node newNode(newX, newY, node.num, node.deep + 1);
if (a[newX][newY] >= '0' && a[newX][newY] <= '4') { // 遇到寶石
newNode.num |= 1 << (a[newX][newY] - '0'); //狀態壓縮記錄寶石數量
}
if ((newNode.x == endNode->x && newNode.y == endNode->y && bit1Count(newNode.num) >= k)) {
return newNode.deep;
}
// 沒有達到終點,則當前點入隊
q.push(newNode);
//如果是傳送門的話就將所有其它傳送門也加入搜索隊列。
if (a[newX][newY] == '$') {
for (int j = 0; j < doorCount; j++) {
Node currNode = doors[j];
if (currNode.x == newX && currNode.y == newY) continue;
Node doorNode(currNode.x, currNode.y, newNode.num, newNode.deep);
q.push(doorNode);
}
}
}
}
return -1;
}
int main() {
int t;
cin >> t; //t組數據
while (t--) {
// Node記錄座標,寶石數目,深度
Node *startNode = NULL, *endNode = NULL;
cin >> r >> c >> k; //r*c矩陣,k個寶石
doorCount = 0;
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
cin >> a[i][j];
switch (a[i][j]) {
case '$': //記錄所有傳送門的位置。
doors[doorCount++] = Node(i, j, 0, 0);
break;
case 'S': // 記錄起點位置
startNode = new Node(i, j, 0, 0);
break;
case 'E': // 記錄終點位置
endNode = new Node(i, j, 0, 0);
}
}
}
int ans=bfs(startNode, endNode);
ans == -1 ? (cout << "oop!" << endl) : (cout << ans << endl);
}
return 0;
}
#include <iostream>
#include <unordered_map>
#include <vector>
#include <queue>
using namespace std;
//16.57
struct Node{
int x,y; // 座標
int num; // 寶石個數
int depth; // 深度
Node(){
}
Node(int xx,int yy,int nn,int dd) :x(xx),y(yy),num(nn),depth(dd) {}
};
int R,C,K,ct=0;
//vector<vector<char>> a(R,vector<char>(C));
char a[200][200];
int dx[]={-1,1,0,0}; //上下左右
int dy[]={0,0,-1,1};
Node jewelNode[11];
int visited[200][200][1 << 5 - 1]; //寶石數目最大11111
int bit1Count(int value) {
unsigned int count = 0;
while (value > 0) { // until all bits are zero
if ((value & 1) == 1) // check lower bit
count++;
value >>= 1; // shift bits, removing lower bit
}
return count;
}
int bfs(Node *startNode,Node *endNode){
// 把startNode加入隊列
visited[startNode->x][startNode->y][0] = 1; //置起點已經訪問
queue<Node> q; // queue<Node*> q行不行?
q.push(*startNode); //起點入隊 q.push(startNode)?
while(!q.empty()){
Node node=q.front();q.pop(); // 取隊首元素,出隊
// 對四個方向遍歷
for(int i=0;i<4;i++){
int newx=node.x+dx[i];
int newy=node.y+dy[i];
// 越界或者已經訪問則跳過--------------visited數組爲什麼是三維的: 包含了寶石的情況
if(newx<0||newx>=R||newy<0||newy>=C||visited[newx][newy][node.num]==1) continue;
// 碰到#則走不通
if(a[newx][newy]=='#') continue;
// 其餘情況均可走,標記爲已經訪問
visited[newx][newy][node.num]=1;
Node tempNode(newx, newy, node.num, node.depth+1); // 存下當前結果
// 碰到寶石
if(a[newx][newy]>='0'&&a[newx][newy]<='4'){
tempNode.num |= 1 << (a[newx][newy]-'0');
}
// 終止條件
if(newx==endNode->x&&newy==endNode->y&&bit1Count(tempNode.num)>=K) return tempNode.depth;
q.push(tempNode);
if(a[newx][newy]=='&'){ // 把所有傳送門加入隊列
for(int j=0;j<ct;j++){
if(jewelNode[j].x==newx&&jewelNode[j].y==newy) continue;
Node tNode(jewelNode[j].x, jewelNode[j].y, tempNode.num, tempNode.depth);
q.push(tNode);
}
}
// 如果遇到傳送門
}
}
return -1;
}
int main()
{
int T=0;
cin>>T;
while(T--){
cin>>R>>C>>K; // R*C矩陣,K種類型寶石
// 讀取字符矩陣
Node *startNode=NULL,*endNode=NULL;
for(int i=0;i<R;i++){
for(int j=0;j<C;j++){
cin>>a[i][j];
switch(a[i][j]){
case 'S': startNode=new Node(i,j,0,0);break; // 記錄起點
case 'E': endNode=new Node(i,j,0,0);break; // 記錄終點
case '$': jewelNode[ct++]=Node(i,j,0,0); // 記錄寶石數量和位置
// 數組爲什麼就不可以 new Node(i,j,0,0)
}
}
}
int ans=bfs(startNode,endNode);
cout<<ans;
// ans == -1 ? (cout << "oop!" << endl) : (cout << ans << endl);
}
}
1251:仙島求藥
#include<stdio.h>
#include <iostream>
#include<string.h>
#include <queue>
using namespace std;
#define inf 0x3f3f3f3f
int vis[100][100];
int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
int m,n; //m行n列
char map[100][100];
// 定義結構體
typedef struct node{
int x,y;
int step;
node() {
}
node(int xx, int yy, int sp) : x(xx), y(yy), step(sp){
}
}Node;
int bfs(Node* startNode,Node* endNode){
if (startNode == NULL || endNode == NULL) return -1;
// 把起點加入隊列
queue<Node*> q;
q.push(startNode);
vis[startNode->x][startNode->y]=1;
while(!q.empty()){
Node* temp=q.front();
q.pop();
int newx,newy;
for(int i=0;i<4;i++){
newx=temp->x+dir[i][0];
newy=temp->y+dir[i][1];
if(newx>=0&&newx<m&&newy>=0&&newy<n){
if(vis[newx][newy]==0&&map[newx][newy]!='#'){ // 未訪問過
Node* newNode=new Node(newx, newy,temp->step + 1);
vis[newx][newy]=1;
if(newx==endNode->x&&newy==endNode->y)
return newNode->step;
q.push(newNode);
// cout<<newNode->x<<" "<<newNode->y<<endl;
}
}
}
}
return -1;
}
int main(){
// 讀取字符矩陣
while(scanf("%d%d",&m,&n)!=EOF&&(m||n)) {
Node* startNode=NULL;
Node* endNode=NULL;
string s="";
// 初始化地圖並記錄地圖起點和終點
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
cin>>map[i][j];
if(map[i][j]=='@') startNode=new Node(i,j,0);
else if(map[i][j]=='*') endNode=new Node(i,j,0);
}
}
int ans=bfs(startNode, endNode);
cout << ans << endl;
}
}
dijkstra模板題
最短路
dijkstra算法:
算法思路
-
指定一個節點,例如我們要計算 ‘A’ 到其他節點的最短路徑
-
引入兩個集合(S、U),S集合包含已求出的最短路徑的點(以及相應的最短長度),U集合包含未求出最短路徑的點(以及A到該點的路徑,注意 如上圖所示,A->C由於沒有直接相連 初始時爲∞)
-
初始化兩個集合,S集合初始時 只有當前要計算的節點,A->A = 0,
U集合初始時爲 A->B = 4, A->C = ∞, A->D = 2, A->E = ∞ -
從U集合中找出路徑最短的點,加入S集合,例如 A->D = 2
-
更新U集合路徑,if ( ‘D 到 B,C,E 的距離’ + ‘AD 距離’ < ‘A 到 B,C,E 的距離’ ) 則更新U
-
循環執行 4、5 兩步驟,直至遍歷結束,得到A 到其他節點的最短路徑
鏈接:https://www.jianshu.com/p/ff6db00ad866
算法模板:
- 求鄰接矩陣
- 求dist矩陣
- 更新鄰接矩陣
輸入用例:
5 7
1 2 4
1 4 2
2 4 1
2 3 4
3 4 1
3 5 3
4 5 7
輸出用例:
6
單源最短路模板:
#include<stdio.h>
#include <iostream>
#include<string.h>
using namespace std;
#define inf 0x3f3f3f3f
int main(){
// 輸入n結點,m條路徑
int adjMatrix[110][110],dist[110],vis[110];
int n,m;
cin>>n>>m;
// 題目要求找到1號點到n號點的最短距離
// 輸入起點,終點,距離
int a,b,c;
// 初始化鄰接矩陣
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j) adjMatrix[i][j]=0;
else adjMatrix[i][j]=inf;
}
}
// 讀取輸入給鄰接矩陣賦值
for(int i=1;i<=m;i++){
cin>>a>>b>>c;
adjMatrix[a][b]=adjMatrix[b][a]=c;
}
// 初始化dist矩陣
for(int i=1;i<=n;i++){
dist[i]=inf;
}
for(int i=1;i<=n;i++){
if(dist[i]>adjMatrix[1][i])
dist[i]=adjMatrix[1][i];
}
vis[1]=1;
//
for(int i=1;i<=n;i++){
int mindis=inf,j=1;
for(int i=1;i<=n;i++){ // 找到剩下的所有點中離起點最近的點,置已訪問
if(vis[i]==0&&mindis>dist[i]){
mindis=dist[i];
j=i;
}
}
vis[j]=1;
// 更新dist矩陣
for(int i=1;i<=n;i++){
if(dist[i]>adjMatrix[j][i]+dist[j])
dist[i]=adjMatrix[j][i]+dist[j];
}
}
cout<<dist[n];
return 0;
}
本題代碼如下:
#include<stdio.h>
#include <iostream>
#include<string.h>
using namespace std;
#define inf 0xfffffff
int map[110][110],dis[110],visit[110];
int n,m;
int dijstra()
{
memset(visit,0,sizeof(visit));
for (int i=1;i<=n;i++)
{
dis[i]=map[1][i]; // dis數組存下起點到其他點的距離
}
visit[1]=1;
dis[1]=0;
for (int i=1;i<=n;i++) // 對n個點遍歷
{
int pos;
int min=inf;
for (int j=1;j<=n;j++)
{
if (visit[j]==0&&min>dis[j]) // 找到最小的dis
{
pos=j;
min=dis[j];
} /// 找出U集合中路徑最短的節點D 加入S集合
}
visit[pos]=1; // 置爲已訪問
for (int j=1;j<=n;j++)
{
// 遍歷,找出起點到j+j到終點的最短路徑
if (!visit[j]&&dis[j]>dis[pos]+map[pos][j])
{
dis[j]=dis[pos]+map[pos][j]; // 更新dis
}
}
}
return dis[n];
}
int main()
{
int i,j;
while(~scanf("%d%d",&n,&m),n||m) // 輸入節點數量和道路數量
{
for(i=1;i<=n;++i)
{
for(j=1;j<=n;++j)
{
map[i][j]=inf;
}
}
int a,b,c;
for(i=1;i<=m;++i)
{
scanf("%d%d%d",&a,&b,&c); // 起點,終點,權值
if(c<map[a][b])
map[a][b]=map[b][a]=c; // 更新map
}
int count=dijstra();
printf("%d\n",count);
}
return 0;
}
最小生成樹模板
kruskal法
例題:
測試輸入的第1行給出評估的道路條數N,村莊數目M;隨後的 N 行,每行給出三個正整數,分別是兩個村莊的編號,以及此兩村莊間道路的成本(也是正整數)。爲簡單起見,村莊從1到M編號。
輸入示例:
9 14
1 2 4
2 3 8
3 4 7
4 5 9
5 6 10
6 7 2
7 8 1
8 9 7
2 8 11
3 9 2
7 9 6
3 6 4
4 6 14
1 8 8
輸出示例:
37
kruskal算法:
加邊法,以下圖A爲起點,手動模擬結果如下所示:
Kruskal算法的高效實現需要一種稱作並查集的結構。
Kruskal算法的過程:
- 將全部邊按照權值由小到大排序。(使用結構體做鄰接矩陣,需要寫sort的cmp)
- 按順序(邊權由小到大的順序)考慮每條邊,只要這條邊和我們已經選擇的邊不構成圈,就保留這條邊,否則放棄這條邊。(並查集檢查find(S)和find(E)是否共一個掌門,若不共則非連通,加入生成樹中,且合併S、V)
- 成功選擇(n-1)條邊後,形成一棵最小生成樹,當然如果算法無法選擇出(n-1)條邊,則說明原圖不連通。
代碼模板:
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define MAXN 11 //頂點個數的最大值
#define MAXM 20 //邊的個數的最大值
using namespace std;
struct edge //邊
{
int u, v, w; //邊的頂點、權值
}edges[MAXM]; //邊的數組
int parent[MAXN]; //parent[i]爲頂點 i 所在集合對應的樹中的根結點
int n, m; //頂點個數、邊的個數
int i, j; //循環變量
void UFset( ) //初始化
{
for( i=1; i<=n; i++ )
parent[i] = -1;
}
int Find( int x ) //查找並返回節點 x 所屬集合的根結點
{
int s; //查找位置
for( s=x; parent[s]>=0; s=parent[s] );
while( s!=x ) //優化方案―壓縮路徑,使後續的查找操作加速。
{
int tmp = parent[x];
parent[x] = s;
x = tmp;
}
return s;
}
//將兩個不同集合的元素進行合併,使兩個集合中任兩個元素都連通
void Union( int R1, int R2 )
{
int r1 = Find(R1), r2 = Find(R2); //r1 爲 R1 的根結點,r2 爲 R2 的根結點
int tmp = parent[r1] + parent[r2]; //兩個集合結點個數之和(負數)
//如果 R2 所在樹結點個數 > R1 所在樹結點個數(注意 parent[r1]是負數)
if( parent[r1] > parent[r2] ) //優化方案――加權法則
{
parent[r1] = r2;
parent[r2] = tmp;
}
else
{
parent[r2] = r1;
parent[r1] = tmp;
}
}
bool cmp( edge a, edge b ) //實現從小到大排序的比較函數
{
return a.w <= b.w;
}
void Kruskal( )
{
int sumweight = 0; //生成樹的權值
int num = 0; //已選用的邊的數目
int u, v; //選用邊的兩個頂點
UFset( ); //初始化 parent[]數組
for( i=0; i<m; i++ )
{
u = edges[i].u; v = edges[i].v;
if( Find(u) != Find(v) ) //u和v不是一個掌門,即不連通
{
printf( "%d %d %d\n", u, v, edges[i].w );
sumweight += edges[i].w;
num++;
Union(u,v); // 合併
}
if( num>=n-1 ) break; // 取到n-1個邊即結束
}
printf( "weight of MST is %d\n", sumweight );
}
int main( )
{
int u, v, w; //邊的起點和終點及權值
scanf( "%d%d", &n, &m ); //頂點數 n,邊數m
for( int i=0; i<m; i++ )
{
scanf( "%d%d%d", &u, &v, &w ); //讀入邊的起點和終點,以及權值
edges[i].u = u; edges[i].v = v; edges[i].w = w;
}
sort(edges,edges+m,cmp);
Kruskal();
return 0;
}
dfs綜合體題
codevs 1004 四子連棋
此題的難點在於
- 模擬棋的行走------------------------------使用空格位置做每次的起點
- 模擬黑白輪流執棋-----------------------需要記錄上一次執棋的顏色
- 有黑白先後手之分------------------------分類深搜取最短的。
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define inf 0x3f3f3f3f
using namespace std;
char a[5][5];
int ans,Ox1=0,Oy1,Ox2,Oy2,dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
bool check() //檢查當前棋盤是否構成四子連棋
{
// 4*4的棋盤,只有行、列、雙斜線四種方式
for(int i=1;i<=4;i++)
{
if (a[i][1]==a[i][2]&&a[i][1]==a[i][3]&&a[i][1]==a[i][4]) return 1;
if (a[1][i]==a[2][i]&&a[1][i]==a[3][i]&&a[1][i]==a[4][i]) return 1;
}
if (a[1][1]==a[2][2]&&a[1][1]==a[3][3]&&a[1][1]==a[4][4]) return 1;
if (a[1][4]==a[2][3]&&a[1][4]==a[3][2]&&a[1][4]==a[4][1]) return 1;
return 0;
}
bool can(int x,int y,char p)
{
return x>=1&&x<=4&&y>=1&&y<=4&&a[x][y]!=p;
}
bool dfs(int x1,int y1,int x2,int y2,char pre,int step) //pre表示先手顏色
{
// dfs(Ox1,Oy1,Ox2,Oy2,'W',0)
if (step==ans) //搜索深度達到上限,停止
{
if (check()) return 1;
else return 0;
}
// 對四個方向遍歷
for(int i=0;i<4;i++)
{
int nx1,ny1,nx2,ny2;
nx1=x1+dir[i][0];
ny1=y1+dir[i][1];
nx2=x2+dir[i][0];
ny2=y2+dir[i][1];
// 如果當前座標爲有效位置
if (can(nx1,ny1,pre))
{
swap(a[x1][y1],a[nx1][ny1]); //交換
if (dfs(nx1,ny1,x2,y2,(pre=='B'?'W':'B'),step+1)) return 1; // 輪流
swap(a[x1][y1],a[nx1][ny1]);
}
if (can(nx2,ny2,pre))
{
swap(a[x2][y2],a[nx2][ny2]);
if (dfs(x1,y1,nx2,ny2,(pre=='B'?'W':'B'),step+1)) return 1;
swap(a[x2][y2],a[nx2][ny2]);
}
}
return 0;
}
int main()
{
// 讀取棋盤
for(int i=1;i<=4;i++)
{
char s[5];
scanf("%s",s);
for(int j=1;j<=4;j++)
{
a[i][j]=s[j-1];
// 分別記錄下O的位置
if (a[i][j]=='O')
{
if (Ox1==0)
Ox1=i,Oy1=j;
else
Ox2=i,Oy2=j;
}
}
}
for(ans=1;ans<=inf;ans++) //ans枚舉深度上限
{
if (dfs(Ox1,Oy1,Ox2,Oy2,'W',0)) break; //黑先手
if (dfs(Ox1,Oy1,Ox2,Oy2,'B',0)) break; //白先手
}
printf("%d",ans);
return 0;
}
dp動態規劃
codevs 1010 過河卒
思路:典型的dp,先置馬的控制點爲1,令其他點均爲0,0代表當前路可走,此處在處理的時候不需要判斷邊界條件,主要此處的巧妙使用。
//刷表法
#include<iostream>
using namespace std;
int n, m, x, y, a[20][20], dp[20][20];
int main(){
cin>>n>>m>>x>>y; //行,列,馬的座標
a[x][y] = 1;
a[x-1][y-2] = a[x-1][y+2] = a[x+1][y-2] = a[x+1][y+2] = 1; // 躺 "日"
a[x-2][y-1] = a[x-2][y+1] = a[x+2][y-1] = a[x+2][y+1] = 1; //正 "日"
dp[0][0] = 1; //初始值, 刷表時不會覆蓋所以可以直接放
for(int i = 0; i <= n; i++){
for(int j = 0; j <= m; j++){
// 0位安全點,可走
if(a[i+1][j]==0) // 右移安全,則右移點到達次數+當前
dp[i+1][j] += dp[i][j];
if(a[i][j+1]==0)
dp[i][j+1] += dp[i][j];
}
}
cout<<dp[n][m]<<"\n";
return 0;
}
codevs 1014 裝箱問題
題目鏈接
揹包問題,注意此處的巧解:
#include <iostream>
#include <algorithm>
using namespace std;
int dp[20005]={0};
int main()
{
int n,v;
cin>>v>>n; //輸入體積和物品數
int volume;
for(int i=1;i<=n;i++)
{
cin>>volume; // 物品體積
for(int j=v;j>=volume;j--) //j爲當前箱子的剩餘體積,要比物體體積大才操作
{
//dp[j]爲箱子剩餘體積爲j時所能裝入的最大體積
dp[j]=max(dp[j],dp[j-volume]+volume); //
}
}
cout<<v-dp[v]<<endl;
return 0;
}
Floyd多源最短路
codevs 1020 孿生蜘蛛
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 105;
const int INF = 0x3f3f3f3f;
int n,t,a,b;
int x,y,maxn = -1,ans = INF;
int num[MAXN][MAXN];
int main()
{
scanf("%d",&n);
memset(num,1,sizeof(num));
for(int i = 1; i <= n; i ++)
num[i][i] = 0;
while(scanf("%d%d%d",&x,&y,&t) == 3)
num[x][y] = num[y][x] = t;
// floyd算法,多源最短路
for(int k = 1; k <= n;k ++)
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
num[i][j] = min(num[i][j],num[i][k] + num[k][j]);
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
{
maxn = -1;
for(int k = 1; k <= n; k ++)
maxn = max(min(num[i][k],num[k][j]),maxn); // 最短的裏面取出來最大的
if(maxn < ans && i != j)
ans = maxn,x = i,y = j;
}
printf("%d %d\n",x,y);
return 0;
}
最小生成樹
prim算法
codevs 1003 電話連線
給出鄰接矩陣,要求求最小生成樹的值。鏈接
注意這裏的dist矩陣並不是某起點到其餘點的距離,而是集合U到集合V的點的較小距離。
因而不像kruskal算法那樣更新距離:
if (!visit[j]&&dis[j]>dis[pos]+map[pos][j]) {
dis[j]=dis[pos]+map[pos][j]; // 更新dis
}
#include<stdio.h>
#include<string.h>
#define inf 99999999
int map[1010][1010],dis[1010],book[1010];
int main()
{
int n,a,b,c,i,j,k,min;
int count,sum;
while(scanf("%d",&n)!=EOF) // 輸入結點數
{
count=sum=0;
memset(book,0,sizeof(book));
// 初始化鄰接矩陣
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
if(i==j)
map[i][j]=0;
else
map[i][j]=inf;
}
// 鄰接矩陣賦值
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
scanf("%d",&a);
if(a<map[i][j])
{
map[i][j]=a;
map[j][i]=a;
}
}
// 記錄第一個點到其他點的距離
for(i=1;i<=n;i++)
dis[i]=map[1][i];
book[1]=1; // 訪問數組
count=1;
// 思路:
/*初始化集合U只包含起點,V包含其餘所有點。 從起點開始,找離當前點最近的點加入集合U中,
並從集合V中移除,標記已訪問; 重複以上步驟,直至集合V空
*/
while(count<n)
{
min=inf;
for(i=1;i<=n;i++) // 對n個點遍歷 ,找出最近的點
{
if(book[i]==0&&dis[i]<min) // 若該點未訪問,且距離更小則更新
{
min=dis[i]; // 更新距離
j=i; // 記錄最近的點的位置
}
}
book[j]=1; // 移除該點,即標記爲已訪問
count++;
sum+=dis[j];
for(k=1;k<=n;k++)
{
if(book[k]==0&&dis[k]>map[j][k]) // 該點未訪問過,且距離更小則更新
dis[k]=map[j][k]; // 更新距離
}
}
printf("%d\n",sum); // 輸出最小生成樹的值
}
return 0;
}
最小生成樹二刷
注意,數組一定要初始化,因爲
**
先將prim算法模板總結如下:
最小生成樹prim算法步驟
1.初始化鄰接矩陣,對角線全0,連接不到則爲inf
2.把起點加入集合U
3.for i 1 -> n : // 遍歷剩下n-1個點
for j 0 -> n : // 遍歷集合V,尋找最近的點
尋找集合V中點dist[]最小的值,記爲min,並標記該點index
把最小點加入集合U中,累加該距離
for k 0 -> n : // 遍歷集合U,更新集合U到集合V的距離
dist[k]=min(dist[k],map[index][k] ); // dist[i]代表未訪問路徑中,集合U和集合V中距離最小的兩點距離
#include<iostream>
#include<cstdio>
using namespace std;
// 11.30
int map[100][100];
// 最小生成樹問題
int main(){
// 輸入結點數n
int n;
cin>>n;
// 輸入鄰接矩陣map
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>map[i][j];
}
}
// 創建dist矩陣,dist爲當前
int dist[100];
for(int i=0;i<n;i++){
dist[i]=map[0][i];
}
// prim算法實現---加點法,其實是n個點裏面不斷加入邊
/* 對於某一點,選定起點a後(集合U),遍歷集合V,找到最近的b點加入
標記點b爲已經訪問(加入集合U),更新dist矩陣,dist矩陣爲起點到其他點的最近距離;
遍歷集合V,找到集合V中離集合U內的點最近的距離點c,加入集合U中,再更新dist矩陣
*/
int vis[100];
for(int i=0;i<n;i++)
vis[i]=0;
vis[0]=1;
int sum=0;
for(int i=0;i<n-1;i++){ // 對n個點遍歷,
int min=0x3f3f3f3f;
int index=0;
for(int j=0;j<n;j++){ // 對集合V遍歷 ,找到集合V中最近的且未訪問過的點
if(vis[j]==0&&dist[j]<min){
min=dist[j];
index=j;
}
}
vis[index]=1; // 加入集合U
sum+=dist[index];
// 更新dist
for(int k=0;k<n;k++){
if(vis[k]==0&&dist[k]>map[index][k])
dist[k]=map[index][k];
}
}
cout<<sum;
}
動態規劃
典型求大數餘類型
935. Knight Dialer
注意對於大數餘法,我們有定律:(a+b)%c=((a%c)+(b%c))%c
,因而我們常用以下形式來計算:
int add(int a, int b) { return (a + b) % MOD;}
因而以下代碼結果是一樣的
#include <iostream>
using namespace std;
int MOD;
int add(int a, int b) { return (a + b) % MOD;}
int main(){
int a,b;
while(cin>>a>>b>>MOD)
cout<<add(a,b)<<" "<<(a+b)%MOD<<endl;
return 0;
}
代碼如下:
class Solution {
const int MOD = 1e9+7;
int add(int a, int b) { return (a + b) % MOD; }
public:
int knightDialer(int N) {
int dp[5111][10]; // dp[i][j]表示走i步,當前處於第j位的走法有多少種
for(int i=0;i<10;i++)
dp[0][i] = 1;
for(int t=1;t<N;t++) {
dp[t][0] = add(dp[t-1][4], dp[t-1][6]);
dp[t][1] = add(dp[t-1][6], dp[t-1][8]);
dp[t][2] = add(dp[t-1][7], dp[t-1][9]);
dp[t][3] = add(dp[t-1][4], dp[t-1][8]);
dp[t][4] = add(dp[t-1][0], add(dp[t-1][9], dp[t-1][3]));
dp[t][5] = 0;
dp[t][6] = add(dp[t-1][1], add(dp[t-1][7], dp[t-1][0]));
dp[t][7] = add(dp[t-1][2], dp[t-1][6]);
dp[t][8] = add(dp[t-1][1], dp[t-1][3]);
dp[t][9] = add(dp[t-1][2], dp[t-1][4]);
}
int ans = 0;
for(int i=0;i<10;i++)
ans = add(ans, dp[N-1][i]);
return ans;
}
};