NOIP算法總結

前言html

       離NOIP還有一個星期,匆忙的把寒假整理的算法補充完善,看着當時的整理以爲那時還年少。第二頁貼了幾張從貼吧裏找來的圖片,看着就很熱血的。旁邊的同窗都勸我不要再放PASCAL啊什麼的了,畢竟咱們的下一級直接學C++。即使我本人對C++也是讚揚有加,不過PASCAL做爲夢的開始終究不能忘記。不像機房中其他的OIERS,我之後並不想學計算機類的專業。當年來學這個競賽就是爲了興趣,感覺計算機之美的。通過時遷,計劃趕不上變化,如今尚處於迷茫之中,也很難說當時作的決定是對是錯。然而我一直堅信迷茫的時候選擇難走的路會看見更好的風景。程序員

      這篇文章簡單的說了一下NOIP考試中會經常使用的算法,可能難度掌握的不是太好,有一部份內容不是NOIP考查範圍,然而隨着難度的增長,看一些更高級的算法也沒有壞處。還有一些很是很是基礎的好比鏈表啊什麼的就直接沒有寫上(別問我爲何整理了那麼多的排序算法)。算法

      最後祝你們在NOIP中取得理想的成績!編程

搜索

DFS

框架

procedure dfs(x);
  var
  begin
     if 達到目標狀態 then 輸出結果並退出過程;
     if 知足剪枝條件 then exit;
     for i:=1 to 搜索寬度 do 
       begin
         備份現場;(注意若是現場使用了全局變量,則須要使用局部變量備份)
         dfs(參數+增量);
         恢復現場;
end;

 

優化

(1)                     最優化剪枝:求最優值時,當前的狀態不管如何不可能比最優值更優,則退出,可與展望結合剪枝數組

(2)                     可行性剪枝:提早判斷該狀態是否能獲得可行解,如不能則退出網絡

(3)                     記憶化搜索:對於已經搜索過的狀態直接退出數據結構

(4)                     改變搜索順序:對於看起來但願更大的決策先進行搜索app

(5)                     優化搜索策略框架

(6)                     預處理找到大致搜索翻譯dom

(7)                     改寫成IDA*算法

(8)                     卡時(注意如今聯賽中禁止使用meml掐時)

BFS

框架

   
 初始化;把初始佈局存入
    設首指針head=0;   尾指針tail:=1repeat    
        inc(head),取出隊列首記錄爲當前被擴展結點;   
        for  i:=1  to   規則數 do    {r是規則編號}
        begin
            if  新空格位置合法  then
            begin
                if  新佈局與隊列中原有記錄不重複
                     tail增1,並把新佈局存入隊尾;
              if  達到目標  then  輸出並退出;
            endenduntil  head>=tail; {隊列空}

優化

判重的優化:hash,二叉排序樹

雙向廣搜或啓發式搜索

改寫成A*算法

二分優化

排序

冒泡排序

var a:array[1..100] of longint;t,n,i,j:longint;
procedure sort;
begin
  for i:=1 to n-1 do{與每一個數都進行比較}
   for j:=1 to n-i do
    if a[j]>a[j+1] then
    begin
    t:=a[j];
    a[j]:=a[j+1];
    a[j+1]:=t;
    end;
end;

 

選擇排序

var a:array[1..100] of longint;t,n,i,j:longint;
procedure sort;
begin
  for i:=1 to n-1 do
   for j:=1+i to n do{大數沉小數浮}
    if a[j]>a[i] then
    begin
    t:=a[j];
    a[j]:=a[i];
    a[i]:=t;
    end;
end;

 

插入排序

var a:array[0..100] of longint;n,i,j,t:longint;
procedure sort;
begin
   for i:=2 to n do
     for j:=1 to (i-1) do
     begin
       if  (a[i]<a[j]) then
       begin
          t:=a[j];
          a[j]:=a[i];
          a[i]:=t;
       end;
     end;
end;

 

桶排序

var a,b:array[0..100] of longint;r,i,j,t,k,n:longint;
procedure sort;
begin
  for i:=0 to 100 do b[i]:=0;{爲B數組清零,小桶內容清零}
  for i:=1 to n do b[a[i]]:=b[a[i]]+1;
{桶的序號就是那個要排序的東西;出現一次,桶裏得旗數加一}
  for i:=0 to 100 do{掃描全部的桶}
    begin
      if b[i]<>0 then{桶裏有旗}
      for j:=1 to b[i] do write(i,' ');{桶的序號就是那個數}
end;
end;

 

快速排序

var a:array[1..100] of longint;
    n,i,h,g:longint;
procedure kp(l,r:longint);{變量不能與全局變量相同,不然會被抹去}
  var b,m,i,j,t:longint;
begin
    i:=l;
    j:=r;
m:=a[(l+r) div 2];{基準數最好從中間取}
    repeat
      while a[j]>m do dec(j);
      while a[i]<m do inc(i);{兩側的哨兵移動}
      if i<=j then
{哨兵未碰面}{「=」利用repeat循環的性質,使repeat循環得以結束}
      begin
        t:=a[j];
        a[j]:=a[i
        a[i]:=t;{交換兩個哨兵的值}
        inc(j);
        dec(j);{哨兵繼續運動}
      end;
    until i>j;
    if j>l then kp(l,j);
    if i<r then kp(i,r);{都是循環不結束後進行的動做}
end;
begin
  read(n);
  for i:=1 to n do read(a[i]);
  kp(1,n); {「一」位置與「N」位置}
  for i:=1 to n-1 do write(a[i],' ');
  write(a[n]);{防止多輸出空格使程序結果出錯}
end.

 

堆排序

var a:array[1..100] of longint;
    n,i,b:longint;
procedure jianshu(i:longint);
  begin
while ((a[i]>a[i*2])or(a[i]>a[i*2+1]))and(i<=n div 2) do
{當父親數大於子女數時而且他有孩子時進行}
      begin
        if a[i*2]<=a[i*2+1]{左兒子小於右兒子}
        then
          begin
            b:=a[i*2]; a[i*2]:=a[i];a[i]:=b;{左右兒子的值互換}
            jianshu(i*2);{繼續爲左兒子建樹}
          end
        else
          begin
            b:=a[i*2+1];a[i*2+1]:=a[i];a[i]:=b;
            jianshu(i*2+1);{上同,不過是爲右兒子建樹}
          end;
      end;
  end;
procedure tiao;
  begin
    while n<>0 do
      begin
        write(a[1]);
        a[1]:=a[n];
        n:=n-1;
        for i:=(n div 2) downto 1 do
         jianshu(i);
      end;
  end;
begin
  read(n);
  for i:=1 to n do
    read(a[i]);
  for i:=(n div 2) downto 1 do
    jianshu(i);
  tiao;
end.

 

數學定理

中國剩餘定理

如有一些兩兩互質的整數m1, m2,… mn,則對任意的整數:a1,a2,...an,如下聯立同餘方程組對模數m1, m2,… mn 有公解:

 

康託展開

    a[i]爲當前未出現的元素中是排在第幾個(從0開始)

把一個整數X展開成以下形式:

X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[2]*1!+a[1]*0!

其中a[i]爲當前未出現的元素中是排在第幾個(從0開始),而且0<=a[i]<i(1<=i<=n)

錯排通項

    考慮一個有n個元素的排列,若一個排列中全部的元素都不在本身原來的位置上,那麼這樣的排列就稱爲原排列的一個錯排。

    f[1]=0;f[2]=1;

    f[n] =(n-1)(f[n-2) + f[n-1])

    f[n]:=n![1-1/1!+1/2!-1/3!……+(-1)^n*1/n!]

    f[n] = (n!/e+0.5),其中e是天然對數的底,[x]爲x的整數部分。

費馬大定理

  • 費馬大定理,又被稱爲「費馬最後的定理」,由法國數學家費馬提出。它斷言當整數n >2時,關於x, y, z的方程 xn + yn = zn 沒有正整數解。
  • 被提出後,經歷多人猜測辯證,歷經三百多年的歷史,最終在1995年被英國數學家安德魯·懷爾斯證實。

費馬小定理

假如a是一個整數,p是一個素數,那麼 ap ≡a (mod p)。

若是a不是p的倍數,這個定理也能夠寫成ap-1 ≡1 (mod p)。----這個更加經常使用

逆元

由費馬小定理:假如p是質數,且gcd(a,p)=1,那麼ap-1≡1(mod p)

逆元:若是ab≡1(mod p),那麼在模p意義下,a、b互爲逆元。

因此,假如p是質數,且gcd(a,p)=1,那麼a的逆元是ap-2

逆元的做用:在模意義下進行除法。乘a的逆元等同於除以a。

歐拉函數

在數論中,對正整數n,歐拉函數是小於或等於n的正整數中與n互質的數的數目。此函數以其首名研究者歐拉命名,它又稱爲φ函數、歐拉商數等。

若m,a爲正整數,且m,a互素,(gcd(a,m) = 1),則aφ(m)≡1,其中爲φ(m)歐拉函數,mod m爲同餘關係。

歐拉定理其實是費馬小定理的推廣。

Stirling

第一類s(p,k)的一個的組合學解釋是:將p個物體排成k個非空循環排列的方法數。

 

s(p,k)的遞推公式: s(p,k)=(p-1)*s(p-1,k)+s(p-1,k-1) ,1<=k<=p-1

邊界條件:s(p,0)=0 ,p>=1  s(p,p)=1  ,p>=0

 

遞推關係的說明:

考慮第p個物品,p能夠單獨構成一個非空循環排列,這樣前p-1種物品構成k-1個非空循環排列,方法數爲s(p-1,k-1);

也能夠前p-1種物品構成k個非空循環排列,而第p個物品插入第i個物品的左邊,這有(p-1)*s(p-1,k)種方法。

 

第二類S(p,k)的一個組合學解釋是:將p個物體劃分紅k個非空的不可辨別的(能夠理解爲盒子沒有編號)集合的方法數。

k!S(p,k)是把p我的分進k間有差異(如:被標有房號)的房間(無空房)的方法數。

   

S(p,k)的遞推公式是:S(p,k)=k*S(p-1,k)+S(p-1,k-1) ,1<= k<=p-1

邊界條件:S(p,p)=1 ,p>=0    S(p,0)=0 ,p>=1

  

遞推關係的說明:

考慮第p個物品,p能夠單獨構成一個非空集合,此時前p-1個物品構成k-1個非空的不可辨別的集合,方法數爲S(p-1,k-1);

也能夠前p-1種物品構成k個非空的不可辨別的集合,第p個物品放入任意一箇中,這樣有k*S(p-1,k)種方法。

  

PS:第一類斯特林數和第二類斯特林數有相同的初始條件,但遞推關係不一樣。

Stirling's approximation

快速求階乘,不推薦用於競賽。

 

數論

GCD&LCM

//GCD 
function gcd(a,b:longint):longint;
begin
if b=0 then gcd:=a
else gcd:=gcd (b,a mod b);
end ;
 
 
//LCM
function lcm(a,b:longint):longint;
begin
if a<b then swap(a,b);
lcm:=a;
while lcm mod b>0 do inc(lcm,a);
end;

 

 

素數

//單個判斷
function prime (n: longint): boolean;
var i longint;
begin
for i:=2 to trunc(sqrt(n)) do
if n mod i=0 then exit(false)
exit(true); 
end;
//篩法打表
procedure main;
var i,j:longint;
begin
    fillchar(f,sizeof(f),true);
    f[0]:=false;f[1]:=false;
    for i:=2 to trunc(sqrt(maxn)) do
    if f[i] then
           begin
            j:=2*i;
            while j<=    maxn do
            begin
                    f[j]:=false;
                    inc(j,i);
                    end;
        end;
end;

 

 

快速冪

 

{a^b mod n}
function f(a,b,n:int64):int64;
var t,y:int64;
begin
    t:=1;
    y:=a;
    while b<>0 do
        begin
            if(b and 1)=1 then t:=t*y mod n;
            y:=y*y mod n;
{這裏用了一個很強大的技巧,y*y即求出了a^(2^(i-1))不知道這是什麼的看原理}
            b:=b shr 1;{去掉已經處理過的一位}
        end;
    exit(t);
end;

 

 

模運算法則

(A+B) mod C = (A mod C + B mod C) mod C

(A-B) mod C = (A mod C - B mod C) mod C

(A * B) mod C = (A mod C) * (B mod C) mod C

(A / B) mod C = ???

結合律

((a+b) mod p + c)mod p = (a + (b+c) mod p) mod p

((a*b) mod p * c)mod p = (a * (b*c) mod p) mod p

分配律 

((a +b)mod p × c) mod p = ((a × c) mod p + (b × c) mod p) mod p

組合和全排列

排列A(n,m)(m是上標)=n!/(n-m)!=nx(n-1)x...xm
組合C(n,m)=n!/m!(n-m)!=[nx(n-1)x...xm]/m!

階乘

{a^b mod n}
function f(a,b,n:int64):int64;
var t,y:int64;
begin
    t:=1;
    y:=a;
    while b<>0 do
        begin
            if(b and 1)=1 then t:=t*y mod n;
            y:=y*y mod n;
{這裏用了一個很強大的技巧,y*y即求出了a^(2^(i-1))不知道這是什麼的看原理}
            b:=b shr 1;{去掉已經處理過的一位}
        end;
    exit(t);
end;

 

 

約瑟夫環問題

遞推公式,設有n我的(0,...,n-1),數m,則第i輪出局的人爲f(i)=(f(i-1)+m-1)%(n-i+1),f(0)=0;

 

例:有M個猴子圍成一圈,每一個有一個編號,編號從1到M。打算從中選出一個大王。通過協商,決定選大王的規則以下:從第一個開始,每隔N個,數到的猴子出圈,最後剩下來的就是大王。要求:從鍵盤輸入M,N,編程計算哪個編號的猴子成爲大王。

var a:array[1..1000] of longint;
    m,n,i,j,sum:longint;
    weizhi:longint=0;
begin
  readln(m,n);
  for i:=1 to m do a[i]:=i;
  sum:=m;
  repeat
   weizhi:=weizhi+1;
   if weizhi>m then weizhi:=weizhi-m;
   if a[weizhi]<>0 then
    begin
     j:=j+1;
     if j=n then
      begin
       a[weizhi]:=0;
       j:=0;
       sum:=sum-1;
      end;
    end;
  until sum=1;
  for i:=1 to m do if a[i]<>0 then write(a[i]);
end.

 

 

Catalan數

h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)h(0) (n>=2)

    
h[0]:=1;
    h[1]:=1;
    for i:=2 to n do
        begin
            j:=i-1;
            k:=0;
            while k<>i do
                begin
                    h[i]:=h[i]+h[k]*h[j];
                    dec(j);
                    inc(k);
                end;
        end;

 

  1. 矩陣連乘: P=a1×a2×a3×……×an,依據乘法結合律,不改變其順序,只用括號表示成對的乘積,試問有幾種括號化的方案?(h(n-1)種)
  2. 一個棧(無窮大)的進棧序列爲1,2,3,…,n,有多少個不一樣的出棧序列。
  3. 對於一個二進制的01串,共n+m位,知足n個1,m個0,且0<=n-m<=1,該串還知足從左向右1的個數永遠大於0的個數。問共有多少種排列方式。
  4. 在一個凸多邊形中,經過若干條互不相交的對角線,把這個多邊形劃分紅了若干個三角形。求不一樣劃分的方案數。
  5. 給定N個節點,能構成多少種不一樣的二叉樹?

