1. 程式人生 > >《挑戰程式設計競賽(第二版)》tmp

《挑戰程式設計競賽(第二版)》tmp

P162 超大揹包問題程式碼 #include <iostream> #include <pair> const int MAX_N =40;
typedef long long ll; int n; ll w[MAX_N], v[MAX_N]; ll W;
pair<ll, ll> ps[l<<(MAX_N/2)];
void solve() { //列舉前半部分
int n2=n/2; for(int i=0; i<1<<n2; i++) { ll sw=0, sv=0; for(int j=0; j<n2; ++j) { if(i>>j&1) { sw+=w[j]; sv+=v[j];
} } ps[i]=make_pair(sw, sv); }
//去除多餘的元素 sort(ps, ps+(1<<n2)); int m=1; for(int i=1; i<1<<n2; ++i) {
if(ps[m-1].second<ps[i].second) ps[m++]=ps[i]; }
//列舉後半部分並求解 ll res=0; for (int i=0; i<1<<(n-n2); ++i) { ll sw=0, sv=0; for(int j=0;j<n-n2; j++) { if(i>>j &1) { sw+=w[n2+j]; sv+=v[n2+j]; } } if(sw<=W) { ll tv=(lower_bound(ps, ps+m, make_pair(W-sw, INF))-1)->second; res=max(res, sv+tv); } }
printf("%ld, ", res); }

P158, n個球體自由落體,彈性碰撞的問題。 #include <iostream> #include <cmath> using namespace std;
const double g =10.0;
int N=3; double H=100.0, R=10.0, T=4.9759;
double y[10];
double calc(double T) { if(T<0) return H; double t=sqrt(2*H/g); int k=(int)(T/t); if(k%2==0) { double d=T-k*t; return H-g*d*d/2; } else { double d=k*t+t-T; return H-g*d*d/2; } }

int main() { for(int i=0; i<N; i++) { y[i]=calc(T-i); } sort(y,y+N); for(int i=0;i<N;i++) { printf("%.2f%c", y[i]+2*R*i/100.0, i+1==N?'\n': ' '); } return 0; }

題目: 給出一字串,求包含此字串中任何一個字元的最短子串。
尺取法:
#include <iostream> #include <set> #include <map> using namespace std;
/* ex.  *  1232221  *  1232224321  *  28022103228  *  11111  */ int P=11; int a[]={1,8,0,2,2,2,0,3,3,2,8}; //int P=10; //int a[]={1,2,3,2,2,2,4,3,2,1}; //int P=7; //int a[]={1,2,3,2,2,2,1}; //int P=5; //int a[]={1,1,1,1,1}; //int a[100];

void solve1() { set<int> all; for (int i=0;i<P;i++) { all.insert(a[i]); } int n=all.size();//原串各種不同字元的總數 int s=0, t=0, num=0; //[s,t]區間各種不同字元的總數 map<int, int> count;//記錄每個字元在[s,t]區間出現的次數 //int res=P; int rs=0, rt=P-1; //記錄最短子串的開始和結束位置 for(;;) { while(t<P && num<n) { if(count[a[t]]++==0) { num++; } if(num==n && ((rt-rs) > (t-s))) //記錄最新的更短串的起止位置 { rs=s; rt=t; } t++; }
if(s>=t) break; for(;s<P;) { if(--count[a[s++]]==0) { num--; } if(num<n) break; if(num==n && ((rt-rs) > (t-s)))//記錄最新的更短串的起止位置 { rs=s; rt=t; } if(s>t) goto end;
}
}
end: printf("rs=%d, rt=%d \n",rs,  rt<P?rt:(P-1));//結果
}

int main() { solve1(); }
題目:給出一個表示式(只有+和*運算子),求任意加括號後的最大值。 例如: 0.6+3*0.7*4+2*2 加括號後  (0.6+3)*(0.7*4+2)*2  最大值為34.56

 解法: #include <stdio.h> #include <stdlib.h>
