【CodeForces】988F·Rain and Umbrellas

本文介绍了一个关于在下雨的数轴上行走并携带雨伞以减少行走成本的问题。利用动态规划的方法,通过分析不同状态转移的情况,求解从起点到终点所需的最小代价。

题目

传送门

题目大意

数轴上有一些区间 (li,ri) ( l i , r i ) 在下雨,在某些点 pi p i 上有伞,伞的重量为 wi w i ,一个点上可能有多把伞,你想通过一个下雨的区间就必须有伞,你可以带很多伞,你行走一个单位长度需要花费你带的所有伞的重量和的代价,问从 0 0 走到a的最小代价。

分析

dp[i][j] d p [ i ] [ j ] 表示拿着第 j j 把伞,走到点i的最小代价(你随时都只最多拿一把伞,但是可能会换伞), j=0 j = 0 表示不拿伞。数据范围比较小,所以直接用 Rain[i] R a i n [ i ] 表示区间 (i1,i) ( i − 1 , i ) 有没有下雨, Umb[i] U m b [ i ] 表示点 i i 上重量最小的伞的编号(显然你只会拿这个点上最轻的伞),Umb[i]=0表示这个点上没有伞。

分三种情况:

  • 如果 (i1,i) ( i − 1 , i ) 没有下雨( Rain[i]=0 R a i n [ i ] = 0 ),就可以在点 i1 i − 1 把伞扔掉,所以:
    dp[i][0]=min(dp[i][0],dp[i1][j])|Rain[i]=0 d p [ i ] [ 0 ] = m i n ( d p [ i ] [ 0 ] , d p [ i − 1 ] [ j ] ) | R a i n [ i ] = 0
  • 不管 (i1,i) ( i − 1 , i ) 有没有下雨,都可以继续拿伞(前提是有伞即 j>0 j > 0 ),所以:
    dp[i][j]=min(dp[i][j],dp[i1][j]+W[j])|j>0 d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i − 1 ] [ j ] + W [ j ] ) | j > 0
  • 如果点 i1 i − 1 上有伞,你就可以换成这把伞(一定是换成 Umb[i1] U m b [ i − 1 ] ),所以:
    dp[i][Umb[i1]]=min(dp[i][Umb[i1]],dp[i1][j]+W[Umb[i1]])|Umb[i1]>0 d p [ i ] [ U m b [ i − 1 ] ] = m i n ( d p [ i ] [ U m b [ i − 1 ] ] , d p [ i − 1 ] [ j ] + W [ U m b [ i − 1 ] ] ) | U m b [ i − 1 ] > 0

注意讨论的是从 i1 i − 1 走到 i i ,如果是从i走到 i+1 i + 1 ,用刷表法即可。
初始化 dp[0][0]=0 d p [ 0 ] [ 0 ] = 0 dp[i][j]= (i0 or j0) d p [ i ] [ j ] = ∞   ( i ≠ 0   o r   j ≠ 0 )

代码

#include<cstdio>
#include<algorithm>
using namespace std;

#define MAXA 2000
#define MAXM 2000
#define INF 0x3f3f3f3f
int N,M,A;
bool Rain[MAXA+5];
int dp[MAXA+5][MAXM+5];
int Umb[MAXA+4],W[MAXM+5];

int main(){
    scanf("%d%d%d",&A,&N,&M);
    for(int i=1;i<=N;i++){
        int l,r;
        scanf("%d%d",&l,&r);
        for(int j=l+1;j<=r;j++)
            Rain[j]=1;
        //Rain[i]: (i-1,i)
    }
    W[0]=INF;//方便更新Umb
    for(int i=1;i<=M;i++){
        int p;
        scanf("%d%d",&p,&W[i]);
        if(W[i]<W[Umb[p]])
            Umb[p]=i;
    }
    fill(dp[0],dp[0]+M+1,INF);//注意这里
    dp[0][0]=0;
    for(int i=1;i<=A;i++){
        fill(dp[i],dp[i]+M+1,INF);
        for(int j=0;j<=M;j++){
            //扔伞
            if(!Rain[i])
                dp[i][0]=min(dp[i][0],dp[i-1][j]);
            //继续拿伞
            if(j>0)
                dp[i][j]=min(dp[i][j],dp[i-1][j]+W[j]);
            //换伞(一定换成Umb[i-1])
            if(Umb[i-1])
                dp[i][Umb[i-1]]=min(dp[i][Umb[i-1]],dp[i-1][j]+W[Umb[i-1]]);
        }
    }
    int Ans=INF;
    for(int i=0;i<=M;i++)//统计Ans,看走到最后拿哪把伞(或者不拿)
        Ans=min(Ans,dp[A][i]);
    if(Ans==INF)
        puts("-1");
    else
        printf("%d",Ans);
}
Codeforces 2123F 问题中,目标是通过重新排列数组 $ a $ 来最小化不动点的数量。所谓“不动点”是指在重新排列后的数组中满足 $ a_i = i $ 的位置。该问题要求设计一种策略,以最优方式重新排列数组元素,使得这样的不动点数量最少。 为了解决这个问题,可以采用贪心算法和图论思想相结合的策略: - 首先,观察到如果某个值 $ i $ 出现了多次(即 $ a_i = i $),那么这些重复的值必须被移动到其他位置,以消除不动点。 - 对于那些没有出现在其索引上的值(例如 $ a_i \neq i $),可以通过交换操作将其移动到合适的位置,从而避免产生新的不动点。 一个有效的解决方案可以基于以下步骤: 1. 构建一个映射表,记录每个值出现的位置。 2. 找出所有当前值等于其索引的位置(即当前的不动点)。 3. 尝试通过交换来消除这些不动点。优先考虑将这些值移动到未被占用的位置,并确保不会引入新的不动点。 4. 在无法完全消除所有不动点的情况下,选择最优的交换策略以尽可能减少不动点的数量。 以下是 Python 中的一个示例实现,用于解决此类问题的基本思路: ```python def minimize_fixed_points(n, a): pos = {} fixed_points = [] # 记录每个值的出现位置,并找出初始的不动点 for i in range(n): if a[i] == i + 1: fixed_points.append(i) if a[i] not in pos: pos[a[i]] = [] pos[a[i]].append(i) # 如果没有重复的值,则可以直接交换以消除所有不动点 result = a[:] for i in fixed_points: found = False for val in pos: if val != i + 1 and len(pos[val]) > 0: j = pos[val].pop() result[i], result[j] = result[j], result[i] found = True break if not found: # 特殊情况处理:当只剩下一个值时 for j in range(n): if j != i and result[j] != j + 1: result[i], result[j] = result[j], result[i] break return result # 示例输入 n = int(input()) a = list(map(int, input().split())) result = minimize_fixed_points(n, a) print(' '.join(map(str, result))) ``` 此代码实现了上述逻辑,并尝试通过交换来最小化不动点的数量。对于大多数情况,它能够有效消除所有不动点;在某些特殊情况下(例如所有值都唯一且存在多个不动点),则需要特别处理。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值