1. 程式人生 > >山區建小學(區間型動態規劃,遞推)

山區建小學(區間型動態規劃,遞推)

描述
政府在某山區修建了一條道路,恰好穿越總共m個村莊的每個村莊一次,沒有迴路或交叉,任意兩個村莊只能通過這條路來往。已知任意兩個相鄰的村莊之間的距離為di(為正整數),其中,0 < i < m。為了提高山區的文化素質,政府又決定從m個村中選擇n個村建小學(設 0 < n < = m < 500 )。請根據給定的m、n以及所有相鄰村莊的距離,選擇在哪些村莊建小學,才使得所有村到最近小學的距離總和最小,計算最小值。
輸入第1行為m和n,其間用空格間隔
第2行為(m-1) 個整數,依次表示從一端到另一端的相鄰村莊的距離,整數之間以空格間隔。
例如
10 3
2 4 6 5 2 4 3 1 3
表示在10個村莊建3所學校。第1個村莊與第2個村莊距離為2,第2個村莊與第3個村莊距離為4,第3個村莊與第4個村莊距離為6,...,第9個村莊到第10個村莊的距離為3。輸出各村莊到最近學校的距離之和的最小值。樣例輸入
10 2
3 1 3 1 1 1 1 1 3
樣例輸出
18

/*
//感謝SilverNebula的https://blog.csdn.net/silvernebula/article/details/51379005
//感謝Always_ease的https://blog.csdn.net/Always_ease/article/details/80527234
思路彙總:
結論1:
    不管每個山村之間隔多遠
    在第i山村到第j個山村中所有點,如果只建一個學校,取(i+j)/2這個山村建學校能使
    總距離最小,無論i+j是奇數還是偶數
    我的不嚴謹推理:如果把學校建兩邊是最差情況,所以儘量往中間建咯
    如果是1,2,3,4這種情況建在2和3其實總距離一樣,可以自己證一下

結論2:
    已經有i個山村j-1個學校,現在建第j個學校,前j-1個山村是絕對不會被挖走而改變路線的
    讓k=j-1,k表示前k個村莊不改變路線,現在讓k一直自增來列舉各種情況(第j個學校到底改變了幾個村莊)
    再找出各種k中最好的情況就好了
    也就是第j個學校的出現把局面分成了
    第1到k各山村和第k到i個山村,第i個山村只有一個學校,也就是那個新學校,所以很好求
    前1到k個山村,就是考慮k個村莊,j-1個學校,這些都是前面遞推出來的,直接套用前面的
步驟:
1.計算任意兩地之間距離,存入陣列a
2.計算任意兩地之間如果只有只有一個學校,那麼兩地之間所有村莊到這個學校
的距離和的最小值(兩地中間建學校,距離總和最小)
3.為f陣列賦上初值,即當1個學校匹配任意村莊的距離和
4.依次考慮加入新學校後有多少村莊為此改變路線,找出最好情況
*/ //我好弱啊,每題都要看答案才能學會做出來 #include<bits/stdc++.h> using namespace std; #define ll long long const ll maxn=520; ll a[maxn][maxn]={0},c[maxn][maxn],f[maxn][maxn]; //陣列a:兩個點之間距離;;;陣列c:閉區間[i,j]內所有點到最近學校的距離和 //陣列f:f[i][j]表示i個村莊j個學校所有村莊到最近學校的距離總和 int main() { ll i,n,t,j,k,l,m,mid,s=INT_MAX;//s是一個很大非常大的值
cin>>m>>n;//m為村莊,n為小學 for(i=1;i<m;i++) scanf("%lld",&a[i][i+1]);//依次輸入第i個村莊到下一個村莊的距離 for(i=1;i<=m;i++)//遍歷每一種i,j配對 for(j=i+1;j<=m;j++) {//陣列a:兩個點之間距離; a[i][j]=a[i][j-1]+a[j-1][j];//比如說a[2][5]=a[2][4]+a[4][5], //先考慮a[j-1][j]前面已經輸入過,再考慮a[i][j]-a[j-1][j]=a[i][j-1], //而a[i][j-1]這個資料在a[i][j],迴圈到a[i][j]時a[i][j-1]已經計算完了, //所以等式成立; //其中a[2][3]和a[3][4]在前面輸入時就已經給出了 a[j][i]=a[i][j];//映象複製剛剛只考慮i<j情況,現在給出另一半 //比如a[2][4]=a[4][2] } for(i=1;i<=m;i++)//遍歷每一種i,j配對 for(j=i+1;j<=m;j++) { mid=(i+j)/2;//讓mid鎖定在序號為中間的村莊 //@1,@2,@3,@4=>>>>@2 (1+4)/2=2 //@1,@2,@3=>>>>@2 (1+3)/2=2 c[i][j]=0;//先讓閉區間[i,j]內所有點到最近學校的距離和為0 //陣列c:閉區間[i,j]內所有點到最近學校的距離和 for(k=i;k<=j;k++)//列舉閉區間[i,j]的各個點為k c[i][j]+=a[k][mid];//c[i][j]+=[i,j]內各個點到mid點的距離和 } for(i=1;i<=m;i++) f[i][1]=c[1][i]; //賦初值,i個村莊只有一個學校時答案是確定的,就是這區間內所有點到中間點的距離和 for(i=1;i<=m;i++) for(j=2;j<=n;j++)//一個學校的情況考慮過了,現在j可以從2開始算 { f[i][j]=s;//先把每種情況的答案初始化為很大的值 for(k=j-1;k<=i;k++)//列舉已有的學校管轄的範圍//k確實最小值為j-1 f[i][j]=min(f[i][j],f[k][j-1]+c[k+1][i]);//找出最小值 //在for迴圈中,f[i][j]經常改變,所以要讓f[i][j]和k對應情況比較多次 }//拿j和j-1時比較 /* 把j看作新學校,本來已經有i個村莊和j-1個學校,現在考慮有多少個村莊要投靠新學校i 毫無疑問,如果原來j-1箇舊學校,那麼能保證j-1位及之前村莊不會改變 */ cout<<f[m][n]<<endl; return 0; }