c语言算法

文章详细解释了二分查找、一次和二次计算前缀和以及使用差分方法在数组操作中的实现,包括构造新数组、插入操作和最终结果计算。
摘要由CSDN通过智能技术生成

目录

动态规划

背包问题

01背包问题

思路

完整代码

完全背包问题

思路

完整代码

多重背包问题

思路

完整代码

二进制优化思路

优化代码

分组背包问题

思路

完整代码


动态规划

背包问题

01背包问题

思路

动态规划:原问题分解为相对简单的子问题的方式求解复杂问题的方法
1状态表示(集合,属性)
2状态转移方程(状态计算,集合划分过程)
3边界确定

i:前i个物品,j:背包目前总体积。所有从前i个物品中选,且总体积不超或j的选法(集合)
f[i][j]:总价值

1.不放入第i个物品:f[i][j]=f[i-1][j]
2.放入第i个物品:f[i][j]=f[i-1][j-v[i]]+V[i]
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+V[i])   

边界:j>=v[i]
i=1;i<=N;i++
j=0;j<=V;j++


if(j>=v[i])
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+V[i]);
else f[i][j]=f[i-1][j];
或
f[i][j]=f[i-1][j];
if(j>=v[i])
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);



int res=0;
for(int i=0;i<=V;i++)
res=max(res,f[N][i]);
printf("%d\n",res);
或
printf("%d\n",f[N][V]);

滚动数组优化:

让j从大到小遍历 

完整代码
//暴力
#include<stdio.h>

int N,V;
const int n=1010;
int v[n],w[n];
int f[n][n];

int max(int a,int b)
{
    if(a>=b)
    return a;
    else return b;
}

int main()
{
    int max(int a,int b);
    scanf("%d %d",&N,&V);
    
    for(int i=1;i<=N;i++)
    {
        scanf("%d %d",&v[i],&w[i]);
    }
    
    int res=0;
    
    for(int i=1;i<=N;i++)
        for(int j=0;j<=V;j++)
        {
            
 //           if(j>=v[i])
   //          f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
     //        else f[i][j]=f[i-1][j];
            f[i][j]=f[i-1][j];
            if(j>=v[i])
            f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
        
//   for(int i=0;i<=V;i++)
//   res=max(res,f[N][i]);
   
//   printf("%d\n",res);
    printf("%d\n",f[N][V]);
   return 0;
    
}
//优化

#include<stdio.h>

int N,V;
const int n=1010;
int f[n];
int v[n],w[n];

int max(int a,int b)
{
    if(a>=b)
    return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++)
    {
        scanf("%d %d",&v[i],&w[i]);
    }
    for(int i=1;i<=N;i++)
    for(int j=V;j>0;j--)
    {
        
        if(j>=v[i])
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
    // int res=0;
    // for(int j=0;j<=V;j++)
    // res=max(res,f[j]);
    // printf("%d\n",res);
    printf("%d\n",f[V]);
    return 0;
}

完全背包问题

思路
完全背包:每个物品可放入多次

第i个不放入:f[i][j]=f[i-1][j]
第i个放入1次:f[i][j]=f[i-1][j-v[i]]+w[i]
第i个放入2次:f[i][j]=f[i-1][j-2*v[i]]+2*w[i]
……
第i个放入k次:f[i][j]=f[i-1][j-k*v[i]]+k*w[i]

f[i][j]     =max(f[i-1][j],     f[i-1][j-v[i]]+w[i],  f[i-1][j-2*v[i]]+2*w[i],……,f[i-1][j-k*v[i]]+k*w[i])
f[i][j-v[i]]=max(f[i-1][j-v[i]],f[i-1][j-2*v[i]]+w[i],f[i-1][j-3*v[i]]+2*w[i],……,f[i][j]=f[i-1][j-(k+1)*v[i]]+k*w[i])
so
f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])

优化:
j=v[i];j<=V;j++ //边界:j>=v[i]
f[j]=max(f[j],f[j-v[i]]+w[i])

01背包:  f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])   //优化从大到小
完全背包:f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])     //优化从小到大
完整代码
//暴力
#include<stdio.h>

