2019-03-29日机房爆零日记

本文记录了机房爆零日记中三道题目的解题思路,包括模拟机器人移动路径、数列求值及虫洞问题。通过分析题目特点,采用矩阵加速、SPFA算法等技巧解决大规模数据处理难题。

2019-03-29日机房爆零日记

T1:机器人

​ 【题目描述】早苗入手了最新的 Gundam 模型。最新款自然有着与以往不同的功能,是它能够自动行走,厉害吧。早苗的新模型可以按照输入的命令进行移动,命令包括‘E’、‘S’、‘W’四种,分别对应东南西北。执行某个命令时,它会向对应方向移动一个单位为新型机器人,它可以执行命令串。对于输入的命令串,每一秒它会按命令一次。执行完命令串的最后一个命令后,会自动从头开始循环。在 0 时刻时人位于(0,0)。求 T 秒后机器人所在位置坐标。

​ 【输入格式】第 1 行:一个字符串,表示早苗输入的命令串,保证至少有 1 个命令

​ 第 2 行:一个正整数 T

​ 【输出格式】2 个整数,表示 T 秒时,机器人的坐标。

​ 【样例输入】

​ NSWWNSNEEWN

​ 12

​ 【样例输出】

​ -1 3

​ 【数据范围】对于 60%的数据 T<=500,000 且命令串长度<=5,000

​ 对于 100%的数据 T<=2,000,000,000 且命令串长度<=5,0

(这道题我是不是在某谷上做过了???)

​ 首先一看题目,非常简单明了、清晰明白,一道赤裸裸的模拟题,毫无困难的算法思想对吧。然后再一看数据范围,2后面九个零,二十亿个命令。显然 O ( n ) O(n) O(n)算法行不通。所以我们应该怎么办?

​ 简单一想发现这是在进行重复劳动。所以我们只要把总命令数用命令条的字符数mod一下就好了。那么我们这个时候一共会进行 T / l e n g t h ( s ) T/length(s) T/length(s)次命令串操作。显然的,我们只需要计算一次命令串的操作,再将这个操作放大,最后加上命令串的前 T m o d &ThinSpace;&ThinSpace; l e n g t h ( s ) T \mod length(s) Tmodlength(s)次操作,就可以得到最终位置。

​ 所以,爆零赛现场并未在此题逗留,然后华丽丽的挂在了T2和T3……

T2:数列

​ 【题目描述】
a[1]=a[2]=a[3]=1;a[x]=a[x-3]+a[x-1] (x>3)
求 a 数列的第 n 项对 1000000007(10^9+7)取余的值。
【输入格式】
第一行一个整数 T,表示询问个数。
以下 T 行,每行一个正整数 n。
【输出格式】
每行输出一个非负整数表示答案。
【样例输入】
3
6
8
10
【样例输出】
4
9
19
【数据范围】
对于 30%的数据 n<=100;
对于 60%的数据 n<=210^7;
对于 100%的数据 T<=100,n<=2
10^9;

​ 这个题目嘛,看到的第一眼就发现这个数列像Fibonacci数列,然后边上已经被数学逼疯的童鞋直接开始推通项公式……然而并无卵用。

​ 想不到正解,那就先骗分吧。

30%

​ 三十分的代码异常简单,就直接手敲一个在线的暴力算法,一点问题都没有。

while (T--){
    cin >> n;
	f[1]=f[2]=f[3]=1;
	for (int i=4;i<=n;i++)
 	   f[i]=f[i-1]+f[i-3];
	cout << f[n] << endl;
}

60%

​ 六十分……也很简单,直接离线打表,读入n,输出f[n]

f[1]=f[2]=f[3]=1;
for (int i=4;i<=maxx;i++)
	f[i]=f[i-1]+f[i-3];
while (T--){
    cin >> n;
    cout << f[n] << endl;
}

100%

​ 这个算法还真的想不出来……毕竟以前没有接触过矩阵加速这个东西。是在赛后想水一发某谷,忽然就找到了这道题,竟然……一模一样!!!传送门

​ 然后果断翻了题解。本人蒟蒻一枚,对这个算法不怎么熟悉,就在下面说说我的见解:

