zoukankan      html  css  js  c++  java
  • CF1106E Lunar New Year and Red Envelopes DP

    原题链接:Lunar New Year and Red Envelopes

    题目大意

    总时长为(n),一共有(k)个红包,第(i)个红包会在([s_i,t_i])时间段中出现,每个红包有一个价值(w_i)以及一个冷却值(d_i)表示如果选取了这个红包,则直到(d_i)时刻都不能再取红包(包含(d_i)).在任意一个时刻(i)时,如果不在冷却时间内,则这个人会选取所有红包中价值最高的红包,如果有多个则选取(d)最大的红包,如果还有重复的就任选一个.

    另外一个人可以执行(m)次干扰,使得他不能在某个时刻领取任何红包,问在执行最优策略下,对方能拿到最少多少钱的红包.

    数据范围:

    (1 leq n,k leq 10^5)

    (1 leq m leq 200)

    (1 leq s_ileq t_i leq d_i leq n)

    思路

    还是比较套路的思考方式,首先考虑没有干扰的前提下,这个人会怎么选取红包.由于每个时刻的选法都是固定的,可以确定一个条件:这个人在每个时刻选取的红包的策略是固定的,这个事情可以预处理出来,记(s_i={w_i,d_i})表示在(i)时刻选取的红包的价值是(w_i),冷却时间是(d_i).暂时不考虑如果本时刻没有红包怎么设置.

    假设当前已经把预处理的(s_i)做出来了,那么进一步的考虑怎么把干扰条件加进去,可以发现干扰策略只关注当前的时刻以及当前执行了多少次干扰,可以直接dp:

    • 状态:(f[i][j])表示当前时刻是(i)且已经执行了(j)次干扰时,对方能拿到的所有价值集合中的最小价值是多少
    • 入口:(f[1][0]=0)其他全为(+infin).
    • 转移:考虑当前(i)时刻是由哪个时刻转移而来的比较困难,考虑从出边转移:如果当前时刻执行了干扰,那么可以直接走到下一个时刻而不产生任何价值的贡献,也即(f[i + 1][j + 1] = f[i][j]),反之如果在此时刻没有执行干扰,则对方一定会在此刻选择一个红包,也就是(f[s[i].d + 1][j]=f[i][j] +s[i].w).由于冷却时间是包含(d_i)的所以要转移到(d_i+1).
    • 出口:由于是出边转移,答案最终不会保存在(f[n][j])上而是(f[n+1][j])上,取(res=min_{jin[0,m]}{f[n+1][j]})即可.

    由于某个时刻可能没有红包,所以还有一种额外的情形就是当前虽然没有执行干扰但是价值也没有贡献,这种情况可以直接把(s[i])设置成(d=i,w=0)代入表达式意义就是转移到下一个时刻但是没有价值.

    考虑预处理部分,由于需要对({w,d})这样的双关键字排序,并且需要动态的指定插入和删除,所以丢map维护,维护当前({w,d})的二元组的数量,如果数量达到(0)就删除,查询时查询两者皆最大者.那么求出所有红包覆盖的时间段这个操作可以用一个事件来维护:({t,w,d,type})表示(t)时刻有一个价值是(w)冷却时间是(d),类型是(type)的时间,如果是增加的事件就往map里面插入,反之就删除.如果当前map里面没有元素就表示这个时刻没有红包可以取反之取最大即可.

    那么最后预处理给所有事件按事件排个序维护一下即可.

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define forn(i,x,n) for(int i = x;i <= n;++i)	
    typedef pair<int,int> pii;
    #define x first
    #define y second
    
    const int N = 1e5+7,M = 205;
    struct Event
    {
    	int t,d,w;
    	int type;
    	bool operator<(const Event& _o)	const
    	{
    		if(t != _o.t)	return t < _o.t;
    		if(w != _o.w)	return w > _o.w;
    		return d > _o.d;
    	}
    };
    pii s[N];
    ll f[N][M];
    int main()
    {
    	int n,m,k;scanf("%d%d%d",&n,&m,&k);
        vector<Event> E;
        forn(i,1,k)
        {
        	int l,r,d,w;scanf("%d%d%d%d",&l,&r,&d,&w);
        	E.push_back({l,d,w,1});
        	E.push_back({r + 1,d,w,-1});
        }
        sort(E.begin(),E.end());
        
        memset(s,-1,sizeof s);
        map<pii,int> st;
        
        int j = 0;
        forn(i,1,n)
        {
        	while(j < E.size() && E[j].t <= i)
        	{
        		auto e = E[j];
        		if(e.type == 1)	st[{e.w,e.d}] ++;
        		else
        		{
        			if(--st[{e.w,e.d}] == 0)	st.erase({e.w,e.d});
        		}
        		++j;
        	}
        	
        	if(st.empty())	s[i] = {0,i};
        	else s[i] = {(*st.rbegin()).x.x,(*st.rbegin()).x.y};
        }
        
        memset(f,0x3f,sizeof f);
        f[1][0] = 0;
        forn(j,0,m)	forn(i,1,n)
        {
        	f[i + 1][j + 1] = min(f[i + 1][j + 1],f[i][j]);
        	f[s[i].y + 1][j] = min(f[s[i].y + 1][j],f[i][j] + s[i].x);	
        }
    	
    	ll res = 1e18;
    	forn(i,0,m)	res = min(res,f[n + 1][i]);
    	printf("%lld",res);
        
        return 0;
    } 
    
  • 相关阅读:
    UVA
    UVALive
    找一
    买书最低价格
    NABCD模型分析
    二维数组--首尾
    结对开发---环
    结对开发---二维数组
    电梯设计需求调研报告
    数组求和(2)
  • 原文地址:https://www.cnblogs.com/HotPants/p/14306525.html
Copyright © 2011-2022 走看看