int N,V;
const int n=1010;
int v[n],w[n];
int f[n][n];

int max(int a,int b)
{
    if(a>=b)
    return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++)
    scanf("%d %d",&v[i],&w[i]);
    for(int i=1;i<=N;i++)
        for(int j=0;j<=V;j++)
        {
            f[i][j]=f[i-1][j];
            if(j>=v[i]) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
            // if(j>=v[i]) f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]); //
            // else  f[i][j]=f[i-1][j];
        }
    printf("%d\n",f[N][V]);
    return 0;
}
//优化
#include<stdio.h>

int N,V;
const int n=1010;
int v[n],w[n];
int f[n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++) scanf("%d %d",&v[i],&w[i]);
    for(int i=1;i<=N;i++)
        for(int j=v[i];j<=V;j++) f[j]=max(f[j],f[j-v[i]]+w[i]);
    printf("%d\n",f[V]);
    return 0;
}

多重背包问题

思路
多重背包:第i个物品最多用s[i]个
01背包的扩展

状态表示:第i个物品可放入0,1,2...s[i]个
k:0-s[i]
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2*v[i]]+2*w[i]...f[i-1][j-k*v[i]]+k*w[i])
k从0开始循环,f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i])

优化
j从大到小,因为上式都是f[i-1]
f[j]=max(f[j],f[j-k*v[i]]+k*w[i])

边界:k=0;k<=s[i]&&k*v[i]<=j;k++
完整代码
//暴力
#include<stdio.h>

int N,V;
const int n=110;
int v[n],w[n],s[n];
int f[n][n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++) scanf("%d %d %d",&v[i],&w[i],&s[i]);
    for(int i=1;i<=N;i++)
        for(int j=0;j<=V;j++)
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
           {
               
            // f[i][j]=f[i-1][j];
            
               f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
           }
    printf("%d\n",f[N][V]);
    return 0;
}
//优化
#include<stdio.h>

const int n=110;
int N,V;
int v[n],w[n],s[n];
int f[n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++) scanf("%d %d %d",&v[i],&w[i],&s[i]);
    for(int i=1;i<=N;i++)
        for(int j=V;j>=0;j--)
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
            f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
    printf("%d\n",f[V]);
    return 0;
}
二进制优化思路

这二进制优化把人看傻了,发明二进制的人是个天才

任何数都能用二进制表示
8:1,2,4                        8  >=2^3(8)   取2^2(4) ,c(2)
10:1,2,4                       10 >=2^3(8)   取2^2(4) ,c(4)
200:1,2,4,8,16,32,64,73       200>=2^7(128) 取2^6(68),c(73)

将s个第i个物品 拆分为 n组(二进制表示)个第i个物品,将前n-1个i物品 变为第i个物品之前的单个物品
所以就转化为01背包问题

        while(k<=s)
        {
            cnt++;
            v[cnt]=a*k;
            w[cnt]=b*k;
            s=s-k;
            k=k*2;
        }
        if(s>0)
        {
            cnt++;
            v[cnt]=a*s;
            w[cnt]=b*s;
        }
优化代码
#include<stdio.h>

int N,V;
const int n1=25000,n2=2010;
int v[n1],w[n1];
int f[n2];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    int cnt=0;
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++)
    {
        int a,b,s;
        scanf("%d %d %d",&a,&b,&s);
        int k=1;
        
        while(k<=s)
        {
            cnt++;
            v[cnt]=a*k;
            w[cnt]=b*k;
            s=s-k;
            k=k*2;
        }
        if(s>0)
        {
            cnt++;
            v[cnt]=a*s;
            w[cnt]=b*s;
        }
    }
    
    for(int i=1;i<=cnt;i++)
        for(int j=V;j>=v[i];j--) f[j]=max(f[j],f[j-v[i]]+w[i]);
    printf("%d",f[V]);
    return 0;
}

分组背包问题

思路
分组背包:有N组物品,每组只能选一个

状态计算:
第i组选第一个:f[i][j]=f[i-1][j]
第i组选第k个:f[i][j]=f[i-1][j-v[i,k]]+w[i,k]

