POI2015 WYC

也許更好的閱讀體驗

Description\mathcal{Description}
給定一張n個點m條邊的帶權有向圖,每條邊的邊權只可能是1,2,3中的一種。將所有可能的路徑按路徑長度排序,請輸出第k小的路徑的長度,注意路徑不一定是簡單路徑,即可以重複走同一個點。
(1n401m10001k1018)(1\leq n\leq 40,1\leq m\leq 1000,1\leq k\leq 10^{18})

Solution\mathcal{Solution}
死毒瘤題,打了一晚上,最後把自己的方法改的和其他人差不多了
邊權不爲11,不好直接套用鄰接矩陣,考慮把一個點拆開,讓一條長度大於11的路徑要到一些沒用的點使得它需要走多次才能走到它該到的點
把一個點nn拆爲n0,n1,n2n_0,n_1,n_2,分別表示距離nn還有0/1/20/1/2的距離,實際上,n0n_0就是原本的點,我們稱之爲實點,n1,n2n_1,n_2則是用來消耗路徑長度的點,稱之爲虛點
先有n2  n1,n1  n0n_2\ \rightarrow\ n_1,n_1\ \rightarrow \ n_0連邊
對一條邊(u,v,w)\left(u,v,w\right),考慮由實點u0u_0走出去一步,那麼距離vv就會減一,所以u0u_0vw1v_{w-1}連一條邊,即連邊(u0,vw1)\left(u_0,v_{w-1}\right)

現在我們可以對現在的矩陣做乘法和快速冪了,每個實點到實點的權值就是方案數
如現在是這個矩陣的kk次方,則矩陣上(u0,v0)\left(u_0,v_0\right)上的權值表示的就是從u0u_0出發,走kk步走到v0v_0的方案數

接下來考慮怎麼求答案,kk很大,自然地就想到了倍增,類似求LCALCA一樣的去確定答案即可

當然,直接對矩陣求pp次方,那麼求出來的矩陣裏的值表示的是剛好走pp步從某個位置走到某個位置的答案
爲了方便的進行判斷,我們需要把矩陣的值表示成走小於等於pp步的方案數
所以要考慮把走過的答案存下來,我們可以用00號點表示所有的方案數
怎麼用00號點表示呢,考慮每次算出一個值後再算下一個值時將當前的答案放到00號點去
所以對每個點實點u0u_0連一條(u0,0)\left(u_0,0\right)的邊即可,而每次計算不能把上次的答案丟了,所以還要連一條(0,0)\left(0,0\right)的邊

這樣當前矩陣的00號點就表示着上一次的答案
我們再弄一個矩陣乘上當前矩陣就可以表示,這個矩陣00向所有的實點連一條邊即可

Code\mathcal{Code}

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年11月06日 星期三 18時58分03秒
*******************************/
#include <cstdio>
#include <fstream>
#define ll long long
#define ld long double
#define rint register int
using namespace std;
const int maxn = 130;
//{{{cin
struct IO{
	template<typename T>
	IO & operator>>(T&res){
		res=0;
		bool flag=false;
		char ch;
		while((ch=getchar())>'9'||ch<'0')	flag|=ch=='-';
		while(ch>='0'&&ch<='9')	res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
		if (flag)	res=~res+1;
		return *this;
	}
}cin;
//}}}
int n,lim,m;
ll k,ans;
//{{{Matrix
struct Matrix{
	ld mat[maxn][maxn];
	Matrix (bool opt=0){
		for (int i=0;i<=lim;++i)
			for (int j=0;j<=lim;++j)	mat[i][j]=opt&&(i==j);
	}
	ld* operator [] (const int &x){	return mat[x];}
	Matrix operator * (Matrix &b){
		Matrix a=*this,s;
		for (int i=0;i<=lim;++i)
			for (int j=0;j<=lim;++j)
				for (int k=0;k<=lim;++k)
					s[i][j]+=a[i][k]*b[k][j];
		return s;
	}
}g[maxn],s;
//}}}
inline int loc (int x,int i){	return (x-1)*3+i+1;}
inline ld sum (Matrix &a) { return a[0][0]-n; }
int main()
{
	freopen("p3597.in","r",stdin);
	freopen("p3597.out","w",stdout);
	cin>>n>>m>>k;
	lim=3*n;

	for (rint i=1;i<=n;++i){
		for (rint j=1;j<=2;++j)	g[0][loc(i,j)][loc(i,j-1)]=1;
		g[0][loc(i,0)][0]=s[0][loc(i,0)]=1;
	}
	g[0][0][0]=1;

	for (rint i=1;i<=m;++i){
		int u,v,d;
		cin>>u>>v>>d;
		++g[0][loc(u,0)][loc(v,d-1)];
	}


	int d;
	for (d=1;;++d){
		g[d]=g[d-1]*g[d-1];
		Matrix t=s*g[d];
		if (sum(t)>=k)	break;
		if (d>=64)	return printf("-1\n"),0;
	}

	for (rint i=d;~i;--i){
		Matrix t=s*g[i];
		if (sum(t)<k){
			s=t;
			ans+=(1ll<<i);
		}
	}

	printf("%lld\n",ans);
	return 0;
}

如有哪裏講得不是很明白或是有錯誤,歡迎指正
如您喜歡的話不妨點個贊收藏一下吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章