题目
思路
十分巧妙的差分前缀和好题。
题目板块完结之后,我看到有很多处理此题的方法,但总感觉差分前缀和比较巧妙。
首先,通过输入我们可以将每个人能在 \(0\) 号点停留的最大时间区间 \([tl,tr]\) ,并将所有人的 \([tl,tr]\) 取交集,得到 \([ll,rr]\) 即表示只有在这个区间中所有人能够聚集在一起。
显然,如果 \(rr-ll-1<k\) 则直接 puts("-1")
即可。
然后怎么办?假设所有人聚集的时间从 \(i\) 开始到 \(i+k+1\) 或者更久,那么我们可以保证他们工作的时常至少为 \(k\) ,那么我们需要找的就是所有人在 \([1,i]\) 时间段内到 \(0\) 的最小花费以及 \([i+k+1,n]\) 时间段返回的最小花费之和。
两种情况是一样的,我们只讨论其中一种,不妨讨论前往 \(0\) 的情况。
对于一个人,我们假设他在 \(i\) 时刻到达 \(0\) 最便宜的价格为 \(m_i\),再将他的所有前往 \(0\) 的航班按时间顺序由小到大排序。
规定:\(t_i\) 表示第 \(i\) 趟航班起飞的时间,\(c_i\) 为其花费,\(m_i\) 为这个人在 \(i\) 时刻到达 \(0\) 的最小花费。
现在我们分析他的第一堂航班和第二趟航班,由于 \(t\) 我们已经按序排序了,现在讨论 \(c\) 的大小关系:
- \(c_1\le c_2\) ,那么这个人坐第一趟航班肯定是最便宜的,而且我们可以对 \(m_i\) 数组进行修改,即 \(\forall m_i,t_1\le i<t_3,m_i=c_1\) ,注意,此处 \(i<t_3\) 而非 \(t_2\) (仔细想想,为什么?);
- \(c_1>c_2\) ,此刻我们发现,在时间 \(i\) 介于区间 \([t_1,t_2)\) 的时候,肯定只能乘坐第一趟航班,但如果 \(i\ge t_2\) ,我们乘坐第二趟航班无疑最优,即当 \(t_1\le i < t_2\) 时,\(m_i=c_1\) ,当 \(i\le t_2\) 时,\(m_i=c_2\) ;
将我们的分析推广到整个体系当中:
在枚举当前是第 \(i\) 趟航班时,保留 \(1,2,\ldots i-1\) 趟航班中的最小花费 \(\min\) 。
- 如果 \(c_i<\min\) ,那么我们更新 \(\min\) ,并用 \(c_i\) 将 \(m_i,i\in [t_i,t_{i+1})\) 全部更新;
- 如果 \(c_i\ge \min\) ,那么我们保留 \(\min\) ,并用 \(\min\) 将 \(m_i,i\in [t_i,t_{i+1}]\) 全部更新;
至于从 \(0\) 返回希望大家自行分析,因为代码中有一些 \(+1\) 如果没有分析是很难弄懂的。
经过分析,我们发现这个操作十分像区间赋值,那么这里就有许多数据结构值得我们使用:
- 线段树
- 树状数组
- 差分前缀和
个人推荐差分前缀和,因为前两者的修改、询问都是 \(\mathcal O(\log N)\) 的,后者修改 \(\mathcal O(1)\) ,询问 \(\mathcal O(\log N)\),而基于我们的分析似乎修改操作十分繁杂,而询问操作只有在最后计算答案时使用。
那么我们对于每一个人,可以开一个 \(ansl[i][t]\) 表示这个人 \(i\) 在 \(t\) 时刻到 \(0\) ,再开一个 \(ansr[i][t]\) 表示这个人再 \(t\) 时刻离开 \(0\) 。但是人有 \(10^5\) 个,时间刻度有 \(10^6\) 个,开二维肯定不限时,但由于人与人之间相互独立,没有什么影响,我们考虑所有人共用一个 \(ansl\) 和一个 \(ansr\) 数组。
具体细节见代码。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<vector>
#include<utility>
using namespace std;
#define rep(i,__l,__r) for(signed i=__l,i##_end_=__r;i<=i##_end_;++i)
#define fep(i,__l,__r) for(signed i=__l,i##_end_=__r;i>=i##_end_;--i)
#define writc(a,b) fwrit(a),putchar(b)
#define mp(a,b) make_pair(a,b)
#define ft first
#define sd second
#define LL long long
#define ull unsigned long long
#define uint unsigned int
#define pii pair< int,int >
#define Endl putchar('\n')
#define CODEFAIL puts("-1"),exit(0)
// #define FILEOI
// #define int long long
// #define int unsigned
#ifdef FILEOI
# define MAXBUFFERSIZE 500000
inline char fgetc(){
static char buf[MAXBUFFERSIZE+5],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXBUFFERSIZE,stdin),p1==p2)?EOF:*p1++;
}
# undef MAXBUFFERSIZE
# define cg (c=fgetc())
#else
# define cg (c=getchar())
#endif
template<class T>inline void qread(T& x){
char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
inline int qread(){
int x=0;char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return f?-x:x;
}
// template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
inline void getInv(int inv[],const int lim,const int MOD){
inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
}
template<class T>void fwrit(const T x){
if(x<0)return (void)(putchar('-'),fwrit(-x));
if(x>9)fwrit(x/10);
putchar(x%10^48);
}
inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
}
const int MAXN=1e5;
const int MAXM=1e5;
const int MAXK=1e6;
const int INF=(1<<30)-1;
int n,m,k,ll,rr;
vector< pii >a[MAXN+5],b[MAXN+5];
//a[i] : 第 i 个点到 0 点的时间, 花费
//b[i] : 0 点到 i 点的时间, 花费
inline void input(){
n=qread(),m=qread(),k=qread();
for(int i=1,d,f,t,c;i<=m;++i){//照常规输入即可
d=qread(),f=qread(),t=qread(),c=qread();
if(f==0)b[t].push_back(mp(d,c));
else a[f].push_back(mp(d,c));
}
}
inline void init(){
ll=-1,rr=INF;
int tl,tr;
rep(i,1,n){
sort(a[i].begin(),a[i].end());
sort(b[i].begin(),b[i].end());
tl=INF,tr=-1;
if(!a[i].empty())tl=a[i].begin()->first;
if(!b[i].empty())tr=b[i].back().first;
if(tl==INF || tr==-1)CODEFAIL;//如果连来的机票或者回去的机票的没有, 直接 gg
ll=Max(ll,tl),rr=Min(rr,tr);//取交集
// printf("i == %d, tl == %d, tr == %d\n",i,tl,tr);
}
// printf("ll == %d, rr == %d\n",ll,rr);
if(ll==-1 || rr==INF || rr-ll-1<k)CODEFAIL;//如果缺机票或者全部人最大的交集都不够 k 天也 gg 了
}
LL ansl[MAXK+5],ansr[MAXK+5],res=(1ll<<60)-1;
inline void solve(){
for(int i=1,now,cost;i<=n;++i){
now=1,cost=INF;//初始化
// puts("For into ansl");
for(int t=0,siz=a[i].size(),tmp;t<siz;++t){
// printf("When i == %d, now == %d, cost == %d\n",i,now,cost);
tmp=a[i][t].first;
ansl[tmp]-=cost;
ansl[now]+=cost;
//差分前缀和的基本操作
//注意:此处有别于下面, 原因在于这里是处理前往 0 点而非离开 0 点
now=tmp,cost=Min(cost,a[i][t].second);
// printf("After i == %d, now == %d, cost == %d\n",i,now,cost);
}
ansl[MAXK+1]-=cost;
ansl[now]+=cost;
now=MAXK,cost=INF;//倒着处理
for(int t=b[i].size()-1,tmp;t>=0;--t){
tmp=b[i][t].first;
ansr[now+1]-=cost,ansr[tmp+1]+=cost;
now=tmp,cost=Min(cost,b[i][t].second);
}
ansr[1]+=cost,ansr[now+1]-=cost;
// printf("After i == %d, the two arr:\n",i);
// rep(t,1,20)writc(ansl[t],' ');Endl;
// rep(t,1,20)writc(ansr[t],' ');Endl;
}
rep(i,1,MAXK)ansl[i]+=ansl[i-1],ansr[i]+=ansr[i-1];//差分前缀和数组的最后一步
// rep(i,1,20)writc(ansl[i],' ');
// Endl;
// rep(i,1,20)writc(ansr[i],' ');
// Endl;
for(int i=ll;i+k+1<=rr;++i)res=Min(res,ansl[i]+ansr[i+k+1]);
writc(res,'\n');
}
signed main(){
#ifdef FILEOI
freopen("file.in","r",stdin);
freopen("file.out","w",stdout);
#endif
input();
init();
solve();
return 0;
}