​ 矩阵加速,首先一定要有矩阵。这个矩阵,是这样子的:
[ F [ i ] F [ i − 1 ] F [ i − 2 ] ] \begin{bmatrix} &amp;F[i]&amp; \\ &amp; F[i-1]&amp;\\ &amp; F[i-2]&amp; \end{bmatrix} F[i]F[i1]F[i2]
​ 然后显而易见的,我们可以将这个矩阵里的项从 f [ i − 1 ] , f [ i − 2 ] , f [ i − 3 ] f[i-1],f[i-2],f[i-3] f[i1],f[i2],f[i3]这么三项里推导出来,就像这样子:
∵ f [ i ] = f [ i − 1 ] + f [ i − 3 ] ∴ f [ i ] = f [ i − 1 ] × 1 + f [ i − 2 ] × 0 + f [ i − 3 ] × 1 f [ i − 1 ] = f [ i − 1 ] × 1 + f [ i − 2 ] × 0 + f [ i − 3 ] × 0 f [ i − 2 ] = f [ i − 1 ] × 0 + f [ i − 2 ] × 1 + f [ i − 3 ] × 0 \because f[i]=f[i-1]+f[i-3] \\ \therefore f[i]=f[i-1]\times 1 + f[i-2]\times 0 + f[i-3]\times 1\\ f[i-1]=f[i-1]\times 1+f[i-2]\times 0+f[i-3]\times 0\\ f[i-2]=f[i-1]\times 0+f[i-2] \times 1+f[i-3]\times 0 f[i]=f[i1]+f[i3]f[i]=f[i1]×1+f[i2]×0+f[i3]×1f[i1]=f[i1]×1+f[i2]×0+f[i3]×0f[i2]=f[i1]×0+f[i2]×1+f[i3]×0
​ 然后我们可以将三个递推式里的系数提取出来,构成一个矩阵,就像这样:
[ 1 0 1 1 0 0 0 1 0 ] \begin{bmatrix} 1 &amp; 0 &amp;1 \\ 1 &amp; 0 &amp;0 \\ 0 &amp;1 &amp;0 \end{bmatrix} 110001100
​ 然后我们发现,就可以使用矩阵快速幂来对这个初始矩阵进行 n − 3 n-3 n3次幂的求解就可以了。

​ 关于矩阵快速幂,还真没什么好说的,仅仅就是把普通快速幂里的乘法换成了矩阵乘法而已。对于掌握快速幂和对矩阵有一点了解的人都可以非常轻松的掌握。

