甲乙小朋友的房子

甲乙小朋友很笨,但甲乙小朋友不会放弃

0%

QR分解

引理

  1. 对于任意的\(A\in C^{n\times n}\),若存在\(n\)阶正交矩阵\(Q\)\(n\)阶上三角矩阵\(R\),使得\(A=QR\),则称\(QR\)\(A\)\(QR\)分解。
  2. \(A\in C^{n\times n}\)可逆,则存在正交矩阵\(Q\)和正对角元的上三角矩阵\(R\),使得\(A=QR\),且表示式唯一。

备注:正交矩阵:\(A^{-1}=A^T\) QR分解定理 ,则A有QR分解: Q是mm的正交矩阵 R是nn的有非负对角元的上三角阵

当m=n,且A非奇异时,上述分解唯一。

计算QR分解的方法

备注: 计算A(j:m,j:n)=(I(m-j+1)-bvvT)A(j:m,j:n)时, I(m-j+1)-bvvT可以表示为:

QR分解的存储方法

用QR分解解线性方程组 \(Ax=b\) \(QRx=b\) \(Rx=Q^{-1}b\) \(x=R^{-1}Q^{-1}b\) \(x=R^{-1}Q^Tb\)

householder方法

利用Householder变换逐步将A化为上三角矩阵。

使用Gauss变换将矩阵化为上三角的理论依据————对于一个给定的向量x,可构造一个初等下三角阵L,使得\(Lx=ae_1\),其中\(e_1\)是I的第一列,a是实数。(I是单位矩阵)

householder变换目的: 求一个初等正交矩阵,使其具有矩阵L的功能。

使得: 可以通过一系列初等正交变换来完成矩阵的上三角化任务。

Householder变换

其中: I是单位矩阵 w是实的单位列向量(单位正交矩阵),\(ww^T=I\)

HouseHolder变换可以将一个向量映射到一个超平面上。

Householder变换的性质

  1. 对称性。\(H^T=H\)
  2. 正交性。\(H^TH=I\)
  3. 对合性。\(H^2=I\)
  4. 反射性(householder变换的物理意义):

Householder变换的用途

它能如Gauss变换一样,可以通过适当选取单位向量w,把一个给定向量的若干指定的分量变为零。 由以上定理可知,对于任意的x,都可以构造出Householder矩阵H,使得Hx的后n-1个分量为零。

Householder变换方法 计算某一行向量的houser变换:

LU分解条件

如果n阶方阵A的各阶顺序主子式 ,即A的各阶顺序主子矩阵Ak都可逆,则存在唯一的单位下三角矩阵L与唯一的非奇异上三角矩阵U,使得A=LU

其中k阶顺序主子式指

LU分解方法