擴展歐幾里德算法

若存在一組解x0,y0,知足b*x0+(a mod b)*y0=d則取x=y0,y=x0-(a div b)*y0,有

ax+by=d這樣,咱們能夠用相似展轉相除的迭代法求解。

function extended-gcd(a, b:longint; var x, y:integer);
var x1, y1 :integer;
begin
     if b=0 then begin
         extended-gcd:=a;
         x:=1; y:=0
     end else begin
         extended-gcd:=extended-gcd(b, a mod b, x1, y1);
         x:=y1;
         y:=x1-(a div b)*y1;
     end;
  end;   

 

對天然數因子的計算

怎樣求一個數有多少個因數?
  對於一個已知的天然數,要求出它有多少個因數,可用下列方法:
  首先將這個已知數分解質因數,將此數化成幾個質數冪的連乘形式,而後把這些質數的指數分別加一,再相乘,求出來的積就是咱們要的結果。
  例如:求360有多少個因數。
  由於360分解質因數可表示爲:360=2^3×3^2×5,2、3、5的指數分別是3、2、1,這樣360的因數個數可這樣計算出:(3+1)(2+1)(1+1)=24個。

怎樣求有n個因數的最小天然數
一樣擁有n個(n爲肯定的數)因數的天然數能夠有多個不一樣的數,如何求出這些數中的最小數?
  這是與上一個問題相反的要求,是上一題的逆運算。
  好比求有24個因數的最小數是多少?
  根據上一問題解決過程的啓示,能夠這樣作,先將24分解因式,把24表示成幾個數連乘積的形式,再把這幾個數各減去1,做爲質數2、3、5、7......的指數,求出這些帶指數的數連乘積,試算出最小數便可。具體作法是:
  由於:24=4×6,  24=3×8, 24=4×3×2,
  如今分別以這三種表示法試求出目標數x:
  (1)、24=4×6,4-1=3,6-1=5
    X=2^5×3^3=864
    (2)、24=3×8,3-1=2,8-1=7
   X=2^7×3^2=1152
  (3)24=4×3×2,4-1=3, 3-1=2, 2-1=1
   X=2^3×3^2×5=360
  綜合(1)、(2)、(3)可知360是有24個因數的最小數。

矩陣乘法

 

矩陣乘法有下列三要素:

(1)可乘原則:前列數 = 後行數

(2)乘積階數:前行數 × 後列數

(3)Cij乘積元素:= 前矩陣的第i行元素與後矩陣的第j列對應元素的乘積之和

矩陣乘法求斐波那契數列的原理:

f(n) 是第n項的值。

f(1)= 1; f(2) =1;

f(n)= f(n-1) + (n-2)

分兩步推導:


位運算

Pascal和C中的位運算符號
    下面的a和b都是整數類型,則:
C語言  |  Pascal語言
——-+————-
a & b  |  a and b
a | b  |  a or b
a ^ b  |  a xor b
  ~a   |   not a
a << b |  a shl b
a >> b |  a shr b
    注意C中的邏輯運算和位運算符號是不一樣的。520|1314=1834,但520||1314=1,由於邏輯運算時520和1314都至關於True。一樣的,!a和~a也是有區別的。

    注意:在數學中TRUE對應一;FALSE對應0

 

經常使用位運算

   === 1. and運算 ===
    and運算一般用於二進制取位操做,例如一個數 and 1的結果就是取二進制的最末位。這能夠用來判斷一個整數的奇偶,二進制的最末位爲0表示該數爲偶數,最末位爲1表示該數爲奇數.

X and –X返回的值是X第一個1出現的位數
   === 2. or運算 ===
    or運算一般用於二進制特定位上的無條件賦值,例如一個數or 1的結果就是把二進制最末位強行變成1。若是須要把二進制元元元元最末位變成0,對這個數or 1以後再減一就能夠了,其實際意義就是把這個數強行變成最接近的偶數。

=== 3. xor運算 ===
    xor運算一般用於對二進制的特定一位進行取反操做,由於異或能夠這樣定義:0和1異或0都不變,異或1則取反。
    xor運算的逆運算是它自己,也就是說兩次異或同一個數最後結果不變,即(a xor b) xor b = a。

    === 4. not運算 ===
    not運算的定義是把內存中的0和1所有取反。使用not運算時要格外當心,你須要注意整數類型有沒有符號。若是not的對象是無符號整數(不能表示負數),那麼獲得的值就是它與該類型上界的差,由於無符號類型的數是用$0000到$FFFF依次表示的。

  === 5. shl運算 ===
    a shl b就表示把a轉爲二進制後左移b位(在後面添b個0)。例如100的二進制爲1100100,而110010000轉成十進制是400,那麼100 shl 2 = 400。能夠看出,a shl b的值實際上就是a乘以2的b次方,由於在二進制數後添一個0就至關於該數乘以2。
    === 6. shr運算 ===
    和shl類似,a shr b表示二進制右移b位(去掉末b位),至關於a除以2的b次方(取整)。咱們也常常用shr 1來代替div 2,好比二分查找、堆的插入操做等等。想辦法用shr代替除法運算可使程序效率大大提升。最大公約數的二進制算法用除以2操做來代替慢得出奇的mod運算,效率能夠提升60%。


經常使用位運算詳細示例

    功能              |           示例            |    位運算
———————-+—————————+——————–
去掉最後一位          | (101101->10110)           | x shr 1
在最後加一個0         | (101101->1011010)         | x shl 1
在最後加一個1         | (101101->1011011)         | x shl 1+1
把最後一位變成1       | (101100->101101)          | x or 1
把最後一位變成0       | (101101->101100)          | x or 1-1
最後一位取反          | (101101->101100)          | x xor 1
把右數第k位變成1      | (101001->101101,k=3)      | x or (1 shl (k-1))
把右數第k位變成0      | (101101->101001,k=3)      | x and not (1 shl (k-1))
右數第k位取反         | (101001->101101,k=3)      | x xor (1 shl (k-1))
取末三位              | (1101101->101)            | x and 7
取末k位               | (1101101->1101,k=5)       | x and (1 shl k-1)
取右數第k位           | (1101101->1,k=4)          | x shr (k-1) and 1
把末k位變成1          | (101001->101111,k=4)      | x or (1 shl k-1)
末k位取反             | (101001->100110,k=4)      | x xor (1 shl k-1)
把右邊連續的1變成0    | (100101111->100100000)    | x and (x+1)
把右起第一個0變成1    | (100101111->100111111)    | x or (x+1)
把右邊連續的0變成1    | (11011000->11011111)      | x or (x-1)
取右邊連續的1         | (100101111->1111)         | (x xor (x+1)) shr 1
去掉右起第一個1的左邊 | (100101000->1000)         | x and (x xor (x-1))


