• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            JulyRina's blog
            welcome to July Rina's blog
            posts - 22,comments - 1,trackbacks - 0

            題目

            有N種物品和一個容量為V的背包,每種物品都有無限件可用。第i種物品的費用是c[i],價值是w[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。

            基本思路

            這個問題非常類似于01背包問題,所不同的是每種物品有無限件。也就是從每種物品的角度考慮,與它相關的策略已并非取或不取兩種,而是有取0件、取1件、取2件……等很多種。如果仍然按照解01背包時的思路,令f[i][v]表示前i種物品恰放入一個容量為v的背包的最大權值。仍然可以按照每種物品不同的策略寫出狀態轉移方程,像這樣:

            f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}

            這跟01背包問題一樣有O(VN)個狀態需要求解,但求解每個狀態的時間已經不是常數了,求解狀態f[i][v]的時間是O(v/c[i]),總的復雜度可以認為是O(V*Σ(V/c[i])),是比較大的。

            將01背包問題的基本思路加以改進,得到了這樣一個清晰的方法。這說明01背包問題的方程的確是很重要,可以推及其它類型的背包問題。但我們還是試圖改進這個復雜度。

            一個簡單有效的優化

            完全背包問題有一個很簡單有效的優化,是這樣的:若兩件物品i、j滿足c[i]<=c[j]且w[i]>=w[j],則將物品j去掉,不用考慮。這個優化的正確性顯然:任何情況下都可將價值小費用高得j換成物美價廉的i,得到至少不會更差的方案。對于隨機生成的數據,這個方法往往會大大減少物品的件數,從而加快速度。然而這個并不能改善最壞情況的復雜度,因為有可能特別設計的數據可以一件物品也去不掉。

            這個優化可以簡單的O(N^2)地實現,一般都可以承受。另外,針對背包問題而言,比較不錯的一種方法是:首先將費用大于V的物品去掉,然后使用類似計數排序的做法,計算出費用相同的物品中價值最高的是哪個,可以O(V+N)地完成這個優化。這個不太重要的過程就不給出偽代碼了,希望你能獨立思考寫出偽代碼或程序。

            轉化為01背包問題求解

            既然01背包問題是最基本的背包問題,那么我們可以考慮把完全背包問題轉化為01背包問題來解。最簡單的想法是,考慮到第i種物品最多選V/c[i]件,于是可以把第i種物品轉化為V/c[i]件費用及價值均不變的物品,然后求解這個01背包問題。這樣完全沒有改進基本思路的時間復雜度,但這畢竟給了我們將完全背包問題轉化為01背包問題的思路:將一種物品拆成多件物品。

            更高效的轉化方法是:把第i種物品拆成費用為c[i]*2^k、價值為w[i]*2^k的若干件物品,其中k滿足c[i]*2^k<=V。這是二進制的思想,因為不管最優策略選幾件第i種物品,總可以表示成若干個2^k件物品的和。這樣把每種物品拆成O(log V/c[i])件物品,是一個很大的改進。

            但我們有更優的O(VN)的算法。

            O(VN)的算法

            這個算法使用一維數組,先看偽代碼:

            for i=1..N     
               for v=0..V
                  f[v]=max{f[v],f[v-cost]+weight}

            你會發現,這個偽代碼與P01的偽代碼只有v的循環次序不同而已。為什么這樣一改就可行呢?首先想想為什么P01中要按照v=V..0的逆序來循環。這是因為要保證第i次循環中的狀態f[i][v]是由狀態f[i-1][v-c[i]]遞推而來。換句話說,這正是為了保證每件物品只選一次,保證在考慮“選入第i件物品”這件策略時,依據的是一個絕無已經選入第i件物品的子結果f[i-1][v-c[i]]。而現在完全背包的特點恰是每種物品可選無限件,所以在考慮“加選一件第i種物品”這種策略時,卻正需要一個可能已選入第i種物品的子結果f[i][v-c[i]],所以就可以并且必須采用v=0..V的順序循環。這就是這個簡單的程序為何成立的道理。

            值得一提的是,上面的偽代碼中兩層for循環的次序可以顛倒。這個結論有可能會帶來算法時間常數上的優化。

            這個算法也可以以另外的思路得出。例如,將基本思路中求解f[i][v-c[i]]的狀態轉移方程顯式地寫出來,代入原方程中,會發現該方程可以等價地變形成這種形式:

            f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]}

            將這個方程用一維數組實現,便得到了上面的偽代碼。

            最后抽象出處理一件完全背包類物品的過程偽代碼:

            procedure CompletePack(cost,weight)     
               for v=cost..V
                  f[v]=max{f[v],f[v-c[i]]+w[i]}

            總結

            完全背包問題也是一個相當基礎的背包問題,它有兩個狀態轉移方程,分別在“基本思路”以及“O(VN)的算法“的小節中給出。希望你能夠對這兩個狀態轉移方程都仔細地體會,不僅記住,也要弄明白它們是怎么得出來的,最好能夠自己想一種得到這些方程的方法。事實上,對每一道動態規劃題目都思考其方程的意義以及如何得來,是加深對動態規劃的理解、提高動態規劃功力的好方法。

            posted @ 2015-02-18 20:31 JulyRina 閱讀(379) | 評論 (0)編輯 收藏

            題目

            有N件物品和一個容量為V的背包。第i件物品的費用是c[i],價值是w[i]。求解將哪些物品裝入背包可使價值總和最大。

            基本思路

            這是最基礎的背包問題,特點是:每種物品僅有一件,可以選擇放或不放。

            用子問題定義狀態:即f[i][v]表示前i件物品恰放入一個容量為v的背包可以獲得的最大價值。則其狀態轉移方程便是:

            f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

            這個方程非常重要,基本上所有跟背包相關的問題的方程都是由它衍生出來的。所以有必要將它詳細解釋一下:“將前i件物品放入容量為v的背包中”這個子問題,若只考慮第i件物品的策略(放或不放),那么就可以轉化為一個只牽扯前i-1件物品的問題。如果不放第i件物品,那么問題就轉化為“前i-1件物品放入容量為v的背包中”,價值為f[i-1][v];如果放第i件物品,那么問題就轉化為“前i-1件物品放入剩下的容量為v-c[i]的背包中”,此時能獲得的最大價值就是f[i-1][v-c[i]]再加上通過放入第i件物品獲得的價值w[i]。

            優化空間復雜度

            以上方法的時間和空間復雜度均為O(VN),其中時間復雜度應該已經不能再優化了,但空間復雜度卻可以優化到O。

            先考慮上面講的基本思路如何實現,肯定是有一個主循環i=1..N,每次算出來二維數組f[i][0..V]的所有值。那么,如果只用一個數組f[0..V],能不能保證第i次循環結束后f[v]中表示的就是我們定義的狀態f[i][v]呢?f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]兩個子問題遞推而來,能否保證在推f[i][v]時(也即在第i次主循環中推f[v]時)能夠得到f[i-1][v]和f[i-1][v-c[i]]的值呢?事實上,這要求在每次主循環中我們以v=V..0的順序推f[v],這樣才能保證推f[v]時f[v-c[i]]保存的是狀態f[i-1][v-c[i]]的值。偽代碼如下:

            for i=1..N     
               for v=V..0
                  f[v]=max{f[v],f[v-c[i]]+w[i]};

            其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相當于我們的轉移方程f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]},因為現在的f[v-c[i]]就相當于原來的f[i-1][v-c[i]]。如果將v的循環順序從上面的逆序改成順序的話,那么則成了f[i][v]由f[i][v-c[i]]推知,與本題意不符,但它卻是另一個重要的背包問題P02最簡捷的解決方案,故學習只用一維數組解01背包問題是十分必要的。

            事實上,使用一維數組解01背包的程序在后面會被多次用到,所以這里抽象出一個處理一件01背包中的物品過程,以后的代碼中直接調用不加說明。

            過程ZeroOnePack,表示處理一件01背包中的物品,兩個參數cost、weight分別表明這件物品的費用和價值。

            procedure ZeroOnePack(cost,weight)     
               for v=V..cost
                  f[v]=max{f[v],f[v-cost]+weight}

            注意這個過程里的處理與前面給出的偽代碼有所不同。前面的示例程序寫成v=V..0是為了在程序中體現每個狀態都按照方程求解了,避免不必要的思維復雜度。而這里既然已經抽象成看作黑箱的過程了,就可以加入優化。費用為cost的物品不會影響狀態f[0..cost-1],這是顯然的。

            有了這個過程以后,01背包問題的偽代碼就可以這樣寫:

            for i=1..N     
               ZeroOnePack(c[i],w[i]);

            初始化的細節問題

            我們看到的求最優解的背包問題題目中,事實上有兩種不太相同的問法。有的題目要求“恰好裝滿背包”時的最優解,有的題目則并沒有要求必須把背包裝滿。一種區別這兩種問法的實現方法是在初始化的時候有所不同。

            如果是第一種問法,要求恰好裝滿背包,那么在初始化時除了f[0]為0其它f[1..V]均設為-∞,這樣就可以保證最終得到的f[N]是一種恰好裝滿背包的最優解。

            如果并沒有要求必須把背包裝滿,而是只希望價格盡量大,初始化時應該將f[0..V]全部設為0。

            為什么呢?可以這樣理解:初始化的f數組事實上就是在沒有任何物品可以放入背包時的合法狀態。如果要求背包恰好裝滿,那么此時只有容量為0的背包可能被價值為0的nothing“恰好裝滿”,其它容量的背包均沒有合法的解,屬于未定義的狀態,它們的值就都應該是-∞了。如果背包并非必須被裝滿,那么任何容量的背包都有一個合法解“什么都不裝”,這個解的價值為0,所以初始時狀態的值也就全部為0了。

            這個小技巧完全可以推廣到其它類型的背包問題,后面也就不再對進行狀態轉移之前的初始化進行講解。

            一個常數優化

            前面的偽代碼中有 for v=V..1,可以將這個循環的下限進行改進。

            由于只需要最后f[v]的值,倒推前一個物品,其實只要知道f[v-w[n]]即可。以此類推,對以第j個背包,其實只需要知道到f[v-sum{w[j..n]}]即可,即代碼中的

            for i=1..N     
               for v=V..0

            可以改成

            for i=1..n     
               bound=max{V-sum{w[i..n]},c[i]}
               for v=V..bound

            這對于V比較大時是有用的。

            小結

            01背包問題是最基本的背包問題,它包含了背包問題中設計狀態、方程的最基本思想,另外,別的類型的背包問題往往也可以轉換成01背包問題求解。故一定要仔細體會上面基本思路的得出方法,狀態轉移方程的意義,以及最后怎樣優化的空間復雜度。

            posted @ 2015-02-18 20:30 JulyRina 閱讀(275) | 評論 (0)編輯 收藏
            題目大意:求圖上單點到單點之間的最短路。
            題目分析:SPFA(Shortest Path Faster Algorithm)是Bellman-Ford算法的一種隊列實現,減少了不必要的冗余計算。
            算法大致流程是用一個隊列來進行維護。 初始時將源加入隊列。 每次從隊列中取出一個元素,并對所有與他相鄰的點進行松弛,若某個相鄰的點松弛成功,則將其入隊。 直到隊列為空時算法結束。
            這個算法,簡單的說就是隊列優化的bellman-ford,利用了每個點不會更新次數太多的特點發明的此算法
            SPFA——Shortest Path Faster Algorithm,它可以在O(kE)的時間復雜度內求出源點到其他所有點的最短路徑,可以處理負邊。SPFA的實現甚至比Dijkstra或者Bellman_Ford還要簡單:
            設Dist代表S到I點的當前最短距離,Fa代表S到I的當前最短路徑中I點之前的一個點的編號。開始時Dist全部為+∞,只有Dist[S]=0,Fa全部為0。
            維護一個隊列,里面存放所有需要進行迭代的點。初始時隊列中只有一個點S。用一個布爾數組記錄每個點是否處在隊列中。
            每次迭代,取出隊頭的點v,依次枚舉從v出發的邊v->u,設邊的長度為len,判斷Dist[v]+len是否小于Dist[u],若小于則改進Dist[u],將Fa[u]記為v,并且由于S到u的最短距離變小了,有可能u可以改進其它的點,所以若u不在隊列中,就將它放入隊尾。這樣一直迭代下去直到隊列變空,也就是S到所有的最短距離都確定下來,結束算法。若一個點入隊次數超過n,則有負權環。
            SPFA 在形式上和寬度優先搜索非常類似,不同的是寬度優先搜索中一個點出了隊列就不可能重新進入隊列,但是SPFA中一個點可能在出隊列之后再次被放入隊列,也就是一個點改進過其它的點之后,過了一段時間可能本身被改進,于是再次用來改進其它的點,這樣反復迭代下去。設一個點用來作為迭代點對其它點進行改進的平均次數為k,有辦法證明對于通常的情況,k在2左右。
            SPFA算法(Shortest Path Faster Algorithm),也是求解單源最短路徑問題的一種算法,用來解決:給定一個加權有向圖G和源點s,對于圖G中的任意一點v,求從s到v的最短路徑。 SPFA算法是Bellman-Ford算法的一種隊列實現,減少了不必要的冗余計算,他的基本算法和Bellman-Ford一樣,并且用如下的方法改進: 1、第二步,不是枚舉所有節點,而是通過隊列來進行優化 設立一個先進先出的隊列用來保存待優化的結點,優化時每次取出隊首結點u,并且用u點當前的最短路徑估計值對離開u點所指向的結點v進行松弛操作,如果v點的最短路徑估計值有所調整,且v點不在當前的隊列中,就將v點放入隊尾。這樣不斷從隊列中取出結點來進行松弛操作,直至隊列空為止。 2、同時除了通過判斷隊列是否為空來結束循環,還可以通過下面的方法: 判斷有無負環:如果某個點進入隊列的次數超過V次則存在負環(SPFA無法處理帶負環的圖)。
            SPFA算法有兩個優化算法 SLF 和 LLL: SLF:Small Label First 策略,設要加入的節點是j,隊首元素為i,若dist(j)<dist(i),則將j插入隊首,否則插入隊尾。 LLL:Large Label Last 策略,設隊首元素為i,隊列中所有dist值的平均值為x,若dist(i)>x則將i插入到隊尾,查找下一元素,直到找到某一i使得dist(i)<=x,則將i出對進行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高約 50%。 在實際的應用中SPFA的算法時間效率不是很穩定,為了避免最壞情況的出現,通常使用效率更加穩定的Dijkstra算法。
            #include <cstdio>
            #include <iostream>
            #include <vector>
            #include <queue>
            using namespace std;
            #define INF (1<<29)
            const int maxn = 1010;

            typedef pair<intint> P;
            vector<P> G[maxn];
            queue<int> que;
            int V, E, dist[maxn];
            bool vis[maxn];

            void spfa(int s) {
                fill(dist, dist+V, INF);
                fill(vis, vis+V, false);
                dist[s] = 0;
                while(!que.empty()) que.pop();
                que.push(s);
                while(!que.empty()) {
                    int u = que.front();
                    que.pop();
                    vis[u] = false;
                    int sz = G[u].size();
                    for(int i=0;i<sz;i++) {
                        int v = G[u][i].first;
                        int w = G[u][i].second;
                        if(dist[v] > dist[u] + w) {
                            dist[v] = dist[u] + w;
                            if(!vis[v]) {
                                vis[v] = true;
                                que.push(v);
                            }
                        }
                    }
                }
            }

            int main() {
                scanf("%d%d" , &E, &V);
                for(int i=0;i<V;i++) G[i].clear();
                for(int i=0;i<E;i++) {
                    int u, v, w;
                    scanf("%d%d%d" , &u, &v, &w);
                    u --; v --;
                    G[u].push_back(make_pair(v, w));
                    G[v].push_back(make_pair(u, w));
                }
                spfa(0);
                printf("%d\n", dist[V-1]);
                return 0;
            }
            posted @ 2015-02-13 19:42 JulyRina 閱讀(239) | 評論 (0)編輯 收藏
            題目大意:求圖上單點到單點之間的最短路。
            題目分析:之前我們在同一道問題上分析過了單源最短路問題的Dijkstra算法
            使用鄰接矩陣實現的Dijkstra算法的時間復雜度是O(|V|^2)。使用鄰接表的話,更新最短距離只需要更新每一條邊即可,因此這部分的時間復雜度是O(|E|)。但是每次要枚舉所有的頂點來查找下一個使用的頂點,因此最終復雜度還是O(|V|^2)。在|E|比較小時,大部分經歷放在了查找下一次要是用的頂點上,因此需要使用合適的數據結構對其進行優化。
            需要優化的是數值的插入(更新)和取出最小值兩個操作,因此使用堆就可以了。把每個頂點當前的最短距離用堆維護,在更新最短距離時,把對應的元素往根的方向移動以滿足堆的性質。而每次從堆中取出的最小值就是下一次要使用的頂點。這樣堆中元素共有O(|V|)個,更新和取出數值的操作有O(|E|)次,因此整個算法的復雜度是O(|E|log(V))
            #include <cstdio>
            #include <iostream>
            #include <queue>
            #include <vector>
            using namespace std;
            #define INF (1<<29)
            const int maxn = 1010;

            typedef pair<intint> P; //first是最短距離,second是頂點的編號
            int V, dist[maxn];
            vector<P> G[maxn];

            void dijkstra(int s) {
                //通過指定greater<P>參數,堆按照first從小到大的順序取出值
                priority_queue<P, vector<P>, greater<P> > que;
                fill(dist, dist + V , INF);
                dist[s] = 0;
                while(!que.empty()) que.pop();
                que.push(P(0, s));
                while(!que.empty()) {
                    P p = que.top(); que.pop();
                    int u = p.second;
                    if(dist[u] < p.first) continue;
                    int sz = G[u].size();
                    for(int i=0;i<sz;i++) {
                        int to = G[u][i].second;
                        int cost = G[u][i].first;
                        if(dist[to] > dist[u] + cost) {
                            dist[to] = dist[u] + cost;
                            que.push(P(dist[to], to));
                        }
                    }
                }
            }

            int main() {
                int m;
                scanf("%d%d" , &m, &V);
                for(int i=0;i<V;i++) G[i].clear();
                for(int i=0;i<m;i++) {
                    int from, to, cost;
                    scanf("%d%d%d" , &from, &to, &cost);
                    from --; to --;
                    G[from].push_back(P(cost, to));
                    G[to].push_back(P(cost, from));
                }
                dijkstra(0);
                printf("%d\n", dist[V-1]);
                return 0;
            }
            posted @ 2015-02-13 19:38 JulyRina 閱讀(319) | 評論 (0)編輯 收藏
            題目大意:求圖上單點到單點之間的最短路。

            題目分析:讓我們考慮沒有負邊的情況。在Bellman-Ford算法中,如果dist[i]還不是最短距離的話,那么即使進行dist[j]=dist[i]+(從i到j的邊的權值)的更新,dist[j]也不會變成最短距離。而且,即使dist[i]沒有變化,每一次循環也要檢查一遍從i出發的所有變。這顯然是很浪費時間的。因此可以對算法作如下修改。
            (1)找到最短距離已經確定的頂點,從他出發更新相鄰頂點的最短距離。
            (2)此后不再需要關心(1)中的“最短距離已經確定的頂點”。
            在(1)和(2)中提到的“最短距離已經確定的”要怎么得到時問題的關鍵。在最開始時,只有起點的最短距離是確定的。而在尚未使用過的頂點中,距離dist[i]最小的頂點就會加入“最短距離已經確定的頂點”的陣營。這是因為由于不會存在負邊,所以dist[i]不會在之后的更新中變小。這個算法叫做Dijkstra算法。
            #include <cstdio>
            #include <iostream>
            #include <vector>
            using namespace std;
            #define INF (1<<29)
            const int maxn = 1010;

            typedef pair<intint> P;
            vector<P> G[maxn];
            int V, E, dist[maxn];
            bool vis[maxn];

            void dijkstra(int s) {
                fill(dist, dist + V, INF);
                fill(vis, vis + V, false);
                dist[s] = 0;
                while(true) {
                    int u = -1;
                    for(int i=0;i<V;i++)
                        if(!vis[i] && (u == -1 || dist[i] < dist[u]))
                            u = i;
                    if(u == -1) break;
                    vis[u] = true;
                    int sz = G[u].size();
                    for(int i=0;i<sz;i++) {
                        int v = G[u][i].first;
                        int w = G[u][i].second;
                        dist[v] = min(dist[v], dist[u] + w);
                    }
                }
            }

            int main() {
                scanf("%d%d" , &E, &V);
                for(int i=0;i<V;i++) G[i].clear();
                for(int i=0;i<E;i++) {
                    int u, v, w;
                    scanf("%d%d%d" , &u, &v, &w);
                    u --; v --;
                    G[u].push_back(make_pair(v, w));
                    G[v].push_back(make_pair(u, w));
                }
                dijkstra(0);
                printf("%d\n", dist[V-1]);
                return 0;
            }
            posted @ 2015-02-13 19:34 JulyRina 閱讀(320) | 評論 (0)編輯 收藏
            題目大意:求圖上單點到單點之間的最短路。

            題目分析:單源最短路問題是固定一個起點,求它到其他所有點的最短路的問題。終點也固定問題叫做兩點之間最短路問題。但是因為單源最短路問題的復雜度是一樣的,因此通常當作單源最短路問題來求解。
            記從起點s出發到頂點i的最短距離為dist[i]。則下述等式成立。
            dist[i] = min{dist[j]+(從j到i的邊的權值)|e=(j,i)∈E}
            如果給定的圖是一個DAG,就可以按托不許給頂點編號,并利用這條遞推關系計算出dist。但是,如果圖中有圈,就無法利用這樣的關系進行計算。
            在這種情況下,記當前到頂點i的最短距離為dist[i],并設初值dist[s]=0,dist[i]=INF(足夠大的常數),再不斷使用這條地推關系式更新dist值,就可以算出新的dist。
            只要途中不存在負圈,這樣的更新操作就是有限的。結束之后的最短操作就是所求的最短距離了。
            #include <cstdio>
            #include <cstring>
            #include <iostream>
            #include <vector>
            using namespace std;
            #define INF (1<<29)
            const int maxn = 1010, maxm = 4040;

            int n, m;

            struct Edge { int from, to, cost; } edge[maxm];
            int V, E, dist[maxn];

            void bellman_ford(int s) {
                for(int i=0;i<V;i++) dist[i] = INF;
                dist[s] = 0;
                while(true) {
                    bool update = false;
                    for(int i=0;i<E;i++) {
                        Edge e = edge[i];
                        if(dist[e.from] != INF && dist[e.to] > dist[e.from] + e.cost) {
                            dist[e.to] = dist[e.from] + e.cost;
                            update = true;
                        }
                    }
                    if(!update) break;
                }
            }

            int main() {
                scanf("%d%d" , &m, &n);
                V = n; E = 2 * m;
                for(int i=0;i<E;i+=2) {
                    Edge e;
                    int from, to, cost;
                    scanf("%d%d%d" , &from, &to, &cost);
                    from --; to --;
                    edge[i].from = from;
                    edge[i].to = to;
                    edge[i].cost = cost;
                    edge[i+1].from = to;
                    edge[i+1].to = from;
                    edge[i+1].cost = cost;
                }
                bellman_ford(0);
                printf("%d", dist[n-1]);
                return 0;
            }
            這個算法叫做Bellman-Ford算法。如果在圖中不存在從s可達的負圈,那么最短路不會經過同一個頂點兩次(也就是說,最多通過|V|-1次),while(true)的循環最多經過|V|-1次,因此,復雜度是O(VE)。反之,如果存在從s可達的負圈,那么在第|V|次循環中也會更新dist的值,因此也可以用這個性質來檢查負圈。如果一開始對所有的i,都把dist[i]設為0,那么可以檢查出所有的負圈。
            bool find_negetive_loop() {
                memset(dist, 0, sizeof(dist));
                
                for(int i=0;i<V;i++) {
                    for(int j=0;j<E;j++) {
                        if(dist[e.to] > dist[e.from] + e.cost) {
                            dist[e.to] = dist[e.from] + e.cost;
                            if(i == V-1) return true;
                        }
                    }
                }
                return false;
            }
            posted @ 2015-02-13 19:32 JulyRina 閱讀(232) | 評論 (0)編輯 收藏
            題目大意:有N(N<100,000)個人要去M(M<10)個星球,每個人只可以去一些星球,一個星球最多容納Ki個人。請問是否所有人都可以選擇自己的星球
            題目分析;直接建立二分圖模型,使用匈牙利算法。
                匈牙利算法可以解決多重匹配,原理和二分圖最大匹配很像。注意不要把可以匹配多個的點分割然后按照正常的二分匹配來做,那樣肯定會掛的。
                解決多重匹配就是記錄一下多重匹配的點(簡稱Y方點)已經匹配了Pi個點。如果Pi<Ki那么就直接上了,否則的話繼續搜索Yi已經匹配的每一個點并將Yi染色。
                因為Yi搜一次就需要染色了,而且Y方點最多是10個,所以每次找增廣路的深度最多是10,這樣就很快了。
            #include <cstdio>
            #include <cstring>
            #include <vector>
            using namespace std;

            const int maxn = 100010;
            const int maxm = 11;
            int y_match[maxn][maxm], g[maxn][maxm], cnt[maxm], capacity[maxn], n, m;
            bool vis[maxm];

            bool dfs(int x) {
                for(int i=0;i<m;i++) {
                    if(g[x][i] == 0 || vis[i] == truecontinue;
                    vis[i] = true;
                    if(cnt[i] < capacity[i]) {
                        y_match[x][cnt[i]++] = x;
                        return true;
                    } else {
                        for(int j=0;j<capacity[i];j++) {
                            if(dfs(y_match[i][j]) == true) {
                                y_match[i][j] = x;
                                return true;
                            }
                        }
                    }
                }
                return false;
            }
            bool hungary(int n) {
                for(int i=0;i<n;i++) {
                    memset(vis, falsesizeof(bool)*(m));
                    if(dfs(i) == false)
                        return false;
                }
                return true;
            }
            int main() {
                while(~scanf("%d%d" , &n, &m)) {
                    memset(cnt, 0, sizeof(int)*(n));
                    for(int i=0;i<n;i++)
                        for(int j=0;j<m;j++)
                            scanf("%d", &g[i][j]);
                    for(int i=0;i<m;i++)
                        scanf("%d", &capacity[i]);
                    if(hungary(n) == true)
                        puts("YES");
                    else
                        puts("NO");
                }
                return 0;
            }
            posted @ 2015-02-13 16:23 JulyRina 閱讀(1421) | 評論 (0)編輯 收藏
            題目大意:動物王國中有三類動物A,B,C,這三類動物的食物鏈構成了有趣的環形。A吃B, B吃C,C吃A。 
            現有N個動物,以1-N編號。每個動物都是A,B,C中的一種,但是我們并不知道它到底是哪一種。 
            有人用兩種說法對這N個動物所構成的食物鏈關系進行描述: 
            第一種說法是"1 X Y",表示X和Y是同類。 
            第二種說法是"2 X Y",表示X吃Y。 
            此人對N個動物,用上述兩種說法,一句接一句地說出K句話,這K句話有的是真的,有的是假的。當一句話滿足下列三條之一時,這句話就是假話,否則就是真話。 
            1) 當前的話與前面的某些真的話沖突,就是假話; 
            2) 當前的話中X或Y比N大,就是假話; 
            3) 當前的話表示X吃X,就是假話。 
            你的任務是根據給定的N(1 <= N <= 50,000)和K句話(0 <= K <= 100,000),輸出假話的總數。 
            題目分析;由于N和K很大,所以必須高效的維護動物之間的關系,并快速判斷是否殘生了矛盾,所以決定采用并查集。
            對于每只動物i創建3個元素i,i+N,i+2N,并用這3*N個元素創建并查集。這個并查集維護如下信息:
            i+xN表示“i屬于種類x”。
            并查集里的每一個組表示組內所有元素代表的都同時發生或不發生。
            同時,在合并之前先判斷合并是否會產生矛盾。
            #include <cstdio>
            #include <iostream>
            using namespace std;

            const int maxn = 50050;

            int father[maxn*3], n, m, ans;

            void init() {
                for(int i=0;i<3*n;i++) father[i] = i;
            }
            int find(int x) {
                return x == father[x] ? x : father[x] = find(father[x]);
            }
            void unite(int x, int y) {
                int a = find(x) , b = find(y);
                father[a] = father[b] = father[x] = father[y] = min(a, b);
            }
            bool check(int d, int x, int y) {
                if(x >= n || y >= n || x < 0 || y < 0)
                    return false;
                if(d == 2 && x == y) 
                    return false;
                if(d == 1) {
                    if(find(x) == find(y+n) || find(x) == find(y+2*n))
                        return false;
                    unite(x, y);
                    unite(x+n, y+n);
                    unite(x+2*n, y+2*n);
                    return true;
                } else {
                    if(find(x) == find(y) || find(x) == find(y+2*n))
                        return false;
                    unite(x, y+n);
                    unite(x+n, y+2*n);
                    unite(x+2*n, y);
                    return true;
                }
            }
            int main() {
                scanf("%d%d" , &n, &m);
                init();
                ans = 0;
                for(int i=0;i<m;i++) {
                    int d, x, y;
                    scanf("%d%d%d", &d, &x, &y);
                    x --; y --;
                    if(check(d, x, y) == false)
                        ans ++;
                }
                printf("%d\n", ans);
                return 0;
            }
            posted @ 2015-02-11 17:33 JulyRina 閱讀(287) | 評論 (0)編輯 收藏
            題目大意:給定長度為N的字符串S,要構造一個長度為N的字符串T。起初,T是一個空串,隨后反復進行下列任意操作。
            從S的頭部刪除一個字符,加到T的尾部
            從S的尾部刪除一個字符,加到T的尾部
            目標是構造字典序盡可能小的字符串。
            題目分析;從字典序的性質上看,無論T的末尾有多大,只要前面部分較小就可以。所以我們可以試一下如下貪心算法:
            不斷取S和T的末尾中較小的一個字符放到T的末尾
            這個算法已經接近正確了,只是針對S的開頭和末尾字符相同的情形還沒有定義。在這種情況下,因為我們希望能夠盡早使用更小的字符,所以就要比較一下下一個字符的大小。下一個字符也有可能相同,因此就有如下算法:
            按照字典序比較字符串S和S反轉后的字符串S'。
            如果S較小,就從S的開頭取出一個文字,放到T的末尾。
            如果S'較小,就從S的末尾取出一個文字,放到T的末尾。
            (若果相同則去哪一個都可以)
            #include <cstdio>

            char s[2002], tmp[2];
            int n;

            int main() {
                while(~scanf("%d", &n)) {
                    for(int i=0;i<n;i++) {
                        scanf("%s", tmp);
                        s[i] = tmp[0];
                    }
                    int l = 0, r = n - 1, cnt = 0;
                    while(l <= r) {
                        int i = 0, left = true;
                        while(l + i <= r - i) {
                            if(s[l+i] < s[r-i]) {
                                break;
                            }
                            if(s[l+i] > s[r-i]) {
                                left = false;
                                break;
                            }
                            i ++;
                        }
                        if(left == true) putchar(s[l++]);
                        else putchar(s[r--]);
                        cnt ++;
                        if(cnt % 80 == 0) putchar('\n');
                    }
                    if(cnt % 80) putchar('\n');
                }
                return 0;
            }
            posted @ 2015-02-11 15:59 JulyRina 閱讀(2225) | 評論 (0)編輯 收藏
            題目大意:有一個大小為N*M的園子,雨后積起了水。八連通的積水被認為是連接在一起的。請求出園子里總共有多少水洼?
            (八連通指下圖中相當于W的*)
            ***
            *W*
            ***
            題目分析;從任意的W開始,不同的用一個vis數組標記是否訪問過。一次DFS后與初始的這個W相連的W就都被標記過了。因此知道途中不再存在W為止,總共進行DFS的次數就是答案了。
            #include <cstdio>
            #include <cstring>

            const int maxn = 101;

            int n, m, ans;
            char g[maxn][maxn];
            bool vis[maxn][maxn];

            void dfs(int x, int y) {
                if(vis[x][y] == true || g[x][y] != 'W') return;
                vis[x][y] = true;
                for(int i=-1;i<=1;i++)
                for(int j=-1;j<=1;j++) {
                    if(!i && !j || x + i < 0 || x + i >= n || y + j < 0 || y + j >= m) continue;
                    dfs(x+i, y+j);
                }
                return;
            }

            int main() {
                while(~scanf("%d%d" , &n, &m)) {
                    for(int i=0;i<n;i++) {
                        scanf("%s", g[i]);
                        memset(vis+i, falsesizeof(bool)*(m));
                    }
                    ans = 0;
                    for(int i=0;i<n;i++)
                        for(int j=0;j<m;j++)
                            if(g[i][j] == 'W' && !vis[i][j]) {
                                ans ++;
                                dfs(i, j);
                            }
                    printf("%d\n", ans);
                }
                return 0;
            }
            posted @ 2015-02-11 15:29 JulyRina 閱讀(536) | 評論 (0)編輯 收藏
            僅列出標題
            共3頁: 1 2 3 
            2020最新久久久视精品爱 | 中文国产成人精品久久亚洲精品AⅤ无码精品 | 久久久久久久精品妇女99| 亚洲αv久久久噜噜噜噜噜| www久久久天天com| 久久精品国产精品亚洲| 色欲久久久天天天综合网| 狠狠久久亚洲欧美专区| 亚洲性久久久影院| 久久青青草原国产精品免费| 无码任你躁久久久久久久| 国产精品美女久久久久| 91久久国产视频| 久久久久久久综合日本| av国内精品久久久久影院| 久久亚洲av无码精品浪潮| 一本色道久久HEZYO无码| 久久国产成人午夜aⅴ影院| 欧美va久久久噜噜噜久久| 蜜桃麻豆www久久国产精品| 精品久久久久久综合日本| 久久天天躁狠狠躁夜夜不卡| 久久国产成人| 久久九色综合九色99伊人| 久久精品a亚洲国产v高清不卡| 亚洲天堂久久久| 中文字幕亚洲综合久久菠萝蜜| 色综合合久久天天综合绕视看| 久久精品夜夜夜夜夜久久| 免费久久人人爽人人爽av| 亚洲精品99久久久久中文字幕| 国产精品激情综合久久| 91精品国产91久久| 97超级碰碰碰碰久久久久| 久久美女人爽女人爽| 国产亚洲美女精品久久久久狼| 国产精品久久久久影视不卡| 久久精品国产清高在天天线| 久久综合给合久久狠狠狠97色| 久久综合给久久狠狠97色 | 久久中文字幕精品|