洛谷P3419 [POI 2005] SAM-Toy Cars

原题传送门

分析

首先,我们注意到,如果在之前都没有用到某一辆小车,则此时它一定还在书架上。

如果现在只用了 \(\le k\) 辆小车,那么我们无需讨论,此时答案即为使用过的小车总数。

如果我们需要加入一辆新的小车,而地板又满了的话,我们就需要从地板上的 \(k\) 个小车中选择一个拿走。这是我们唯一需要决策的地方。

现在我们只需要看拿走哪辆小车可以使得答案最优即可。不难发现在此之前的序列与我们的决策完全无关,所以让我们定义 \(nxt_i\)\(i\) 号小车下次被玩的时间点(下述讨论过程中不再被玩设为 \(p+1\) ,但是技术上来说采取 \(0\) 方便一些,阅读完毕即可明白),并以此衡量某辆小车的重要性。

大家应该都有这样的感觉,好像选 \(nxt_i\) 最大的会更优一点,下面让我们证明一下。

假设我们现在已经将最优解范围缩小到了 \(i,j\) 两辆小车中,且 \(nxt_i<nxt_j\)

若我们选择保留 \(j\) ,则我们需要在 \(nxt_i\) 时刻将 \(i\) 再次加回,此时需要踢掉一个,如果我们在此时选择把 \(j\) 踢掉,那它在这段时间内什么都没有干,只是占位,显然我们在第一次决策时就将 \(j\) 踢掉会更优;如果我们并未选择踢掉 \(j\) ,那就说明我们有能力使得这一段时间内地板上一直都有 \(j\) ,同理我们也可以用同样的操作来让这段时间内地板上一直都有 \(i\),从而将这一次的加入推迟\(nxt_j\) 时。这显然是不劣的。

上述结论是容易推广到 \(k\) 辆小车的。我们每次取出当前可能是最优的两个选择并去掉一个,最终剩下的一定是 \(nxt\) 最大的那个选择。

所以,依照上述思路,我们可以写出代码并通过本题。

#include<cstdio>
#include<set>
#include<utility>
using namespace std;
const int mn=1e5+5,mp=5e5+5;
int n,k,p;
int a[mp],nxt[mp];
int lst[mn];
int ans;
set<int>s;
// 我们这里的set只存了当前所有小车的nxt,可以直接通过a[nxt]来找到它对应的颜色。要是他后面没人那就不用呆在这里面了,可以玩完秒踢
int main()
{
    scanf("%d %d %d",&n,&k,&p);
    for(int i=1;i<=p;i++)scanf("%d",&a[i]);
    // 预处理出nxt,如果后面没有该小车则nxt=0
    for(int i=p;i>=1;i--)
    {
        nxt[i]=lst[a[i]];
        lst[a[i]]=i;
    }
    for(int i=1;i<=p;i++)
    {
        if(*s.begin()==i)// 地板上有这个小车了(当前集合里最小的nxt肯定是i)
        {
            s.erase(i);
            if(nxt[i])s.insert(nxt[i]);// 不再玩的可以秒踢,因为拿走车是没有代价的
            continue;
        }
        if(s.size()==k)s.erase(*(--s.end()));// 哥们让个位置谢谢
        ans++;
        if(nxt[i])s.insert(nxt[i]);
    }
    printf("%d\n",ans);
}
posted @ 2025-06-08 16:30  ikusiad  阅读(42)  评论(0)    收藏  举报