補碼

計算機用$0000到$7FFF依次表示0到32767的數,剩下的$8000到$FFFF依次表示-32768到-1的數。32位有符號整數的儲存方式也是相似的。稍加註意你會發現,二進制的第一位是用來表示正負號的,0表示正,1表示負。這裏有一個問題:0原本既不是正數,也不是負數,但它佔用了$0000的位置,所以有符號的整數類型範圍中正數個數比負數少一個。對一個有符號的數進行not運算後,最高位的變化將致使正負顛倒,而且數的絕對值會差1。也就是說,not a實際上等於-a-1。這種整數儲存方式叫作「補碼」。

動態規劃

PS:鑑於網上有大量的專門講述DP的文章,這裏很少作累贅的說明。總而言之,DP做爲NOIP必考算法,熟練的掌握是極其必要的。

特徵

1、最優子結構

      若是問題的一個最優解中包含了子問題的最優解,則該問題具備最優子結構。也稱最優化原理。

      最優子結構也能夠理解爲「總體最優則局部最優」。反之不必定成立。

2、重疊子問題

      在解決整個問題時,要先解決其子問題,要解決這些子問題,又要先解決他們的子子問題 ……。而這些子子問題又不是相互獨立的,有不少是重複的,這些重複的子子問題稱爲重疊子問題。

      動態規劃算法正是利用了這種子問題的重疊性質,對每個子問題只解一次,然後將其解保存在一個表中,之後再遇到這些相同問題時直接查表就能夠,而不須要再重複計算,每次查表的時間爲常數。

3.無後效性原則

已經求得的狀態,不受未求狀態的影響。

步驟

一、找出最優解的性質,並刻畫其結構特徵;
   二、遞歸地定義最優值(寫出動態規劃方程);
   三、以自底向上的方式計算出最優值;記憶化搜索(樹型)、遞推

   四、根據計算最優值時獲得的信息,構造一個最優解。

關鍵

狀態轉移方程的構造是動態規劃過程當中最重要的一步,也是最難的一步.對於大多數的動態規劃,尋找狀態轉移方程有一條十分高效的通道,就是尋找變化中的不變量(已經求得的值).定量處理的過程也就是決策實施的過程.

格式

動態規劃的通常倒推格式爲:

f[Un]=初始值;
for k←n-1 downto 1  do          //枚舉階段
   for U取遍全部狀態 do            //枚舉狀態
      for X取遍全部決策 do         //枚舉決策
         f[Uk]=opt{f[Uk+1]+L[Uk,Xk]}; 
 
L[Uk,Xk]:狀態Uk經過策略Xk到達狀態Uk+1的費用
輸出:f[U1]:目標

 

動態規劃的通常順推格式爲:

f[U1]=初始值;
for k←2 to n  do              //枚舉每個階段
   for U取遍全部狀態 do
      for X取遍全部決策 do
         f[Uk]=opt{f[Uk-1]+L[Uk-1,Xk-1]}; 
 
L[Uk-1,Xk-1]:
狀態Uk-1經過策略Xk-1到達狀態Uk  的費用
輸出:f[Un]:目標

 

推薦參考資料

劉永輝:《經過金礦模型介紹動態規劃》

dd_engi:《揹包九講》

ncepu:《動態規劃_重新手到專家》

董的博客:《揹包問題應用》

知乎問題:什麼是動態規劃?動態規劃的意義是什麼?

推薦習題

PS:以CODEVS爲主要OJ,POJ上的DP題目早有人整理

揹包型動態規劃:1014,1068

序列型動態規劃:1044,1576,3027

區間型動態規劃:1048,1154,1166

棋盤性動態規劃:1010,1169,1219,1220,

劃分型動態規劃:1017,1039,1040

樹型動態規劃:1163,1380

 

圖論算法

迴路問題

概念補充:奇點就是從這個點出發的線有奇數條的點。

Euler迴路

定義:若圖G中存在這樣一條路徑,使得它恰經過G中每條邊一次,則稱該路徑爲歐拉路徑。若該路徑是一個圈,則稱爲歐拉(Euler)迴路。

充要條件:圖連通且無奇點

Hamilton迴路

定義:通過圖的每一個頂點且僅一次的算法

充要條件:圖連通且奇點數爲0個或2個

一筆畫問題

如何判斷一個圖形是否能夠一筆不重地畫出

  ■⒈凡是由偶點組成的連通圖,必定能夠一筆畫成。畫時能夠把任一偶點爲起點,最後必定能以這個點爲終點畫完此圖。

  ■⒉凡是隻有兩個奇點的連通圖(其他都爲偶點),必定能夠一筆畫成。畫時必須把一個奇點爲起點,另外一個奇點終點。

  ■⒊其餘狀況的圖都不能一筆畫出。(奇點數除以二即可算出此圖需幾筆畫成。)

Einstein學起了畫畫,
此人比較懶~~,他但願用最少的筆畫畫出一張畫。。。
給定一個無向圖,包含 n 個頂點(編號1~n),m 條邊,求最少用多少筆能夠畫出圖
中全部的邊

解法:注意此題可能圖不是聯通的,因此須要用並查集預處理後完成

var n,m,i,u,v,sum1,sum2,mid:longint;
    b,f:array[0..1010] of longint;
 
procedure intt;
begin
    assign(input,'draw.in');
    assign(output,'draw.out');
    reset(input);
    rewrite(output);
end;
 
procedure outt;
begin
    close(input);
    close(output);
end;
 
procedure sort(l,r: longint);
      var
         i,j,x,y: longint;
      begin
         i:=l;
         j:=r;
         x:=f[(l+r) div 2];
         repeat
           while f[i]<x do
            inc(i);
           while x<f[j] do
            dec(j);
           if not(i>j) then
             begin
                y:=f[i];
                f[i]:=f[j];
                f[j]:=y;
                y:=b[i];
                b[i]:=b[j];
                b[j]:=y;
                inc(i);
                j:=j-1;
             end;
         until i>j;
         if l<j then
           sort(l,j);
         if i<r then
           sort(i,r);
      end;
 
function root(x:longint):Longint;
begin
    if f[x]=x then exit(x) else root:=root(f[x]);
    f[x]:=root;
    exit(root);
end;
 
begin
    intt;
    readln(n,m);
    for i:=1 to n do f[i]:=i;
    for i:=1 to m do
        begin
            read(u,v);
            if root(u)<>root(v) then
                f[root(u)]:=root(v);
            inc(b[u]);
            inc(b[v]);
        end;
    for i:=1 to n do mid:=root(i);
    sort(1,n);
    v:=n;
    while v>1 do
        begin
            while f[v-1]<>f[v] do dec(v);
            inc(sum2);
            mid:=f[v];
            while f[v]=mid do
                begin
                    if b[v] mod 2=1 then inc(sum1);
                    dec(v);
                end;
            if sum1>0 then sum1:=sum1-2;
            sum2:=sum2+sum1 div 2;
            sum1:=0;
        end;
    writeln((sum1 div 2)+sum2);
    outt;
end.

 

 

圖的遍歷

DFS

遍歷算法(遞歸過程):

  1)從某一初始出發點i開始訪問: 輸出該點編號;並對該點做被訪問標誌(以避免被重複訪問)。

  2)再從i的其中一個未被訪問的鄰接點j做爲初始點出發繼續深搜。(按序號從小到大的順序訪問)

  3)當i的全部鄰接點都被訪問完,則退回到i的父結點的另外一個鄰接點k再繼續深搜。

  直到所有結點訪問完畢。

var a:array[1..100,1..100]of longint;p:array[1..100] of boolean;m,n,j:longint;
procedure init;
var i,x,y:longint;
begin
 readln(n);readln(m);
 for i:=1 to m do
  begin
   read(x,y);a[x,y]:=1;a[y,x]:=1;
  end;
end;
procedure dfs(k:longint);
var i:longint;
begin
  write(k,' ');p[k]:=true;
  for i:=1 to m do
  if ((p[i]=false)and(a[k,i]=1)) then dfs(i);{k(i)是具體的點}
end;
begin
  fillchar(p,sizeof(p),false);
  init;
  dfs(1);
end.

 

BFS

按層次遍歷:

    從圖中某結點i出發,在訪問了i以後依次訪問i的各個不曾訪問的鄰接點(按序號由小到大的順序),

    而後分別從這些鄰接點出發按廣度優先搜索的順序遍歷圖,直至圖中全部可被訪問的結點都被訪問到。

var a:array[1..100] of longint;
    closed:longint=1;
    open:longint=0;
    t:array [1..100,1..100] of longint;
    p:array [1..100]of boolean;
    j,n,m,g:longint;
procedure add(x:longint);
begin
 inc(closed);
 a[closed]:=x;
end;
function del:longint;
begin
  inc(open);
  del:=a[open];
end;
procedure init;
var i,x,y:longint;
begin
 readln(n);
 readln(m);
 for i:=1 to m do
  begin
   read(x,y);
   t[x,y]:=1;
   t[y,x]:=1;
  end;
end;
procedure bfs(j:longint);
var i,k:longint;
begin
  a[1]:=(j);
  write(j,' ');
  p[j]:=true;
  while open<closed do
  begin
  inc(open);
  k:=a[open];
  for i:=1 to n do
  if ((p[i]=false)and(t[k,i]=1)) then
   begin
     write(i,' ');
     p[i]:=true;
     inc(closed);
     a[closed]:=i;
  end;
  end;
end;
begin
  for g:=1 to m do
  begin
  p[g]:=false;
  a[g]:=0;
  end;
  init;
  bfs(1);
end.

 

最短路徑

dijkstra算法

  • 算法思想

設給定源點爲Vs,S爲已求得最短路徑的終點集,開始時令S={Vs} 。當求得第一條最短路徑(Vs ,Vi)後,S爲{Vs,Vi} 。根據如下結論可求下一條最短路徑。

設下一條最短路徑終點爲Vj ,則Vj只有:

◆  源點到終點有直接的弧<Vs,Vj>;