typedef struct { float value;//該區間最大值         int divid;//此區間的劃分點 char set;//變更標識,0未計算;1已計算 } Texp;
//float num[]={0.2, 0.2, 0.5, 2.0, 6.0, 0.5};   //存放運算數 //表示式為  0.6+3*0.7*4+2*2 float num[]={0.6, 3, 0.7, 4, 2, 2}; Texp m[8][8] ; //表示運算數陣列的i到j區間的最大值 //char oper[]="+**+*";        //存放運算子號 char oper[]="+**+*";
int n;         //表示式中運算數的個數

float getMin(float a, float b) { return a-b>0.000001?b:a; }
float getMax(float a, float b) {     return a-b>0.000001?a:b; }
float getValue(float a, float b, int k) {          if('+' == oper[k])          { return a+b;          }          else          {             return a*b;          }
} //初始化m陣列 void init_Texp() { for(int i=0; i<n; ++i) for(int j=0; j<n; ++j) { m[i][j].set=0; m[i][j].value=0.0; m[i][j].divid=-1; } }

float set_m(int s, int e) { float v=0.0, v1=0.0; if(s>=e) { v=num[s];                 m[s][e].divid=s;         } else { for(int k=s;k<e;++k) { if(m[s][k].set==0) { m[s][k].value=set_m(s,k); m[s][k].set=1; } if(m[k+1][e].set==0) { m[k+1][e].value=set_m(k+1,e); m[k+1][e].set=1; } //在區間[s,e]中任意劃分中的最大運算值 v1=getValue(m[s][k].value, m[k+1][e].value, k); //v=getMax(v,v1);                          if(v1-v>0.000001) { m[s][e].divid=k; v=v1; } } } return v; } //列印各個數運算的先後順序 void trace_m(Texp *t, int s, int e, int level) { int k=(*t).divid; if(k-s>=1) { trace_m(&m[s][k], s, k, level+1); } if(e-k>=1) { trace_m(&m[k+1][e], k+1, e, level+1); }
if(e!=s) { printf("level<%d>: (%d,%d)\n", level, s, e); } }
int main() { n=sizeof(num)/sizeof(float); init_Texp(); float t=set_m(0, n-1); printf("max result=%f\n", t);//列印最大值 trace_m(&m[0][n-1], 0, n-1, 0);//列印解法
         return 0;
}

題目: 設A和B是兩個字串,要用最少的字元操作,將字串A轉換為字串B,這裡的操作限定為: (1)刪除一個字元; (2)插入一個字元; (3)將一個字元變成另一個字元。 將字串A轉換成字串B所用的最少運算元稱為字串A到B的編輯距離,記為f(A, B),請設計演算法求f(A, B)。

解法: 設所給的兩字串分別為A=A[1..m]和B=B[1..n]; 狀態表示:考慮一般情形,即從字元子串A[1..i](按序)變換到字元子串B[1..j]的最少字元操作問題,設d(i, j)=f(A[1..i], B[1..j]),顯然,2個單字元a,b之間的編輯距離:當a!=b時,為f(a, b)=1;當a=b時,為f(a, b)=0,則原問題的解為f(m, n)。 最優子結構性質:設E=e[1] … e[k-1]e[k],k=d(i, j)為從字串A[1..i]按序變換得到字串B[1..j]的一個最少字串操作序列(即d(i, j)的一個最優解),則最後一個操作e[k]必屬於以下3種操作之一: (1)將字元A[i]改為字元B[j](如果A[i]=B[j],則e[k]為空操作,不參加計數),此時E1=e[1]…e[k-1]為d(i-1, j-1)的一個最優解; (2)刪除字元A[i],此時E1=e[1]…e[k-1]為d(i-1, j)的一個最優解; (3)插入字元B[j],此時E1=e[1]…e[k-1]為d(i, j-1)的一個最優解。 綜上可見,該問題具有最優子結構性質,可建遞迴關係如下: d(i, j)=min{d(i-1, j-1)+f(A[i], B[j]), d(i-1, j)+1, d(i, j-1)+1} 初始條件:d(i, 0)=i, i=0到m,d(0, j)=j, j=0到n。 問題的解為:d(m, n)
程式: #include <stdlib.h> #include <stdio.h> #include <string.h>
int f(char a, char b) { if(a!=b) return 1; else  return 0; }

int min(int a, int b, int c) { int d; d=a<b?a:b; d=d<c?d:c; return d; }

//兩個字串的長度 #define M (16) #define N (15) //其他測試資料 //char A[]="lessp"; //char B[]="timedss"; ////char A[]="2020293"; //char B[]="21200191"; //char A[]="82025828"; //char B[]="83382068"; //char A[]="17220580"; //char B[]="1781118010"; char A[]="lesspagechecklog"; char B[]="timedoutwaiting";
//lena, lenb: 表示到A字串下標lena和B字串下標lenb為止的最小編輯距離 //d: 動態規劃用到的記錄陣列 int dp( int lena, int lenb, int d[][N+1]) { int i, j; for(i=0; i<=lena; ++i) d[i][0]=i; for(j=0; j<=lenb; ++j) d[0][j]=j; for(i=1; i<=lena; ++i) { for( j=1;j<=lenb;++j) { d[i][j]=min( d[i-1][j-1]+f(A[i-1], B[j-1]), d[i-1][j]+1, d[i][j-1]+1 ); } } return d[lena][lenb]; }

int main() { int d[M+1][N+1]; printf("A=%s\nB=%s\ndp=%d", A, B, dp(M,N, d)); return 0;
}
//------------------------------------------------------------ 例題: 給出一個數列(有正有負),求此數列中從某個位置開始連續若干個數和的最大值,簡稱最大子段和。 例如:{-2, 4, -3, 1, -1, 5};  最大子段和是6; 子段是從{4,-3,1, -1, 5}
#include <stdlib.h> #include <stdio.h>
#define MIN_INF (-99999)

int max_sum=MIN_INF; int l=0, r=999;//表示解答結果的子段在原陣列中的左,右下標
//解法一: 這是遞迴解法,相比動態規劃真是遜色不少 void max_ziduanhe(int x, int y, int digit[], int len) { int sum_l=0, sum_r=0, sum=0 ; int i; printf("max_ziduanhe(%d, %d)\n",x, y); if(x>y) return ; //else if(x==y) // return digit[x]>max_sum ? digit[x]:max_sum; else  { for (i=x; i<=y; ++i) sum+=digit[i]; if(max_sum< sum) max_sum=sum; //往右找到第一個正數 for(i=x+1; i<=y; ++i) if(digit[i]>0) break; int k=i; for(;i<=y;++i) sum_l+=digit[i]; if(max_sum<sum_l) { l=k; max_sum=sum_l; } max_ziduanhe(k, y, digit, len);
//往左找到第一個正數 for(i=y-1; i>=x; --i) if(digit[i]>0) break; k=i; for(; i>=x; --i) sum_r+=digit[i]; if(max_sum<sum_r) { r=k; max_sum=sum_r; } max_ziduanhe(x, k, digit, len); //return max_sum; } } //解法二: 這個方法是用動態規劃,程式和時空複雜度都少很多,一級棒。 /* 思路:

定義b[j]:

含義:從元素i開始,到元素j為止的所有的元素構成的子段有多個,這些子段中的子段和最大的那個。

那麼:

如果:b[j-1] > 0, 那麼b[j] = b[j-1] + a[j]

如果:b[j-1] <= 0,那麼b[j] = a[j]

我們要求的最大子段和,就是是b[j]陣列中最大的那個元素。

*/

int use_DP (int digit[], int len)        {           int sum = 0 ;           int temp = 0 ; int i;            for ( i = 0; i < len; ++ i) {               if (temp > 0) {                   temp += digit[i] ;               }               else {   l=i;                 temp = digit[i] ;               }               if (temp > sum) {   r=i;                 sum = temp ;               }           }           return sum ;       }  

int main() { //用例 int digit[]={11, -4, 13, -5, -3, -2, 3}; //int digit[]={-2, 11, -4, 13, -5, 1, -3}; //int digit[]={-2, 11, -4, 13, -5, 6, -2}; //int digit[]={-2,4, -3,1,-1,5,-15, 12, -4, 13, -5,-3, -2, 3}; //int digit[]={-2, 4, -3, 1, -1, 5}; int len=sizeof(digit)/sizeof(int); printf("len=%d\n", len); l=0;r=len-1; //呼叫解法一求解 max_ziduanhe(l, r, digit, len); printf("sum=%d l=%d r=%d\n",max_sum, l, r); // 呼叫解法二求解         l=0;r=len-1; printf("use_DP()=%d, l=%d, r=%d\n", use_DP(digit, len), l, r);
return 0;

}
//-------------------------------------------------------------- #2.7.3節的Bride the prisoner題目的解法 #這是錯誤解法,因為這裡放入佇列時,區間和A元素沒有必然的對應關係。不過可以參考下deque用來做FIFO佇列的用法 #e.x. P=9, A=[1, 3, 4, 6, 7] def make_priovity_sequence(P, A):     from collections import deque     pq=[]   #priovity queue     s1=(1, P)     qujian=deque()     qujian.append(s1)     sum=0     while A and qujian:         s1=qujian.popleft()         mid=(s1[0]+s1[1])/2         juli_min=P         for m in A:             juli=abs(m-mid)             if juli<juli_min:                 m_min=m     #must be assigned to m_min at least onece                 juli_min=juli         pq.append(m_min)    #the number to fetch out         A.remove(m_min)         sum=sum+s1[1]-s1[0]                  if m_min-1>=s1[0]:             qujian.append((s1[0], m_min-1))         if m_min+1<=s1[1]:             qujian.append((m_min+1, s1[1]))                      pq.append(sum) #返回的最後一個值不是釋放者編號,而是總金幣數            return pq
##2.7.3節的Bride the prisoner題目的解法 #這是正確解法,遞迴方式 #s0,s1分別表示區間的兩端,A為要求輸入的釋放者編號數列(假設已按從小到大排列),pq為最終輸出的釋放方法數列 #演算法關鍵思路是每次都找離中點最近的那個來釋放 def make_priovity_seq(s0, s1, A, pq):          if A and s0<=s1:         juli_min=s1-s0+1         mid=(s0+s1)/2         for m in A:             juli=abs(m-mid)             if juli<juli_min:                 m_min=m     #must be assigned to m_min at least onece                 juli_min=juli         pq.append(m_min)    #the number to fetch out         #A.remove(m_min)         pos=A.index(m_min)         A1=A[0:pos]         A2=A[pos+1:]                  sum0=make_priovity_seq(s0, m_min-1, A1, pq)          sum1=make_priovity_seq(m_min+1, s1, A2, pq)         return s1-s0+sum0+sum1;  #返回費用              else:         return 0

#上面函式呼叫方法: if __name__ == '__main__':          #s=make_priovity_sequence(20,[3,6,14])     aq=[]     sum=make_priovity_seq(1, 20,[3,6,14], aq)     print(sum)