24-3-16dp优化+背包+状压dp 笔记

括号序列 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
const int mod = 1e9+7;
string s;
int cntl, cntr, cnt;
ll dp[5010][5010], pre[5010], minn[5010];//minn是i号前至少需要的左括号个数,pre是i号前所填左括号小于等于的个数
//函数参数(需要填充的对应括号数,是否为左括号填充)
ll solve(int ans, bool isl){
    if(ans == 0) return 1;
    //初始化
    memset(dp,0,sizeof(dp));
    memset(pre,0,sizeof(pre));
    memset(minn,0,sizeof(minn));
    //括号反转,方向反且顺序反
    if(!isl){//
        for(int i = 0; s[i]; i++){
            if(s[i] == '(') s[i] = ')';
            else s[i] = '(';
        }
        reverse(s.begin(), s.end());
    }
    //计算第pos个左(右)括号前最少需要的右(左)括号数minn[pos]
    int pos1 = 0, pos2 = 0;
    for(int i = 0; s[i]; i++){//初始化minn
        if(s[i] == ')')
            minn[++pos1] = pos2;//minn[pos]为前面左括号个数
        else
            pos2++;
    }
    //dp,pre初始化
    if(minn[1] > 0) pre[0] = dp[1][0] = 1;//第一个前就要填,更新第一个前填0个左括号的方案
    for(int i = 1; i <= ans; i++){
        dp[1][i] = 1; pre[i] = pre[i-1] + 1;//第一个前填的各种方案
        //多填一个多出一种方案
    }
    //第一维:从2到所含有的最大对应括号数
    for(int i = 2; i <= pos1; i++){//pos1右括号个数
        //第二维
        //小于最小填充数部分前缀和更新为0
        for(int j = 0; j < i-minn[i]; j++) pre[j] = 0;
        for(int j = i-minn[i]; j <= ans; j++){//减去i号前必填的左括号,剩下要填的左括号
            dp[i][j] = pre[j];//i号前填j个左括号的方案数为填数小于等于j个的方案数
            printf("dp[%d][%d]=%d\n",i,j,dp[i][j]);
            //在填充范围内部分更新前缀和
            if(j - 1 < 0) pre[j] = dp[i][j];//填左括号为0的方案数为i号前填j个左括号的方案数
            else pre[j] = (pre[j-1] + dp[i][j]) % mod;//填左括号不为0的情况是将i号前填j个和填j-1的方案数相加
            printf("pre[%d]=%d\n",j,pre[j]);
        }
    }
    return dp[pos1][ans];
}
int main(void) {
    cin >> s;
    //计算所需括号数量
    cnt = cntl = cntr = 0;
    for (int i = 0; s[i]; i++) {
        if (s[i] == '(') cnt++;
        else cnt--;
        if (cnt < 0) {
            cntl++;
            cnt = 0;
        }
    }
    cntr = cnt;
    //调用函数输出结果
    cout << solve(cntl, true) * solve(cntr, false) % mod;
    return 0;
}

背包问题

二维01
#include<bits/stdc++.h>
int v[1000],w[1000],f[100][1000];// f[i][j], j体积下前i个物品的最大价值 
int main(void){
	int n, m;   
    cin >> n >> m;
    for(int i = 1; i <= n; i++) 
        cin >> v[i] >> w[i];
    for(int i=1;i<=n;i++){
    	for(int j=1;j<=m;j++){
    		if(j<v[i])f[i][j]=f[i-1][j];//装不下不装,防止下一步越界
    		else f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
		}
	}
	cout<<f[n][m];
}
一维01
#include<bits/stdc++.h>
#include<iostream>
using namespace std;
int v,w,f[1000],m,n;//背包体积为i时的还能装下的最大价值
int main(void){
	cin>>n>>m;
	memset(f,0,sizeof(f));
	for(int i=0;i<n;i++){
		cin>>v>>w;
		for(int j=m;j>=v;j--){
			f[j]=max(f[j],f[j-v]+w);//第一个物品的放入更新了各种
			printf("f[%d]:%d",j,f[j]);
			cout<<" "<<f[j-v]+w<<" ";
		}
	}
	cout<<f[m];
}
完全背包问题

想法:用while(j - v[i] >= 0)循环可以降低为一维数组 

#include<bits/stdc++.h>
using namespace std;

int v[1000], w[1000], f[1001][1001]; // f[i][j], j体积下前i个物品的最大价值 

int main(void) {
    int n, m;   
    cin >> n >> m;
    for(int i = 1; i <= n; i++) 
        cin >> v[i] >> w[i];
        
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j <= m; j++) {
            f[i][j] = f[i-1][j];
            if(j - v[i] >= 0)//取代了物品数量挨个试的循环
                f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]);//状态转移,放入两个物品即放入一个再更新
            cout << "f[" << i << "][" << j << "]: " << f[i][j] << endl;
        }
    }
    cout << f[n][m];
    return 0;
}
多重背包问题

