DP习题(2)

1 篇文章 0 订阅
本文介绍了三道算法竞赛题目,涉及动态规划在分组背包问题、最优化选择及路径规划中的应用。通过分析题意,提出解决思路,如将问题转化为分组背包问题以求最大LCM,按时间顺序优化菜品端出策略,以及利用DP解决网格路径规划问题,以获取最高分数。
摘要由CSDN通过智能技术生成

2020CCPC威海 L Clock Master

题意:给定一个正整数 b b b ,找到一些正整数 ( t 1 , t 2 , … , t n ) (t_1,t_2,\dots,t_n) (t1,t2,,tn) ,使得 t 1 + t 2 + ⋯ + t n ≤ b t_1+t_2+\dots+t_n\le b t1+t2++tnb ,并且使得 l c m ( t 1 , t 2 , … , t n ) lcm(t_1,t_2,\dots,t_n) lcm(t1,t2,,tn) 最大,求这个最大值,答案会很大,所以对这个最大的 lcm 对 e 取对数

思路

  • 首先可以想到: ( t 1 , t 2 , … , t n ) (t_1,t_2,\dots,t_n) (t1,t2,,tn) 每一个 t i t_i ti 单独取一个素数的幂次,这样的贡献是最大的
  • 然后就发现有那么多的素数的幂,从中取一个子集,使答案最大。
  • 但是决策太多了,想到其实可以把这些数看成是物品,那么就变成一个分组背包问题了
  • 分组的原因是:同一个素数的幂,出现两次贡献会重复
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=1e5+5;

int t,b;

const int N=31000;
int prime[N+10],visit[N+10],pcnt;
void init()
{
    for(int i=2; i<=N; ++i)
    {
        if(!visit[i]) prime[++pcnt]=i;
        for(int j=1; j<=pcnt&&i*prime[j]<=N; ++j)
        {
            visit[i*prime[j]]=1;
            if(i%prime[j]==0) break;
        }
    }
}

struct Node
{
    int p,cnt;
    double val;
} pp[3500];

int C[30010][30];
double dp[30010];
double ans[30010];

int main()
{
    init();
    int b=30000;
    int i=1,n=0;
    while(prime[i]<=b)
    {
        int p=prime[i];
        int tt=1;
        double tmp=log(p);
        while(p<=b)
        {
            C[prime[i]][tt]=p;
            p*=prime[i];
            tt++;
        }
        pp[++n]= {prime[i],tt-1,tmp};
        i++;
    }
    for(int i=1; i<=n; ++i)
    {
        for(int j=b; j>=1; --j)
        {
            if(prime[i]>j) break;
            int p=pp[i].p,cnt=pp[i].cnt;
            double val=pp[i].val;
            for(int k=0; j>=C[p][k]&&k<=cnt; ++k)
                dp[j]=max(dp[j],dp[j-C[p][k]]+val*k);
        }
    }
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&b);
        printf("%.8lf\n",dp[b]);
    }
    return 0;
}

CF1437C. Chef Monocarp

链接:https://codeforces.com/contest/1437/problem/C

题意:有 n 道菜,每道菜最佳的端出时间是 t i t_i ti 。每分钟可以端出一道菜,假设当前的时间为 T 。那么这道菜的不愉快值为 ∣ T − t i ∣ |T-t_i| Tti ,问在什么时候端出不愉快值的总和最小。输出这个最小值。 ( 1 ≤ n ≤ 200 , 1 ≤ t i ≤ n ) (1\le n \le 200 ,1\le t_i \le n) (1n200,1tin)

思路:又是一个决策很多的题,不知道什么时候端出哪道菜比较合适。

  • 这里求的绝对值,是可以将每道菜按 t i t_i ti 从小到大排序,在最靠近的时间选择对应的菜是比较优的

  • 设 dp[i][j] 表示前 i 分钟选择了 j 道菜的最小不愉快值。
    d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − 1 ] + a b s ( i − t j ) dp[i][j]=min(dp[i-1][j],dp[i-1][j-1]+abs(i-t_j) dp[i][j]=min(dp[i1][j],dp[i1][j1]+abs(itj)

  • 还可以跑费用流,这么多的决策,直接让它随便跑。1 到 n 道菜一边向源点连边流量为 1 费用为 0 。然后向 1 到 400 每个都连一条边 ,流量 1 ,费用为 a b s ( i − t j ) abs(i-t_j) abs(itj)。 最后 时间 1 到 400 向汇点连边

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;

int q,n;
int t[maxn],dp[410][210];

int main()
{
    scanf("%d",&q);
    while(q--)
    {
        scanf("%d",&n);
        for(int i=1; i<=n; ++i) scanf("%d",&t[i]);
        sort(t+1,t+1+n);
        memset(dp,1,sizeof(dp));
        dp[0][0]=0;
        for(int i=1; i<=2*n; ++i)
        {
            for(int j=0; j<=n; ++j)
            {
                dp[i][j]=dp[i-1][j];
                dp[i][j]=min(dp[i][j],dp[i-1][j-1]+abs(i-t[j]));
            }
        }
        int ans=1e9;
        for(int i=1; i<=2*n; ++i) ans=min(ans,dp[i][n]);
        printf("%d\n",ans);
    }
    return 0;
}

C. The Hard Work of Paparazzi

链接:https://codeforces.com/contest/1427/problem/C

题意:给你一个 r × r r\times r r×r 的网格,起点在 (1, 1),每次只能向相邻的格子移动,移动速度为 1 。给定 n 个时间点和坐标,如果你在确定的时间点待在确定的坐标,那么就获得 1 分。问最多获得几分? ( 1 ≤ r ≤ 500 , 1 ≤ n ≤ 1 0 5 , 1 ≤ t i ≤ 1 0 6 ) (1\le r \le 500 ,1\le n \le 10^5,1\le t_i \le 10^6) (1r500,1n105,1ti106)

思路:又是很混乱,乱七八糟不知道要怎么一步一步更新

  • 其实,这样就很明显是要 dp 了 。假设一个坐标的时间为 t i t_i ti ,那么所有时间小于 t i t_i ti 的都可以向 t i t_i ti 更新。
  • 然后这里还限制了棋盘的大小为 500 ,那么也就是说,假设用 t j t_j tj 来更新 t i t_i ti t i − t j ≥ 2 × r − 2 t_i-t_j \ge 2\times r-2 titj2×r2 时,说明了可以走遍整个棋盘了,那么可以直接拿前面得到的最大值,来更新当前的 t i t_i ti ,这样就降低了复杂度。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;

int n,r;
int t[maxn],x[maxn],y[maxn];
int dp[maxn],pref[maxn];

int main()
{
    scanf("%d%d",&r,&n);
    for(int i=1; i<=n; ++i) scanf("%d%d%d",&t[i],&x[i],&y[i]);
    t[0]=0,x[0]=1,y[0]=1;
    dp[0]=0;
    for(int i=1; i<=n; ++i)
    {
        dp[i]=-n;
        for(int j=i-1; j>=0; --j)
        {
            if(t[i]-t[j]>=2*r-2)
            {
                dp[i]=max(dp[i],pref[j]+1);
                break;
            }
            if(t[i]-t[j]>=abs(x[i]-x[j])+abs(y[i]-y[j]))
                dp[i]=max(dp[i],dp[j]+1);
        }
        pref[i]=max(pref[i-1],dp[i]);
    }
    printf("%d\n",pref[n]);
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值