由于L存在可逆矩阵L',即LL'=E 则L'A=LL'U=U 因此得出一般的分解方法: 通过对(A,E)做初等行变换得到(U,L'),再由L'得到L.

其中:

  1. L是单位下三角矩阵(对角线上的系数都为1的下三角矩阵);L'也是单位上三角矩阵;
  2. U是非奇异上三角矩阵;

求解方法

对于一般的线性方程组: \[Ax=b\] 如果我们能将A分解成\(A=LU\),即一个下三角阵和一个上三角阵U的乘积,那原方程组的解x便可由下面两步得到:

  1. 用前代法解\(Ly=b\)的y;
  2. 用回代法解\(Ux=y\)的x.

对于列主元消去法,求线性方程组\(Ax=b\)的计算过程可以按照以下步骤进行:

  1. 求A的列主元LU分解\(PA=LU\)
  2. 解下三角形方程组\(Ly=Pb\)
  3. 解上三角形方程组\(Ux=y\)

实现

前代法: 需要注意的是,回代法在计算的过程中需要考虑到主元位置序列,并同时对b进行调整。

double * forwardSub(double *lu,int n,double *b,int *p){
    //double *y=new double[n];
    double temp;
      //按qivot对b行变换,与LU匹配
      for(int i=0; i<n-1; i++)  
      {
            temp = b[p[i]];
            b[p[i]] = b[i];
            b[i]=temp;
      }
    for(int i=0; i<n; i++)
            for(int j=0; j<i; j++)
                b[i]=b[i]-lu[n*i+j]*b[j];
    b[n-1]=b[n-1];
    return b;
}

回代法: 由于U是上三角矩阵,所以要从x[n-1]开始计算。

double * backSub(double *b,double *lu,int n){
     for(int i=n-1; i>=0; i--)
      {
            for(int j=n-1; j>i; j--)
                b[i]=b[i]-lu[n*i+j]*b[j];
            b[i]=b[i]/lu[n*i+i];
      }
    return b;
}

计算:

bool guass(double *lu,int *p,double*b,int n){
    double * y = forwardSub(lu,n,b,p);
    double * x = backSub(y,lu,n);
    std::cout<<endl<<"the sulution is ; "<<endl;
    for(int i=0;i<n;i++){
        std::cout<<x[i]<<"\t";
    }
    std::cout<<endl;
}

输出:

代码:

#include <iostream>
#include<cstdio>
#include<cstdlib>
#include<iomanip>
#define LIM -100000000
using namespace std;
double *luReult;
void printmat(double *mat,int n,string s){
    std::cout<<endl<<endl<<s<<endl;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            printf("%.5f\t",mat[i*n+j]);
        }
        std::cout<<endl;
    }
}
void printarr(int *mat,int n,string s){
    std::cout<<endl<<endl<<s<<endl;
    for(int i=0;i<n;i++){
        std::cout<<mat[i]<<"\t";
        //printf("%s\t",mat[i]);
    }
}
int findMainNumber(double *a,int n,int r){
    float maxa=-99999;
    int maxaIdx=-1;
    //printmat(a,n,"a is now :");
    for(int i=r;i<n;i++){
        if(a[i*n+r]>maxa){
            maxa=a[i*n+r];
            maxaIdx=i;
        }
    }
    return maxaIdx;
}
void exchange(double *a,int n,int e,int r){
    float temp=0;
    for(int i=0;i<n;i++){
        temp = a[r*n+i];
        a[r*n+i]=a[e*n+i];
        a[e*n+i]=temp;
    }
}
bool lu(double* a, int* pivot, int n)//矩阵LU分解
{
      for(int k=0;k<n;k++){
        //寻找第k列的主元
        int p = findMainNumber(a,n,k);
        exchange(a,n,p,k);//交换k行和p行
        pivot[k]=p;//记录置换矩阵p
        if(a[k*n+k]!=0){
                for(int i=k+1;i<n;i++){//部分下三角L
                    a[i*n+k]=a[i*n+k]/a[k*n+k];
                }
                for(int i=k+1;i<n;i++){//计算上三角U
                    for(int j=k+1;j<n;j++){
                        a[i*n+j]=a[i*n+j]-a[i*n+k]*a[k*n+j];
                    }
                }
        }else{
            return true;//矩阵奇异
        }
      }
      /*
      //计算下三角L
      double temp=0;
       for(int i=0; i<n-2; i++)//i行k列
            for(int k=i+1; k<n-1;k++)
            {
                    temp=a[n*pivot[k] + i];
                    a[n*pivot[k] + i]=a[k*n + i];
                    a[k*n + i]=temp;
            }
        */
      luReult=a;
      printmat(a,n,"lu");
      return false ;
}
double radio(int a,int b){
    return (double)(a)/(double)(b);
}
void buildHilbert(double *a,double *b,int n){
    for(int r=0;r<n;r++){
        for(int j=0;j<n;j++){
            a[r*n+j]=radio(1,j+r+1);
            b[r]=b[r]+a[r*n+j];
        }
    }
}
void exchangeb(double *b,int n,int r,int e){
    double temp=0;
    temp=b[e];
    b[e]=b[r];
    b[r]=temp;
    /*r(int i=0;i<n;i++){
        temp=b[r*n+i];
        b[r*n+i]=b[e*n+i];
        b[e*n+i]=temp;
    }*/
}
double * forwardSub(double *lu,int n,double *b,int *p){
    //double *y=new double[n];
    double temp;
      //按qivot对b行变换,与LU匹配
      for(int i=0; i<n-1; i++)
      {
            temp = b[p[i]];
            b[p[i]] = b[i];
            b[i]=temp;
      }
    for(int i=0; i<n; i++)
            for(int j=0; j<i; j++)
                b[i]=b[i]-lu[n*i+j]*b[j];
    b[n-1]=b[n-1];
    return b;
}

double * backSub(double *b,double *lu,int n){
     for(int i=n-1; i>=0; i--)
      {
            for(int j=n-1; j>i; j--)
                b[i]=b[i]-lu[n*i+j]*b[j];
            b[i]=b[i]/lu[n*i+i];
      }
    return b;
}

bool guass(double *lu,int *p,double*b,int n){
    double * y = forwardSub(lu,n,b,p);
    double * x = backSub(y,lu,n);
    std::cout<<endl<<"the sulution is ; "<<endl;
    for(int i=0;i<n;i++){
        std::cout<<x[i]<<"\t";
    }
    std::cout<<endl;
}
int main()
{
    int n=6;//矩阵是n*n的
    double *b=new double[n];
    double *a=new double[n*n];
    /*double input[n*n]={0.001,1.00,1.00,2.00};
    a=input;
    double inputb[n]={1.00,3.00};
    b=inputb;*/
    buildHilbert(a,b,n);
    printmat(a,n,"a:");
    int *pivot=new int[n*n];
    luReult=new double[n*n];
    lu(a,pivot,n);
    printarr(pivot,n,"p:");
     guass(luReult,pivot,b,n);
    //cout << "Hello world!" << endl;
    return 0;
}

