CF - 853B (Jury Meeting)

题意:给定 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);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值