Jewel Pairs
Jewel Pairs
我想写一篇人能看懂的题解
\(n\) 个宝石,有权值 \(w\) 和颜色 \(c\),如果两颗宝石 \((i,j)\) 满足 \(c_i \ne c_j \land w_i+w_j \le L\),那么可以匹配,每个宝石只能匹配一次,求匹配宝石的最大权值和。
贪心。
一、
简单观察发现是一个一般图最大权匹配。但是复杂度很劣,我也不会。
考虑既然是匹配问题能否转化为二分图。
于是将宝石分为 \(w \le \frac{L}{2}\) 和 \(w \gt \frac{L}{2}\) 两类,我们把前一种称为“小宝石”,后一种称为“大宝石”。
好的性质就是大宝石只能和小宝石匹配,于是仅对大宝石来说,我们可以直接做二分图匹配了。
除去和大宝石匹配的小宝石,小宝石内部还可以连,但假如我们知道哪些小宝石在最终答案里和大宝石匹配,那么就可以对剩下小宝石数量最多的颜色是否大于剩余总数量的二分之一分类讨论。
具体而言,令 \(f_c\) 为颜色 \(c\) 和大宝石匹配完剩下的小宝石数量,剩下总数量为 \(R\)。
-
\(\exists\ c,\ f_c \gt \frac{R}{2}\):那么无论如何匹配 \(c\) 颜色一定匹配不完,所以删掉其中最小的。
-
\(\forall\ c,\ f_c \le \frac{R}{2}\):总有方案使其全都能匹配,注意 \(R\) 为奇数时要删掉一个最小的。
赛时没有考虑加粗那句话,跑完网络流直接贪心选小宝石,莫名奇妙过了部分分。但显然不对,这没有考虑小宝石的匹配方案。
二、
上面只是一个很粗糙的雏形,还有很多问题没有解决:
-
如何确定哪些大宝石在最终答案里?
-
如何知道哪些小宝石在最终答案里和大宝石匹配?
第一个问题是 \(water\) 的,因为根据贪心策略,从大到小匹配大宝石肯定是能匹配就匹配(每次都匹配剩余宝石最多的颜色),所以直接贪心就可以确定。
但是这样好像就无法确定选哪些小宝石了?
于是下面都是在解决这个问题。
将大宝石的权值定义为 \(L-w_i\),然后和小宝石的 \(w_i\) 一起排序,那么每个大宝石可以匹配的就是前缀中所有颜色不冲突的小宝石。
不妨先令 \(mx_c\) 表示颜色 \(c\) 最多有多少个小宝石和大宝石匹配,\(f_c\) 表示最少剩下多少个(就是\(tot_{c,small}\) - \(mx_c\))。
\(mx_c\) 也是贪心可以求的,让所有颜色不是 \(c\) 的且确定能匹配的大宝石都选 \(c\) 就行了。然后就知道 \(f_c\) 了。
知道 \(f_c\) 有啥用?
-
如果存在一个颜色,最小的剩余个数都大于 \(\frac{R}{2}\),那么这个颜色一定匹配不完。
-
否则可以证明一定存在一种方案使所有颜色的宝石剩余个数都不大于 \(\frac{R}{2}\),也就是都能匹配完(特判奇数)(这一定比上面那种优的)。
考虑先任意构造一种匹配,假如存在 剩余量大于二分之一的颜色(颜色数为二时不存在这种情况),那么可以把多的剩余量交换给其他颜色(选能换且剩余量最小的颜色换),由于颜色数大于二,所以一定可以。
如果最终不存在方案使每一种颜色剩余量都不大于二分之一,说明最终存在一种颜色剩余量大于二分之一,且至少存在另一种等于二分之一,这显然是不可能的。
接下来就是删去不选的宝石,如果是第一种情况,那么只能在一种颜色里删,否则任意删。
选权值大的小宝石保留,贪心选,从大到小选小宝石使“匹配数量”最多,而最多就是能匹配的大宝石的数量,已经确保能取到了。
所以将小宝石大宝石反过来跑一遍上面的匹配就好了。
具体细节看代码,有注释。
code
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define fi first
#define se second
const int N = 1e5+5;
int T,n,cntd[N],mx[N],lst[N];
LL L;
struct J
{
LL w; int c;
inline J (LL _w=0,int _c=0):w(_w),c(_c) {}
};
vector<J> a,b;
int main()
{
freopen("hokaku.in","r",stdin);
freopen("hokaku.out","w",stdout);
scanf("%d",&T);
while(T--)
{
scanf("%d%lld",&n,&L);
a.clear(),b.clear();
for(int i=1;i<=n;i++)
{
int x,y; scanf("%d%d",&y,&x);
if(x>(L>>1)) a.emplace_back(L-x,y+n);//大点
else a.emplace_back(x,y);//小点
}
auto cmp = [&](const J &x,const J &y) {return x.w==y.w?x.c<y.c:x.w<y.w;};
sort(a.begin(),a.end(),cmp);
auto sol = [&](vector<J> &v,int &id,int &cnt)
/*
有两个作用,一个是贪心选点,尽量多,另一个是求出最大剩余量。
两者没有直接关系,不要弄混,单独求前一个是 water 的,后一个直接贪也是 water 的,看起来抽象只是因为两坨 water 交叉在一起了。
*/
{
for(int i=1;i<=n;i++) mx[i]=cntd[i]=lst[i]=0;
int up=0,dn=0;
/*
dn:当前总小点数
cntd:c 颜色小点数
up:目前已经匹配的大点数
lst:上一个颜色 c 出现及以前的位置中已经匹配的大点数
mx:c 颜色的小点最多被匹配了多少个
*/
vector<J> mch,rst;
for(auto p:v)
{
int c=p.c;
if(c>n)
{
c-=n; mx[c]=min(cntd[c],mx[c]+up-lst[c]); lst[c]=up;//从上一个 c 到现在有 up-lst[c] 个不是 c 颜色的大点被匹配。让它们全匹配 c 颜色。
//下面判断当前这个大点能否匹配,贪心,这里和上面一行没什么关系。
if(up-mx[c]<dn-cntd[c]) //match
//前面是 (最少的)匹配的小点颜色不是 c 的大点个数,后面是一共有的 颜色不是 c 的小点个数,如果有剩下的,可以匹配。
{
up++; lst[c]++; mch.emplace_back(p);
}
else rst.emplace_back(p); // can't match
}
else
{
mx[c]=min(cntd[c],mx[c]+up-lst[c]); lst[c]=up;//从上一个 c 到现在有 up-lst[c] 个不是 c 颜色的大点被匹配。让它们全匹配 c 颜色。
cntd[c]++; dn++; mch.emplace_back(p);
}
}
int R=dn-up,tmp; //剩余总小点数
id=cnt=0;
for(int c=1;c<=n;c++)
{
mx[c]=min(cntd[c],mx[c]+up-lst[c]);//细节,最后剩了一丢丢,补上
if((tmp=((cntd[c]-mx[c])<<1)-R)>0) cnt=tmp,id=c;
}
if(!id) cnt=R&1;//如果最小值都不大于二分之一的话,那么存在方案使最终都不大于二分之一
//考虑先任意构造一种匹配,假如存在 数量大于二分之一的颜色(颜色数为二时不存在这种情况),那么可以把多的剩余量交换其他颜色(选能换且剩余数量最小的换),由于颜色数大于二,所以一定可以。
//如果最终不存在方案使每一种颜色剩余数都不大于二分之一,说明至少存在一种颜色剩余量大于二分之一,另一种等于二分之一,这显然是不可能的。
return make_pair(mch,rst);
};
int id,cnt;
a=sol(a,id,cnt).fi; reverse(a.begin(),a.end());//这里解决的问题是小点可选可不选,贪心选大点。并且求出每种颜色小点的最大匹配数。
LL ans=0;
for(auto p:a)
{
if(p.c>n)
{
ans+=L-p.w; b.emplace_back(p.w,p.c-n);
}
else
{
ans+=p.w;
if(!id||id==p.c) b.emplace_back(p.w,p.c+n);
//如果有 id 颜色的剩余量大于二分之一,那么它无法匹配完,只能在这个颜色里删。
//否则任意删。
}
}
b=sol(b,id,id).se; reverse(b.begin(),b.end());//这里只需要解决尽量选权值大的 小点
//贪心选小点,从大到小选小点使“匹配数量”最多,而最多就是能匹配的大点的数量,一定能取到。
//如果只加入了 id 一种颜色,多选肯定不劣,且最大匹配数是已知的,所以剩下的数量一定不小于 cnt
for(int i=0;i<cnt;i++) ans-=b[i].w;
printf("%lld\n",ans);
}
return 0;
}
浙公网安备 33010602011771号