LU分解:可以将一个矩阵分解为一个单位下三角矩阵和一个上三角矩阵乘积(有时是它们和一个置换矩阵的乘积)。LU分解主要应用在数值分析中,用来解线性方程、求反矩阵或计算行列式。

LU分解条件

如果n阶方阵A的各阶顺序主子式 ,即A的各阶顺序主子矩阵Ak都可逆,则存在唯一的单位下三角矩阵L与唯一的非奇异上三角矩阵U,使得A=LU

其中k阶顺序主子式指

LU分解方法

由于L存在可逆矩阵L',即LL'=E 则L'A=LL'U=U 因此得出一般的分解方法: 通过对(A,E)做初等行变换得到(U,L'),再由L'得到L.

其中:

  1. L是单位下三角矩阵(对角线上的系数都为1的下三角矩阵);L'也是单位上三角矩阵;
  2. U是非奇异上三角矩阵;

直接消去法的矩阵解释

计算过程如图所示:

目的:将[A|E]-->[U|L]形式,其中U是上三角矩阵

过程:每轮将一列的下三角消为0,就相当于给A左乘一个下三角单位矩阵Lk(对角线为1,保证变换后对角线元素不变)。

第一轮消元:将第一列的下三角消为0,相当于对A1左乘矩阵L1,即: 其中, 我们注意到li1恰好使得第i行的第一列元素变为零,即ai1-li1*a11=0。

第二轮消元:将第二列的下三角消为0,对应于

第k轮消元: 其中,

把k轮消元相乘,即: 整个消元过程为

总结 初始(第一步):

迭代第r步: 即 ur=f(u1,u2,...,u(r-1),l1,l2,...,l(r-1)),第r项由前r-1项计算得到 lr=g(u1,u2,...,u(r-1),l1,l2,...,l(r-1))

计算顺序:

需要注意的是,我们在计算时需要判断本阶的顺序主子式是否为零。

代码实现 备注:其中的piovt暂时没有卵用。

输入矩阵:

bool lu(double *a,int *pivot,int n){//矩阵是n*n的
double test = det(a,r,n);//检验本阶顺序主子式是否为0
 if(test==0){
    return true;
 }
//数组a在内存中按行优先次序存放
//pivot:输出参数
//pivot[0,n)中存放主元的位置排列
//成功时返回false,否则返回ture
//int len = sizeof(a)/sizeof(a[0]);
double *U,*L;
U = new double[n*n];
L = new double[n*n];
set0(U,n);
set0(L,n);
//先算urj(固定r,计算完所有的urj),再算ljr(固定i,计算完所有的ljr)
for(int r=0;r<n;r++){
    //计算urj(固定r,计算完所有的urj)
    //计算ljr(固定i,计算完所有的ljr)
    for(int j=r;j<n;j++){
        double sum_lu1=0;
        double sum_lu2=0;
        for(int k=0;k<r;k++){
            sum_lu1 = sum_lu1+L[r*n+k]*U[k*n+j];
            sum_lu2 = sum_lu2+L[j*n+k]*U[k*n+r];
        }
        U[r*n+j]=a[r*n+j]-sum_lu1;
        if(j==r){
            L[j*n+r]=1;
        }else{
            L[j*n+r]=a[j*n+r]-sum_lu2;
            L[j*n+r]=L[j*n+r]/U[r*n+r];
        }
    }
}
return false;
}

结果:

不知道为什么,我的答案与网上的之前学长做过的这道题的答案统统不一样。仔细检查后还是查不出来。又用Matlab验证了一下LU分解,MATLAB跑的结果为: 与我自己的L矩阵一致。决定还是相信自己。

列主元高斯消去法

主要思想是在每次计算第r行时,选取当前第r列下的最大的a[i,r]的所在的第i行作为主元,即将第i行与第r行交换,再进行计算。

主要理论见参考文献-北邮理学院数值分析课件chapter5.

代码:

#include <iostream>
#include<cstdio>
#include<cstdlib>
#include<iomanip>
#define LIM -100000000
using namespace std;
double *luReult;
void printmat(double *mat,int n,string s){
    std::cout<<endl<<endl<<s<<endl;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            printf("%.5f\t",mat[i*n+j]);
        }
        std::cout<<endl;
    }
}
void printarr(int *mat,int n,string s){
    std::cout<<endl<<endl<<s<<endl;
    for(int i=0;i<n;i++){
        std::cout<<mat[i]<<"\t";
        //printf("%s\t",mat[i]);
    }
}
int findMainNumber(double *a,int n,int r){
    float maxa=-99999;
    int maxaIdx=-1;
    //printmat(a,n,"a is now :");
    for(int i=r;i<n;i++){
        if(a[i*n+r]>maxa){
            maxa=a[i*n+r];
            maxaIdx=i;
        }
    }
    return maxaIdx;
}
void exchange(double *a,int n,int e,int r){
    float temp=0;
    for(int i=0;i<n;i++){
        temp = a[r*n+i];
        a[r*n+i]=a[e*n+i];
        a[e*n+i]=temp;
    }
}
bool lu(double* a, int* pivot, int n)//矩阵LU分解
{
      for(int k=0;k<n;k++){
        //寻找第k列的主元
        int p = findMainNumber(a,n,k);
        exchange(a,n,p,k);//交换k行和p行
        pivot[k]=p;//记录置换矩阵p
        if(a[k*n+k]!=0){
                for(int i=k+1;i<n;i++){//部分下三角L
                    a[i*n+k]=a[i*n+k]/a[k*n+k];
                }
                for(int i=k+1;i<n;i++){//计算上三角U
                    for(int j=k+1;j<n;j++){
                        a[i*n+j]=a[i*n+j]-a[i*n+k]*a[k*n+j];
                    }
                }
        }else{
            return true;//矩阵奇异
        }
      }
      /*
      //计算下三角L
      double temp=0;
       for(int i=0; i<n-2; i++)//i行k列
            for(int k=i+1; k<n-1;k++)
            {
                    temp=a[n*pivot[k] + i];
                    a[n*pivot[k] + i]=a[k*n + i];
                    a[k*n + i]=temp;
            }
        */
      luReult=a;
      printmat(a,n,"lu");
      return false ;
}
double radio(int a,int b){
    return (double)(a)/(double)(b);
}
void buildHilbert(double *a,double *b,int n){
    for(int r=0;r<n;r++){
        for(int j=0;j<n;j++){
            a[r*n+j]=radio(1,j+r+1);
            b[r]=b[r]+a[r*n+j];
        }
    }
}
void exchangeb(double *b,int n,int r,int e){
    double temp=0;
    temp=b[e];
    b[e]=b[r];
    b[r]=temp;
    /*r(int i=0;i<n;i++){
        temp=b[r*n+i];
        b[r*n+i]=b[e*n+i];
        b[e*n+i]=temp;
    }*/
}

