CF553E Kyoya and Train

Description

给定一张 n 个点 m 条边的无重边无自环的有向图,你要从 1 号点到 n 号点去。
如果你在 t 时刻之后到达n 号点,你要交 x 元的罚款。
每条边从 a i a_i ai b i b_i bi ,走过它需要花费 c i c_i ci元,多次走过同一条边需要多次花费。
走过每条边所需的时间是随机的,对于 k∈[1,t], p i , k 1 0 5 \dfrac{p_{i,k}}{10^5} 105pi,k 表示走过第 i 条边需要时间 k 的概率。因此如果多次走过同一条边,所需的时间也可能不同。
你希望花费尽可能少的钱(花费与罚款之和)到达 n 号点,因此每到达一个点,你可能会更改原有的计划。
求在最优决策下,你期望花费的钱数。
n ≤ 50 , m ≤ 100 , t ≤ 2 × 1 0 4 , x , c i ≤ 1 0 6 , ∑ k = 1 t p i , k = 1 0 5 n \leq 50,m \leq 100,t \leq 2 \times 10^4,x,c_i \le 10^6 ,\sum_{k=1}^t p_{i,k} = 10^5 n50m100t2×104x,ci106,k=1tpi,k=105 ,答案精度误差 ≤ 1 0 − 6 \leq 10^{-6} 106


Solution

FFT优化spfa多状态转移

正着设初始状态并不对劲,正难则反。
算了 我⑧说了 看这里

还是很能体现概率期望的灵活性。

转移时其实只有后面的时间能够影响前面的时间,其实是一个分层转移。那么我们可以通过CDQ分治时间计算,时间复杂度为 O ( m t l o g 2 t ) O(mtlog^2t) O(mtlog2t)

这里写的是sb做法,直接FFT转移SPFA,时间复杂度 O ( n m t l o g t ) O(nmtlogt) O(nmtlogt)
dijkstra的话因为无法有效比较两个状态的优劣(第二维时间太多)而没法写


Code

#include<bits/stdc++.h>
using namespace std;
struct edge{
	int y,z,nxt;
}e[110];
int head[60],cnt;
void add(int x,int y,int z){
	cnt++;
	e[cnt].y=y;
	e[cnt].z=z;
	e[cnt].nxt=head[x];
	head[x]=cnt;
}
long long d[60];
bool vis[60];
struct data{
	int x;
	long long s;
	bool operator <(const data &v)const{
		return s>v.s;
	}
};
priority_queue<data>q1;
const double pi=acos(-1.0);
int n,m,t,S,bit,limit=1,rev[200010];
struct cp{
	double x,y;
	cp(double xx=0,double yy=0){
		x=xx,y=yy;
	}
}a[200010],b[200010],c[200010];
double p[110][200010],dis[60][200010];
cp operator + (cp u,cp v){
	return cp(u.x+v.x,u.y+v.y);		
}
cp operator - (cp u,cp v){
	return cp(u.x-v.x,u.y-v.y);		
}
cp operator * (cp u,cp v){
	return cp(u.x*v.x-u.y*v.y,u.x*v.y+u.y*v.x);	
}
void dijkstra(){
	for(int i=1;i<n;i++) d[i]=1e15;
	q1.push((data){n,0});
	while(!q1.empty()){
		data x=q1.top();
		q1.pop(),vis[x.x]=0;
		for(int i=head[x.x];i;i=e[i].nxt){
			int y=e[i].y,z=e[i].z;
			if(d[y]>d[x.x]+z){
				d[y]=d[x.x]+z;
				if(!vis[y]){
					q1.push((data){y,d[y]});
					vis[y]=true;
				}
			}
		}
	}
	//for(int i=1;i<=n;i++)
	//cout<<i<<" "<<d[i]<<endl;
}
void FFT(cp *A,int type){
	for(int i=0;i<limit;i++)
	if(i<rev[i]) swap(A[i],A[rev[i]]);
	for(int mid=1;mid<limit;mid<<=1){
		cp wn(cos(pi/mid),type*sin(pi/mid));
		for(int r=mid<<1,j=0;j<limit;j+=r){
			cp w(1,0);
			for(int k=0;k<mid;k++,w=w*wn){
				cp x=A[j+k],y=w*A[j+mid+k];
				A[j+k]=x+y,A[j+mid+k]=x-y;
			}	
		}
	}
	if(type==-1)
	for(int i=0;i<limit;i++)
	A[i].x/=limit;
}
void mul(double *A,double *B){
	for(int i=0;i<=2*t;i++)
	a[i]=cp(A[i],0),b[i]=cp(B[i],0);//,cout<<a[i].x<<" "<<b[i].x<<endl;;
	for(int i=2*t+1;i<limit;i++)
	a[i]=b[i]=cp(0,0);
	FFT(a,1),FFT(b,1);
	for(int i=0;i<limit;i++)
	c[i]=a[i]*b[i]; FFT(c,-1);
}
queue<int>q2; 
void spfa(){
	for(int i=1;i<=n;i++){
		for(int j=0;j<=t;j++)
		dis[i][j]=1e15;
		for(int j=t+1;j<=2*t;j++)
		dis[i][j]=d[i]+S; 
	}
	for(int j=0;j<=t;j++)
	dis[n][j]=0;
	q2.push(n);
	while(!q2.empty()){
		int x=q2.front();
		q2.pop(),vis[x]=0;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].y,z=e[i].z;
			mul(dis[x],p[i]);
			bool flag=false;
			for(int j=0;j<=t;j++)
			if(dis[y][j]>c[t+j+1].x+z){
				dis[y][j]=c[t+j+1].x+z;
				flag=true;
			}
			if(flag&&!vis[y]){
				q2.push(y);
				vis[y]=1;
			}
		}
	}
}
int main(){
	int x,y,z;
	scanf("%d%d%d%d",&n,&m,&t,&S);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		add(y,x,z);
		for(int j=1;j<=t;j++){
			scanf("%lf",&p[i][j]);
			p[i][j]/=100000;
		}
		reverse(p[i]+1,p[i]+t+1);
	}
	dijkstra();
	while(limit<=4*t)
	limit<<=1,bit++;
	for(int i=0;i<limit;i++)
	rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
	spfa();
	//for(int i=0;i<=2*t;i++)
	printf("%.10lf",dis[1][0]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值