◆ 從Vs 出發到Vj 的這條最短路徑所通過的全部中間頂點一定在S中。即只有這條最短路徑的最後一條弧纔是從S內某個頂點鏈接到S外的頂點Vj 。

          若定義一個數組dist[n],其每一個dist[i]份量保存從Vs 出發中間只通過集合S中的頂點而到達Vi的全部路徑中長度最小的路徑長度值,則下一條最短路徑的終點Vj一定是不在S中且值最小的頂點,即:

           dist[i]=Min{ dist[k]| Vk∈V-S }

       利用上述公式就能夠依次找出下一條最短路徑。

  • 示例程序
  • 算法思想
var a:array[1..100,1..100]of integer;//鄰接矩陣
    flag:array[1..100] of boolean;//已經找到最短路徑的節點標誌
    path:array[1..100]of integer;
    w,x,n,i,j,min,minn,k:integer;
begin
readln(n,k);
for i:=1 to n do//讀取鄰接矩陣,無路徑寫-1
    begin
    for j:=1 to n do 
begin
read(a[i,j]);
If a[i,j]=-1 then a[I,j]:=maxint;
End;
    readln;
    end;
fillchar(flag,sizeof(flag),false);//標明全部節點都未找到最短路徑
flag[k]:=true;                       //源節點除外
fillword(path,sizeof(path) div 2,k);
path[k]:=0;
minn:=k;//標記最小的點
for x:=2 to n do
    begin
    min:=32767;//標記要找下一個最短路徑點的距離
    for i:=1 to n do//找下一點點
        if (a[k,i]<min) and (flag[i]=false) then
            begin
            min:=a[k,i];
            minn:=i;
            end;
    flag[minn]:=true;//標記下一個點的找到
    for j:=1 to n do //更新最短路徑
        if (j<>minn) and (a[k,minn]+a[minn,j]<a[k,j]) and (flag[j]=false) then
        begin 
        a[k,j]:=a[k,minn]+a[minn,j];
        path[j]:=minn;
        end;
    end;
for    i:=1 to n do write(a[k,i],' ');//輸出源點到各個點的距離
writeln;
for    i:=1 to n do write(path[i],' ');//輸出源點到各個點的距離
end.

 

floyd算法

在原路徑裏增長一個新結點,若是產生的新路徑比原路徑更小,則用新路徑值代替原路徑的值。這樣依次產生n個矩陣(n爲網絡結點數)

用公式表示就是,對於K=1,2,3…n,第k個矩陣

 

運算過程當中K從1開始,而 i,j 則分別從1到n取遍全部值,而後k加1,直到k等於n時中止。

  • 示例程序
  • 算法思想
var st,en,f:integer;
    k,n,i,j,x:integer;
    a:array[1..10,1..10] of integer;//鄰接矩陣
    path:array[1..10,1..10] of integer;//path[a,b]爲A點到B點的最短路徑要走的第一個點
begin
    readln(n);//讀取節點數
    for i:=1 to n do//讀取鄰接矩陣
        begin
            for j:=1 to n do
                begin
                    read(k);
                    if k<>0 then a[i,j]:=k
                    else a[i,j]:=maxint;
                    path[i,j]:=j;
                end;
            readln;
        end;
for x:=1 to n do//I,J點之間加入X點,判斷是否更短,並更新。
    for i:=1 to n do
    for j:=1 to n do
    if a[i,j]>a[i,x]+a[x,j] then
        begin
            a[i,j]:=a[i,x]+a[x,j];
            path[i,j]:=path[i,x];
        end;
    readln(st,en);//讀取開始結束點
    writeln(a[st,en]);//寫出開始結束長度
    f:=st;
    while f<> en do//寫出路徑
        begin
            write(f);
            write('-->');
            f:=path[f,en];
        end;
    writeln(en);
end.

 

spfa算法

2.1.SPFA算法須要什麼

  SPFA須要用到一個先進先出的隊列Q。

  SPFA須要對圖中的全部頂點作一個標示,標示其是否在隊列Q中。
2.2.SPFA是怎樣執行的

  2.2.1 SPFA的初始化

  SPFA的初始化和Dijkstra相似。

  先把全部頂點的路徑估計值初始化爲代價最大值。好比:maxint。

  全部頂點都標記爲不在隊列中。

  起始頂點放入隊列Q中。

  起始頂點標記在隊列中。

  起始頂點的最短路徑估計值置爲最小值,好比0。

  而後下面是一個循環

  2.2.2 SPFA循環

  循環結束的條件是隊列Q爲空。第一次進入循環的時候,只有起始頂點一個元素。

  每次循環,彈出隊列頭部的一個頂點。

  對這個頂點的全部出邊進行鬆弛。若是鬆弛成功,就是出邊終點上對應的那個頂點的路徑代價值被改變了,且這個被鬆弛的頂點不在隊列Q中,就把這個被鬆弛的頂點入隊Q。注意,這裏頂點入隊的條件有2:1.鬆弛成功。2.且不在隊列Q中。

  當隊列Q沒有了元素。算法結束

  • 示例程序
  • 算法思想
const
    maxp=10000;{最大結點數}
    maxv=100000;{最大邊數}
var
    p,c,s,t:longint;{p,結點數;c,邊數;s:起點;t:終點}
    a,b:array[1..maxp,0..maxp]of longint;
    {a[x,y]存x,y之間邊的權;b[x,c]存與x相連的第c個邊的另外一個結點y}
    d:array[1..maxp]of integer;{隊列}
    v:array[1..maxp]of boolean;{是否入隊的標記}
//    rn:array[1..maxp]of integer;{各個點的入隊次數}
    dist:array[1..maxp]of longint;{到起點的最短路}
    head,tail:longint;{隊首/隊尾指針}
procedure init;
var
    i,x,y,z:longint;
begin
  read(p,c);
  for i:=1 to c do
    begin
     readln(x,y,z);
     {x,y:一條邊的兩個結點;z:這條邊的權值}
     inc(b[x,0]);b[x,b[x,0]]:=y;a[x,y]:=z;
     {b[x,0]:以x爲一個結點的邊的條數,b[x,a]:x點鏈接的第A個節點}
     inc(b[y,0]);b[y,b[y,0]]:=x;a[y,x]:=z;{無向圖的操做,取捨}
   end;
  readln(s,t);
end;
procedure spfa(s:longint);
var i,j,now,sum:longint;
begin
  fillchar(d,sizeof(d),0);
  fillchar(v,sizeof(v),false);
  for j:=1 to p do dist[j]:=maxlongint;
  dist[s]:=0;
  v[s]:=true;
  d[1]:=s;
// rn[s]:=1;
  {隊列的初始狀態,s爲起點}
  head:=1;tail:=1;
  while head<=tail do{隊列不空,注意:此處head>tial未空}
   begin
    now:=d[head];
    for i:=1 to b[now,0]do{now指向的全部節點}
    if dist[b[now,i]]>dist[now]+a[now,b[now,i]]then
    {若是最短路徑大於now節點的最短路徑和now節點到該節點的路徑之和}
    begin
     dist[b[now,i]]:=dist[now]+a[now,b[now,i]];{修改最短路}
     if not v[b[now,i]]then{擴展結點入隊——若是此節點還沒有入隊,則入隊,且表示}
     begin
      inc(tail);
      d[tail]:=b[now,i];
      v[b[now,i]]:=true;
//    inc(rn[b[now,i]]);
//    if rn[b[now,i]]>c then begin writeln('fu huan');halt; end;
       end;
   end;
   v[now]:=false;{釋放結點,必定要釋放掉,由於這節點有可能下次用來鬆弛其它節點}
   inc(head);
 end;
end;
procedure print;
begin
  writeln(dist[t]);
end;
begin
  init;
  spfa(s);
  print;
end.

 

最小生成樹

prim算法

假設N=(V,E)是連通網,TE是N上最小生成樹中邊的集合。

一、算法實現:算法從U={u0}(u0∈V),TE={}開始,重複執行下述操做:

在全部u∈U,v∈V-U的邊(u,v)中找一條代價最小的邊(u0 ,v0),將其併入集合TE,同時將v0併入U集合。

當U=V則結束,此時TE中必有n-1條邊,則T=(V,{TE})爲N的最小生成樹。時間複雜度爲0(n2)

二、算法思想:普里姆算法構造最小生成樹的過程是從一個頂點U={u0}做初態,不斷貪心尋找與U中頂點相鄰且代價最小的邊的另外一個頂點,擴充到U集合直至U=V爲止。

  • 優化方法

     咱們很容易就能夠發現prim算法的關鍵:每次如何從生成樹T到T外的全部邊中,找出一條最小邊。例如,在第k次前,生成樹T中已有k個頂點和(k-1)條邊,此時,T到T外得全部邊數爲k*(n-k),固然,包括沒有邊的兩頂點,咱們記權值爲「無窮大」的邊在內,從如此多的邊中查找最短邊,時間複雜度爲O(k(n-k)),顯然沒法知足咱們的指望。

      咱們來看O(n-k)的方法:假定在進行第k次前已經保留着從T中到T外的每個頂點(共n-k個)的各一條最短邊,在進行第k次時,首先從這(n-k)條最短邊中,找出一條最最短邊(它就是從T到T外的最短邊),假設爲(vi,vj),此步須要進行(n-k)次比較;而後把邊(vi,vj)和頂點vj併入T中的邊集TE和頂點集U中,此時,T外只有n-(k+1)個頂點,對於其中的每一個頂點vt,若(vj,vt)邊上的權值小於原來保存的從T中到vt的最短邊的權值,則用(v,vt)修改之,不然,保持原最小邊不變。這樣就把第k次後T中到T外的每個頂點vt的各一條最短邊都保留下來了,爲第(k+1)次最好了準備。這樣Prim的總時間複雜度爲O(n^2)。

  • 示例程序(優化後)
  • 算法思想
const
  max=1000;
var
  map:array[1..MXN,1..MXN] of longint;
  cost:array[1..MXN] of longint;
  visit:array[1..MXN] of boolean;
  i,j,n,m,x,y,v:longint;
function prim():longint;
  var
   i,j,min,mini,ans:longint;
  begin
   ans:=0;
   for i:=1 to n do begin visit[i]:=false;cost[i]:=maxlongint;end;
//visit[i]是i點是否被訪問的標誌,cost[i]是到i點的最小權邊。
   for i:=2 to n do
     if map[1,i]<>0 then cost[i]:=map[1,i];visit[1]:=true;
for i:=1 to n-1 do
    begin
     min:=maxlongint;
     for j:=1 to n do
        if not visit[j] and (cost[j]<min) then 
           begin min:=cost[j];mini:=j;end;
     visit[mini]:=true;inc(ans,min);
     for j:=1 to n do
      if not visit[j] and (map[mini,j]>0) and (map[mini,j]<cost[j]) then cost[j]:=map[mini,j];
//更新圈內圈外存儲的最短距離
    end;
   exit(ans);
  end;
