题解 P6879 【[JOI 2020 Final] スタンプラリー 3】

· · 题解

个人博客链接

题目

题目(ja)

题目大概意思就是,沿着湖边有 n 个邮票展台,JOI君可以顺时针或者逆时针移动,每移动一个单位长度需要 1 秒,邮票展台在 T_i 时间后会被移除,问JOI君最多可以收集多少个邮票。

为什么要把仅有的日语水平用到奇怪的地方啊

(P.S. 有官方的英语翻译版为什么题都做完了才发现

题目(en)

(P.S. 洛谷上有这道题的翻译版,但是把邮票展台翻译成了黑白熊(?这是什么)

题目(zh)

思路

看到 N \leq 200 ,上手当然是动态规划。

而且可以开到三维,甚至玄学优化后的四维。

这样一来,先是想用dp数组表示能够收集到的邮票个数,但是这样一来时间就没法放进状态里(因为 T \leq 10^9 )。于是果断看官方题解

事实上,在看到题解里关于Subtask2的解法的时候,我就瞬间悟了。

(图片版权信息:©保坂結)

(注:图上文字意思依次为:

但是事实上之前我已经想到了可以用区间dp,这样就有了:

dp_0[l][r][k]

dp_1[l][r][k] 设周长为 $m$ , $a_i$ 表示第 $i$ 个邮票展台的坐标, $t_i$ 表示第 $i$ 个邮票展台的移除时间。由于题目里的坐标是顺时针给出,对于原点顺时针方向的点,可以直接用更靠顺时针方向的点坐标与其相减得到距离,而对于逆时针方向的点,需要用总数减去个数来获取其下标,如果两个点刚好跨过原点,还需要用周长减去两者的距离才能得到移动的距离。这些关系在纸上画图推导一下就可以推出,此处不继续做详细说明。 不难发现,有如下转移: 当 $dp_0 \neq \text{INF}$ 时,对于 $dp_0$ ,设 $$tNow = dp_0[l][r][i]$$ $$t_0 = tNow+a_{n-l+1}-a_{n-l}$$ $$t_1 = tNow+m-(a_{n-l+1}-a_{r+1})$$ 则: $$ dp_0[l+1][r][i+(t_0 \leq t_{n-l})] = \min\left\{\begin{aligned} &dp_0[l+1][r][i+(t_0 \leq t_{n-l})] \\ &t_0 \end{aligned}\right. $$ $$ dp_1[l][r+1][i+(t_1 \leq t_{r+1})] = \min\left\{\begin{aligned} &dp_1[l][r+1][i+(t_1 \leq t_{r+1})] \\ &t_1 \end{aligned}\right. $$ 当 $dp_1 \neq \text{INF}$ 时,对于 $dp_1$ ,则有: $$tNow = dp_1[l][r][i]$$ $$t_0 = tNow+m-(a_{n-l}-a_{r})$$ $$t_1 = tNow+a_{r+1}-a_{r}$$ 转移方程与 $dp_0$ 同理。 初始化的时候把所有 $dp$ 置为无穷大,并把 $a_{n+1}$ 置为 $m$ (为了求得从原点到逆时针方向第一个邮票展台的距离),最后统计答案的时候求出: $$ ans = \max_i $$ 其中: 存在 $l,r$ 使得 $dp_0[l][r][i]\neq \text{INF}$ 或 $dp_1[l][r][i]\neq \text{INF}$ 。 上代码: ```C++ #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> using std::min; using std::max; inline int& min_to(int& a,int b){ return a=min(a,b); } const int MAXN = 205; int t[MAXN]; int a[MAXN]; int dp[MAXN][MAXN][MAXN][2]; signed main(){ int n,m;scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",a+i); for(int i=1;i<=n;i++)scanf("%d",t+i); memset(dp,0x3f,sizeof(dp)); dp[0][0][0][0]=dp[0][0][0][1]=0; a[n+1]=m; for(int c=0;c<n;c++){ for(int l=0;l<=n;l++){ int r=c-l; for(int i=0;i<=n;i++){ if(dp[l][r][i][0]!=0x3f3f3f3f){ int tNow = dp[l][r][i][0]; int time0 = tNow+a[n-l+1]-a[n-l]; int time1 = tNow+m-(a[n-l+1]-a[r+1]); bool canGet0 = time0 <= t[n-l]; bool canGet1 = time1 <= t[r+1]; min_to(dp[l+1][r][i+canGet0][0],time0); min_to(dp[l][r+1][i+canGet1][1],time1); } if(dp[l][r][i][1]!=0x3f3f3f3f){ int tNow = dp[l][r][i][1]; int time1 = tNow+a[r+1]-a[r]; int time0 = tNow+m-(a[n-l]-a[r]); bool canGet0 = time0 <= t[n-l]; bool canGet1 = time1 <= t[r+1]; min_to(dp[l+1][r][i+canGet0][0],time0); min_to(dp[l][r+1][i+canGet1][1],time1); } } } } int ans = 0; for(int l=0;l<=n;l++)for(int r=0;r<=n;r++)for(int i=0;i<=n;i++){ if(dp[l][r][i][0]!=0x3f3f3f3f || dp[l][r][i][1]!=0x3f3f3f3f) ans=max(ans,i); } printf("%d\n",ans); return 0; } ``` ~~(P.S. 刚把程序写出来的时候区间左端点的变量名`l`和周长`l`重名了,调了半节课才发现,遂把周长改成了`m`~~ 这道题给了我们一个新的思路,就是如果某个状态不好表示,可以把状态变成值,所求变为下标,最后扫描一遍$dp$统计答案。