​ 然后下面就是我们的代码了老师我相信你不会想看P的代码,就拷了一下标程(逃

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<algorithm>
const int mod=1000000007;
typedef long long arr[3][3];
int n,T;
long long ans;
arr opt={{0,0,1},{1,0,0},{0,1,1}},b,t;

using namespace std;

void mul(arr a,arr b,arr c){ //矩阵乘法
    memset(t,0,sizeof(t));
    for (int k=0;k<3;k++)
        for (int i=0;i<3;i++)
            if (a[i][k])
                for (int j=0;j<3;j++)
                    t[i][j]+=a[i][k]*b[k][j];//其实这里就是把A*B赋值到T矩阵里
    for (int i=0;i<3;i++)
        for (int j=0;j<3;j++)
            if (t[i][j]<mod) c[i][j]=t[i][j];
            else c[i][j]=t[i][j] % mod; //但我不明白为什么一定要分开来模
    									//两个循环应该可以合并成一个的
}

int main(){
    cin >> T;
    while (T--){
        cin >> n;
        arr a={{1,0,0},{0,1,0},{0,0,1}};
        memcpy(b,opt,sizeof(b));
        for (n-=3;n>0;n>>=1){ //快速幂的板子
            if (n&1) mul(a,b,a); //原来这里应该是a*=b;
            mul(b,b,b);//原来这里应该是b*=b;
        }
        ans=(a[2][0]+a[2][1]+a[2][2]) % mod;//这里就是提取矩阵的第三行,加起来之后就是答案
        printf("%d\n",ans);
    }
    return 0;
} 

T3:Holes

Problem 3 虫洞(holes.cpp/c/pas)

【题目描述】

​ N 个虫洞,M 条单向跃迁路径。从一个虫洞沿跃迁路径到另一个虫洞需要消耗一定量的燃料和 1 单位时间。虫洞有白洞和黑洞之分。设一条跃迁路径两端的虫洞质量差为 delta。

​ 1.从白洞跃迁到黑洞,消耗的燃料值减少 delta,若该条路径消耗的燃料值变为负数的话,取为 0。

​ 2.从黑洞跃迁到白洞,消耗的燃料值增加 delta。

​ 3.路径两端均为黑洞或白洞,消耗的燃料值不变化。

​ 作为压轴题,自然不会是如此简单的最短路问题,所以每过 1 单位时间黑洞变为白洞,白洞变为黑洞。在飞行过程中,可以选择在一个虫洞停留 1 个单位时间,如果当前为白洞,则不消耗燃料,否则消耗 s[i]的燃料。现在请你求出从虫洞 1 到 N 最少的燃料消耗,保证一定存在 1 到 N 的路线。

【输入格式】

​ 第 1 行:2 个正整数 N,M

​ 第 2 行:N 个整数,第 i 个为 0 表示虫洞 i 开始时为白洞,1 表示黑洞。

​ 第 3 行:N 个整数,第 i 个数表示虫洞 i 的质量 w[i]。

​ 第 4 行:N 个整数,第 i 个数表示在虫洞 i 停留消耗的燃料 s[i]。

​ 第 5…M+4 行:每行 3 个整数,u,v,k,表示在没有影响的情况下,从虫洞 u到虫洞 v 需要消耗燃料 k。

【输出格式】

​ 一个整数,表示最少的燃料消耗。

【样例输入】

​ 4 5

​ 1 0 1 0

​ 10 10 100 10

​ 5 20 15 10

​ 1 2 30

​ 2 3 40

​ 1 3 20

​ 1 4 200

​ 3 4 200

【样例输出】

​ 130

【数据范围】

​ 对于 30%的数据: 1<=N<=100,1<=M<=500

​ 对于 60%的数据: 1<=N<=1000,1<=M<=5000

​ 对于 100%的数据: 1<=N<=5000,1<=M<=30000

​ 其中 20%的数据为 1<=N<=3000 的链 1<=u,v<=N, 1<=k,w[i],s[i]<=2

​ T3这道题从看到开始心态就是炸裂的,一开始干脆就直接手打了一个bfs爆搜,直接完美爆零……

​ 直接赛后看题解,发现这题好像也不难……

​ 因为黑洞与白洞的性质完全不一样,我们干脆可以把一个虫洞拆成一个黑洞和一个白洞,然后根据题目的意思不断连边,就可以构成一个n*2的有向无环图。然后再跑一遍SPFA,就可以妥妥的满分了……

我比赛的时候是脑抽吗,竟然去打了bfs……

#include<cstdio>
#include<cstring>
#define rep(i,n) for(int i=1;i<=n;i++)
#define abs(x) (x > 0 ? x : -(x))
const int maxn=10010,maxm=70010,maxq=16383;
int n,m,u,v,k,sz,l,r,x,delta,p[maxn],w[maxn],d[maxn],q[maxq+1],en[maxn],pre[maxm],g[maxm],len[maxm];
bool Inq[maxn];
inline void Ins(int u,int v,int d){
	pre[++sz]=en[u];en[u]=sz;g[sz]=v;len[sz]=d;
}
void Init_And_Set_Map(){
	scanf("%d%d",&n,&m);
	rep(i,n) scanf("%d",&p[i]);
	rep(i,n) scanf("%d",&w[i]);
	rep(i,n){
		scanf("%d",&k);
		Ins(i+i-1,i+i,0);
		Ins(i+i,i+i-1,k);
	}
	rep(i,m){
		scanf("%d%d%d",&u,&v,&k);
		if (p[u]==p[v]){
			Ins(u+u-1,v+v,k);
			Ins(u+u,v+v-1,k);
		}else{
			delta=abs(w[u]-w[v]);
			Ins(u+u-1,v+v-1, (k-delta>0 ? k-delta : 0));
			Ins(u+u,v+v, k+delta);
		}
	}
}
void SPFA()
{
	memset(d,0x7F,sizeof(d));
	q[0]=p[1]+1;d[q[0]]=l=r=0;
	while ((r+1&maxq) != l){
		x=q[l];l=(l+1)&maxq;Inq[x]=0;
		for(int i=en[x];i;i=pre[i]){
			v=g[i];
			if (d[x]+len[i]<d[v]){
				d[v]=d[x]+len[i];
				if (Inq[v]) continue;
				Inq[v]=1;
				if (d[v]<d[q[l]]) q[l=(l-1)&maxq]=v;
				else q[r=(r+1)&maxq]=v;
			}
		}
	}
}
int main(){
	Init_And_Set_Map();
	SPFA();
	printf("%d",d[n+n-1]<d[n+n] ? d[n+n-1] : d[n+n]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值