s[i]:每组个数(表示个数,注意边界)
完整代码
#include<stdio.h>

int N,V;
const int n=110;
int s[n];
int v[n][n],w[n][n];
int f[n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++) 
    {
        scanf("%d",&s[i]);
        for(int j=0;j<s[i];j++) scanf("%d %d",&v[i][j],&w[i][j]);
    }
    for(int i=1;i<=N;i++)
        for(int j=V;j>=0;j--)
            for(int k=0;k<s[i];k++) 
            {
                if(j>=v[i][k])
                f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
            }
    printf("%d\n",f[V]);
    return 0;
}

线性DP

数字三角形

最长上升子序列

 思路
基础版
f[i]:以第i个数结尾的子序列的集合,f[i]表示子序列的长度
f[i]=f[i-1]+1

i=1;i<=N;i++
f[i]=1
j=1;j<=i;j++
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1)


优化版
优化版得先看一遍二分,整数二分边界问题真受够了

子序列长度从1开始,长度为1,长度为2...每个长度下以数字i结尾的子序列,只存i最小的子序列,存在q[i]中
//因为如果一个数能排在3后面,就一定能排在1后面。当长度一定时,子序列结尾的数字 ,只要记录一个数字最小的子序列
a[i]:数字
遍历a[i],二分法找a[i]可以跟在哪个数字(q[i])后面
int len=0,l=0,r=len;
if(q[mid]<a[i]) l=mid //答案在左边<a[i]的部分,但是不包含a[i]
else r=mid+1
len=max(len,r+1)  //r是二分答案,是最大<a[i]的数,a[i]应该接在r后面,a[i]一旦接在r后面,子序列长度+1,以a[i]为结尾的最长子序列应属于r+1,len是右边界

完整代码
//基础版
#include<stdio.h>

int N;
const int n=1010;
int a[n],f[n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d",&N);
    for(int i=1;i<=N;i++) scanf("%d",&a[i]);
    for(int i=1;i<=N;i++)
    {
        f[i]=1;
        for(int j=1;j<i;j++)
        {
            if(a[j]<a[i])
            f[i]=max(f[i],f[j]+1);  //卧草好聪明的办法
        }
    }
        
            
    int res=0;
    for(int i=1;i<=N;i++)
    res=max(res,f[i]);
    printf("%d\n",res);
    return 0;
}

//优化版
#include<stdio.h>

int N;
const int n=100010;
int a[n],q[n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d",&N);
    for(int i=0;i<N;i++) scanf("%d",&a[i]);  //二分法只能从0开始循环
    for(int i=0;i<N;i++) q[i]=a[i];
    int len=0;
	q[0]=-2e9;  //先给q[0]赋值,第一次for只会用到q[0],
    for(int i=0;i<N;i++)
    {
        int l=0,r=len;
        
        while(r-l>0)
        {
            int mid=(l+r+1)/2;
            if(q[mid]<a[i]) l=mid;    //第一次循环q[mid]=q[0],已经赋值过了
            else r=mid-1;
        }
        len=max(len,r+1);
        q[r+1]=a[i];  //这一步就相当于给q[]赋值了
    }
    printf("%d\n",len);
    return 0;
}

最长公共子序列

思路
状态表示
f[i][j]:第一个序列a[]前i个数字的子序列、第二个子序列b[]前j个数字的子序列、构成的所有公共子序列(集合)的长度最大值

状态计算
用01表示最长公共子序列是否包含a[i]、b[j]
00:f[i][j]=f[i-1][j-1]
01:f[i-1][j]
//01:子序列不包含a[i],包含b[j]
//f[i-1][j]:a[]中前i-1个数字的子序列,b[]中前j个数字的子序列,构成的公共子序列(集合)的长度最大值
/*所以f[i-1][j]一定不包含a[i],有包含b[j]也有不包含b[j]
  所以f[i-1][j]>01*/
//max(a,b,c)=max(max(a,b),max(b,c))
10:f[i][j-1]  //同理
11:f[i-1][j-1]+1
//+1而不是+2,因为只有a[i]==b[j]才会出现11

