题解:P16692 染色 Plus
lailai0916 · · 题解
题意简述
每种颜色只能用于一个给定区间,涂一格有固定费用。需要给每个位置选择一种可用颜色,并保证任意连续
解题思路
先证明每个位置只需保留费用最低的三种可用颜色。
取任意合法方案。若位置
把位置
接下来求出每个位置的三种最低费用颜色。把所有颜色按费用递增排序,再依次扫描它的可用区间。每个位置只需被访问三次;取得三种颜色后,后续颜色都不再有用。
用并查集维护每个位置右侧第一个尚未填满的位置。设 fa[i] 表示从
下面进行动态规划。令
设结尾的同色段从
其中决策范围为:
把只与
对每种颜色分别维护一个单调队列。队列元素记录决策点
查询位置
只在颜色
每个位置至多有三种保留颜色。生成
为了避免覆盖仍要使用的上一层状态,代码先为当前位置的所有颜色加入决策点。随后,再统一计算当前位置的状态。
当
排序的时间复杂度为
正确性证明
最低费用的三种颜色足以保留,这一点已由逐点替换证明。替换后的颜色与左右两色都不同,所以不会扩大任何已有同色段;替换又不增加费用。因此,至少存在一个最优方案只使用每个位置保留的颜色。
再证明动态规划转移。任意以颜色
最后证明单调队列。队列只保存当前长度限制内的决策点,并按
因此,算法完整覆盖一个最优的前三颜色方案,并准确求出每个状态,最终答案正确。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=200005;
const ll inf=0x3f3f3f3f3f3f3f3f;
struct Color
{
int l,r,c;
}a[N];
struct Queue
{
vector<pair<int,ll>> q;
int head=0;
void add(int x,ll y)
{
if(head==q.size())
{
q.clear();
head=0;
}
while(q.size()>head&&q.back().second>=y)q.pop_back();
q.push_back({x,y});
}
ll ask(int x,int k)
{
while(head<q.size()&&q[head].first<x-k+1)head++;
return head==q.size()?inf:q[head].second;
}
}que[N];
int fa[N];
ll f[N];
vector<int> use[N];
int find(int u){return u==fa[u]?u:fa[u]=find(fa[u]);}
bool cmp(Color x,Color y){return x.c<y.c;}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=m;i++)cin>>a[i].l>>a[i].r>>a[i].c;
sort(a+1,a+m+1,cmp);
for(int i=1;i<=n+1;i++)fa[i]=i;
for(int i=1;i<=m;i++)
{
for(int j=find(a[i].l);j<=a[i].r;j=find(j+1))
{
use[j].push_back(i);
if(use[j].size()==3)fa[j]=find(j+1);
}
}
for(int i=1;i<=n;i++)
{
for(auto x:use[i])
{
ll val=i==1?0:inf;
if(i>1)for(auto y:use[i-1])if(x!=y)val=min(val,f[y]);
if(val<inf)que[x].add(i-1,val-1LL*(i-1)*a[x].c);
}
for(auto x:use[i])f[x]=que[x].ask(i,k)+1LL*i*a[x].c;
}
ll ans=inf;
for(auto x:use[n])ans=min(ans,f[x]);
cout<<(ans>inf/2?-1:ans)<<'\n';
return 0;
}