begin
  readln(n,m);
  for i:=1 to m do
   begin
    readln(x,y,v);
    if (map[x,y]=0) or (map[x,y]>v) then
     begin
      map[x,y]:=v;map[y,x]:=v;
     end;
   end;
  writeln(prim());
end.

 

Kruskal算法

先構造一個只含 n 個頂點、而邊集爲空的子圖,把子圖中各個頂點當作各棵樹上的根結點,以後,從網的邊集 E 中選取一條權值最小的邊,若該條邊的兩個頂點分屬不一樣的樹,則將其加入子圖,即把兩棵樹合成一棵樹,反之,若該條邊的兩個頂點已落在同一棵樹上,則不可取,而應該取下一條權值最小的邊再試之。依次類推,直到森林中只有一棵樹,也即子圖中含有 n-1 條邊爲止。
  時間複雜度爲爲O(e^2), 使用並查集優化後複雜度爲 O(eloge),與網中的邊數有關,適用於求邊稀疏的網的最小生成樹。

   設圖G的度爲,G=(V,E),設該圖的最小生成樹爲T=(V,TE),設置邊的集合TE的初始狀態爲空集。將圖G中的邊按權值從小到大排好序,而後從小的依次開始選取,若選取得邊使生成樹T不造成迴路,則把它併入TE中,保留做爲T的一條邊;若選取得邊使生成樹造成迴路,則捨棄;如此繼續進行,直到使TE中包含n-1條邊爲止。

實現克魯斯卡爾(Kruskal)算法時,要解決如下兩個問題:

1.選擇代價最小的邊(堆排序,或簡單選擇排序);

2.斷定邊所關聯的兩個頂點是否在同一個連通份量中(集合)

  • 算法的關鍵與優化:

     kruskal算法實現過程當中的關鍵和難點在於:如何判斷欲加入的一條邊是否與生成樹中已經保留的邊造成迴路?咱們能夠將頂點劃分到不一樣的集合中,每一個集合的頂點表示一個無迴路的連通份量。初始時,咱們把n個頂點劃分到n個集合中,每一個集合只有一個頂點,代表頂點之間互不相通。當選取一條邊時,若它的兩個頂點分屬兩個不一樣的集合,則代表此邊連通了兩個不一樣的連通份量,因每一個連通份量無迴路,因此連同後獲得的連通份量仍無迴路。所以這條邊應該保留,且合併成一個連通份量,若選取得邊的兩個頂點屬於同一個連通份量,則應捨棄,由於一個無迴路的連通份量內加入一條新邊必然產生迴路。

  • 示例程序
const MXN=1000;
 type
  rqmap=record
   s,t,v:longint;
  end;
 var
  map:array[1..MXN*MXN] of rqmap;
  father:array[1..MXN] of longint;
  n,m,i,ingraph,ans:longint;
procedure qsort(b,e:longint);//排序
  var
   i,j,x:longint;
   t:rqmap;
  begin
   i:=b;j:=e;x:=map[(i+j)>>1].v;
   while (i<=j) do
    begin
     while (map[i].v<x) do inc(i);
     while (map[j].v>x) do dec(j);
     if (i<=j) then begin t:=map[i];map[i]:=map[j];map[j]:=t;inc(i);dec(j);end;
    end;
   if i<e then qsort(i,e);
   if j>b then qsort(b,j);
  end;
 function find(x:longint):longint; 
  begin
   if (father[x]=x) then exit(x);
   father[x]:=find(father[x]);//路徑壓縮
   exit(father[x]);
  end;
procedure union(a,b:longint); //並查集
  begin
   father[find(a)]:=find(father[b]);
  end;
 begin
readln(n,m);
  for i:=1 to n do father[i]:=i;
  for i:=1 to m do readln(map[i].s,map[i].t,map[i].v);
  qsort(1,m);ans:=0;ingraph:=1;i:=0;
  while (ingraph<n) do
   begin
    inc(i);
    if find(map[i].s)<>find(map[i].t) then
     begin
      inc(ingraph);inc(ans,map[i].v);union(map[i].s,map[i].t);
     end;
   end;
  writeln(ans);
end.

 

topology排序

總體思想:掃描縱列,若是縱列上沒有任何後繼節點,則找到,把這一列的橫排都填爲零

var a:array[1..100,1..100] of longint;
    s:array[1..100] of longint;
    ans:array[1..100] of longint;
    i,j,n,kg,x,w:longint;
begin
  readln(n);
  fillchar(a,sizeof(a),0);
  fillchar(s,sizeof(s),0);
  for i:=1 to n do
   for j:=1 to n do
    begin
     read(a[i,j]);
    end;
  i:=1;
  x:=1;
  for j:=1 to n do
   begin
    kg:=0;
    for w:=1 to n do
     if a[w,j]=1 then inc(kg);
    if kg=0 then
     begin
      ans[x]:=j;
      inc(x);
      for w:=1 to n do a[j,w]:=0;
     end;
   end;
  for i:=1 to n do
   write(ans[i],' ');
end.

 

 

關鍵路徑

利用拓撲排序

const maxx=50;
var a:array[1..maxx,1..maxx]of longint;//鄰接矩陣
    p:array[1..maxx] of longint;//拓撲路徑
    b:array[1..maxx] of longint;//b[i]表示第i個點到起點的距離
    c:array[1..maxx] of longint;//c[i]表示在關鍵路徑時i點的前驅
    pd:array[1..maxx] of boolean;
    n,m,i,j,k:longint;
procedure change;
var i,j:longint;
begin
  fillchar(a,sizeof(p),0);
  fillchar(b,sizeof(b),0);
  fillchar(c,sizeof(c),0);
  fillchar(p,sizeof(p),0);
  fillchar(pd,sizeof(pd),false);
end;
procedure tp(k:longint);
var i,j:longint;
begin
    if k=n+1 then exit;
    for i:=1 to n do
    begin
        if (b[i]=0) and (pd[i]) then
        begin
            p[k]:=i;//拓撲序列
            pd[i]:=false;
            for j:=1 to n do if a[i,j]<>0 then dec(b[j]);//b[j] 用來存入度
            tp(k+1);
        end;
    end;
end;
procedure main;
var i,j:longint;
begin
  b[p[1]]:=0;
  c[p[1]]:=0;
  for i:=2 to n do
   for j:=1 to n do
    if b[p[j]]+a[p[j],p[i]]>b[p[i]] then
     begin
      b[p[i]]:=b[p[i]]+a[p[j],p[i]];
      c[p[i]]:=p[j];
     end;
end;
procedure output;
var i:longint;
begin
  for i:=1 to n-1 do
   write(c[i],'-->');
  writeln(c[n]);
  writeln(b[n]);
end;
begin
  change;
  readln(n);
  for i:=1 to n do
   for j:=1 to n do
    read(a[i,j]);
  tp(1);
  main;
  output;
end.

 

按照概念

(1)關鍵路徑:AOE網中,從事件i到j的路徑中,加權長度最大者稱爲i到j的關鍵路徑(Critical Path),記爲cp(i,j)。特別地,始點0到終點n的關鍵路徑cp(0,n)是整個AOE的關鍵路徑。

(2)事件最先/晚發生時間

etv:事件vi的最先發生時間爲,從始點到vi的最長(加權)路徑長度,即cp(0,i)

ltv:事件vi的最晚發生時間爲:在不拖延整個工期的條件下,vi的可能的最晚發生時間。即ltv(i) = etv(n) - cp(i, n)

要從右往左推

(3)活動最先/晚開始時間

活動ak=<vi, vj>的最先開始時間ete(k):等於事件vi的最先發生時間,即ete(k) = etv(i) = cp(0, i)

活動ak=<vi, vj>的最晚開始時間lte(k)定義爲:在不拖延整個工期的條件下,該活動的容許的最遲開始時間,

即lte(k) = ltv(j)–len(i, j),這裏,ltv(j)是事件j的容許的最晚發生時間,len(i, j)是ak的權。

活動ak的最大可利用時間:定義爲lte(k)-ete(k)

4.關鍵路徑:是活動的集合

