1. 程式人生 > >bzoj1975: [Sdoi2010]魔法豬學院

bzoj1975: [Sdoi2010]魔法豬學院

思路:可以用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
*/