环形变换(矩阵快速幂+特殊矩阵加速)(北理16校赛)


时间限制1秒 内存限制64M

题目描述:

        给一个等距划分为n个格子的环形容器,每个格子内有一个数,取值为 [ 0 , m ) ,给定距离 d ,定义一次操作之后,每个格子的值变为与它在环上距离(第i号格子与第j号格子的环上距离定义为 min \lbrace{} \left| i-j \right|, n-\left| i-j \right| \rbrace )不超过 d 的所有格子在操作之前的值之和模 m 。请你计算 k 次操作之后每个格子的值是多少?


输入格式:

        输入第一行为数据组数 T ( T <= 10 ) 。

        每组数据有两行。第一行为4个整数n ( 1 <= n <= 500 ) , m ( 1 <= m <= 1000000 ) , d ( 0 <= d <= n/2 ) , k ( 1 <= k <= 10000000 ) 。第二行为 n 个 [ 0 , m ) 的整数,表示每个格子的初始值。

 

输出格式:

        对于每组数据,首行输出“Case #x:”,其中x是指第x个用例,接下来一行输出与输入对应的位置格子的最终结果。

 

样例输入:

        1

        5 3 1 1

        1 2 2 1 2

 

样例输出:

        Case #1:

        2 2 2 2 1


题目大意:

              一个环形的数列,每次操作每个数变为和他距离d以内的数之和,问d次操作后每个位置是什么数字

解题思路:

             由于每个数的下一次操作后的结果就是当前的周围2d-1个数的线性组合,所以可以构造矩阵,对矩阵中每一行,将当前行的数在操作中要加的数字对应的位置设为1,那么这个矩阵乘k次方再乘初始的数字向量即为答案,但是这样是不够的!!,因为500*500*500*40*log(1e7)会t,而且栈也开不下,oj也不能扩栈,所以需要优化。

             当时做的时候也是一时没有思路,然后打了个表,发现每次乘完的矩阵都有着奇妙的性质:

             1.有对称性

             2.下一行是上一行的数字右移一位得到的(最后一个移到第一个位置)

有了这个性质,就可以只记录下矩阵的第一行,考察矩阵乘的方式,求解这个第一行的每一列就是将第一行和要乘的矩阵每一列线性组合,而要成的每一列由对称性就是每一行,而每一行又都是可以由第一行右移得到的,通过这种方式,时间复杂度/=500(求矩阵中每个数复杂度不变,但是只需要一行数所以/500),而且腰不疼了,腿不算了,连开栈外挂都不需要了。(乱入广告。。

//#pragma comment(linker,"/STACK:1024000000,1024000000") 
#include <iostream>
#include<stdio.h>
#include <algorithm>
#include <vector>
#include <cstring>
#include <cmath>
#include <map>
#include <queue>
#include <string>
#include <set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int INF=0x3f3f3f3f;
#define CLR0(a) (memset(a,0,sizeof(a)))
#define CLR1(a) (memset(a,-1,sizeof(a)))
#define CLRf(a) (memset(a,0x3f,sizeof(a)))
const int maxn=0;
int n,m,d,k;
struct mat{ll a[550];};
ll abss(ll x)
{
	if(x>0)return x;
	return -x;
}
mat mat_mul(mat a,mat b)
{
	mat c;
	for(int i=0;i<n;i++)
	{
		c.a[i]=0;
		for(int j=0;j<n;j++)
		{
			c.a[i]+=a.a[j]*b.a[(j-i+n)%n]%m;
		}
	}
	return c;
}
mat qpow(mat a,int b)
{
	mat ans;
	CLR0(ans.a);
	ans.a[0]=1;
	while(b)
	{
		if(b&1)ans=mat_mul(ans,a);
		a=mat_mul(a,a);
		b>>=1;
	}
	return ans;
}
ll num[550],ans[550];
mat tem;
int main()
{
	int t,ca=1;
	scanf("%d",&t);
	while(t--)
	{
		CLR0(ans);
		scanf("%d%d%d%d",&n,&m,&d,&k);
		for(int i=0;i<1;i++)
			for(int j=0;j<n;j++)
				tem.a[j]=((min(abss(i-j),n-abss(i-j))<=d)?1:0);
		for(int i=0;i<n;i++)scanf("%lld",&num[i]);
		tem=qpow(tem,k);
		for(int i=0;i<n;i++)
			for(int j=0;j<n;j++)
					ans[i]+=(tem.a[(j-i+n)%n]*num[j])%m;
		printf("Case #%d:\n",ca++);
		for(int i=0;i<n;i++)
			printf("%lld%c",(ans[i]+m)%m,i==n-1?'\n':' ');
	}
	return 0;
}


           

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值