若活動ak的最大可利用時間等於0(即(lte(k)=ete(k)),則稱ak 爲關鍵活動,不然爲非關鍵活動。

顯然,關鍵活動的延期,會使整個工程延期。但非關鍵活動否則,只要它的延期量不超過它的最大可利用時間,就不會影響整個工期。

const maxx=100;
var a:array[1..maxx,1..maxx] of longint;
    et,eet:array[1..maxx] of longint;
    //eet表示活動最先能夠開始的時間,et表示活動最遲應該開始的時間
    n,i,j,k,x,y,w:longint;
    min,max:longint;
begin
  fillchar(a,sizeof(a),char(-1));
  fillchar(et,sizeof(et),0);
  fillchar(eet,sizeof(eet),0);
  readln(n);
  readln(x,y,w);
  while x<>0 do
   begin
     a[x,y]:=w;
     readln(x,y,w);
   end;
  eet[1]:=0;
  for i:=2 to n do
   begin
    max:=0;
    for j:=1 to n do
     if a[j,i]<>-1 then
      if a[j,i]+eet[j]>max then
        max:=a[j,i]+eet[j];
    eet[i]:=max;
   end;
  et[n]:=eet[n];
  for i:=n-1 downto 1 do//最遲開始時間須要倒着推
   begin
    min:=maxint;
    for j:=1 to n do
        if et[j]-a[i,j]<min then//事件vi的最晚發生時間爲:在不拖延整個工期的條件下,vi的可能的最晚發生時間。即ltv(i) = etv(n) - cp(i, n)
       min:=et[j]-a[i,j];
   if a[i,j]<>-1 then
    et[i]:=min;
   end;
  for i:=1 to n-1 do
   if et[i]=eet[i] then
   //若是最先開始時間和最晚開始時間相同即認爲是關鍵事件
     write(i,'-->');
  writeln(n);
  writeln(max);
end.
//默認爲節點按順序輸入,即序號前面的結點會影響序號後面結點的活動

 

次短路

刪邊:每次刪最短路上的一條邊,用Dijkstra+堆求單源最短路徑,則每次求最短路徑時間複雜度爲O(N*log(N+M) + M),因此總的時間複雜度爲O(N*M*log(N+M) + M^2)。

次小生成樹

{O(n^3)}

先求MST 並記錄選擇的邊

依次刪掉這些邊(n-1次)作MST求出新的生成樹的值

則這些值中最小的即爲the Second MST

 

{O(n^2)}

在求MST的同時,對於新加入的節點,max[i][j]記錄已加入的節點i到新加入節點j的路徑上邊權最大的值。

求完後mst記錄最小生成樹的權值和,枚舉每一條不在樹中的邊u~v,必然與MST造成一個圈,max[u][v]中存的就是圈中除了新加入的邊外最大的邊了。那麼加入u~v後,生成樹的權值min=mst+g[u][v]-max[u][v];

最小的min就是次小生成樹

匈牙利算法

var
  g:array[1..maxn,1..maxm]of boolean;  //圖,g[i,j]:節點i到節點j是否有邊
  y:array[1..maxm]of boolean;          //訪問標記,y[i]:節點i是否已被訪問過
  link:array[1..maxm]of longint;       //與節點i匹配的點,link[i]=0 表示i沒有被匹配
 
function find(v:longint):boolean; //從v出發找匹配
var 
  i:longint;
begin
  for i:=1 to m do                         //枚舉與v相連的點i
    if g[v,i] and (not y[i]) then          //若是i與v相連且還未被訪問過
    begin
      y[i]:=true;                          //標記i已被訪問過
      if (link[i]=0)or find(link[i]) then  
//若是i無匹配或原來的匹配點link[i]能夠另找一個匹配
      begin
        link[i]:=v;                        //讓v-i與i匹配
        find:=true;                        //匹配成功並返回
        exit;
      end;
  end;
  find:=false;                            //匹配不成功
end;
 
begin
  //read the graph into array g[][]
  for i:=1 to n do
  begin
    fillchar(y,sizeof(y),0);
    if find(i) then inc(ans);
  end;
  //now then ans stores the max match
end.

 

 

博弈

取對稱狀態

Alice and Bob decide to play a funny game. At the beginning of the game they pick n(1 <= n <= 106) coins in a circle, as Figure 1 shows. A move consists in removing one or two adjacent coins, leaving all other coins untouched. At least one coin must be removed. Players alternate moves with Alice starting. The player that removes the last coin wins. (The last player to move wins. If you can't move, you lose.) 
若是n<=2那麼先手的會得到勝利,當n>=3時,先手的走了一步之後,後手的能夠把這個一個大圖分紅兩個徹底相同的小圖,每步都是如此,則在n步之後,先手的總會無棋可取,後手的得到勝利。

var t,n,i:longint;
begin
    while true do
        begin
            readln(n);
            if n=0 then halt;
            if n<=2 then writeln('Alice')
            else writeln('Bob');
        end;
end.

 

取NIM值

Here is a simple game. In this game, there are several piles of matches and two players. The two player play in turn. In each turn, one can choose a pile and take away arbitrary number of matches from the pile (Of course the number of matches, which is taken away, cannot be zero and cannot be larger than the number of matches in the chosen pile). If after a player’s turn, there is no match left, the player is the winner. Suppose that the two players are all very clear. Your job is to tell whether the player who plays first can win the game or not.

  隨機博弈指的是這樣的一個博弈遊戲,目前有任意堆石子,每堆石子個數也是任意的,雙方輪流從中取出石子,規則以下:1)每一步應取走至少一枚石子;每一步只能從某一堆中取走部分或所有石子;2)若是誰取到最後一枚石子就勝。也就是尼姆博弈(Nimm Game)。必敗局面:也叫奇異局勢不管作出何出操做,最終結果都是輸的局面。必敗局面通過2次操做後,能夠達到另外一個必敗局面。必勝局面:通過1次操做後能夠達到必敗局面。即當前局面不是必敗局面就是必勝局面,而必勝局面能夠一步轉變成必敗局面。

  最終狀態:
  (1)最後剩下一堆石子;(必勝局面)

  (2)剩下兩堆,每堆一個;(必敗局面)

(3)當石子剩下兩堆,其中一堆只剩下1顆,另外一堆剩下多於n顆石子時,當前取的人只需將多於1顆的那一堆取出n-1顆,則局面變爲剛纔提到的必敗局面。(必勝局面)

判斷當前局勢是否爲必勝(必敗)局勢:
    1)把全部堆的石子數目用二進制數表示出來,當所有這些數按位異或結果爲0時當前局面爲必敗局面,不然爲必勝局面;
    2)在必勝局面下,由於全部數按位異或的結果
是大於零的,那麼經過一次取,將這個(大於其它全部數按位異或的結果的)數降低到其它全部數按位異或的結果,這時局面就變爲必敗局面了。

定理:一組天然數中必然存在一個數,它大於等於其它全部數按位異或的結果。

證實:原命題等價於,設a1^a2^... ^an=p,p≠0時,必存在k,使得ak^p<ak< span="">(當p=0時,對於任意的k,有ak^p=ak)。
    設p的最高位是第q位,則至少存在一個k,使得ak的第q位也是1,而ak^p的第q位爲0,因此ak^p<ak
      補綴一點,(a^b)^b=a^(b^b)=a^0=a,因此ak^p至關於「其它全部數按位異或的結果」。

var x,y,n:int64;i:longint;
procedure main;
begin
    y:=0;
    read(n);
    for i:=1 to n do
        begin
            read(x);
            y:=y xor x;
        end;
    if y=0 then writeln('No')
    else writeln('Yes');
end;
begin
    while not seekeof do
        main;
end.

 

分治

二分法找最值

var n,m,a1,b1,a2,b2,i:longint;
    q:array[1..100]of longint;
procedure fenzhi(a,b:longint;var max,min:longint);
var d,max1,max2,min1,min2:longint;
begin
  if a=b then
   begin
    max:=q[a];
    min:=q[a];
   end
   else
    if b-a=1 then
     begin
      if a>b then
       begin
        max:=q[a];
        min:=q[b];
       end
      else
       begin
        max:=q[b];
        min:=q[a];
       end;
      end
    else
     begin
      d:=(a+b) div 2;
      fenzhi(a,d,max1,min1);
      fenzhi(d+1,b,max2,min2);
      if max1>max2 then max:=max1
      else max:=max2;
      if min1<min2 then min:=min1 else min:=min2;
     end;
end;
begin
  read(n);
  for i:=1 to n do read(q[i]);
  m:=n div 2;
  fenzhi(1,m,a1,b1);
  fenzhi(m+1,n,a2,b2);
  if b1<b2 then write(b1,' ') else write(b2,' ');
  if a1>a2 then write(a1,' ') else write(a2,' ');
end.

 

回溯

N皇后

procedure try(i:longint);
var j:longint;
begin
    if i=n+1 then begin print;exit;end;
      for j:=1 to n do
        if a and b[j+i] and c[j-i] then begin
          x:=j;
       a[j]:=false; b[j+i]:=false; c[j-i]:=false;
       try(i+1);
       a[j]:=true; b[i+j]:=true; c[j-i]:=true;
     end;
end;

 

Hanoi Tower  