int main()
{
    int n=6;//矩阵是n*n的
    double *b=new double[n];
    double *a=new double[n*n];
    /*double input[n*n]={0.001,1.00,1.00,2.00};
    a=input;
    double inputb[n]={1.00,3.00};
    b=inputb;*/
    buildHilbert(a,b,n);
    printmat(a,n,"a:");
    int *pivot=new int[n*n];
    luReult=new double[n*n];
    lu(a,pivot,n);
    printarr(pivot,n,"p:");
    //cout << "Hello world!" << endl;
    return 0;
}

输出:

参考文献

  1. LU 分解
  2. 矩阵分析——LU分解

最大流问题:给定流网络、源节点、汇点,找到值最大的一个流。

Ford-Fullkerson方法

主要思想:循环增加流的值。

几个概念

残存网络\(G_f\)

残存网络简单定义:

残存网络=网络容量Capacity-流量网络flow

残存网络形式化定义:

说明: 第一项很好理解,就是=网络容量Capacity-流量网络flow

Description

Advanced Cargo Movement, Ltd. uses trucks of different types. Some trucks are used for vegetable delivery, other for furniture, or for bricks. The company has its own code describing each type of a truck. The code is simply a string of exactly seven lowercase letters (each letter on each position has a very special meaning but that is unimportant for this task). At the beginning of company's history, just a single truck type was used but later other types were derived from it, then from the new types another types were derived, and so on.

Movement:运动 trucks:卡车 exactly seven lowercase letters :正好七个小写字母 derived:派生

Today, ACM is rich enough to pay historians to study its history. One thing historians tried to find out is so called derivation plan -- i.e. how the truck types were derived. They defined the distance of truck types as the number of positions with different letters in truck type codes. They also assumed that each truck type was derived from exactly one other truck type (except for the first truck type which was not derived from any other type). The quality of a derivation plan was then defined as 1/Σ(to,td)d(to,td)

quality:质量

where the sum goes over all pairs of types in the derivation plan such that to is the original type and td the type derived from it and d(to,td) is the distance of the types. Since historians failed, you are to write a program to help them. Given the codes of truck types, your program should find the highest possible quality of a derivation plan.

Input

The input consists of several test cases. Each test case begins with a line containing the number of truck types, N, 2 <= N <= 2 000. Each of the following N lines of input contains one truck type code (a string of seven lowercase letters). You may assume that the codes uniquely describe the trucks, i.e., no two of these N lines are the same. The input is terminated with zero at the place of number of truck types.

Output

For each test case, your program should output the text "The highest possible quality is 1/Q.", where 1/Q is the quality of the best derivation plan. Sample Input

4 aaaaaaa baaaaaa abaaaaa aabaaaa 0 # Sample Output

The highest possible quality is 1/3.

解决方案

t:表示卡车的七个字母的字符串 d(t0,td)=t0和td两卡车的字符串不相同的个数