在01基础上增补

for(int i=0;i<n;i++){
		cin>>a>>b>>c;
		for(int j=0;j<c;j++){
			v[cnt]=a;
			w[cnt]=b;
			cnt++;
		}
	}
#include<bit/stdc++.h>
#include<iostream>
using namespace std;
int v[100],w[100],f[1000],m,n,a,b,c;//背包体积为i时的还能装下的最大价值
int main(void){
	cin>>n>>m;
	memset(f,0,sizeof(f));
	int cnt=0;
	for(int i=0;i<n;i++){
		cin>>a>>b>>c;
		for(int j=0;j<c;j++){
			v[cnt]=a;
			w[cnt]=b;
			cnt++;
		}
	}
	for(int i=0;i<cnt;i++)
		for(int j=m;j>=v[i];j--){
			f[j]=max(f[j],f[j-v[i]]+w[i]);//第一个物品的放入更新了各种
			printf("f[%d]:%d",j,f[j]);
			cout<<" "<<f[j-v[i]]+w[i]<<" ";
		}
	cout<<f[m];
}

​
二进制优化

原因在于分成1,2,4...个打包,每个数都可以通过不同的打包组合拼成

#include <bits/stdc++.h>

using namespace std;

const int N = 11 * 1000 + 10, M = 2010;

int v[N], w[N];
int f[M];

int main()
{
    int  n, m;
    scanf("%d %d", &n, &m);

    int cnt = 0;     // 将物品重新分组后的顺序
    for (int i = 1; i <= n; i ++)
    {
        int a, b, s;    // a 体积, b 价值, s 每种物品的个数
        scanf("%d %d %d", &a, &b, &s);

        int k = 1;   // 二进制拆分 打包时每组中有 k 个同种物品
        while (k <= s)  // 即y总说的: 最后一组的物品个数 < 2^(n+1)   1 2 4 8 16 ... 2^n 2^(n+1)
        {
            cnt ++;
            v[cnt] = a * k;  // 每组的体积
            w[cnt] = b * k;  // 每组的价值
            s -= k;
            k *= 2;  // 注意是 k * 2,每次增长一倍,不是k * k
        }

        if (s > 0)   // 二进制拆分完之后 剩下的物品个数分为新的一组
        {
            cnt ++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }

    n = cnt;  // 所有的组数即为 01背包中的物品个数

    // 写01背包模板
    for (int i = 1; i <= n; i ++)
        for (int j = m; j >= v[i]; j --)
            f[j] = max(f[j], f[j - v[i]] + w[i]);

    printf("%d", f[m]);

    return 0;
}

分组背包问题

想法:换成一维数组,输入s在i循环内,与01问题写法相同。因为 f[j-v[k]]继承的一定是上一组的值(由体积决定)

#include<bits/stdc++.h>
using namespace std;

const int N=110;
int f[N][N];  //只从前i组物品中选,当前体积小于等于j的最大值
int v[N][N],w[N][N],s[N];   //v为体积,w为价值,s代表第i组物品的个数
int n,m,k;

int main(void){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        for(int j=0;j<s[i];j++){
            cin>>v[i][j]>>w[i][j];  //读入
        }
    }

    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            f[i][j]=f[i-1][j];  //不选 不选表示不选第 i 组物品的所有物品,只从前 i−1 组物品里面选
            for(int k=0;k<s[i];k++){
                if(j>=v[i][k])     f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);  
            }
        }
    }
    cout<<f[n][m]<<endl;
}

质数判断

int gcd(int a, int b){
    return b == 0 ? a : gcd(b, a % b);
}
if(gcd(a,b)==1)printf("yes");

回路计数

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[1<<21][25];//dp[ i ][ j ]表示从状态 i 到 j 的路径数
//1<<21记录这些楼有没有走过
inline int gcd(int a, int b){
    return b == 0 ? a : gcd(b, a % b);
}
int main(void) {
    ll res = 0;
    dp[1][0] = 1;//初始化
    for (int i = 1; i < (1 << 21); i++) {//
        for (int j = 0; j < 21; j++) {
            if (!(i >> j & 1)) continue;//i的第j位为1,走过了
            for (int k = 0; k < 21; k++) {
                if ((i >> k & 1) || gcd(j + 1, k + 1) != 1) continue;
                //i的第k位为1,或互质,走不通
                dp[i + (1 << k)][k] += dp[i][j];//第k楼标记走过,加上原来的路径
            }
        }
    }
    for (int i = 0; i < 21; i++)
        res += dp[(1 << 21) - 1][i];//i是最后终止的楼
    cout << res;
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值