i=1;i<=N
j=1;j<=M
{
f[i][j]=max(f[i][j-1],f[i-1][j])
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1)

!!!注意
scanf("%c",&a[i])会读取空格,换行符
只能用scanf("%s",a+1)
完整代码
#include<stdio.h>

int M,N;
const int n=1010;
char a[n],b[n];
int f[n][n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&M);
    // for(int i=1;i<=n;i++) scanf("%c",&a[i]);
    // for(int i=1;i<=M;i++) scanf("%c",&b[i]);
    // scanf%c会在每次读取字符时保留换行符或空白字符,应该直接读取整个字符串,而不是逐个字符读取
    scanf("%s",a+1);
    scanf("%s",b+1);
    
    for(int i=1;i<=N;i++)
    {
        for(int j=1;j<=M;j++)
        {
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(a[i]==b[j])
            f[i][j]=max(f[i][j],f[i-1][j-1]+1);
        }
    }
    printf("%d\n",f[N][M]);
    return 0;
}

最短编辑距离

思路
 
状态表示:集合:方法。把A[i]序列变为B[j]序列需要的所有方法的集合
状态计算:1.删:f[i-1][j]+1
        2.增:f[i][j-1]+1
        3.改:f[i-1][j-1]+1

初始化
for (int i = 0; i <= n; i++) f[i][0] = i;
for (int i = 0; i <= n; i++) f[0][i] = i;
3.if(a[i] == b[i]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
  else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
完整代码
#include<stdio.h>

const int N = 1010;  //这行必须放在char A[N]前,,我记得以前不用啊
int n, m;
char A[N], B[N];  //注意char类型
int f[N][N];

int min(int a, int b)
{
    if(a <= b) return a;
    else return b;
}

int main()
{
    scanf("%d %s", &n, A + 1);
    scanf("%d %s", &m, B + 1);
    for (int i = 0; i <= n; i++) f[i][0] = i;
    for (int j = 0; j <= m; j++) f[0][j] = j;
    for (int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
        {
            f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
            if(A[i] == B[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
            else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
        }
    printf("%d\n", f[n][m]);
    return 0;
}
 

 

基础算法

二分法

用y总的话来说,整数二分的边界问题,是真TM蛋疼

思路

//整数
i=0;i<n;i++
q[i]
int l,r;
l=0,r=n-1
//左边界
while(l<r)
mid=(l+r)/2
if(a[mid]>=k) r=mid  //每次选答案所在的区间,区间一定要将答案覆盖掉
else l=mid+1

printf()
if()
//右边界
else {
int l=0,r=n-1
while()
mid=(l+r+1)/2
if(a[mid]<=k) l=mid;
r=mid-1

printf()

//浮点数
取后六位:while(r-l>1e-8)
浮点数二分比整数二分舒服多了

完整代码

//整数
#include<stdio.h>

int n,q;
const int N=100010;
int a[N];

int main()
{
    scanf("%d %d",&n,&q);
    // for(int i=1;i<=n;i++)
    for(int i=0;i<n;i++) 
    scanf("%d",&a[i]);
    while(q--)
    {
        int k;
        scanf("%d",&k);
        int l,r;
        // l=1,r=n;
        l=0,r=n-1;
        while(l<r)
        {
            int mid=(l+r)/2;
            if(a[mid]>=k) r=mid;
            //每次选答案所在的区间,区间一定要将答案覆盖掉
          
            else l=mid+1;
        }
        
        if(a[l]!=k) printf("-1 -1\n");
        else{
            printf("%d ",l);
            // int l=1,r=n;
            l=0,r=n-1;
            while(l<r)
            {
                int mid=(l+r+1)/2;
                if(a[mid]<=k) l=mid;
                else r=mid-1;
            }
         printf("%d\n",l);
        }
    }
    return 0;
}

//浮点数
//求一个数的三次方根
#include <stdio.h>

int main()
{
    double n;
    scanf("%lf",&n);
    double l=-10000.0,r=10000.0;
    while(r-l>1e-8)
    {
        double mid=(l+r)/2;
        if(mid*mid*mid>=n) r=mid;
        else l=mid;
    }
    printf("%lf\n",l);
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值