每个卡车都是由另一个卡车派生而来。 现在给出n种不同型号的truck,问怎样使quality的值最小————即找到一条连接所有truck的最短路径

带权图分为有向和无向,无向图的最短路径又叫做最小生成树,有prime算法和kruskal算法;有向图的最短路径算法有dijkstra算法和floyd算法。

最小生成树问题:给定一个连通无向图,寻找一颗无环树,使得树上所有边权值之和最小。

最小生成树的贪心策略的通用方法

问题描述

已知:连通无向图\(G=(V,E)\),权重函数\(w:E \rightarrow R\) 循环不变式:在每边循环之前,A是某颗最小生成树的一个子集。 伪代码

GENERIC-MST(G,w)
A={}
while A does not form a spanning tree // 当A不是一个生成树时
    find an edge(u,v) that is safe for A // 找到一条A的安全边
    A = A ∪ {(u,v)}
return A

注:安全边是指加入A后,不会使得A违反循环不变式。即 A ∪ {(u,v)}也是某颗最小生成树的一个子集

问题:如何寻找安全边

求安全边

定理:设A是图G的某最小生成树的一个子集。设(S,V-S)是图G中尊重集合A的任意一个切割,又设(u,v)是横跨切割(S,V-S)的一条轻量级边,则边(u,v)对于集合A是安全的。

切割:图中的线 切割尊重集合A:集合A中不存在横跨切割的边(如图a中的灰粗线构成的集合) 轻量级边:横跨一个切割的所有边中权重最小的边(不唯一)。

Kruskal和Prim关系

term 集合A 说明 共性
Kruskal 森林 结点是原图结点,安全边是权重最小的连接两个不同分量的边 都是用具体的规则来确定安全边的方法
Prim 安全边是连接A和A之外某个节点的边中权重最小的边

Kruskal算法

寻找安全边:在所有连接两个不同树的边里,找到权重最小的边(u,v)

伪代码

MST-KRUSKAL(G,w)
A={}//A是森林
for each vertex v∈G.V
    MAKE-SET(v)//将集合A初始化为空集,并创建M颗子树,每棵树各含一个结点
sort the edges of G.E into nondecreasing order by weight w //对边按照权重排序
for each edge(u,v)∈G.E,taken in nondecreasing order by weight//按权重顺序遍历边
    if FIND-SET(v)!=FIND-SET(u)//(u,v)不在一棵树
        A= A ∪ {(u,v)}
        UNION(u,v)
return A

时间复杂度

\(O(ElgV)\)

特点

图的存贮结构采用边集数组,且权值相等的边在数组中排列次序可以是任意的.该方法对于边相对比较多的不是很实用,浪费时间.

c++实现(写的很好,一定要看):Kruskal算法

Prim算法

寻找安全边:A总是一颗树。从单一顶点开始,逐步扩大树中所含顶点的数目,直到遍及连通图的所有顶点。

为了快速选择新边, 在算法执行的过程中,所有不在树A中的结点都存放在一个基于key属性的最小优先队列Q中。

u.key : 连接结点u和中结点的所有边中最小边的权重。 u.π:u在中的父节点

描述

输入:连通图G,边权w,最小生成树的根节点r

初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {}

循环:重复以下操作,直到\(V_{new}=V\)

  1. 在集合E中选取权值最小的边(u,v),其中u是集合Vnew中的元素,而v则是V中没有加入Vnew的顶点;
  2. 将v加入Vnew中,将(u,v)加入Enew中;

输出:使用集合Vnew和Enew来描述所得到的最小生成树。

图例:

伪代码

MST-PRIM(G,w,r)//r是最小生成树的根节点
for each u∈G.V
    u.key=∞ 
    u.π=NIL //u在树中的父节点
r.key=0
Q=G.V
while Q!={}
    u=EXTRACT-MIN(Q)//某条横跨切割(V-Q,Q)的一个轻量级边的端点
    for each v∈G.Adj[u]//遍历u的邻接表
    if v∈Q and w(u,v)<v.key
        v.π=u
        v.key=w(u,v)

时间复杂度

通过邻接矩阵图表示的简易实现中,找到所有最小权边共需O(V2)的运行时间。使用简单的二叉堆与邻接表来表示的话,普里姆算法的运行时间则可缩减为O(E log V),其中E为连通图的边数,V为顶点数。如果使用较为复杂的斐波那契堆,则可将运行时间进一步缩短为O(E + V log V),这在连通图足够密集时(当E满足Ω(V log V)条件时),可较显著地提高运行速度

参考文献

  1. 维基百科

Description

