DP训练 cdoj1354 柱爷很忙 [状压DP]

本文探讨了一种基于动态规划的任务调度算法,旨在寻找N个任务的最优执行顺序,以最小化最大传递时间。通过考虑任务的特性及约束条件,利用状态压缩技巧实现高效求解。
找到原题 真当我不做CDOJ?

工作 (work.pas/cpp/c)

【题目描述】
有N件事,每件事有两个属性a,b,现在你要以某种顺序做完这N件事,考虑这个人目前做的事情是i,他做的前一件事是j,那么他做这件事的代价就是(a[i] | a[j]) – (a[i] & a[j]),如果前面没有做事,那么代价就是a[i],但是事情总有轻重缓急之分,按原本顺序的事i最多能推迟到做完任意件紧接着事i之后的事j,i < j <= i + b[i]后做,即原本顺序的事k,k > i + b[i] ,不能在事i之前完成。
【输入格式】
输入的第一行是一个整数N,表示要做N件事。
接下来N行,每行两个数,表示 a[i]和b[i]。
【输出格式】
输出一个整数表示最小可能的最大传递时间
【样例输入】
2
5 1
4 0
【样例输出】
5
【数据范围】
20%的数据保证:1 <= N <= 15。
100%的数据保证:1 <= N , a[i] <= 1000 , 1 <= b[i] <= 7。

思考

dp(i,j,k)考虑了前i件事,同时距离i最近的j件事的状态,k表示最近的做的事距离i的距离。
每次决策只有两种,
先取出之间没做的某件事v做了,即
dp[i][j|(1<<v)][v]=min(dp[i][j|(1<<v)][v],dp[i][j][k]+((k==19)?a[iv]:calc(a[iv],a[ik])))
将第i件事压下暂时不做,即
dp[i+1][(j(1<<7))<<1][min(k+1,19)]=min(dp[i+1][(j(1<<7))<<1][min(k+1,19)],dp[i][j][k])
显然这两种转移已经包含了所有情况。
复杂度O(n28162)

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

const int maxn = 1e3 + 25;
const int inf = 0x7f7f7f7f ;

int a[maxn] , b[maxn] , N , dp[maxn][1 << 8][20] ;
vector < int > sa;

inline void update( int & x , int v ){ x = min( x , v ); }
inline int cal ( int x , int y ){ return (x | y) - (x & y); }
inline int get ( int x , int y ){ return x >> y & 1 ;}

void Init(){
    memset( dp , 0x7f , sizeof( dp ) );
}


int main(int argc,char *argv[]){
    scanf("%d",&N);
    for(int i = 1 ; i <= N ; ++ i) scanf("%d%d" , a + i , b + i);
    Init();
    dp[0][ ( 1 << 8 ) - 1][19] = 0;
    for(int i = 0 ; i <= N ; ++ i)
        for(int j = 0 ; j < (1 << 8) ; ++ j)
            for(int k = 0 ; k < 20 ; ++ k)
                if( dp[i][j][k] != inf ){
                    int ed = min( i , 8 ) , limit = inf;
                    for( int v = ed - 1 ; v >= 0 ; -- v) if( (( j >> v) & 1) == 0 ) limit = min( limit , i - v + b[ i - v ] );
                    if( get( j , 7 ) == 1 ) update( dp[ i + 1 ][ (j^(1<<7))<<1 ][ min( k + 1 , 19 ) ] , dp[i][j][k] );
                    for( int v = ed - 1 ; v >= 0 && i - v <= limit ; -- v) if( (j >> v & 1) == 0 ) update( dp[i][j | (1 << v)][v] , dp[i][j][k] + ( (k == 19) ? a[ i - v ] : cal( a[ i - v ] , a[ i - k ] )  )   );
                }
    int ans = inf;
    for(int i = 0 ; i < 20 ; ++ i) ans = min( ans , dp[N][(1<<8)-1][i] );
    printf("%d\n" , ans );
    return 0;
}
### CDOJ 1805 矩阵 题解 #### 问题分析 题目描述了一个 \( N \times M \) 的矩阵,其中每个元素要么是 \( 1 \),要么是 \( -1 \)[^1]。对于每一行 \( A_i \) 和每一列 \( B_j \),定义它们分别为该行和该列所有元素的乘积。目标是计算满足条件的所有不同矩阵的数量,即所有的 \( A_i, B_j \) 均等于给定值 \( K \)(\( K = 1 \) 或者 \( K = -1 \))[^1]。 此问题的核心在于如何通过动态规划或其他方法高效地枚举并统计符合条件的矩阵数量。 --- #### 动态规划思路 为了求解这个问题,可以采用动态规划的思想来优化时间复杂度。以下是具体的实现逻辑: 1. **态定义** 定义 `dp[i][j]` 表示构建一个大小为 \( i \times j \) 的矩阵,并使每行和每列的乘积均为 \( K \) 所需的不同方案数[^2]。 2. **初始化** 对于单行或多列的情况,显然只有一种方式能够满足条件(全填入 \( 1 \)),因此有: \[ dp[1][j] = dp[i][1] = 1 \quad (i, j \geq 1) \] 3. **转移方程** 转移关系基于子问题分解: - 如果当前矩阵的高度大于宽度 (\( i > j \)),则可以通过减少高度的方式继承更小子问题的结果; \[ dp[i][j] = dp[i-j][j] + dp[i][j-1] \] - 若两者相等,则额外增加一种新的可能性; \[ dp[i][i] = 1 + dp[i][i-1] \] - 否则保持不变: \[ dp[i][j] = dp[i][i] \] 4. **最终结果提取** 输入整数 \( n \),表示需要解决的是 \( n \times n \) 大小的正方形矩阵情况下的计数值,故只需返回 `dp[n][n]` 即可完成任务。 --- #### 实现代码 下面是完整的程序实现,采用了上述提到的态转换规则以及边界处理机制: ```cpp #include <iostream> using namespace std; #define MAXN 121 int dp[MAXN][MAXN] = {0}; // 初始化 DP 数组 void init_dp() { int i, j; for (i = 1; i <= 120; ++i) { dp[1][i] = dp[i][1] = 1; // 初始条件设置 } for (i = 2; i <= 120; ++i) { for (j = 2; j <= 120; ++j) { if (i < j) { dp[i][j] = dp[i][i]; } else if (i == j) { dp[i][j] = 1 + dp[i][j - 1]; } else if (i > j) { dp[i][j] = dp[i - j][j] + dp[i][j - 1]; } } } } int main() { init_dp(); // 进行动态规划预处理 int n; while (cin >> n && n >= 1) { cout << dp[n][n] << endl; // 输出对应规模下矩阵数目 } return 0; } ``` --- #### 结果解释 运行以上代码后,输入任意自然数 \( n \),即可得到对应的 \( n \times n \) 尺寸矩阵中符合要求的总数目。注意这里假定了行列长度一致的情形;如果扩展到一般矩形区域,则还需进一步调整算法框架适应变化范围内的参数设定。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值