zoukankan      html  css  js  c++  java
  • CF939F

    好神奇的dp...

    首先有一个很简单的思想:设dp[i][j]表示目前到了第i分钟,朝上的面被烤了j分钟的情况下所需的最小交换次数

    那么有转移:dp[i][j]=min(dp[i-1][j],dp[i-1][i-j]+1)

    这一点很好理解,就是讨论现在向上这面上一分钟的状态:如果上一分钟这一面也朝上,那么就直接继承,如果上一分钟这一面朝下,那么就要翻一次,同时之前朝上的面的被烤的时间就是i-j

    但这个转移显然是O(n^2)的,这样做过不去这道题。

    所以我们考虑优化

    可是,这个转移在某种意义上已经达到了最优,所以不太好优化了...

    那么我们只能重构状态

    我们发现,能对肉进行翻面的时间只有这k个给定的时间段,且k<=100,那么我们考虑:能否把一个时间段整体更新,将时间复杂度降成O(nk)呢?

    这样我们必须重新设计状态:记状态dp[i][j]表示目前到了第i个时间段的结尾,朝上这一面的肉被烤的时间为j

    那么我们就要重新考虑转移:

    首先我们观察一下规律:

    可以发现:在一段可以翻转的区间内,真正翻转的次数只可能有0,1或2三种,所以我们只需按这三种情况分别讨论转移即可

    (关于上面这句话的解释:如果你翻转了三次或更多,那么你一定可以将某几次翻转合并后变成上述情况,也就是说上述情况就足够覆盖所有状态)

    翻转次数为0的情况很好判断,直接继承上一次的状态即可

    如果翻转次数为2,那么这相当于原来朝上的面现在还朝上,原来朝下的面现在还朝下,只是中途把朝上的面翻过去烤了一下而已。

    那么我们可以枚举在之前朝上的面在这一段时间内被烤的时间k,那么在这段区间之前这一面被烤的之间就是j-k

    于是可以进行转移:dp[i][j]=min(dp[i-1][j-k]+2)

    但是这样做显然时间不够优越,所以我们要采取策略来优化!

    我们推一发式子:

    large ecause kleq r-l

    large 	herefore -kgeq l-r

    large 	herefore j-kgeq j+l-r

    large p=j-k,那么:

    large pgeq j+l-r

    发现这样的形式就可以用单调队列维护了。

    从小向大枚举j,然后推进单调队列中维护即可

    至于翻转次数为1的情况,相当于现在朝上的面原先朝下,那么如果现在朝上的面被烤的时间为j,枚举现在朝下的面在这一区间内被烤的时间为k,那么r-j就是现在朝下的面被烤的时间,而我们枚举了朝下的面在这一区间内被烤的时间为k,那么在进入这一区间之前现在朝下的面原来朝上,已经被烤过的时间就是r-j-k!

    这样有转移:dp[i][j]=min(dp[i-1][r-j-k])+1!

    用类似上述的方法,同样构造单调队列转移,注意此时要倒序枚举,因为我们枚举的东西事实上起到的是j+k的作用,所以r-枚举的东西就要倒序

    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <cstdlib>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    int dp[2][1000005];
    int que[1000005];
    int n,k;
    int main()
    {
    	scanf("%d%d",&n,&k);
    	int now=1,past=0;
    	memset(dp[past],0x3f,sizeof(dp[past]));
    	dp[0][0]=0;
    	while(k--)
    	{
    		memcpy(dp[now],dp[past],sizeof(dp[now]));
    		int l,r;
    		scanf("%d%d",&l,&r);
    		int head=1,tail=0;
    		for(int i=0;i<=min(n,r);i++)
    		{
    			while(head<=tail&&dp[past][que[tail]]>=dp[past][i])
    			{
    				tail--;
    			}
    			que[++tail]=i;
    			while(head<=tail&&que[head]<i-(r-l))
    			{
    				head++;
    			}
    			dp[now][i]=min(dp[now][i],dp[past][que[head]]+2);
    		}
    		head=1,tail=0;
    		for(int i=r;i>=0;i--)
    		{
    			while(head<=tail&&dp[past][que[tail]]>=dp[past][r-i])
    			{
    				tail--;
    			}
    			while(head<=tail&&que[head]<l-i)
    			{
    				head++;
    			}
    			que[++tail]=r-i;
    			dp[now][i]=min(dp[now][i],dp[past][que[head]]+1);
    		}
    		swap(now,past);
    	}
    	if(dp[past][n]>=0x3f3f3f3f)
    	{
    		printf("Hungry
    ");
    	}else
    	{
    		printf("Full
    %d
    ",dp[past][n]);
    	}
    	return 0;
    } 
  • 相关阅读:
    golang 数据结构 优先队列(堆)
    leetcode刷题笔记5210题 球会落何处
    leetcode刷题笔记5638题 吃苹果的最大数目
    leetcode刷题笔记5637题 判断字符串的两半是否相似
    剑指 Offer 28. 对称的二叉树
    剑指 Offer 27. 二叉树的镜像
    剑指 Offer 26. 树的子结构
    剑指 Offer 25. 合并两个排序的链表
    剑指 Offer 24. 反转链表
    剑指 Offer 22. 链表中倒数第k个节点
  • 原文地址:https://www.cnblogs.com/zhangleo/p/10764152.html
Copyright © 2011-2022 走看看