bzoj1975: [Sdoi2010]魔法猪学院
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1975
思路:可以用A*做,但是要手写堆或者用pb_ds的堆,不然会被卡空间
(原题256M,bzoj64M...)
设出发点为S,结束点为T,边权为val(e),边的出发点为head(e),到达点为tai(e)
也可以用论文方法,参见俞鼎力的《堆的可持久化和k短路》论文
首先我们建出最短路树,如果有多条任取一条以保证其树形结构
那么这棵树就是一个以结束点T为根的树
对于图中每条边e,定义它的偏离值delta(e)=val(e)+dis(tail(e),T)-dis(head(e),T)
表示换成这条边后,路径长度会增加多少
我们定义一条路径p中不在最短路径树中的边按从S到T的顺序构成的序列为s(p)
那么路径p的长度就是最短路长+p中所有边的偏离值
并且该序列中相邻两条边e,f满足head(f)是tail(e)在最短路径树上的祖先(包括自己)
这个性质很显然,相邻两条边之间只会有树边或者没有其他边,树边又总是从儿子指向父亲
然后我们要证明,满足该条件的序列和一条路径是一一对应的
这个也比较显然,两点间的树上路径当然只有一条
现在的问题就是求权值第k小这种序列
用一个优先队列维护,每个节点记录两个值,路径长度和结束点,
初始加入一个空序列,表示最短路
我们对每个点建立一个堆,存储所有以该点及该点祖先出发的边,因为序列要满足上面的性质
怎样得到一个新序列呢
我们可以把当前边替换成堆中的次大边,也可以新加入一条该边结束点的堆中的边以得到一个新序列
观察发现,复杂度在建立每个点的堆上
但我们并不需要这么做
树上儿子节点的堆只是在树上父亲节点的堆的基础上多加了一些边而已
所以写个可持久化的堆就可以了
(卡空间真是丧心病狂....,动态调整了一发可持久化堆的大小才卡过...)
upd:可持久化堆大小可以缩到maxm的4.5倍,空间只有30M左右,妈妈再也不用担心我被卡空间了..
#include<cmath> #include<queue> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define PI pair<double,int> #define mp(a,b) make_pair(a,b) #define fi first #define se second #define pb(a) push_back(a) const int maxn=5010,maxm=200010; const double eps=1e-8; using namespace std; int n,m,pre[maxm],now[maxn],son[maxm],tot,fa[maxn],root[maxn],ans=1;double maxv,val[maxm],dis[maxn],mind;bool vis[maxn]; struct Edge{int x,y;double v,dta;}E[maxm]; priority_queue<PI,vector<PI>,greater<PI> > q;//用来记录所有序列的堆,first是偏离值之和,second是结束节点 void add(int a,int b,double c){pre[++tot]=now[a],now[a]=tot,son[tot]=b,val[tot]=c;} bool is0(double a){return fabs(a)<eps;} vector<int> s[maxn]; struct node{int ch[2],dis,ed;double val;}t[(int)(maxm*9)]; void read(int &x){ char ch; for (ch=getchar();!isdigit(ch);ch=getchar()); for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; } struct Tltree{ #define ls t[p].ch[0] #define rs t[p].ch[1] int tot; int newnode(int son,double v){ t[++tot]=(node){0,0,0,son,v}; return tot; } int merge(int x,int y){ if (!x||!y) return x+y; if (t[x].val>t[y].val) swap(x,y); int p=++tot; t[p]=t[x],rs=merge(y,t[x].ch[1]); if (t[ls].dis<t[rs].dis) swap(ls,rs); t[p].dis=t[rs].dis+1; return p;//返回p,不是tot!!! } }h;//用来存储从每个点自己及祖先出发的边的堆 struct Toppgraph{//预处理出每个点到n的距离 int pre[maxm],now[maxn],son[maxm],tot;double val[maxm]; void add(int a,int b,double c){pre[++tot]=now[a],now[a]=tot,son[tot]=b,val[tot]=c;} void prework(){ memset(dis,127,sizeof(dis)); dis[n]=0,q.push(mp(0,n)); while (!q.empty()){ int x=q.top().se;double d=q.top().fi;q.pop(); for (int y=now[x];y;y=pre[y]) if (dis[son[y]]>d+val[y]+eps) dis[son[y]]=d+val[y],q.push(mp(dis[son[y]],son[y])); } while (!q.empty()) q.pop(); mind=dis[1]; } }opp; void dfs(int x){ bool find=0; if (x!=n){ for (int y=now[x];y;y=pre[y]){ double delta=dis[son[y]]+val[y]-dis[x]; if (son[y]==fa[x]&&is0(delta)&&!find) find=1;//如果有权值相同的,都在最短路径树上的边,任取一条即可 else root[x]=h.merge(root[x],h.newnode(son[y],delta));//否则扔进该点的堆里 } } for (int y=opp.now[x];y;y=opp.pre[y]){//从T开始 反着往回建树边 int v=opp.son[y]; if (!fa[v]&&is0(dis[x]+opp.val[y]-dis[v]))//如果还没确定是哪条,且该边可能在最短路上,就把它当作树边 fa[v]=x,root[v]=root[x],dfs(v); } } int main(){ //freopen("magic1.in","r",stdin),freopen("magic.out","w",stdout); int x,y;double z;t[0].dis=-1; scanf("%d%d%lf",&n,&m,&maxv); for (int i=1;i<=m;i++) read(x),read(y),scanf("%lf",&z),add(x,y,z),opp.add(y,x,z); opp.prework(),root[n]=0,fa[n]=n,dfs(n); //for (int i=1;i<=n;i++) printf("i=%d fa=%d %.1lf\n",i,fa[i],dis[i]); //for (int i=1;i<=n;i++) printf("i=%d root=%d\n",i,root[i]); if (!root[1]) return printf("%d\n",ans),0; memset(vis,0,sizeof(vis)),vis[n]=1; q.push(mp(t[root[1]].val,root[1])); maxv-=dis[1]; while (!q.empty()&&dis[1]+q.top().fi<=maxv){ maxv-=dis[1]+q.top().fi; PI now=q.top(); //printf("top=%.1lf\n",q.top().fi); q.pop(),ans++; for (int i=0;i<2;i++){//可以换成次大的以得到k+1短 int next=t[now.se].ch[i]; if (next) q.push(mp(now.fi-t[now.se].val+t[next].val,next)); } int ed=t[now.se].ed; if (root[ed]) q.push(mp(now.fi+t[root[ed]].val,root[ed]));//也可以多加一条合法的不在最短路径树中的边得到k+1短 } printf("%d\n",ans); return 0; } /* 4 6 14.9 1 2 1.5 2 1 1.5 1 3 3 2 3 1.5 3 4 1.5 1 4 1.5 */