{h(n)=2*h(n-1)+1   h(1)=1

初始銅片分佈在3個柱上,給定目標柱goal;h[1..3,0..n]存放三個柱的狀態,now與nowp存最大的不到位的銅片的柱號和編號,h[I,0]存第I個柱上的個數。

procedure hanoi(n,a,b,c:byte); {將第n塊銅片從a柱經過b柱移到c柱上}
begin
  if n=0 then exit;
  hanoi(n-1,a,c,b); {將上面的n-1塊從a柱經過c柱移到b柱上}
  write(n,’moved from’,a,’to’,c);
  hanoi(n-1,b,a,c);{ 將b上的n-1塊從b柱經過a柱移到c柱上}
end;

 

高精度計算

高精度加法

var s1,s2:string;
    a,b:array[1..100] of longint;
    la,le,len,i:longint;
begin
  readln(s1);
  readln(s2);
  la:=length(s1);
  for i:= 1 to la do a[i]:=ord(s1[la+1-i])-48;
  le:=length(s2);
  for i:=1 to le do b[i]:=ord(s2[le+1-i])-48;
  len:=la;
  if le>len then len:=le;
  for i:=1 to len do
    begin
      a[i+1]:=a[i+1]+(a[i]+b[i]) div 10;
      a[i]:=(a[i]+b[i]) mod 10;
    end;
  if a[len+1]>0 then len:=len+1;{判斷最高位}
  for i:=len downto 1 do write(a[i]);
end.

 

高精度減法

const n=1000;
type arr=array[0..n]of longint;
var a,b:arr;s:string;
function f:boolean;
var i:longint=1;
begin
  f:=true;
  while ((i<n)and(a[i]=b[i])) do inc(i);
  if a[i]<=b[i] then f:=false;
end;
procedure init(var p:arr);
var m:char;
    k,i:longint;
begin
   k:=1;
   read(m);
   while m in['0'..'9'] do
   begin
      p[k]:=ord(m)-48;k:=k+1;read(m);{使得可以不斷讀數}
   end;
   for i:=1 to k do
   begin
      p[n+i-k]:=p[i];p[i]:=0;{把最小位放在前面,便於順次詳見}
   end;
end;
procedure work;
var t,g:longint;i:longint;
begin
  if f then write('');
  if not f then
  begin
  s:='-';
{判斷減數和被減數大小,若是被減數大於減數就填負號後交換位置}
  for i:=1 to n do
   begin
    t:=a[i];a[i]:=b[i];b[i]:=t;
   end;
  end;
   g:=0;{進位標誌}
  for i:=n downto 1 do
  begin
  if a[i]>=b[i]+g then
  begin
   a[i]:=a[i]-b[i]-g;g:=0;
  end{不須要借位時}
  else
  begin
   a[i]:=10+a[i]-b[i]-g;g:=1;
  end;
  end;
end;
procedure output;
var i:longint=1;
begin
  while((a[i]=0)and(i<n))do inc(i);
{當在範圍限度內且爲零時就不斷不斷向後移以尋找作完減法後的最高位}
  while i<n do
  begin
   write(a[i]);inc(i);
  end;
end;
begin
  fillchar(a,sizeof(a),0);fillchar(b,sizeof(b),0);
  init(a); readln;
  init(b);readln;
  work;
  write(s);output;
end.

 

高精度乘法

const max=200;
type arr=array[1..max]of longint;
     arrr=array[1..2*max] of longint;
var a,b:arr;c:arrr;k,s1,s2,n:longint;
procedure init(var p:arr);
var s:string; i:longint;
begin
   read(s);n:=length(s);
   if s='0' then
   begin
    write('0');halt;
   end;
   for i:=n downto 1 do
    p[n-i+1]:=ord(s[i])-48;{把小位數放在前面}
end;
procedure work;
var i,j,x,l:longint;
begin
  for i:=1 to s1 do
   for j:=1 to s2 do
   begin
    l:=a[i]*b[j];
c[i+j-1]:=c[i+j-1]+l;
{解決錯位相加問題,a、b數組的值正好放在c數組【i+j-1】的位置上,就能夠用這個位置原來的數加上新乘出的數解決問題,在運算過程當中,a、b數組裏面的值不能改變,因此必需要新開一個新數組}
   end;
  for i:=1 to k do
    begin
      c[i+j]:=c[i+j]+c[i+j-1] div 10;
{這一步必須在前面不然下面一步就改變了c數組裏面的值}
      c[i+j-1]:=c[i+j-1] mod 10;
    end;
  while c[k]=0 do dec(k);{尋找最高位}
  x:=c[k];
  while x>0 do
   begin
    c[k]:=x mod 10;
    x:=x div 10;
    inc(k);
   end;{解決最高位問題}
end;
procedure output;
var i:longint;
begin
 for i:=k-1 downto 1 do write(c[i]);
end;
begin
  fillchar(a,sizeof(a),0); fillchar(b,sizeof(b),0);fillchar(c,sizeof(c),0);
  init(a);readln;s1:=n;
  init(b);readln; s2:=n;
  k:=s1+s2; {乘法得出結果後的最高位數爲兩個位數的因數之和}
  work;output;
end.

 

高精度除法

type arr=array[0..10000]of longint;
var a,b,c,d:arr;a1,b1,n,j,I,len:longint;s,s1,s2:string;
procedure init;
begin
  readln(s);len:=length(s);
  a1:=pos(' ',s);s1:=copy(s,1,a1-1);s2:=copy(s,a1+1,len-a1);a1:=length(s1);
  for i:=1 to a1 do a[i]:=ord(s1[a1-i+1])-48;
  b[0]:=length(s2);
  for i:=1 to b[0] do b[i]:=ord(s2[b[0]-i+1])-48;
end;
function f:boolean;
var i:longint;
begin
  if d[0]>b[0] then exit(true);
  if d[0]<b[0] then exit(false);
  for i:=d[0] downto 1 do
  begin
   if d[i]>b[i] then exit(true)
   else if d[i]<b[i] then exit(false);
  end;
  exit(true);
end;
procedure change(x:longint);
var i:longint;
begin
  inc(d[0]);
  for i:=d[0] downto 2 do d[i]:=d[i-1];
  d[1]:=x;
end;
function work:boolean;
var i:longint;
begin
  if f=false then exit(false);
  for i:=1 to d[0] do
    begin
     d[i+1]:=d[i+1]-1+(d[i]+10-b[i]) div 10;
     d[i]:=(d[i]+10-b[i])mod 10;
    end;
  while (d[d[0]]=0) and(d[0]>1) do dec(d[0]);
  exit(true);
end;
procedure output;
var i:longint;
begin
  while (c[a1]=0) and(a1>1) do dec(a1);
  for i:=a1 downto 1 do write(c[i]);
  writeln;
end;
begin
  fillchar(a,sizeof(a),0);
  fillchar(b,sizeof(b),0);
  fillchar(c,sizeof(c),0);
  fillchar(d,sizeof(d),0);
  init;
  if a1<b[0] then
   begin
    write('0...');
    for j:=a1 downto 1 do write(a[j]);
    halt;
   end;
  for j:=a1 downto 1 do
   begin
    change(a[j]);
    while work do inc(c[j]);
   end;
  output;
end.

 

高精度階乘

{高精度乘單精度}
const max=2000;
var a:array[1..max] of longint;n,i,j,l,q:longint;
begin
  readln(n);
  a[1]:=1;l:=1;
{l表示前面的數依次的階乘}
  for i:=2 to n do
  begin
    for j:=1 to l do a[j]:=a[j]*i;{作乘法}
    for j:=1 to l do
    begin
      a[j+1]:=a[j+1]+a[j] div 10;
      a[j]:=a[j] mod 10;{處理進位問題}
    while a[l+1]<>0 do
    begin
      l:=l+1;
      a[l+1]:=a[l+1]+a[l] div 10;
      a[l]:=a[l] mod 10;
end;
{處理最高位及數位問題}
    end;
  end;
  for i:=l downto 1 do write(a[i]);
end.

 

高級數據結構  

二叉樹

//二叉樹的先序遍歷
type note=record
     father,lch,rch:longint;end;
var a:array[1..100] of note;n,j,t,m:longint;s:longint;
procedure duru;
var i:longint;f,l,r:longint;
begin
 read(n);
 for i:=1 to n do
  begin
  readln(f,l,r);
  a[f].lch:=l;{f的左孩子爲l}
  a[f].rch:=r;{f的右孩子爲r}
  if l<>0 then a[l].father:=f;{若是有左孩子那麼左孩子的父親是f}
  if r<>0 then a[r].father:=f;{若是有右孩子那麼右孩子的父親是f}
  end;
end;{輸入的過程,第一個是父親,後兩個爲左右孩子,0表示空}
function root:longint;
var i:longint;
begin
 for i:=1 to n do{i表示結點}
  if a[i].father=0 then
begin
 root:=i;exit;{若是這個結點的父親爲零,則這個結點就是根}
end;
end;{找根的程序}
procedure xiangen(t:longint);
begin
  if t<>0 then
   begin
    write(t,' ');{先根}
    xiangen(a[t].lch);{再對左子樹先根遍歷,左孩子當根}
    xiangen(a[t].rch);{再對右子樹先根遍歷,後孩子當根}
  end;
end;{進行先序遍歷並輸出的程序}
begin
 duru;m:=root;
 writeln(m);xiangen(m);
end.
 
 
//二叉樹的中序遍歷
procedure zhonggen(t:longint);
begin
  if t<>0 then
   begin
    write(t,' ');
    zhonggen(a[t].lch);
    zhonggen(a[t].rch);
  end;
end;
 
 
//二叉樹的後序遍歷
procedure hougen(t:longint);
begin
  if t<>0 then
   begin
    hougen(a[t].lch);
    hougen(a[t].rch);
    write(t,' ')
  end;
end//已知二叉樹的前序遍歷與中序遍歷求後序遍歷
var s1,s2:string;
procedure work(s1,s2:string);
var b,a:longint;{b,a的值在不斷改變,必須放在局部變量裏}
begin
  if s1<>'' then
   begin
    b:=length(s1);
    a:=pos(s1[1],s2);
    work(copy(s1,2,a-1),copy(s2,1,a-1));
    work(copy(s1,a+1,b-a),copy(s2,a+1,b-a));
    write(s1[1]);
   end;
end;
begin
  readln(s1);readln(s2);
  work(s1,s2);
end.

 

 

並查集

{已按秩優化}
a:array[1..max] of longint;//a[i]表示i的前驅
function find(i:longint):longint;//非遞歸算法找i的根
var j,k,t:longint;
begin
       j:=i;
       while a[j]<>0 do j:=a[j]; // 順着i向上找根
       find:=j;
       k:=i; //從i開始對子樹結點進行路徑壓縮
       while a[k]<>0 do begin t:=k;k:=a[k];a[t]:=j;end;
end; 
function root(i:longint):longint;//遞歸算法找i的根
var j,k,t:longint;
begin
       if a[i]=0 then exit(i); //若i爲根,返回自己結點序號
       root:=root(a[i]); //不然繼續向上找
       a[i]:=root;
end;
procedure union(x,y:longint);//合併
begin
  if root[x]=root[y] then
   else f[y]:=x;
end;

 

樹狀數組

{求2^k的代碼:}
function  lowbit(t:longint):longint;
begin
    lowbit:=t and(t xor (t-1));
end;
 
{當想要查詢一個SUM(n)(求A[1]..A[n]的和),能夠依據以下算法便可:
step1:令sum = 0,轉第二步;
step2:假如n <= 0,算法結束,返回sum值,不然sum = sum + Cn,轉第三步;
step3:令n = n–lowbit(n),轉第二步。}
function getsum(k:integer):integer;
var t:integer;
begin
    t:=0;
    while k>0 do
        begin
            t:=t+c[k];     
            k:=k-lowbit(k);
        end;
    getsum:=t;
end;
 
{修改算法以下(給某個結點i加上x):
step1: 當i > n時,算法結束,不然轉第二步;
step2: Ci = Ci + x, i = i + lowbit(i)轉第一步。
i = i +lowbit(i)這個過程實際上也只是一個把末尾1補爲0的過程。}
procedure add(i:longint;x:longint);
begin
    while i<=n do
        begin
            c[i]:=C[i]+x;
            i:=i+lowbit(i);
        end;
end;

 

線段樹 

type xtree=record
    a,b,left,right,cover:longint;
    end;
//a,b是區間左右端點,當a=b表示一個葉子節點;left,right表示左右兒子,cover表示是否被覆蓋
 
var tree:array[1..1000] of xtree;
    c,d,number,tot:longint;
    //tot爲一共有多少個結點
 
procedure make(a,b:longint);
var now:longint;//now必須爲局部變量,dfs中枚舉變量同樣的道理
begin
    inc(tot);
    now:=tot;
    tree[now].a:=a;
    tree[now].b:=b;
    tree[now].cover:=0;
    if a+1<b then
        begin
            tree[now].left:=tot+1;
            make(a,(a+b)div 2);
            tree[now].right:=tot+1;
            //若now爲全局變量,建左子樹會修改now的值,致使此處建樹不正確
            make((a+b) div 2,b);
        end;
end;
//建樹過程
 
procedure insert(num:longint);
begin
    if (c<=tree[num].a)and(tree[num].b<=d) then
        inc(tree[num].cover)
    //若當前區間被[c,d]覆蓋,則cover+1
    else
        begin
            if c<(tree[num].a+tree[num].b)div 2 then insert(tree[num].left);
            if d>(tree[num].a+tree[num].b)div 2 then insert(tree[num].right);
        end;
    //不然將區間[c,d]插到左右子樹中
end;
//插入線段[c,d](c,d爲全局變量)到線段樹第num個節點區間
 
procedure delete(num:longint);
begin
    if (c<=tree[num].a)and(tree[num].b<=d) then
        dec(tree[num].cover)
    //若當前區間被[c,d]覆蓋,則cover-1
    else
        begin
            if c<(tree[num].a+tree[num].b)div 2 then delete(tree[num].left);
            if d>(tree[num].a+tree[num].b)div 2 then delete(tree[num].right);
        end;
    //不然將區間[c,d]在左右子樹中刪除
end;
//在線段樹第num個節點區間中刪除線段[c,d]
 
procedure count(num:longint);
begin
    if tree[num].cover>0 then
        number:=number+(tree[num].b-tree[num].a)
    //若當前區間被徹底覆蓋,則累加到number全局變量中
    else
        begin
            if tree[num].left>0 then count(tree[num].left);
            if tree[num].right>0 then count(tree[num].right);
        end;
     //不然,若爲部分覆蓋,則累加左右子樹的測度
end;
//計算整個線段樹的測度(被覆蓋區間的總長度)
 
begin
    readln(c,d);
    make(c,d);
    readln(c,d);
    insert(1);//插入線段[c,d];
    readln(c,d);
    delete(1);//刪除線段[c,d]
    count(1);//計算線段樹的測度
    writeln(number);
end.    

 

二叉搜索樹

{treap示範代碼:
標準的代碼縮進風格,優美的算法實現。
經典標程,徹底掌握後水平會提升不少
不改變bst的性質(在bst全部子樹中均知足:左子樹的全部節點<=根<=右子樹的全部節點)
經過旋轉操做,使根的hr最小(即全部的hr構成堆的關係)}
var
  //l[i],r[i],v[i]:i號結點的左兒子、右兒子,關鍵值
  //hr[i]:i號節點的優先值(treap全部子樹中,根的hr必須是最小的)
  //s[i]:i號節點爲根的子樹節點總數
  l,r,hr,s,v:array[0..2000000]of longint;
  n,root,m:longint;
   
procedure init;//初始化
begin
  readln(n);
  m:=0;
  //randomize;  //考試要求程序每次運行結果必須一致,慎用。確實要用:randomize 100;
  fillchar(s,sizeof(s),0);
  fillchar(l,sizeof(l),0);
  fillchar(r,sizeof(r),0);
  root:=0;
end;
 
//旋轉是平衡二叉樹的精髓,它不會改變bst的性質(左子樹<=根<=右子樹)
//左旋使樹的左子樹深度+1,右子樹深度-1
//右旋使樹的右子樹深度+1,左子樹深度-1
procedure l_rotate(var x:longint);inline;//左旋以x爲根的子樹(注意var參數及意義)
var
  y:longint;
begin
  y:=r[x];    //保存x的右兒子到y中
  r[x]:=l[y]; //將y的左兒子做爲x的右兒子
  l[y]:=x;    //x做爲y的左兒子
  s[y]:=s[x]; //維護旋轉後的子樹大小
  s[x]:=s[l[x]]+s[r[x]]+1;
  x:=y;   //y爲根
end;
 
procedure r_rotate(var x:longint);inline;//右旋以x爲根的子樹
var
  y:longint;
begin
  y:=l[x];
  l[x]:=r[y];
  r[y]:=x;
  s[y]:=s[x];
  s[x]:=s[l[x]]+s[r[x]]+1;
  x:=y;
end;
 
//插入(遞歸,if key<=root,則插入到左子樹,不然到右子樹,直到盡頭再新建節點)
procedure insert(var k,key:longint);inline;
begin
  if k=0 then//已到盡頭,新建節點並寫入key及隨機值hr
    begin
      inc(m);
      v[m]:=key;
      s[m]:=1;
      hr[m]:=random(maxlongint);
      k:=m;//修改k,使父節點指向當前節點(修改前從父節點指向0)
      exit;
    end;
  inc(s[k]);
  if key<=v[k] then//若key<=根則插入到左子樹,不然到右子樹
    begin
      insert(l[k],key);//若l[k]=0,到下層則新建節點並修改l[k]=m
      if hr[l[k]]>hr[k] then  //旋轉
        r_rotate(k);
      exit;
    end;
  if key>v[k] then
    begin
      insert(r[k],key);
      if hr[r[k]]>hr[k] then
        l_rotate(k);
      exit;
    end;
end;
 
{刪除:在k號節點爲根的子樹中刪除key
基本方法:因爲是靜態結構,爲了提升效率,並沒真正刪除
若找到則刪除,若沒找到,則刪除查找盡頭的節點
主程序中需判斷返回值,若不等於key,從新插入key便可
找到後的處理:
    若爲葉節點,直接刪除,不然,將要刪除的節點左子樹的最右節點(思考:爲何?)代替它
}
function delete(var k:longint;key:longint):longint;inline;
begin
  dec(s[k]);//維護節點總數
  //若是找到,或已到盡頭
  if (key=v[k])or(l[k]=0)and(key<=v[k])or(r[k]=0)and(key>v[k]) then
    begin
      delete:=v[k];//返回要刪除的節點(不必定=key)
      if (l[k]=0)or(r[k]=0) then //若左右子樹只有一個,則讓兒子代替根便可
        begin
          k:=l[k]+r[k];//用兒子替換當前要刪除的節點
          exit;
        end;
      v[k]:=delete(l[k],key+1);//k左右子樹都有,則用左子樹的最右節點替換k
      exit;
    end;
  if key<=v[k] then//若k<=v[k],則在左子樹中刪,不然,在右子樹中刪
    exit(delete(l[k],key));
  if key>v[k] then
    exit(delete(r[k],key));
end;
 
function find(var k,key:longint):boolean;inline;//查找
begin
   if k=0 then//遞歸邊界
      exit(false);
 
   if key>v[k] then
      find:=find(r[k],key)
   else
      find:=(v[k]=key)or find(l[k],key);
end;
 
//key的排名(key排在第幾,按從小到大的順序)
function rank(var t,key:longint):longint;inline;
begin
   if t=0 then
      exit(1);
   if key<=v[t] then
      rank:=rank(l[t],key)
   else
      rank:=s[l[t]]+1+rank(r[t],key);
end;
 
function select(var t:longint;k:longint):longint;inline;//選擇排在第k位的數
begin
   if k=s[l[t]]+1 then//若找到第k位的節點,則返回節點key值
      exit(v[t]);
 
   if k<=s[l[t]] then//遞歸
      select:=select(l[t],k)
   else
      select:=select(r[t],k-1-s[l[t]]);
end;
 
function pred(var t,key:longint):longint;inline;//找key的前趨
begin
   if t=0 then
      exit(key);
   if key<=v[t] then//key<=根,原問題等價於在左子樹中找key
      pred:=pred(l[t],key)
   else begin  //key>根,原問題等價於在右子樹中找key,但右兒子返回時,要判斷你是否是key的前趨
      pred:=pred(r[t],key);//右子樹的返回值
      if pred=key then   //若是右子樹的返回值=key說明在右子樹中沒找到key的前趨
         pred:=v[t];     //右子樹沒有key的前趨,那你就是了。
   end;
end;
 
function succ(var t,key:longint):longint;inline;//找key的後繼
begin
   if t=0 then
      exit(key);
   if v[t]<=key then
      succ:=succ(r[t],key)
   else begin
      succ:=succ(l[t],key);
      if succ=key then
         succ:=v[t];
   end;
end;
 
procedure order;inline;//操做解釋和執行
var
  a,b:longint;
begin
  readln(a,b);
  case a of
     1:insert(root,b);
     2:delete(root,b);
     3:writeln(find(root,b));
     4:writeln(rank(root,b));
     5:writeln(select(root,b));
     6:writeln(pred(root,b));
     7:writeln(succ(root,b));
   end;
end;
 
procedure main;inline;//主模塊
var
  i:longint;
begin
  init;
  for i:=1 to n do
    order;
end;
 
begin//主程序
  main;
end.  

 

進制轉換

1.將m進制數n轉化成一個十進制數

{輸入共一行表示m進制的n化成十進制的數。}
var s:string;sum,n,m,i,r,t,c:longint;
begin
    readln(s);
    val(copy(s,pos(' ',s)+1,length(s)),m,c);
delete(s,pos(' ',s),length(s));
{使整數m中放進制,字符串s中放須要的數}
    t:=1;n:=length(s);sum:=0;
    for i:=n downto 1 do
    begin
      case s[i] of
        '0'..'9':r:=ord(s[i])-48;
        'A'..'Z':r:=ord(s[i])-55;
      end;
      sum:=sum+r*t;
      t:=t*m;
    end;
    write(sum);
end.

 

2.將十進制數n轉換成m進制數 

{輸入共一行表示n的m進制。}
var
  a:char;
  i,n,m:integer;
  c:array[10..16]of char;    //存儲10~16對應的7個字母
procedure nm(n:integer); //M爲全局變量,因此這裏只要N就能夠了
var r:integer;   
begin
  if n=0 then exit;           //直到商等於0
  r:=n mod m;               //求餘數
  nm(n div m);              //反向取餘數
  if r>=10 then write(c[r]) else write(r);  //大於10的數特殊處理
end;
begin
  a:='A';
  for i:=10 to 16 do
    begin c[i]:=a; a:=succ('A') end;      
 //後繼函數,即前一個或後一個ASCII碼錶明的字符. 如:pred('b')='a',succ('b')='c'. 
  readln(n,m);             //輸入
  nm(n);                    //引用過程,過程當中包含輸出。
end

 

                 

 


後記

本文未涉及的內容包括但不限於:

哈希(HASH)

強連通份量

離散化

尺取法

網絡流

計算幾何(包括凸包)

2-SAT

LCA

倍增法

KMP算法

後綴數組

Grundy數

trie樹

AC自動機

FFT快速傅里葉變換

斯坦納樹

單純形法

矩陣

三分搜索

α-β剪枝

離散對數

微積分

樹鏈剖分

負進制轉換

羣論

 

不過沒有關係,這些基本都屬於NOIP涉及不到的範圍,學無止境,把握好當下的纔是關鍵!

參考文獻

 百度百科

《數學》第三冊 蘇州大學出版社

《動態規劃學案》 山東省實驗中學 王乃廣

《PASCAL省選模板》island

《NOIP2012最終征途》亟隱 Slack

《天然數的因數》桃花島主

《二分圖的最大匹配、完美匹配和匈牙利算法》pi9nc

 

推薦文章&書籍&博客&網站

    劉汝佳《算法競賽入門經典1&2》

    啊哈磊《啊哈!算法》

    Thomas H.Cormen等 《算法導論》

    編程之美小組《編程之美》

    秋葉拓哉等《挑戰程序設計競賽》

    Charles Petzold 《CODE》

    顧森《思考的樂趣》

   《NOI導刊》(一本雜誌,已經停辦,不過能夠找來之前的看看)

   《騙分導論》

   《人品導論》

    博客園

    CSDN社區

    MATRIX67

    HZWER

    圖靈社區

 楓伶憶《大話二進制,八進制,十進制,十六進制之間的轉換

   免費程序員圖書下載(http://it-ebooks.info/)

   快速查詢數列(http://oeis.org/)

   在線學習:計蒜客

   在線學習:萌碼

   在線學習:實驗樓

   使用技巧:PDF自由轉換(http://smallpdf.com/cn)

 

PS:其實在競賽之路上幫助最大的仍是大牛神犇們的博客,從中能夠學到不少的東西。我也養成了寫博客的習慣,作完題寫寫博客加加註釋,整理思路的同時還能助人爲樂。寫博客推薦去博客園或是CSDN社區,這是專門面向程序猿的博客,支持本身設置板式,代碼高亮。由於有羣體限制,因此交流起來也更方便。

 

PS:有關NOIP技巧整理和測評網站OJ整理的文章請移步本人技術博

 

聯繫我

       技術博:川漢唐

       郵箱:gryangqingli@163.com

       QQ:1031304332

 

二零一五年七月 初稿

二零一五年十月 定稿

 

 

圖片君和連接君好像領便當了,可能看起來有些不方便,你們想看完整版仍是去百度文庫下一份吧:http://wenku.baidu.com/view/31b31db43169a4517623a38e

本文原創,轉載請說明出處,歡迎用於學習方面的交流,用於商業用途前請先聯繫做者。http://www.cnblogs.com/yangqingli/

相關文章
相關標籤/搜索