《挑战程序设计竞赛(第二版)》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)

猜你喜欢

转载自blog.csdn.net/william_djj/article/details/83312683
今日推荐