差分约束裸题。
计算当前线路中最小的级别(比较始发站和终点站)。
整条线路中所有大于这个级别的都必须停靠
所有未停靠的站点的级别一定小于这个级别
也就是说所有未停靠的即为级别低,记为A
所有停靠的站点级别一定比A的高,记作B
得到公式$B ≥ A + 1$
根据很明显是一道差分约束问题。
根据差分约束的概念,我们从所有的A向所有的B连一条权值为1的有向边。
然后根据差分约束的套路,我们还要设一个界限才能求出最大值。
因为所有车站级别都是正值,所以$A≥1$,也就是从0向所有的A中的点连一条权值为1 的有向边。我们常常用直接给dist
数组赋值为1代替。
但是由于实际数据范围较大
最坏情况下是有1000
趟火车,每趟有1000
个点,每趟上限有500
个点停站,则有(1000 - 500)
个点不停站,不停站的点都向停站的点连有向边,则总共有 $500 * 500 * 1000 = 2.5 * 10^8$,差分约束的spfa
有可能超时。
由于本题中的所有点的权值都是大于0,并且一定满足要求$=>$所有车站都等级森严$=>$不存在环$=>$可以拓扑排序得到拓扑图使用递推求解差分约束问题。
因此整体的思路为:
- 拓扑排序得拓扑图
- “至少”$=>$要求的是最小值$=>$所有条件的下界中取最大值$=>$最长路,因此我们,根据拓扑序跑最长路递推即可。
- 答案为满足所有约束条件的解中最大值既是题目要求的最高的级别
一个建图的优化:
如果直接暴力建图就会建$O(nm)$条边,也就是$2*10^8$个点,时间和空间都有可能超时。
我们可以在中间建一个虚拟节点,左边向虚拟点连0,虚拟点向右边连1等价于左边向右边连1
这样只会建$O(n + m)$条边
注意的是本题一共m条线路,每条线路一个虚拟源点,所以一共会有n + m 个点。
当年出题人并没有卡这一点,不然要卡死一大批有志青年
如下图所示:
优化前:
然后就是代码了
别忘了一共有n + m 个点
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 2007, M = 5000007;
int n, m;
int ver[M], nex[M], edge[M], head[N], tot;
int din[N];
int vis[N];
int q[N];
int dist[N];
void add(int x, int y, int z){
ver[tot] = y;
edge[tot] = z;
nex[tot] = head[x];
head[x] = tot ++ ;
din[y] ++ ;
}
void toposort(){
int hh = 0, tt = -1;
for(int i = 1;i <= n + m;++i){//一共n + m个点,要遍历所有的点
if(!din[i]){
q[++ tt] = i;
if(tt == N)tt = 0;
}
}
while(hh <= tt){
int x = q[hh ++ ];
if(hh == N)hh = 0;
for(int i = head[x];~i;i = nex[i]){
int y = ver[i];
if(-- din[y] == 0){
q[++ tt] = y;
if(tt == N)tt = 0;
}
}
}
}
int main(){
scanf("%d%d", &n,&m);
memset(head,-1,sizeof head);
for(int i = 1;i <= m;++i){
memset(vis,0,sizeof vis);
int t,stop;
scanf("%d",&t);
int start = n, end = 1;
while(t -- ){
scanf("%d",&stop);//输入的是编号
start = min(start, stop);
end = max(end, stop);
vis[stop] = true;//代表该站要停靠.
}
int source = n + i;//n + 1
for(int j = start;j <= end;++j){//该线路上的所有经过的站点的编号一定在始发站和终点站之间
if(vis[j])//要停靠,说明是右部点
add(source, j, 1);
else add(j, source, 0);
}
}
toposort();
for(int i = 1;i <= n;++i)//A ≥ 1
dist[i] = 1;
for(int i = 0;i < n + m;++i){//手写的队列是从0开始的
int x = q[i];
for(int j = head[x];~j;j = nex[j]){
int y = ver[j], z = edge[j];
dist[y] = max(dist[y], dist[x] + z);
}
}
int res = 0;
for(int i = 1;i <= n;++i)//满足所有约束条件的解中最大值既是题目要求的最高的级别
res = max(res, dist[i]);
printf("%d\n",res);
return 0;
}
我怎么感觉上面的循环队列写的不太对呢?
如果tt==N,不就数组越界了吗?本题没有出错,是因为没有机会跑完全部数组吧
同问
我找数据debug了一下,事实证明你说的没有错,它就是越界了,但是它没有报错的原因并不是没有机会跑完全部数组,而是程序在开数组的时候,下标是可以越界的,acwing,是允许大概几百个int往后越界不报sf,dev的话就完全不会报错,当然d[-1]这种肯定是会的,所以仅仅越界一个就无所谓了
我以为这东西越界就不行,原来也是可以越界几个的~这真是太坑了~
建议写成tt++ 这样就不会越界了
感觉很不错的,直到看到了const int N = 2007, M = 5000007;
佬,我想问下为什么在初始化dist的时候,对于虚拟点是不做处理呢?本身定义是让虚拟点作为不停靠站点中的最大值,那么让其为1应该也可以吧?但是却会WA掉
考虑一种边界当全部停靠的时候,由于是这样连的边所以会导致最小的级数是从2开始的就会大一个
你可以把建图时候的0,1调换一下位置就不会WA掉了
让其为1是不可以的。因为题目说的是停靠点大于不停靠点,而不是停靠点大于不停靠点加一
大佬写的很好,但我有一个疑问希望大佬们解答一下
q[]存储了拓扑序,又为什么用循环队列来写?
我觉得一是队列数组大小足够没必要
二是循环队列也不能存储整个拓扑序
在这里跪求大佬解答orz
-------来自蒟蒻的求助
为什么我这个过不了 tle了 是因为用stl的queue速度不如数组吗
#include[HTML_REMOVED]
using namespace std;
const int N=500000;
int e[N],ne[N],h[N],w[N],in[N],inn[N];
int dist[N],ans[N];
int s,sum,v,unpd;
int idx=1;
int n,m;
int cnt=0;
int vis[N];
void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx;
in[b];
}
void topsort(){
queue[HTML_REMOVED] q;
for(int i=1;i<=n+m;i){
if(in[i]==0){
q.push(i);
ans[cnt]=i;
}
}
while(q.size()){
int t=q.front();
q.pop();
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
in[j]–;
if(in[j]==0){
q.push(j);
ans[++cnt]=j;
}
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){//m趟车次
memset(vis,0,sizeof vis);
int t,stop;
cin>>t;//每趟车次停靠t个站
int start=n,end=1;
while(t–){
}
赞的
每趟线上500停靠点如何估算的,下面评论没懂。。
应该是上限是1000,但是取500时乘积
最大
停的站设为x,未停的站为1000-x,故未优化建边为(1000-x)*x,求最大值时为500
基本不等式啊
赞一个👍👍
dist存的是暂时的最高级别吗,怎么莫名有种Dijkstra算法感觉
~i
什么意思
相当于
i!=-1;
每趟上线500个停靠点是怎么估算出来的?
和一定, 差小积大
二次函数$n*(1000-n)$求最大值,当且仅当$n=500$
寫得很詳細