Several currency exchange points are working in our city. Let us suppose that each point specializes in two particular currencies and performs exchange operations only with these currencies. There can be several points specializing in the same pair of currencies. Each point has its own exchange rates, exchange rate of A to B is the quantity of B you get for 1A. Also each exchange point has some commission, the sum you have to pay for your exchange operation. Commission is always collected in source currency. For example, if you want to exchange 100 US Dollars into Russian Rubles at the exchange point, where the exchange rate is 29.75, and the commission is 0.39 you will get (100 - 0.39) * 29.75 = 2963.3975RUR. You surely know that there are N different currencies you can deal with in our city. Let us assign unique integer number from 1 to N to each currency. Then each exchange point can be described with 6 numbers: integer A and B - numbers of currencies it exchanges, and real RAB, CAB, RBA and CBA - exchange rates and commissions when exchanging A to B and B to A respectively. Nick has some money in currency S and wonders if he can somehow, after some exchange operations, increase his capital. Of course, he wants to have his money in currency S in the end. Help him to answer this difficult question. Nick must always have non-negative sum of money while making his operations.

currency exchange points:货币兑换点 commission:佣金 assign :分配

每个货币兑换点只能互换两种货币。兑换点可重复。每个点都有自己的兑换率,A to B 的兑换率是指1个A能兑换的B的数量。

兑换时收取一定的佣金,佣金是以来源货币来collect的。例如,如果你想将100dolars兑换成Russian Bules, 兑换点的兑换率是29.75,费用是0.39,,那么你会得到的钱是 (100 - 0.39) * 29.75 = 2963.3975RUR.

假设每种兑换的点数量是1-N,那么每个兑换点可以由6个num来描述:

interger A and B 兑换货币的序号 RAB A to B兑换率 CAB A to B 的佣金 RBA B to A 兑换率 CBA B to A 的佣金

Nick有些S货币,他很好奇他能否通过货币兑换的方式对他的资金进行增值。最终他希望他拿到还是S货币。帮他解决这个问题。Nick在这个过程中不能借钱。

Input

The first line of the input contains four numbers: N - the number of currencies, M - the number of exchange points, S - the number of currency Nick has and V - the quantity of currency units he has.

The following M lines contain 6 numbers each - the description of the corresponding exchange point - in specified above order. Numbers are separated by one or more spaces.

1<=S<=N<=100, 1<=M<=100, V is real number, 0<=V<=103.

For each point exchange rates and commissions are real, given with at most two digits after the decimal point, 10-2<=rate<=102, 0<=commission<=102.

Let us call some sequence of the exchange operations simple if no exchange point is used more than once in this sequence. You may assume that ratio of the numeric values of the sums at the end and at the beginning of any simple sequence of the exchange operations will be less than 104.

corresponding:相应的

第一行包含四个数字: N 货币的种类 M 兑换点的数量 S Nick的货币的序号 V Nick拥有的钱

接下来的M行,每行包含六个数字,描述相应的兑换点的属性。 数字由一个或多个空格隔开

1<=S<=N<=100, 1<=M<=100, V is real number, 0<=V<=103.

每个兑换点的兑换率和手续费都是实数,至多精确到两位小数。

10-2<=rate<=102, 0<=commission<=102

如果在操作序列中不适用多个兑换点,我们可以发起一些简单的兑换操作。你可以假设:(最后的总和/)

输出数据

如果nick能够实现他的愿望,则输出YES,否则输出NO。

样例输入

3 2 1 20.0 1 2 1.00 1.00 1.00 1.00 2 3 1.10 1.00 1.10 1.00

思路

将货币看做点,每种兑换规则为边,两点的路径长度为兑换后的钱数。建图之后可以看出题意为求图中是否存在正环,用Bellman-Ford求最长路径,如果存在正环输出YES,不存在输出NO。

单源最短路径问题:给定一个图 \(G=(V,E)\) ,我们希望找到从给定源节点\(s\in V\)到每个节点\(v \in V\)的最短路径。

先总结:

Bellman-Ford算法:采用动态规划进行设计。待总结。简单,还能侦探含源节点的负权重回路。 Dijkstra算法:采用贪心算法范式进行设计。复杂度低,但要求权重非负。

最短路径树

