题解:P17199 [KOI 2026 #2] 工厂
lailai0916 · · 题解
题意简述
每名员工在所属日期的白天,以及相邻的两个夜间工作。 每个时段都按熟练度排序,将相邻两人配成一组。 需要选择若干员工,使所有时段人数为偶数, 并最大化生产利润减去基本工资与夜间补贴。
解题思路
把应聘者按熟练度从小到大排序并依次扫描。
对每个日期
扫描开始前有
先把目标改成最小化:
考虑所属日期为
若发生
下面把夜间补贴拆到相邻熟练度之间的间隙。
定义边界状态
设当前与下一名应聘者的熟练度差为
这是因为一组员工的熟练度差, 恰好等于两个端点之间所有相邻熟练度差之和。 逐个间隙统计,便会准确还原每一组的补贴。
如果为每个前缀与每天都建立变量,点数会达到
维护 now[d],表示日期 now[D_i],新节点为 now[D_i]=i。
每个熟练度间隙出现时,
使用当前各天节点加入全部相邻日期的绝对值代价。
所有代价都是二进制变量的一次项或绝对值项,可以转成最小割。
代码规定源点侧表示变量值
对于
对于一次项
- 若
k\ge0 ,从节点向汇点连容量为k 的边; - 若
k<0 ,利用kx=k+(-k)(1-x) ,把k 加入常数,并从源点向节点连容量为-k 的边。
员工代价中的
求出最大流后,从源点沿正剩余容量搜索即可得到最小割两侧。 一名员工的旧、新状态节点分处两侧, 当且仅当该天奇偶性发生翻转,也就是这名员工被雇用。
任意合法雇用方案都会唯一确定扫描过程中的全部状态。 员工项准确给出其基本工资与白天利润, 间隙项准确给出全部夜间补贴, 所以对应割的代价等于该方案的总工资减去生产利润。
反过来,无穷容量边保证每天最终状态为
建立所有间隙项需要
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=505;
const ll inf=0x3f3f3f3f3f3f3f3f;
struct person
{
int a,b,c,d,id;
}p[N];
struct edge
{
int v,r;
ll w;
};
struct flow
{
vector<edge> g[N];
int d[N],cur[N];
void add(int u,int v,ll w,ll r=0)
{
int x=g[u].size();
int y=g[v].size();
g[u].push_back({v,y,w});
g[v].push_back({u,x,r});
}
bool bfs(int s,int t)
{
memset(d,-1,sizeof d);
queue<int> q;
d[s]=0;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
for(const edge &e:g[u])
if(e.w&&d[e.v]==-1)
{
d[e.v]=d[u]+1;
q.push(e.v);
}
}
return d[t]!=-1;
}
ll dfs(int u,int t,ll lim)
{
if(u==t)return lim;
ll res=0;
int siz=g[u].size();
for(int &i=cur[u];i<siz&&lim;i++)
{
edge &e=g[u][i];
if(!e.w||d[e.v]!=d[u]+1)continue;
ll w=dfs(e.v,t,min(lim,e.w));
if(!w)continue;
e.w-=w;
g[e.v][e.r].w+=w;
lim-=w;
res+=w;
}
return res;
}
ll dinic(int s,int t)
{
ll res=0;
while(bfs(s,t))
{
memset(cur,0,sizeof cur);
ll w;
while((w=dfs(s,t,inf)))res+=w;
}
return res;
}
void find(int s,bool vis[])
{
memset(vis,0,sizeof(bool)*N);
queue<int> q;
vis[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
for(const edge &e:g[u])
if(e.w&&!vis[e.v])
{
vis[e.v]=1;
q.push(e.v);
}
}
}
}f;
int now[N],pre[N];
ll coef[N],cost[N][N];
bool vis[N];
void add(int x,int y,ll w)
{
if(x==y||w==0)return;
if(x>y)swap(x,y);
cost[x][y]+=w;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>p[i].a>>p[i].b>>p[i].c>>p[i].d;
p[i].id=i+1;
}
sort(p,p+n,[](const person &x,const person &y)
{
return x.a<y.a;
});
int s=n;
int t=n+1;
fill(now,now+m+1,t);
for(int i=0;i<n;i++)
{
int x=now[p[i].d];
pre[i]=x;
add(x,i,p[i].c);
if(x!=t)coef[x]-=p[i].b;
coef[i]+=p[i].b;
now[p[i].d]=i;
if(i==n-1)continue;
ll w=p[i+1].a-p[i].a;
add(t,now[1],w);
for(int j=1;j<m;j++)add(now[j],now[j+1],w);
add(t,now[m],w);
}
for(int i=1;i<=m;i++)add(t,now[i],inf);
for(int i=0;i<=t;i++)
for(int j=i+1;j<=t;j++)
if(cost[i][j])f.add(i,j,cost[i][j],cost[i][j]);
ll val=0;
for(int i=0;i<n;i++)
if(coef[i]>=0)f.add(i,t,coef[i]);
else
{
val+=coef[i];
f.add(s,i,-coef[i]);
}
val+=f.dinic(s,t);
f.find(s,vis);
vector<int> ans;
for(int i=0;i<n;i++)
if(vis[i]!=vis[pre[i]])ans.push_back(p[i].id);
int siz=ans.size();
cout<<-val<<'\n';
cout<<siz<<'\n';
for(int i=0;i<siz;i++)
{
if(i)cout<<' ';
cout<<ans[i];
}
cout<<'\n';
return 0;
}