P6772-[NOI2020]美食家【矩阵乘法,倍增】

前言

我考场(线上赛)切NOI的题了!
在这里插入图片描述


正题

题目链接:https://www.luogu.com.cn/problem/P6772


题目大意

n n n个点 m m m条边,每个城市有不同的愉悦值,从 1 1 1出发,要求经过 T T T的时间后回到点 1 1 1(不能原地停留。

特别的是有 k k k个美食节,在第 t i t_i ti个时刻如果在第 x i x_i xi个城市就可以额外获得 y i y_i yi的愉悦值,求路径上最大愉悦值


解题思路

首先矩阵乘法变形后是可以求最长路的

之后之前有一道题目 [ P O I 2015 ] W Y C [POI2015]WYC [POI2015]WYC【矩阵乘法,倍增】和这题很像,因为边权最大只有5,所以可以将每个点分成 5 5 5个点就可以跑矩阵乘法了。这样有 250 250 250个点,跑一次矩阵乘法是 O ( 25 0 3 log ⁡ T ) O(250^3\log T) O(2503logT),不考虑美食节的情况下可以过,但是如果有美食节时间复杂度就接近 O ( k ∗ 25 0 3 log ⁡ T ) O(k*250^3\log T) O(k2503logT)显然不可过。

然后之前有一道 N O I   O n l i n e NOI\ Online NOI Online的题目 N O I   O n l i n e # 3 NOI\ Online \#3 NOI Online#3提高组魔法值正解是先预处理出 l o g T log T logT个矩阵然后再用向量乘矩阵

我们就得出了解法,我们先预处理使得 F i = A 2 i F_i=A^{2^i} Fi=A2i,然后这样我们用向量乘矩阵就是 O ( n 2 ) O(n^2) O(n2)的,之后每次做矩阵到每一个美食节,然后对于在那一天的情况加上一个愉悦值即可,可以通过本题。

时间复杂度 : O ( ( n w ) 3 log ⁡ T + ( n w ) 2 k log ⁡ T ) :O((nw)^3\log T+(nw)^2k\log T) :O((nw)3logT+(nw)2klogT)


c o d e code code

#include
#include
#include
#define p(x,y) ((x)*5+(y))
#define ll long long
using namespace std;
const ll N=51;
struct matrix{
	ll a[N*5][N*5];
}f[32];
struct node{
	ll t,x,y;
}h[210];
ll n,m,T,k,tot,Size,a[N*5],p2[32],c[N*5];
matrix operator*(const matrix &a,const matrix &b){
	matrix c;memset(c.a,0xcf,sizeof(c.a));
	for(ll i=0;i<Size;i++)
		for(ll j=0;j<Size;j++)
			for(ll k=0;k<Size;k++)
				c.a[i][j]=max(c.a[i][j],a.a[i][k]+b.a[k][j]);
	return c;
}
void mul(matrix &b){
	memcpy(c,a,sizeof(c));
	memset(a,0xcf,sizeof(a));
	for(ll i=0;i<Size;i++)
		for(ll j=0;j<Size;j++)
			a[j]=max(a[j],b.a[i][j]+c[i]);
	return; 
}
void power(ll b){
	for(ll i=0;i<=tot;i++)
		if(b&p2[i])mul(f[i]);
	return;
}
bool cmp(node x,node y)
{return x.t<y.t;}
int main()
{
//	freopen("delicacy.in","r",stdin);
//	freopen("delicacy.out","w",stdout);
	scanf("%lld%lld%lld%lld",&n,&m,&T,&k);
	Size=n*5;
	memset(f[0].a,0xcf,sizeof(f[0].a));
	for(ll i=0;i<n;i++){
		scanf("%lld",&c[i]);
		for(ll j=0;j<4;j++)
			f[0].a[p(i,j+1)][p(i,j)]=0;
	}
	for(ll i=1;i<=m;i++){
		ll x,y,w;
		scanf("%lld%lld%lld",&x,&y,&w);x--;y--;
		f[0].a[p(x,0)][p(y,w-1)]=c[y];
	}
	p2[0]=1;
	while(p2[tot]*2<=T){
		tot++;p2[tot]=p2[tot-1]*2;
		f[tot]=f[tot-1]*f[tot-1];
	}
	for(ll i=1;i<=k;i++)
		scanf("%lld%lld%lld",&h[i].t,&h[i].x,&h[i].y),h[i].x--;
	sort(h+1,h+1+k,cmp);
	ll now=0;h[++k]=(node){T,0,0};
	memset(a,0xcf,sizeof(a));a[0]=c[0];
	for(ll i=1;i<=k;i++){
		power(h[i].t-now);
		a[p(h[i].x,0)]+=h[i].y;
		now=h[i].t;
	}
	if(a[0]<0)printf("-1");
	else printf("%lld",a[0]);
}

你可能感兴趣的:(数论and数学,倍增,luogu,NOI,矩阵乘法,倍增)