一颗根节点为s的最短路径树是一个有向子图\(G'=(V',E')\),这里\(V'\in V\),\(E'\in E\),满足: 1. \(V'\)是图G中从源节点s可以到达的所有结点的集合; 2. \(G'\)形成一颗根节点为s的树; 3. 对于所有结点\(v\in V'\),图\(G'\)中从结点s到结点v的唯一简单路径是图G中从结点s到结点v的一条最短路径。

松弛操作

对每个节点v,我们维持一个属性v.d,记录s→v的最短路径估计

松弛操作:比较s→u→v与s→v的d,然后进行更新。

RELEAX(u,v,w)
    if v.d>u.d+w(u,v)
        v.d=u.d+w(u,v)
        v.π=u

Bellman-Ford算法

目标:解决单源最短路径问题 条件:边权重可负 输入:带权有向图\(G=(V,E)\)和权重函数\(w:E→R\) 输出:布尔值,是否存在一个从源节点可以到达的负权重回路。若存在,则算法无解。

思路:Bellman-Ford通过对边进行松弛操作来渐进地降低从源节点s到每个节点v的最短路径估计值v.d,直到该估计值与实际的最短路径权重\(δ(s,v)\)相同为止。

初始函数:

INITIALIZE-SINGLE-SOURCE(G,s)
    for eahc vertex v∈G.V
        v.d=∞
        v.π=null
    s.d=0

算法

BELLMAN-FORD(G,w,s)
    INITIALIZE-SINGLE-SOURCE(G,s)//对每个点v.d和v.π初始化
    for i=1 to |G.V|-1 //对每个边处理V-1次
        for each edge(u,v)∈G.E //遍历所有的边
            RELAX(u,v,w)
    for each edge(u,v)∈G.E
        if v.d>u.d+w(u,v)
            return False
        else
            return True

复杂度\(O(VE)\)

对每个边处理V-1的原因:设p是从s到v的最短路径,则p最多包含V-1条边。

如下图所示的极限条件,v0-v5路径应该为<v0,v1,v2,v3,v4,v5>

原本v0-v5的路径是灰色的。 第一轮松弛后,v0-v5路径变为<v0,v1,v5>,即黑色的 同理,第二轮松弛后,v0-v5路径变为<v0,v1,v2,v5> 因此要松弛V-1次才可以

Dijkstra算法

条件:所有边的权重非负 思想:由上述性质可知,如果存在一条从i到j的最短路径(Vi.....Vk,Vj),Vk是Vj前面的一顶点。那么(Vi...Vk)也必定是从i到k的最短路径。为了求出最短路径,Dijkstra就提出了以最短路径长度递增,逐次生成最短路径的算法。即:每次找到离源点最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。

Dijkstra算法的关键:维持一组节点集合S,从s到该集合中的每个节点的最短路径已经被找到。算法重复从V-S中选择最短路径估计最小的节点u,将u加入结合S,然后对所有从u出发的边进行松弛。

DIJKSTRA(G,w,s)
    INITIALIZE-SINGLE-SOURCE(G,s)//对v.d和v.π初始化
    S={}
    Q=G.V
    while Q.length != 0
        u = UXTRACT-MIN(Q)//从V-S中选出最短路径估计最小的节点u
        S=S∪{u}
        for each vertex v∈G.Adj[u]
            RELAX(u,v,w)

算法解释 二维数组 e 来存储顶点之间边的关系,初始值如下:

我们还需要用一个一维数组 dis 来存储 1 号顶点到其余各个顶点的初始路程,如下。 我们将此时 dis 数组中的值称为最短路的“估计值”

图的关系如图所示,1是源点

既然是求 1 号顶点到其余各个顶点的最短路程,那就先找一个离 1 号顶点最近的顶点。通过数组 dis 可知当前离 1 号顶点最近是 2 号顶点。当选择了 2 号顶点后,dis[2]的值就已经从“估计值”变为了“确定值”,即 1 号顶点到 2 号顶点的最短路程就是当前 dis[2]值。 原因:目前离 1 号顶点最近的是 2 号顶点,并且这个图所有的边都是正数,那么肯定不可能通过第三个顶点中转,使得 1 号顶点到 2 号顶点的路程进一步缩短了。因为 1 号顶点到其它顶点的路程肯定没有 1 号到 2 号顶点短,

既然选了 2 号顶点,接下来再来看 2 号顶点有哪些出边呢。有 2->3 和 2->4 这两条边。先讨论通过 2->3 这条边能否让 1 号顶点到 3 号顶点的路程变短。也就是说现在来比较 dis[3]和 dis[2]+e[2][3]的大小。其中 dis[3]表示 1 号顶点到 3 号顶点的路程。dis[2]+e[2][3]中 dis[2]表示 1 号顶点到 2 号顶点的路程,e[2][3]表示 2->3 这条边。所以 dis[2]+e[2][3]就表示从 1 号顶点先到 2 号顶点,再通过 2->3 这条边,到达 3 号顶点的路程。

我们发现 dis[3]=12,dis[2]+e[2][3]=1+9=10,dis[3]>dis[2]+e[2][3],因此 dis[3]要更新为 10。这个过程有个专业术语叫做“松弛”。即 1 号顶点到 3 号顶点的路程即 dis[3],通过 2->3 这条边松弛成功。这便是 Dijkstra 算法的主要思想:通过“边”来松弛 1 号顶点到其余各个顶点的路程。

同理通过 2->4(e[2][4]),可以将 dis[4]的值从 ∞ 松弛为 4(dis[4]初始为 ∞,dis[2]+e[2][4]=1+3=4,dis[4]>dis[2]+e[2][4],因此 dis[4]要更新为 4)。

刚才我们对 2 号顶点所有的出边进行了松弛。松弛完毕之后 dis 数组为:

以此类推,此处不再多加阐述。

参考文献算法 7:Dijkstra 最短路算法

有向无环图的单源最短路径问题

来日再填坑。

题目描述

Assume the coasting is an infinite straight line. Land is in one side of coasting, sea in the other. Each small island is a point locating in the sea side. And any radar installation, locating on the coasting, can only cover d distance, so an island in the sea can be covered by a radius installation, if the distance between them is at most d.

We use Cartesian coordinate system, defining the coasting is the x-axis. The sea side is above x-axis, and the land side below. Given the position of each island in the sea, and given the distance of the coverage of the radar installation, your task is to write a program to find the minimal number of radar installations to cover all the islands. Note that the position of an island is represented by its x-y coordinates.

coasting:滑行 infinite:无穷的 Cartesian coordinate system:笛卡尔坐标系

已知:海岸线是x轴,以上是海,以下是陆地。雷达安装在海岸线上,覆盖半径是d。 目标:求能够覆盖所有岛屿的雷达安装数目。 需需要注意的是,海岛坐标在x-y坐标系中。

Input

The input consists of several test cases. The first line of each case contains two integers n (1<=n<=1000) and d, where n is the number of islands in the sea and d is the distance of coverage of the radar installation. This is followed by n lines each containing two integers representing the coordinate of the position of each island. Then a blank line follows to separate the cases.

The input is terminated by a line containing pair of zeros

输入一般包含好几组case测试数据。

每个case的第一行是(n,d) 然后是n行岛屿坐标

最后以(0,0)结尾

Output

For each test case output one line consisting of the test case number followed by the minimal number of radar installations needed. "-1" installation means no solution for that case.

思路

这道题问的是怎样放雷达使其放的雷达数目最少而能够探测到所有的岛屿,这里需要转换为求每个岛屿的能放雷达的区间的问题:

抽象问题:每个小岛都对应一个区域,这个区域内的雷达都能探测到这个小岛,把这N个区域求出来,问题现在就变成了,最少放置几个点,能使得每个区域内都至少有一个点.

这道题目的关键之处就是把面的问题转换成线的问题,每一个座海岛在x轴上有一个区间,在这个区间里面的雷达都可以侦测到海岛,区间的范围即是[x-sqrt(dd – yy), x+sqrt(dd+yy)],然后先以右端点为基进行从小到大排序,然后把第一个雷达默认放在最左端的xmax,接下来的点只要是xmin小于当前xmax就可以不用增加雷达,如果xmax == xmin的话也不用增加雷达。然后如果xmax < xmin的话就加一个雷达,然后以xmin所属区间的xmax为基进行比较。

代码

import java.io.PrintWriter;

import java.util.Arrays;
import java.util.Scanner;
public class Main {
    static class Range implements Comparable<Range>{
        double left,right;
        public Range(double left,double right){
            this.left = left;

            this.right = right;
        }
        @Override

        public int compareTo(Range range) {
            if(range.left == left){
                return ((Double)right).compareTo((Double)(range.right));
            }else{
                return ((Double)left).compareTo((Double)(range.left));
            }
        }

        @Override
        public String toString() {
            return "(" + left + "," + right + ")";
        }

    }


    public static void main(String[] args) {
        Scanner scn = new Scanner(System.in);

        PrintWriter out = new PrintWriter(System.out);
        int n ,d,x,y,num;
        double dx;
        Range[] ranges;
        int index = 0;
        while(true){
            num = 0;
            n = scn.nextInt();
            d = scn.nextInt();
            if(n == 0){
                break;
            }
            ranges = new Range[n];
            for(int i = 0; i < n; i++){
                x = scn.nextInt();
                y = scn.nextInt();
                if(y > d){
                    num = -1;
                }
                dx = Math.sqrt(d*d - y*y);
                ranges[i] = new Range(x - dx, x + dx);
            }
            Arrays.sort(ranges);//���
            if(num != -1){
                num = calute(ranges);
            }
            out.format("Case %d: %d\n",++index,num);
        }
        out.flush();
            
    }
    
    private static int calute(Range[] ranges) {
        int num = 1;
        int n = ranges.length;
        Range preRange = ranges[0],range;
        for(int i = 1; i < n; i++){
            range = ranges[i];
            if(range.left >= preRange.left && range.left <= preRange.right){
                preRange.left = range.left;
                if(range.right < preRange.right){
                    preRange.right = range.right;
                }
            }else{
                num++;
                preRange = range;
            }
        }
        return num;
    }
}

numpy结构数组

在c语言中,我们可以使用关键字struct定义结构类型。和c语言一样,numpy也可以创建结构定义,这样可以很方便的读取二进制的C语言结构数组,将其转换为numpy数组对象,假设我们定义的结构数组如下(C语言描述):

struct Person{
     char name[30];
     int    age;
     float weight; 
};

我们在python中可以自定义类型如下:

persontype = np.dtype({
'names':['name','age','weight'],
'formats':['S30','i','f']},align = True)

参考文献numpy中结构数组