菜即的算法学习笔记3.21

筛法函数

筛法函数,我是在寻找约数和的时候发现的
什么是筛法函数?
筛法函数的作用?
就比如求前n个数的每一个数的约数和(约数不含这个数本身)
例如:6的约数和为 1+2+3=6,4的约数和为 1+2=3
筛法函数可以用在,寻找前n个数的约数和
这是常规的筛法函数求前n个数的质数:

bool s[10005]={1,1};//0和1啥也不是,定为1
int a[10005],ps;//a数组存最后的质数,ps为这个数组的下标
//全局数组初值全为0
void sf(){//筛法函数
    for (int j=2;j<10000;j++)//这里的j的含义就是约数
    	if (!s[j]){//s[j]=0,表明j不是合数(合数标记为1)
   		 a[ps++]=j;//因为2是质数,所以先存入2这个质数,数组下标+1
   		 for (int k=j*2;k<10000;k+=j)//k=j*2,因为j是约数,所以j*2明显也是约数,每次+j,保证是j的倍数
    //这里的每一个k都是约数,因为可以被j整除!
   		 s[k]=1;//k一定是合数,标记!
    }
}
//最后s数组中标1的数为合数,0为质数,s数组可以找出前10000个数中的质数和合数分别是谁
//数组a即为前10000的每一个质数
//时间复杂度约为n

通过简单的变形

void prime(){
    for (int i=1;i<=n;i++)//i为每一个约数
    for (int j=i*2;j<=n;j+=i)//j也是约数
    //比如i=1时1*2=2,2的约数有1
    //		2+1=3,3+1=4 ...(n-1)+1=n 每一个数的约数都有1
    //i=2时 2*2=4,4的约数有2
    //		2+2=4,4+2=6... 每一个j都有约数=2
    a[j]+=i;
}

数组a[j]就是数j的约数和

void Mya(){
    for(int i=2;i<=n;i++){
        for(int j=1;j<=i/2;j++){
            if(i%j==0) a[i]+=j;
        }
    }
}

这是我本来写的代码,好像时间复杂度更高一点…好头大啊0.0,自己也解释不清了,那就暂时先记住吧,以后用得到的时候再来补充…

背包!

背包刚开始学习好像都是在教用二维数组解决,01背包:

#include <bits/stdc++.h>

using namespace std;
int T,M;
int s[105],v[105];
int dp[105][1005];
int Max(int a,int b){
    return a>b?a:b;
}
int main()
{
    cin>>T>>M;
    for(int i=1;i<=M;i++){
        cin>>s[i]>>v[i];
    }
    for(int i=1;i<=M;i++) dp[i][0]=0;
    for(int i=1;i<=T;i++) dp[0][i]=0;

    for(int i=1;i<=M;i++){
        for(int j=1;j<=T;j++){
            if(j>=s[i]){
                dp[i][j]=Max(dp[i-1][j],dp[i-1][j-s[i]]+v[i]);
            }else{
                dp[i][j]=dp[i-1][j];
            }
        }
    }

    cout<<dp[M][T];
    return 0;
}

这个图是倒着推的,上面代码是正着推的…
请添加图片描述
另外还有一维数组解决背包问题的办法

#include <bits/stdc++.h>
using namespace std;
int w[105], val[105];
int dp[1005];
int main()
{
    int t,m;    
    scanf("%d%d",&t,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&w[i],&val[i]);
    }
    for(int i=1;i<=m;i++) 
    {
        for(int j=t;j>=0;j--) 
        {
            if(j>=w[i])
            {
                dp[j]=max(dp[j-w[i]]+val[i], dp[j]);
            }
        }
    }    
    printf("%d",dp[t]);
    return 0;
}

一维数组解决背包问题的注意点就是内层循环要倒着来

for(int i=1;i<=m;i++) 
    {
        for(int j=t;j>=0;j--) 
        {
            if(j>=w[i])
            {
                dp[j]=max(dp[j-w[i]]+val[i], dp[j]);
            }
        }
    }    

如果不倒着来,可能会造成重复放同一个物品的情况,比如第一个物品重量为5,背包容量为10,就会有

i=1:
dp[5] = max(dp[0]+20, dp[5]);
dp[6] = max(dp[1]+20, dp[6]);
dp[7] = max(dp[2]+20, dp[7]);
dp[8] = max(dp[3]+20, dp[8]);
dp[9] = max(dp[4]+20, dp[9]);
dp[10] = max(dp[5]+20, dp[10]);

这里的dp[10]要表达的含义是:背包容量为10的时候,放不放该物品,如果要放该物品,那么背包的容量就需要减去该物品的体积,也就是10-5=5,括号里面的dp[5]的含义应该是,背包容量为5的时候,只考虑上一个物品之前的物品的最优解,但是dp[5]在开始的时候却被赋予了新的值,此时dp[5]表示的是已经放了该物品
此处的dp[5]是新值,不是旧值!
dp[10]进行了重复放入
dp[i]中的i是增加的,括号中dp[j]的j也是增加的,由于j<i,所以会出现重复放入的情况
如果是倒着来

i=1:
dp[10] = max(dp[5]+20, dp[10]);
dp[9] = max(dp[4]+20, dp[9]);
dp[8] = max(dp[3]+20, dp[8]);
dp[7] = max(dp[2]+20, dp[7]);
dp[6] = max(dp[1]+20, dp[6]);
dp[5] = max(dp[0]+20, dp[5]);

这样一来,括号内的值就全都是旧值了!
一维数组解决背包问题的最优代码:

#include <bits/stdc++.h>
using namespace std;
int w,val;
int dp[1005];
int main()
{
    int t,m;    
    scanf("%d%d",&t,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&w,&val);
        for(int j=t;j>=w;j--) 
        {
                dp[j]=max(dp[j-w]+val, dp[j]);
        }
    }    
    printf("%d",dp[t]);
    return 0;
}

在输数据的时候进行dp,即节省了空间,又加快了运行速率,这好像叫,状态压缩?现在还不太懂,以后用到的话再详细研究

背包题目

题目描述
选取和不超过S的若干个不同的正整数,使得所有数的约数(不含它本身)之和最大。

输入格式
输入一个正整数S。

输出格式
输出最大的约数之和。
其实这道题很容易就能想到背包(连我都能很容易想到…)

dp[j]=Max(dp[j],dp[j-i]+a[i]);

很容易就能想到吧,i表示小于S的数,01背包,取不取这个数,如果取了那么容量S就减i,否则就等于 旧值,但还是做了一点变形…导致我还是搜了题解才做了出来…被自己菜哭了 || _||

void solve(){
    for(int i=2;i<=S;i++){//i表示需要判断01的数,也就是取不取的数..
        for(int j=i;j<=S;j++){
            dp[j]=Max(dp[j],dp[j-i]+a[i]);
        }
    }
}

大概就是…这样吧…
请添加图片描述
其实我还联想到一道题,就是最长增长子序列

 for(int i=1;i<num;i++){
        for(int j=0;j<i;j++){
            if(a[j] <= a[i]){
                dp[i]=max(dp[i],dp[j]+1);
            }
        }
    }

挺像的吧…这个也是先固定一点,固定的点表示求该点之前的最长增长子序列的长度,然后对其前面的点进行判断,如果前面的值比这个固定的点小,那么就在旧值和新值之间取最大值,因为这里的j总是小于i,所以不存在重复判断的情况…就是前面重复放入同一个物品那样的情况…
感觉就是那么回事,但是又搞不很清楚里面的原理…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值