题目
思路
十分巧妙的差分前缀和好题。
题目板块完结之后,我看到有很多处理此题的方法,但总感觉差分前缀和比较巧妙。
首先,通过输入我们可以将每个人能在 \(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; }
似乎有点贪心?
来源:https://www.cnblogs.com/Arextre/p/12291465.html