こんにゃく-Z__Xの隅にうずくまっで書かれました...
2020年2月7日には、グラフ理論、および\(dpが\)ついに終わりを迎えました。振り返ってみると、それは決して味わうように...多くを経て、あまりにも多くの私に来て常に伸びています...
私は記念して名誉を持っています 経過グラフ理論と\(DP \) 。
グラフ理論
メモリマップ
まず、グラフ理論の基礎:ストレージ。ここで、いくつかの記憶構造であり、
隣接行列
激しい最も簡単なストレージ構造、2次元メモリアレイ;
注:これは、特定の対象を見るために、読み出しモードです。
cpp cin >> n >> m; for (int i=1;i=m;i++) { cin >> i >> j >> x; a[i][j]=a[j][i]=x; }
(星の鎖の前に)隣接リスト
前者星の鎖と呼ばれる隣接テーブルは、実際のアイデアのリストである。
開くことが最初\(linkk \) 、アレイ(\ [I] linkk)\表される(\ I \)第1のエッジ数、の出発点として\(E \)アレイ預金側、\(E [I] .Y \)はエンドポイントを表し、\(E [I] .V \)が、重みである(\ E [i]は.next \)は、辺の数を表し、
隣接リストは、コア挿入機能です。void insert(int x,int y,int v) //x为起点,y为终点,v为权值。 { e[++t].y=y; e[t].v=v; e[t].next=linkk[x]; linkk[x]=t; }
クエリに似た別の同様に重要なサイクル:
for (int i=linkk[x];i;i=e[i].next)
サイドテーブル(アレイ側)
の単純な記憶構造が、同じ考えは非常に単純であり、記憶されている全ての側面に置くことである\(E \)開始と終了重みを格納する配列。struct node { int x,y; //起点和终点 int v; //权值 }e[maxm];
トラバースグラフ
DFSはトラバース
隣接行列を\(DFS \)トラバーサル:void dfs(int k); { printf("%d",k); f[k]=true; for (int i=1;i<=n;i++) if (!f[i] && a[k][i]) dfs(i); }
隣接リスト\(DFS \)トラバーサル:
void dfs(int k) { for (int i=linkk[k];i;i=e[i].next) if(!vis[e[i].y]) { vis[e[i].y]=1; dfs(e[i].y); } }
BFSはトラバース
隣接行列を\(BFS \)トラバーサル:void bfs(int i); { memset(q,0,sizeof(q)); int head=1,tail=1; q[1]=i; f[i]=true; while (head<=tail) { k=q[head]; cout>>k; for (int j=1;j<=n,j++) if (a[k][j] && !f[j]) { q[++tail]=j; f[j]=true; } head++; } }
最短
フロイド
の(K \)\通過点として、更新\(I \)に(J \)\最短パス
cpp for (int k=1;k<=n;k++) for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) if (dis[i][k]+dis[k][j]<dis[i][j]) dis[i][j]=dis[i][k]+dis[k][j];
ダイクストラ
たびに未評価点標識オフ点の距離の最短点を見つけるには、トランジットとしてこの点を有しますポイントの更新ポイントは、最短非標識
:注意\(ダイクストラ\)負の重みをサポートしていません
cpp void dijkstra(int st); { memset(f,0,sizeof(f)); memset(dis,0,sizeof(dis)); for (int i=1;i<=n;i++) dis[i]=a[st][i]; f[st]=1; dis[st]=0; for (int i=1;i<n;i++) { int min=0xfffffff, k=0; for (int j=1;j<=n;j++) if (!f[j] && (dis[j]<min)) min=dis[j],k=j; if (k==0) return; //找不到距离最短的点了 f[k]=1; //把k加入集合1; for (int j=1;j<=n;j++) //三角形迭代更新最短距离 if (!f[j] && dis[k]+a[k][j]<dis[j]) dis[j]=dis[k]+a[k][j]; } }
ベルマンフォード
サポートを否定リング
cpp int bellman_ford() { memset(dis,10,sizeof(dis)); //初值 dis[1]=0; //起点到起点的距离为0 for (int i=1;i<=n;i++) //最多迭代n次 { bool p=0; //是否有松弛标记 for (int j=1;j<=tot;j++) //tot条边 { int ax=a[j].x,ay=a[j].y,av=a[j].v; //第j条边起点,终点,长度 if (dis[ax]+av<dis[ay]) //三角形迭代 { dis[ay]=dis[ax]+av; p=1; //松弛标记 } } if (p==0) return 0; //无松弛 } return 1; //有负环 }
SPFA
\(ベルマン・フォード\)のアップグレード版\(ベルマン・フォード\)反復はなかったです最後に、同じように長い時間オーバー点「たるみ」からの改善\(X-は\) 、単に「たるみを保持するために、キュー内のBFSと、他を指すことができます$ X¥緩和を見てみましょう「ポイントを超えます。
cpp void SPFA(int k) //SPFA模板 { memset(dis,10,sizeof(dis)); //初值 memset(vis,0,sizeof(vis)); //清空标记数组,vis[k]表示点k是否在队列中 dis[k]=0; vis[k]=1; int head=1,tail=1; q[head]=k; //点k进队 for (head=1;head<=tail;head++) { int x=q[head]; //取出队头 for (int i=linkk[x];i>0;i=e[i].next) //邻接表查询 { if (dis[x]+e[i].v<dis[e[i].y]) //迭代 { dis[e[i].y]=dis[x]+e[i].v; if (vis[e[i].y]==0) //若此点没有标记,标记,进队 { vis[e[i].y]=1; q[++tail]=e[i].y; } } } vis[x]=0; //出队 } }
最小スパニングツリー
プリム
注:ここで指定されたコードは、ツリーエッジ重みを最小全域を求めるされ
cpp void prim(int st) //prim算法 { memset(vis,0,sizeof(vis)); memset(dis,0,sizeof(dis)); for (int i=1;i<=n;i++) dis[i]=a[st][i]; //赋值交叉边 vis[st]=1; int ans=-1e9; for (int i=1;i<=n-1;i++) { int Min=1e9,mini=0; for (int j=1;j<=n;j++) if (!vis[j] && dis[j]<Min) Min=dis[j],mini=j; //找到距离最近的点 vis[mini]=1; //标记 ans+=dis[mini]; //累加边权和 for (int j=1;j<=n;j++) if (!vis[j] && dis[j]>a[mini][j]) { dis[j]=a[mini][j]; //迭代 } } printf("%d",ans); }
クラスカル
エッジを追加する、小から大とサイドを取る大へ、次いで小さなにエッジの重みに応じて必要使用に互いに素なセット決意アルゴリズムを\(( X、Y)\)環が存在する場合、環を形成し、この側面を放棄、又はエッジを追加するかどうか\((X、Y)\) 。あなたが加入している場合(。N - 1 \)\ ;側面、終了
\(クラスカル\)アルゴリズムを最も難易度が追加エッジを判断する方法です\((x、y)は\ ) 、決定されたエッジの形のリングであれば\((Xを、Y)\)調査は、父親の直接決意に焦点を当てたような通信があれば図面にされ、そしてYたかどうかを2つの頂点は、Xは、同じです。
cpp void Kruskal(int k) //克鲁斯卡尔 { for (int i=1;i<=n;i++) father[i]=i; //开始每个点的父节点都是它本身 int cnt=0; for (int i=1;i<=m;i++) { int v=getfather(e[i].x); int u=getfather(e[i].y); if (v!=u) //判断e[i].x和e[i].y是否存在于同一集合内 { merge(u,v); //合并 ans+=e[i].v; if (++cnt==n-1) //最小生成树生成结束 { break; } } } }
トポロジカルソート
ポイント非循環グラフに並べ替えが存在しなければならない、
トポロジカル順序キューを維持するために、ここで使用さ
cpp void Topsort() //拓扑排序 { int head=1,tail=0; for (int i=1;i<=n;i++) if (in[i]==0) q[++tail]=i; //初始化 for (head=1;head<=tail;head++) { int x=q[head]; //取出队头 for (int i=linkk[x];i;i=e[i].next) //邻接表查询 { int y=e[i].y; if (--in[y]==0) q[++tail]=y; //入队 } if (tail==n) return; } }
cpp 注意: 1. 有向图的拓扑序列不唯一; 2. 如果图中存在环,无拓扑序列; 3. 还有一种写法用栈来实现拓扑排序。
DP
DPエントリー
バックパック
詳細ここでスタンプ、
再帰的な
例を与えるためにここには、より多くのノーと言う
aで与えられていない\(N- \)以下に示すように、デジタル三角形の行番号:
7。
3. 8。
8 1 0。
2 7 4 4
4 5 2。 65したがって、最大パスのデジタル和が(各ステップの数を渡すことが、三角形の上から下への経路を計算するためのアルゴリズムを設計しようとのみ行く右又は数から最も近い低いレベルの数の左側)。
cpp for (int i=1;i<=n;i++) for (int j=1;j<=i;j++) { cin >> a[i][j]; } for (int i=1;i<=n;i++) for (int j=1;j<=i;j++) { f[i][j]=max(f[i-1][j],f[i-1][j-1])+a[i][j]; }
LCS-は最長のサブシーケンスの問題は上昇しない、LIS-最長のサブシーケンスの問題は該当しない
ミサイル迎撃:古典的な主題は二つの問題を包含していました。
傍受敵の攻撃するためには、科学者たちは、ミサイルシステムが欠陥を持って、ミサイルシステムのセットを開発している:最初のラウンドは、任意の高さに到達することができ、各シェルをした後、最初のサーブが、前のレベルよりも高くすることはできません。
いくつかのミサイルは、現在高さを与えられている\(H [I] \) (\(H [I] \)の(<= 50000 \)は\、ミサイル迎撃ミサイルを塞ぐことができるどのくらいの正の整数である)、システムの計算をどのように多くのミサイル迎撃のセットが必要な場合は、すべてのミサイルを迎撃する必要がありますか?
アイデア:
最初の質問それは明らかです\(LCS \)テンプレート;
第二は、依頼することです\(LIS \)テンプレートを、なぜ?(TaccaはしませんOOO、OOO〜)for (int i=1;i<=n;i++) //LCS { dp[i]=1; for (int j=0;j<=i-1;j++) if (h[i]<=h[j]) dp[i]=max(dp[i],dp[j]+1); } int Max=-0xfffffff; for (int i=1;i<=n;i++) Max=max(Max,dp[i]); cout << Max << endl; for (int i=1;i<=n;i++) //LIS { dp[i]=1; for (int j=0;j<=i-1;j++) if (h[i]>h[j]) dp[i]=max(dp[i],dp[j]+1); } Max=-0xfffffff; for (int i=1;i<=n;i++) Max=max(Max,dp[i]); cout << Max << endl;
間隔DP
それはそれを適用するためのボードである場合は、最も古典的な石の合併について、ここに記載されています。
遊び場円形ディスプレイの周りの\(N \) 、ストーンパイルにマージする順序を有するようにスタック石である。それぞれの時間だけ、所定の選択された隣接\(2 \)スタック新しいパイルに、合わせたスコアのレコードの新しい石束の数。
試験は、計算するためのアルゴリズム設計\(N \)に結合重ね石\(1 \)スタック最小スコアと最大スコア。for (int i=1;i<=n;i++) { cin >> a[i]; sum[i]=sum[i-1]+a[i]; //前缀和,前i个石子堆的总数量 } memset(f,10,sizeof(f)); for (int i=1;i<=n;i++) f[i][i]=0; for (int len=2;len<=n;len++) //区间长度 { for (int i=1;i<=n-len+1;i++) //左端点 { int j=i+len-1; //右端点 for (int k=i;k<=j;k++) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]); //i到j的区间最优值通过中间点k来更新 } } cout << f[1][n];
二次元DP
次元\(DP \) 、最も古典的な馬はGuohe銅を停止しているか、それは、ここでの例を作るためにこの質問を取る必要があります。
ボード上の\(A \) Guohe銅、ターゲットを来る必要性を指し示す\( B \)ポイント。死の歩行ルール:ダウンして、または右にすることができます。
ボード上で同時に\(C \)ポイント馬、他の馬の制御点と呼ばれる全ての点まで馬がジャンプ点とステップのもう一方の側があります。いわゆる「馬はGuohe銅を停止しました。」
表現座標チェス盤、\(A \)ポイント\((0、0)\) \ (B \)ポイント\((N、M)( N、M \) の以下(\ 15 \) 、整数\()\) 、所与を必要と同じ馬の位置座標。今から死を計算するように依頼する(A \)\ポイントに達することができます\(B \)パスのポイント数を、馬が静止しているとしての位置を、ステップバイない兵士歩く馬の行くステップ。scanf("%d %d %d %d",&n,&m,&x,&y); n++;m++;x++;y++; memset(f,0,sizeof(f)); for (int i=0;i<=8;i++) { int xx=x+fx[i],yy=y+fy[i]; if (xx>=1 && xx<=n && yy>=1 && yy<=m) { f[xx][yy]=1; } } dp[1][1]=1; for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) if (f[i][j]==0 && !(i==1 && j==1)) { dp[i][j]=dp[i-1][j]+dp[i][j-1]; } printf("%d",dp[n][m]);
デュアルプロセスDP
EMM ...最長共通部分列、それの例と。
配列が形成されている(おそらく除去されていない)複数の文字を除去する(必ずしも連続していない)自由に文字列内の文字の所定の配列から文字の配列を指します。
だから、与えられた文字列こと(\ "1。X0、X1、...、XM-" X- =)\、シーケンス(= "Y0、Y1 Y \ \、...、YK-1")がある\(X- \)サブシーケンスは、Xは、厳密に増加シーケンスインデックスに存在しているので、すべてのために\(J = 0,1、...、 K-1、 そこYJ = xijを\) 。
例えば、\(X- = "ABCBDAB" \)、\ (Y = "BCDBは" \)である\(X- \)の順序で。
2つの文字列を考えると、彼らの最長共通部分列を得ました。
while (cin >> ch,ch!='.') a[++n]=ch;
while (cin >> ch,ch!='.') b[++m]=ch;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if (a[i]==b[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
}
cout << dp[n][m];