快速幂优化DP

快速幂优化dp

思路 思路 思路

一般的背包dp的思路都是一个物品一个物品的转移比如

	for(int i = 1; i <= n; i ++)
	 	for(int j = m; j >= 0; j --)
		 	f[i][j] = max(f[i][j], f[i][j-v]+w)
		 
	这样的都是从前i-1个物品向前i个物品转移,也就是一个个转移

快速幂优化dp的思想就是将物品数n二进制拆分
比如拆分成100101的二进制形式
这样我们就只需要转移6次也就是log(n)次
这里有个前提就是每个物品的体积和价值的计算方式都一样

和快速幂很像,快速幂不就是也是拆分次方数n,然后底数累乘2
遇到n>>i & 1的时候就答案乘一下累乘的底数,实现n到log(n)的优化
这里也一样,每次都算一次转移x个物品的最大价值,这里的x就相当于
快速幂里面的底数m,就比如第一次我们算转移两个物品,第二次就算转移4个物品,第三次就算转移8个物品,和m累乘2道理一样
然后当n>>i & 1的时候我们就把转移x个物品的价值转移到答案数组里面
最后的答案数组里面存的就是转移n个物品的最大价值了

  • 代码
    题目链接:牛客题目

三层循环未优化tle代码

思路类似于分组背包了,每个计划都选择一个天数,但数组开不了那么大,需要滚动数组优化
但是这是恰好选择问题,注意初始化为负无穷,还有一点就是每个任务都要选择一种完成的天数,所以每次转移前还需要初始化一下,确保一定选择一个天数,这样下来n三次方复杂度,tle


#include
using namespace std;
const int N = 4e5 + 10;
int f[2][N];
int k, n, m;
int v[N];
 
int main()
{
    cin >> k >> n >> m;
    for(int i = 1; i <= k; i ++)
    {
        cin >> v[i];
    }
    memset(f, -0x3f, sizeof(f));
    f[0&1][0] = 0;
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 0; j <= m; j ++)
        {
            f[i&1][j] = -0x3f3f3f3f;
            for(int p = 1; p <= k; p ++)
            {
                if(j >= p)
                f[i&1][j] = max(f[i&1][j], f[(i-1)&1][j-p] + v[p]);
            }
        }
    }
    cout << f[n&1][m] << endl;
     
    return 0;
}

快速幂优化代码

时间复杂度从O(n^3) 通过快速幂转移物品数n 降低为 O(n^2logn)


#include
using namespace std;
const int N = 4e4 + 10;
int a[N];//花费体积为i价值为a[i];
int f[30][N];//花费体积为i的最大价值
int d[30][N];//转移2的i次方物品花费体积不超过j最大价值
int idx = 1;
int main()
{
    int n, m, w;
    cin >> m >> n >> w;//调换了下顺序,纯属个人喜好
    //习惯把n当作物品数,w需要花费的体积,m可以选择的最大体积
    
    for(int i = 1; i <= m; i ++)
    cin >> a[i];
   
    memset(f, -0x3f, sizeof(f));//因为题目要求是恰好花费体积j,所以初始化全为负无穷
    memset(d, -0x3f, sizeof(d));//因为题目要求是恰好花费体积j,所以初始化全为负无穷
    f[0][0] = 0;
    
    for(int i = 1; i <= m; i ++)
    {
        d[0][i] = a[i];//转移一个(2的0次方)物品花费体积为i的价值
    }
   
    for(; n > 0; n /= 2, idx ++)//对物品数n进行二进制拆分 1001010101
    {
        if(n & 1)//该合并物品了
        {
            for(int i = w; i >= 0; i --)//枚举体积
            {
                for(int j = 0; j <= i; j ++)//枚举要合并的物品(2的idx-1次方个物品)的体积
                //这里为什么是idx-1是因为10101 当遇到2的二次方时,这时候的idx已经是3了,因为idx从1开始的,0次方也就是d[0][i]我们初始化时已经算完了,所以遇到的1是在上一次算出来的
                {
                    f[idx][i] = max(f[idx][i], f[idx-1][i-j] + d[idx-1][j]);
                }
            }
        }
        else
        {
        //这里要再保存一次答案数组,因为下次合并要从上一次合并后的状态转移过来
        //也就是1000101 倒数第二个1要从倒数第一个1再加上要转移4个物品的状态转移过来,所以需要将最后一次合并一直保持下去
            for(int i = 0; i <= w; i ++)
            {
                f[idx][i] = f[idx-1][i];  
            }
        }
        
        //这里相当于快速幂里面的底数m累乘,在这里的意义就是转移2的idx-1次方的物品向转移2的idx次方个物品的状态转移
        for(int i = w; i >= 0; i --)//枚举花费的体积
        {
            for(int j = 0; j <= i; j ++)//枚举要转移过来的2的idx-1次方个物品花费的体积
            {
               d[idx][i] = max(d[idx][i], d[idx-1][j] + d[idx-1][i-j]);
            }
        }
        
    }
    //答案就是最后一次合并后花费w体积的价值
    //因为判断for循环退出条件也idx+1了,所以最后一次合并是第idx-1次
    cout << f[idx-1][w] << endl;
    
    return 0;
}

你可能感兴趣的:(动态规划,算法,动态规划)