• CF 939F. Cutlet


    F. Cutlet


    第一次接触这样的动规设计。有收获


    首先,我们先简化一下问题:

    1. (k)((kleq100))个给定区间,给这(2n)个区间染色((0)(1)),规定相邻颜色断点必须在给定区间内,问是否有合法的方案使两种颜色格子数均为(n),以及最少的断点。

    考虑可行性:

    不妨设计DP:(dp(i,j,op)|opin{0,1})指前(i)个格颜色(op)染了(j)个各种的最少断点。

    那么,

    • 若第(i)个格子不在给定区间内部,转移到(dp(i-1,j-1,op))
    • 若在给定区间,不妨设该区间编号为(k)。转移到(dp(l_k o i-1,j',op xor 1)+1)

    这样的做法的时间复杂度为(O(n^2k))


    接下来,认真思考,可以发现在题目中,断点仅存在于给定区间中。基于这个性质,我们可以重新构造一个更精简的状态。

    不妨设:(dp(i,j,op)|opin{0,1}),代表前个给定区间染了(j)个格子的最少断点。

    那么,我们考虑一个区间的断点数量:

    1. 没有断点;
    2. 有一个断点;
    3. 有两个断点;
    4. 有多于(2)个断点;

    显然,多于(2)个断点的一定不可能成为最优解。

    那么,我们就可以讨论断点个数即可。换言之,

    • 当没有断点时,有(dp(i-1,j,op))
    • 有一个断点时,有(dp(i-1,j-k,op xor 1)+1| kleq j,0leq kleq r_i-l_i);这里我们枚举断点。
    • 有两个断点时,有(dp(i-1,j-k,op)+2| kleq j,0leq kleq r_i-l_i);这里我们枚举中部的区间长度。

    时间复杂度还是没有变化,但是我们已经有了可以用单调队列优化的雏形。


    优化


    [dp(i,j,op)=minegin{cases} & ext dp(i-1,j,op)\& ext dp(i-1,r_{i-1}-(j-k),op xor 1)+1 (kleq j,0leq kleq r_i-l_i)\& ext dp(i-1,j-k,op)+2 (kleq j,0leq kleq r_i-l_i)end{cases} ]

    该式子第(i)项仅和第(i-1)项有密切关系。


    我们换元:

    [dp(i,j,op)=minegin{cases} & ext dp(i-1,j,op)\& ext dp(i-1,k,op xor 1)+1 (kgeq 0,r_{i-1}-jleq kleq r_{i-1}-(l_i+j-r_i))\& ext dp(i-1,k,op)+2 (kgeq 0,l_i+j-r_ileq kleq j)end{cases} ]

    这种情况直接单调队列优化。


    不过,这样的转移特别的的复复杂,细节极容易搞错。我们可以依照博弈问题改变一下状态表示:设(dp(i,j))指前(i)个区间,另一种颜色还需要(j)个格子染色。接下来,就优化掉了一维,较之前细节少多了。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define RE register
    #define CLR(x, y) memset(x,y,sizeof x)
    #define FOR(i, x, y) for(RE int i=x;i<=y;++i)
    #define ROF(i, x, y) for(RE int i=x;i>=y;--i)
    #define int LL
    using namespace std;
    
    typedef long long LL;
    
    const int MAXN = 100 + 5, TIME = 2e5 + 10;
    
    template <class T> void read(T &x)
    {
    	bool mark = false;
    	char ch = getchar();
    	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true;
    	for(x = 0; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
    	if(mark) x = -x;
    }
    
    int n, k, hh, tt, l[MAXN], r[MAXN], q[TIME], dp[2][TIME];
    
    signed main()
    {
    	read(n), read(k);
    	FOR(i, 1, k) read(l[i]), read(r[i]);
    	FOR(i, 0, TIME) dp[0][i] = dp[1][i] = TIME;
    	dp[0][0] = 0;
    	FOR(i, 1, k)
    	{
    		hh = 1, tt = 1;
    		FOR(j, 0, n) dp[i & 1][j] = dp[(i - 1) & 1][j];
    		FOR(j, 0, r[i] - min(r[i], n)) 
    		{
    			while(hh < tt && dp[(i - 1) & 1][q[tt - 1]] >= dp[(i - 1) & 1][j]) -- tt;
    			q[tt ++] = j;
    		}
    		ROF(j, min(r[i], n), 1)
    		{
    			while(hh < tt && q[hh] < l[i] - j) ++ hh;
    			if(hh < tt) dp[i & 1][j] = min(dp[i & 1][j], dp[(i - 1) & 1][q[hh]] + 1); 
    			
    			if(r[i] - j < n)
    			{
    				while(hh < tt && dp[(i - 1) & 1][q[tt - 1]] >= dp[(i - 1) & 1][r[i] - j + 1]) -- tt;
    				q[tt ++] = r[i] - j + 1;
    			}
    		}
    		hh = 1, tt = 2;
    		FOR(j, 1, n)
    		{
    			while(hh < tt && q[hh] < l[i] + j - r[i]) ++ hh;
    			if(hh < tt) dp[i & 1][j] = min(dp[i & 1][j], dp[(i - 1) & 1][q[hh]] + 2);
    			if(j < n)
    			{
    				while(hh < tt && dp[(i - 1) & 1][q[tt - 1]] >= dp[(i - 1) & 1][j + 1]) -- tt;
    				q[tt ++] = j + 1;
    			}
    		}
    	}
    	if(dp[k & 1][n] == TIME) puts("Hungry"); 
    	else printf("Full
    %d
    ", dp[k & 1][n]);
    	return 0;
    }
    

    总结:

    1. 在没有认真分析问题的时候,连最基本的DP表示都没有想出来,这说明了要踏下心去认真分析;
    2. 有时代码细节过多,可能是算法不够精炼;
    3. DP中博弈简化。
  • 相关阅读:
    ztree : 增删改功能demo与自定义DOM功能demo的结合
    CF786B Legacy 线段树优化建图
    UVA11992 Fast Matrix Operations 一次开多棵线段树
    P3950 部落冲突 树链剖分
    洛谷P1471 方差 线段树维护区间方差
    2019.7.26 T1 树剖+双标记
    P1505 [国家集训队]旅游
    NOIP2015 运输计划 树上差分+树剖
    P1373 小a和uim之大逃离 四维dp,维护差值
    Pyhton之subprocess模块和configparser模块
  • 原文地址:https://www.cnblogs.com/zach20040914/p/14556075.html
Copyright © 2020-2023  润新知