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;
    } 
  • 相关阅读:
    You need to use a Theme.AppCompat theme
    Objective-C中nil与release的区别与用法
    objective-c中自己创建的对象为什么不能调用release
    NSString类的相关用法
    [Bug-IOS]
    中国有什么旅游社交网站吗?
    om.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException
    【剑指Offer学习】【面试题22:栈的压入、弹出序列】
    2-08. 用扑克牌计算24点(25) (ZJU_PAT 数学 枚举)
    在北京工作了两年,如今跳槽到了广州,社保公积金该怎样办理?
  • 原文地址:https://www.cnblogs.com/zhangleo/p/10764152.html
Copyright © 2011-2022 走看看