Codeforces 1487E - Cheap Dinner (数据结构、排序)
Educational Codeforces Round 104 (Rated for Div. 2) E. Cheap Dinner
题意
给定\(n_1\)种第一道菜的价格\(a_i\),\(n_2\)种第二道菜的价格\(b_i\),\(n_3\)种饮料的价格\(c_i\),\(n_4\)种甜点的价格\(d_i\)
有\(m_1\)种组合\((x_i,y_i)\),描述编号为\(x_i\)的第一道菜与编号为\(y_i\)的第二道菜不能搭配
有\(m_2\)种组合\((x_i,y_i)\),描述编号为\(x_i\)的第二道菜与编号为\(y_i\)的饮料不能搭配
有\(m_3\)种组合\((x_i,y_i)\),描述编号为\(x_i\)的饮料与编号为\(y_i\)的甜点不能搭配
问是否能够每种种类的食物都分别挑选一种,它们可以互相搭配且总花费最小
限制
\(1\le n_1,n_2,n_3,n_4\le 150000\)
\(1\le a_i,b_i,c_i,d_i\le 10^8\)
\(1\le m_1,m_2,m_3\le 200000\)
思路
很容易想到是费用流的模板题,但费用流会出现\(n^2\)条边,这题显然不可行
由于每种限制仅针对两种种类的食物,所以可以想到拆成三部分的二分图来做(当然不是二分图匹配)
我们可以先处理第二道菜
原本\(b_i\)表示的是“编号为\(i\)的第二道菜的价格”
我们可以将\(\{a\}\)引入,从而让\(b_i\)表示“编号为\(i\)的第二道菜与某一种第一道菜搭配所能得到的最小花费”
即\(b_i=b_i+min\{a_j\}\),其中\(a_j\)与\(b_i\)能够搭配
处理好\(b_i\)后,接下来处理饮料时
原本\(c_i\)表示的是“编号为\(i\)的饮料的价格”
引入\(\{b\}\),从而让\(c_i\)表示“编号为\(i\)的饮料与其所能搭配的\(b_i\)相加后,能得到的最小花费”
即\(c_i=c_i+min\{b_j\}\),其中\(b_j\)与\(c_i\)能够搭配
\(d_i\)同理,最终表示“编号为\(i\)的甜点与其所能搭配的\(c_i\)相加后,能得到的最小花费”
显而易见,答案就是\(d_i\)中的最小值
以将\(\{a\}\)引入\(b_i\)为例
如果我们想求出\(b_i=b_i+min\{a_j\}\),其中\(a_j\)与\(b_i\)能够搭配
最简便的方法就是枚举所有\(a_j\),但这样的时间复杂度就会来到\(O(n^2)\),显然不可行
我们可以将数据存入结构体,记录原本的编号以及价格,将\(\{a\}\)进行排序
我们可以将所有不合法的搭配\((x,y)\)存进set容器中,由于是对每个\(b_i\)找出最小的\(a_j\),所以往编号为\(y\)的set容器内插入值\(x\),表示\(\{b\}\)中的\(y\)不能与\(\{a\}\)中的\(x\)进行搭配
然后我们按顺序遍历排序后的\(\{a\}\),每次在编号为\(i\)的set容器中二分查找是否存在一个值为\(j\)的元素
如果不存在,说明此时的\(b_i\)与\(a_j\)能够进行搭配,由因为保证了\(a_j\)是目前的最小值,所以直接让\(b_i=b_i+a_j\)即可,其后结束此次遍历
这种方法的时间复杂度为\(O(nlogm)\)
将\(\{b\}\)引入\(c_i\),将\(\{c\}\)引入\(d_i\)同理,详见代码注释
代码
(560ms/4000ms)
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=150050;
struct node
{
int id,val;
bool operator < (const node& a) const
{
return val<a.val;
}
}a[maxn],b[maxn],c[maxn],d[maxn];
int n1,n2,n3,n4;
set<int> v1[maxn],v2[maxn],v3[maxn];
void input()
{
cin>>n1>>n2>>n3>>n4;
rep(i,1,n1)
cin>>a[i].val,a[i].id=i;
rep(i,1,n2)
cin>>b[i].val,b[i].id=i;
rep(i,1,n3)
cin>>c[i].val,c[i].id=i;
rep(i,1,n4)
cin>>d[i].val,d[i].id=i;
int m1,m2,m3;
cin>>m1;
while(m1--)
{
int a,b;
cin>>a>>b;
v1[b].insert(a); //往编号为b的容器内插入a
}
cin>>m2;
while(m2--)
{
int a,b;
cin>>a>>b;
v2[b].insert(a);
}
cin>>m3;
while(m3--)
{
int a,b;
cin>>a>>b;
v3[b].insert(a);
}
}
void solve()
{
input();
sort(a+1,a+1+n1); //对{a}进行排序
rep(i,1,n2) //遍历所有bi
{
bool flag=false;
rep(j,1,n1) //对于每一个bi,遍历排完序后的{a}
{
if(v1[i].find(a[j].id)==v1[i].end()) //如果aj与bi能够搭配
{
b[i].val=min(a[j].val+b[i].val,INF); //直接相加即可,注意与无穷大取小,方便数据处理
flag=true;
break; //找到之后就可以直接跳出此次遍历
}
}
if(!flag)
b[i].val=INF; //如果不存在搭配,将其标记为无穷大
}
sort(b+1,b+1+n2); //下同
rep(i,1,n3)
{
bool flag=false;
rep(j,1,n2)
{
if(v2[i].find(b[j].id)==v2[i].end())
{
c[i].val=min(b[j].val+c[i].val,INF);
flag=true;
break;
}
}
if(!flag)
c[i].val=INF;
}
sort(c+1,c+1+n3);
rep(i,1,n4)
{
bool flag=false;
rep(j,1,n3)
{
if(v3[i].find(c[j].id)==v3[i].end())
{
d[i].val=min(c[j].val+d[i].val,INF);
flag=true;
break;
}
}
if(!flag)
d[i].val=INF;
}
sort(d+1,d+1+n4);
if(d[1].val==INF)
cout<<"-1\n";
else
cout<<d[1].val<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}