题意:给定 n,m,k 表示有 n+1 个城市,1~n每个城市都有一个成员需要赶往0号城市开k天的会议,而现在一共有 m 个航班,每个航班的信息包括 d,f,t,c (分别表示起飞的日期,出发城市,目的城市,票价),出发和目的城市其一必为0号;问是否有方案满足这 n 个成员可以飞到0号城市开满k天的会议后再各自回家(成员在0号城市呆多久不影响),若有则输出最少需要花费,若不能则输出-1;
分析:
Ⅰ:先来考虑无解的情况:
①有城市没有往返航班,即不能来回至少一次;
②一个城市最远的往返航班时间间隔小于k天;
③找不出共同k天满足所有成员在这k天之前能来到0号城市,并在这k天之后回家;
整合一下②③就是遍历1~n每个城市,找到第 i 个城市去0号的最早航班ai和回家的最晚航班bi;
则 L=max(a1,a2,...,an),R=min(b1,b2,...,bn); 若 R-L-1<k 则无解;
Ⅱ:然后考虑最小花费,用差分统计去0号的前缀和 和 回家的后缀和,然后在L,R之间找到最小花费即可,代码中有注释;
#include<vector>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1E5+10;
const int K = 1E6+10;
const ll INF= 1E18;
int n,m,k,L,R;
ll pre[K],suf[K];
struct node{
int d,w;
node(){}
node(int _d,int _w){
d=_d,w=_w;
}
};
bool cmp1(node a,node b){
if(a.d==b.d) return a.w<b.w;
return a.d<b.d;
}
bool cmp2(node a,node b){
if(a.d==b.d) return a.w<b.w;
return a.d>b.d;
}
vector<node>vt1[N],vt2[N]; //vt1[i]记录i号城市去0号的航班;
//vt2[i]记录从0号返回i号城市的航班;
void input(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++){
int f,t,d,c;
scanf("%d%d%d%d",&d,&f,&t,&c);
if(!f) vt2[t].push_back(node(d,c));
if(!t) vt1[f].push_back(node(d,c));
}
}
bool check(){
L=-1,R=1E7;
for(int i=1;i<=n;i++){
sort(vt1[i].begin(),vt1[i].end(),cmp1); //去0号的航班,按日期升序排序;
sort(vt2[i].begin(),vt2[i].end(),cmp2); //从0号回家的航班,按日期降序排序;
if(vt1[i].size()==0) {L=-1 ;break;} //没有往返航班
if(vt2[i].size()==0) {R=1E7;break;} //-----------
L=max(L,vt1[i][0].d);
R=min(R,vt2[i][0].d);
}
if(L==-1||R==1E7||R-L-1<k){
puts("-1");
return 1;
}
return 0;
}
void solve(){
for(int i=1;i<=n;i++){
int cost=0; //cost记录的是之前的票价;
for(int j=0;j<vt1[i].size();j++){
int d=vt1[i][j].d,w=vt1[i][j].w;
if(!(cost==0||cost>w)) continue;
pre[d]-=cost;
pre[d]+=w;
cost=w;
}
//稍微理解差分的概念不难知道,若当前票价小于之前的票价,则
//更新小票价,并且差分一下;
cost=0;
for(int j=0;j<vt2[i].size();j++){
int d=vt2[i][j].d,w=vt2[i][j].w;
if(!(cost==0||cost>w)) continue;
suf[d]-=cost;
suf[d]+=w;
cost=w;
}
}
for(int i=2;i<K;i++) pre[i]+=pre[i-1];
//差分之后做一遍前缀和之后,pre[i]表示第i天之前所有人到达0号的花费;
for(int i=K-2;i>=1;i--) suf[i]+=suf[i+1];
//-------------后缀和---,sum[i]表示第i天之前所有人回家的花费;
ll ans=INF;
//因为差分只是统计,所以我们得从L开始,并且不能超过R,因为L天之后,每个城市去的航班至少才
//都有一次,同理,R天之前,返回所有城市的航班才至少都有一个,这就是为什么我们之前一定要先特
//判无解的原因
for(int i=L;i+k+1<=R;i++) ans=min(ans,pre[i]+suf[i+k+1]);
printf("%lld",ans);
}
int main()
{
input();
if(check()) return 0;
solve();
}
另外还可以用贪心做:
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int M = 1E5+10;
const int K = 1E6+10;
const ll INF= 1E18;
int n,m,k,cost[M];
bool vis[M];
ll pre[K],suf[K];
struct node{
int to,d,w;
node(){}
node(int _to,int _d,int _w){
to=_to,d=_d,w=_w;
}
};
bool cmp1(node a,node b){
return a.d<b.d;
}
bool cmp2(node a,node b){
return a.d>b.d;
}
vector<node>vt1,vt2;
void cal(vector<node>vt,ll num[]){
fill(num,num+K,INF);
memset(vis,0,sizeof(vis));
int tot=0; ll sum=0;
for(int i=0;i<vt.size();i++){
node top=vt[i];
int to=top.to,d=top.d,w=top.w;
if(vis[to]){
if(cost[to]>w){
sum-=cost[to];
sum+=w;
cost[to]=w;
}
}
else{
vis[to]=1;
sum+=w;
cost[to]=w;
tot++;
}
if(tot==n){ //这个是重点,只有累计到n个城市都有了才记录
num[d]=sum;
}
}
}
ll solve(){
sort(vt1.begin(),vt1.end(),cmp1);
sort(vt2.begin(),vt2.end(),cmp2);
cal(vt1,pre);
cal(vt2,suf);
for(int i=2;i<K;i++) pre[i]=min(pre[i],pre[i-1]);
for(int i=K-2;i>=1;i--) suf[i]=min(suf[i],suf[i+1]);
ll ans=INF;
for(int i=1;i+k<K;i++) ans=min(ans,pre[i-1]+suf[i+k]);
return ans==INF?-1:ans;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++){
int d,f,t,c;
scanf("%d%d%d%d",&d,&f,&t,&c);
if(!f) vt2.push_back(node(t,d,c));
if(!t) vt1.push_back(node(f,d,c));
}
ll ans=solve();
printf("%lld",ans);
}