纪中DAY5做题小结

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/ha_ing/article/details/99728010

T1:直角三角形

Description
  二维平面坐标系中有N个点。
  从N个点选择3个点,问有多少选法使得这3个点形成直角三角形。

Input
  第一行包含一个整数N(3<=N<=1500),表示点数。
  接下来N行,每行包含两个用空格隔开的整数表示每个点的坐标,坐标值在-10^9 到10^9之间。
  每个点位置互不相同。

Output
  输出直角三角形的数量。

Sample Input
输入1:

3
4 2
2 1
1 3

输入2:

4
5 0
2 6
8 6
5 7

输入3:

5
-1 1
-1 0
0 0
1 0
1 1

Sample Output
输出1:

1

输出2:

0

输出3:

扫描二维码关注公众号,回复: 7573622 查看本文章
7

简要思路:这题 O ( n 3 ) O(n^3) 暴力枚举肯定会超时,而且如果是计算平方和必须用long long增大常数。我们考虑是否能找到一种 O ( n 2 ) O(n^2) 的做法。首先,我们要优化找到直角三角形的方法,摒弃传统的平方和方法,改用向量判断,只要注意特判与 x x 轴, y y 轴平行的情况,并且注意将向量缩短(同时除以共同的GCD)以方便统计同类向量的数量,以及统一将向量的 x x 轴改为正数,如果 x x 轴以前为正数则将 y y 轴的数值取反(也就是将向量旋转 18 0 180^。 ,这不影响对垂直的判定)。这样一来,我们只要先选中一个点,用上述方法枚举其他的点与原先选中的点构造一组向量,统计相同向量的数目。接下来,再用同样的方法构造向量,置换 x x 轴与 y y 轴并取反 y y 轴反向查找,找到的向量就是与原先被处理前向量相垂直的向量,将答案加上找到向量的数量,只需用 O ( n 2 ) O(n^2) 枚举即可得出结果。
还有一点要注意,就是本题对常数的限制较为严格,记得加上各种比赛允许的常数优化,并且不要用long long以至增大常数。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#include <algorithm>
#define ll long long
#define mp(a,b) make_pair(a,b)
#define pr pair<int,int>
using namespace std;
const int N = 2005;
int n;
map<pr,int> f;
int ans = 0;
int dx , dy;
struct node{
	int x;
	int y;
}num[N];
void read( int & res ) {
	res = 0;
	int pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
int gcd( int a , int b ) {
	return b == 0 ? a : gcd( b , a % b );
}
int main () {
	read(n);
	for ( register int i = 1 ; i <= n ; ++i ) {
		read( num[i].x );
		read( num[i].y );
	}
	for ( register int i = 1 ; i <= n ; ++i ) {
		f.clear();
		for ( register int j = 1 ; j <= n ; ++j ) {
			if ( i != j ) {
				dx = num[i].x - num[j].x;
				dy = num[i].y - num[j].y;
				if ( !dx ) {
					pr now = mp(0,1);
					if ( f.find(now) != f.end() ) {
						f[now] = f[now] + 1;
					} else {
						f[now] = 1;
					}
				} else if ( !dy ) {
					pr now = mp(1,0);
					if ( f.find(now) != f.end() ) {
						f[now] = f[now] + 1;
					} else {
						f[now] = 1;
					}
				} else {
					if ( dx < 0 && dy < 0 ) {
						dx = -dx;
						dy = -dy;
					} else if ( dx < 0 || dy < 0 ) {
						dx = -abs(dx);
						dy = abs(dy);
					}
					int d = gcd( abs(dx) , abs(dy) );
					dx /= d;
					dy /= d;
					pr now = mp( dx , dy );
					if ( f.find(now) != f.end() ) {
						f[now] = f[now] + 1;
					} else {
						f[now] = 1;
					}
				}
			}
		}
		for ( register int j = 1 ; j <= n ; ++j ) {
			if ( i != j ) {
				dx = num[i].x - num[j].x;
				dy = num[i].y - num[j].y;
				if ( !dx ) {
					pr now = mp(1,0);
					if ( f.find(now) != f.end() ) {
						ans += f[now];
					}
				} else if ( !dy ) {
					continue;
				} else {
					if ( dx < 0 && dy < 0 ) {
						dx = -dx;
						dy = -dy;
					} else if ( dx < 0 || dy < 0 ) {
						continue;
					}
					int d = gcd( abs(dx) , abs(dy) );
					dx /= d;
					dy /= d;
					pr now = mp(-dy,dx);
					if ( f.find(now) != f.end() ) {
						ans += f[now];
					}
				}
			}
		}
	}
	printf("%d",ans);
	return 0;
}

T2:排序

Description
  你收到一项对数组进行排序的任务,数组中是1到N个一个排列。你突然想出以下一种特别的排序方法,分为以下N个阶段:
  •阶段1,把数字1通过每次交换相邻两个数移到位置1;
  •阶段2,用同样的方法把N移到位置N;
  •阶段3,把数字2移到位置2处;
  •阶段4,把数字N-1移到位置N-1处;
  •依此类推。
  换句话说,如果当前阶段为奇数,则把最小的未操作的数移到正确位置上,如果阶段为偶数,则把最大的未操作的数移到正确位置上。
  写一个程序,给出初始的排列情况,计算每一阶段交换的次数。

Input
  第一行包含一个整数N(1<=N<=100000),表示数组中元素的个数。
  接下来N行每行一个整数描述初始的排列情况。

Output
  输出每一阶段的交换次数。

Sample Input
输入1:

3
2
1
3

输入2:

5
5
4
3
2
1

输出3:

7
5
4
3
7
1
2
6

Sample Output
输出1:

1
0
0

输出2:

4
3
2
1
0

输出3:

4
2
3
0
2
1
0

Hint
【数据范围】
  70%的数据N<=100

简要思路:这题我一开始的思路是用归并排序,不过毫无悬念地超时了。实际上,这题有个非常快并且简单的做法,就是用树状数组保存一个数左边有多少个数没处理完,处理完一个点后将它的贡献删除掉。处理大数时用差分思想即可,时间复杂度为 O ( n log n ) O(n \log n)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e5 + 5;
int tree[N] , pos[N];
int n;
inline void read( int & res ) {
	res = 0;
	int pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
inline int lowbit( int x ) {
	return x & ( -x );
}
inline int query( int x ) {
	int ans = 0;
	for (  ; x ; x -= lowbit(x) ) {
		ans += tree[x];
	}
	return ans;
}
inline void update( int x , int y ) {
	for ( ; x <= n ; x += lowbit(x) ) {
		tree[x] += y;
	}
	return;
}
int main () {
	read(n);
	int num;
	for ( register int i = 1 ; i <= n ; ++i ) {
		read( num );
		pos[num] = i;
		update( pos[num] , 1 );
	}
	int ans;
	for ( register int i = 1 ; i <= n ; ++i ) {
		int j;
		if ( i % 2 ) {
			j = ( i / 2 ) + 1;
			ans = query( pos[j] - 1 );
		} else {
			j = n + 1 - ( i / 2 );
			ans = query( n ) - query( pos[j] );
		}
		update( pos[j] , -1 );
		printf("%d\n",ans);
	}
	return 0;
}

T3:自行车赛

Description
  翠亨村举行一场自行车赛,翠亨村有N个路口(编号1到N),另有M条双向边连接起来。下面有几个定义:
  •路径:由一系列边组成,满足后一条边的起点为前一条边的终点;
  •简单路径:每个路口最多经过一次的路径;
  •环:起点和终点在同一个路口的简单路径。
  保证每对路口之间至少有一条路径相连,除此之外还满足每条边最多只会出现在一个环中。
  你的任务是找出最长的满足以下两个条件的路径:
  •起点可以在任意路口,但终点必须在1号路口;
  •路径可能多次经过同一个路口,但每条边最多只会经过一次。

Input
  第一行包含两个整数N和M(2<=N<=10000,1<=M<=2N-2),表示路口数量和边的数量。
  接下来M行,每行包含两个不同的整数A和B(1<=A,B<=N),表示A和B之间存在一条边直接相连,两个路口之间最多只有一条边直接相连。

Output
  输出最长的比赛路径的长度。

Sample Input
输入1:

4 3
1 2
1 3
2 4

输入2:

6 6
1 2
1 3
2 4
3 4
3 5
5 6

输入3:

5 6
1 2
2 3
3 4
4 5
5 3
3 1

Sample Output
输出1:

2

输出2:

5

输出3:

6

简要思路:以前一直不明白自己怎么会搞不定这题,后面才发现题目给的是标准的毒瘤 仙人掌图。根据题意,我们不难看出,这道题的图只有两种边,不是属于其中某一个环就是属于图的割边集,边只有两种,那应该不会太难吧。但是,这反而是本题最大的难点,在这样的情形下,一个点可能属于多个环,由此衍生的情况无穷无尽,变幻多端(为什么在情人节出这么毒瘤的题啊 )。如果还没有看出本题情况多的,请自行想出一种做法,再看看能否考虑到以下情况:

图1
环上有个节点有一条长链,是走环还是走环上的链?

图2
走完所在环以及环上儿子节点所在环还是走完链牺牲部分环?

图3
问题同上,但加上走法是否改变?

图4
正解不难自己看出但自己的算法能找出正解吗?

首先,我偷懒地贴上某位dalao翻译的题解:
以下内容如果看不下去请忍(ma)耐(shang)一(tiao)下(guo)(但不要漏了标黄语句)

首先将寻找终点为1号结点的路径改为寻找起点为1号结点的路径(转换思想,别说你没想到)
然后对于每一条边,它要么是环的一部分,要么是一个桥。
首先可以用tarjan算法辨认一条边是环的一部分还是桥,并记录dfn,lowlink等数值。
对于一个环,我们说它“悬挂”在结点X上,当且仅当X是这个环中dfn数值最小的;对于一个桥,我们说它“悬挂”在结点X上,当且仅当X是这个桥中dfn较小的那一个。
对于每个结点X,我们定义它的子图为结点X以及“悬挂”在X上的环的所有结点的子图以及“悬挂”在X上的桥的另一结点的子图的并集(以后我也这么表述)
对于每一个结点X维护两个数值,circle(X) - X的子图中始于X终于X的路径,path(X) - X的子图中始于X终于任意结点的路径。
circle(X)可以被简单的递归计算为“悬挂”在X上的所有环的长度之和以及那些环上的每一个结点Y的circle(Y)之和。
对于path(X),我们可以选择以下三种方案进行计算,并取它们结果的最大值:
一、始于X终于X的路径。即path(X)=circle(X)
二、首先在X的子图上走一个环,然后再通过一个“悬挂”在X上的桥并进入一个新的子图,使得path(X)=circle(X)+path(Y),其中Y是桥的另一边的结点
三、通过X上所有的“悬挂”在X上的环,直到到达环上的一个被选定的结点才结束。在这种情况下有两个方向到达该结点,我们需要选择路径较长的那一个
最后输出path(1)即可。

根据以上思路,我敲出了以下五十分代码,这份代码能正确判断上述四种情况,在这份代码中我使用时间戳来判断环的长度,这份代码唯一失策的地方在于它在判断一个父节点到“挂起”的子环上的某一点的最长路径时,没有考虑到子环上的节点也有挂起的子环,导致误判最长路径。
这份代码没有注释,实在看不懂不要硬扛了,毕竟这不是AC代码,请跳过。

//五十分
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 10005 , M = 55005;
int vis[N] , head[N] , sr[N] , mas[N] , pos[N] , dfn[N] , low[N] , path[N] , msp[N] , belong[N] , len[M];
struct node{
	int next;
	int to;
}str[M];
int pd[M];
int n , m , bcnt = 1 , tot , totcir;
void read( int & res ) {
	res = 0;
	int pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
void insert( int from , int to ) {
	str[++bcnt].next = head[from];
	head[from] = bcnt;
	str[bcnt].to = to;
	return;
}
void tarjan( int cur , int fa ) {
	low[cur] = dfn[cur] = ++tot;
	for ( int i = head[cur] ; i ; i = str[i].next ) {
		int sn = str[i].to;
		if ( sn == fa ) {
			continue;
		}
		if ( !dfn[sn] ) {
			pos[sn] = pos[cur] + 1;
			tarjan( sn , cur );
			if ( mas[sn] && mas[sn] != cur ) {
				mas[cur] = mas[sn];
				belong[cur] = belong[sn];
			}
			if ( low[cur] > low[sn] ) {
				low[cur] = low[sn];
			}
			if ( low[sn] > dfn[cur] ) {
				pd[i] = pd[i ^ 1] = 1;
			}
		} else if ( dfn[sn] < low[cur] ) {
			low[cur] = dfn[sn];
			mas[cur] = sn;
			belong[cur] = ++totcir;
			len[totcir] = pos[cur] - pos[sn] + 1;
			sr[sn] += len[totcir];
		}
	}
	return;
}
void dfs( int cur , int fa ) {
	vis[cur] = 1;
	path[cur] = sr[cur];
	int mtot = 0;
	for ( int i = head[cur] ; i ; i = str[i].next ) {
		int sn = str[i].to;
		if ( sn == fa ) {
			continue;
		}
		if ( !vis[sn] ) {
			dfs( sn , cur );
		}
		if ( pd[i] ) {
			mtot = max( mtot , path[sn] + 1 );
		}
	}
	path[cur] = sr[cur] + mtot;
	path[cur] = max( path[cur] , msp[cur] );
	if ( mas[cur] ) {
		msp[mas[cur]] = max( msp[mas[cur]] , path[cur] + max( pos[cur] - pos[mas[cur]] , len[belong[cur]] - ( pos[cur] - pos[mas[cur]] ) ) );
		sr[mas[cur]] += sr[cur];
	}
	return;
}
int main () {
	read(n);
	read(m);
	int a , b;
	for ( int i = 1 ; i <= m ; ++i ) {
		read(a);
		read(b);
		insert( a , b );
		insert( b , a );
	}
	tarjan( 1 , 0 );
	dfs( 1 , 0 );
	printf("%d",path[1]);
	return 0;
}

附上能卡掉我五十分代码的数据以及图示:
输入:

40 50
36 1
1 14
20 1
20 31
31 11
6 11
1 6
31 35
16 35
16 31
11 39
16 21
21 8
20 27
1 29
24 8
22 24
22 26
8 26
16 33
15 33
16 15
9 35
9 25
25 37
38 37
35 38
4 6
10 4
10 34
34 6
5 22
30 5
18 30
18 22
32 29
40 32
29 40
14 13
13 19
19 14
22 7
2 5
3 2
12 3
12 17
17 5
33 23
28 23
28 33

输出

35

图示:
图5
好吧,看来靠别人还是不太好,最后还得自己想出一个好办法啊。使用死板的数型DP难以处理卡掉我五十分代码的情况。后来,我上网查了一下仙人掌树的处理方法,发现直接将环换成特殊但不构成环的边是处理仙人掌树的一种重要思想。对于本题,我们考虑对桥原封不动,对于环则将"挂起"于环的节点与环的其他节点各连一条边,去掉原来的环边。并且对两种新边打上不同的标记。
如下图:
图5
经过上述处理后:
图6
标黑的为先前环上的节点。

以下是代码,有注释,通过注释加手动绘图理解一下吧。

//满分代码
#include <iostream>
#include <cstdio>
#include <cstring>
#define N 10005
#define M 40025
using namespace std;
int visp[N] , viss[M] , fa[N] , head[N] , head1[N] , head2[N] , f[N] , dis1[N] , dis2[N];
/*f[]表示某个节点所“悬挂”的最长子图*/
/*dis2[]表示某个节点所“悬挂”的所有环的长度以及环上所有子节点所“悬挂”的环的长度*/ 
/*dis1[]表示某个子节点在环上到“悬挂”于环的父节点的最短距离(包括自身“悬挂”的环),
用减法算出最长距离(不包括自身“悬挂”的环),至于是否包括自身“悬挂”的环,则要通过
f[]贮存(这也是为什么不直接算最长距离)*/
int n , m , bcnt , bcnt1 , bcnt2 , apcir;
/*apcir表示未处理的环数*/ 
struct no{
	int next;
	int to;
}str[M] , str1[M];
struct node{
	int next;
	int to;
	int val;
}str2[M];
inline void read( int & res ) {
	res = 0;
	int pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar(); 
	}
	res *= pd;
	return;
}
inline void insert( int from , int to ) {//存的是原始边 
	str[++bcnt].next = head[from];
	head[from] = bcnt;
	str[bcnt].to = to;
	return;
}
inline void insert1( int from , int to ) {//存的边用来访问处理环 
	str1[++bcnt1].next = head1[from];
	head1[from] = bcnt1;
	str1[bcnt1].to = to;
	return;
}
inline void insert2( int from , int to , int val ) {//存的是处理后的边 
	str2[++bcnt2].next = head2[from];
	head2[from] = bcnt2;
	str2[bcnt2].to = to;
	str2[bcnt2].val = val;//打标记 
	return;
}
inline int min( int x , int y ) {
	return x < y ? x : y;
}
inline int max( int x , int y ) {
	return x > y ? x : y;
}
inline void findcircle( int top , int cur ) {
	insert2( top , cur , 1 );
	if ( fa[cur] == top ) {//搜完一个完整的环 
		dis2[top] += dis1[cur] + 1;
		dis1[cur] = min( dis1[cur] , dis2[cur] + 1 );
		return;
	}
	dis1[fa[cur]] = dis1[cur] + dis2[fa[cur]] + 1;//先只考虑一边,便于统计环的总长 
	findcircle( top , fa[cur] );
	dis1[cur] = min( dis1[cur] , dis1[fa[cur]] + 1 + dis2[cur] );//考虑两边 
	return;
}
inline void dfs( int cur ) {
	visp[cur] = 1;
	for ( int i = head[cur] ; i ; i = str[i].next ) {
		if ( !viss[i] ) {
			viss[i^1] = viss[i] = 1;
			int sn = str[i].to;
			if ( visp[sn] ) {
				insert1( sn , cur );
				apcir++;//出现了一个环
			} else {
				int det = apcir;
				dfs( sn );
				fa[sn] = cur;//记录fa值,回溯搜环时要用到。 
				if ( det == apcir ) {/*判断其是否为链儿子节点。原理:节点sn的出现没有改变未处理环的数量
				,证明它与父节点连的边是桥,不属于任何一个环*/ 
					insert2( cur , sn , 0 );
				}
			}
		}
	}
	for ( int i = head1[cur] ; i ; i = str1[i].next ) {//处理环 
		int sn = str1[i].to;
		dis1[sn] = dis2[sn] + 1;
		findcircle( cur , sn );
		apcir--;//处理完了一个环
	}
	for ( int i = head2[cur] ; i ; i = str2[i].next ) {//用新边得出最终值 
		int sn = str2[i].to;
		int pos = str2[i].val;
		if ( pos ) {
			f[cur] = max( f[cur] , f[sn] + dis2[cur] - dis1[sn] );//第一种情况
		} else {
			f[cur] = max( f[cur] , dis2[cur] + f[sn] + 1 );//第二种情况
		}
	}
	f[cur] = max( f[cur] , dis2[cur] );//第三种情况 
	return;
}
int main () {
	read(n);
	read(m);
	bcnt = 1;
	int x , y;
	for ( int i = 1 ; i <= m ; ++i ) {
		read(x);
		read(y);
		insert( x , y );
		insert( y , x ); 
	}
	dfs( 1 );
	printf("%d",f[1]);
	return 0;
}

如果还不能理解,勤动脑思考吧,我也帮不了你了。

T4:小L的数列

Description
图7

Input
一行两个整数n和k。
之后1行k个正整数b1…bk。
之后1行k个正整数f1…fk。

Output
输出一个整数表示fn

Sample Input
【样例输入1】

5 4
1 2 3 4
4 3 2 1

【样例输入2】

100000 4
1 2 3 4
12 23 34 45

Sample Output
【样例输出1】

27648

【样例输出2】

33508797

Data Constraint
对于30%的数据,n≤10000.
对于另外20%的数据,bi=1,n≤1000000.
对于另外20%的数据,f[1]…f[k-1]=1.
对于另外20%的数据,k≤30.
对于100%的数据,1≤k≤200,1≤n≤40000000,1≤bi,fi≤998244352.

Hint
样例解释: 1 2 2 3 3 3 4 4 4 4 = 27648 1*2*2*3*3*3*4*4*4*4=27648

简要思路:这题坑不少,要小心各种卡常。注意这题要用矩阵来转移状态,但是不能直接转移 f f 的值,而是要转移乘幂的值(也不知道有哪位dalao能直接转移f的值让我膜一下 )。最后,要利用费马小定理,即 a p 1 1 ( m o d p ) a^{p - 1} \equiv 1 \pmod p ,当 p p 是质数时这个定理一定成立。
所以,一个数 a a n n 次幂取模一个质数 p p 时,可得 a n a n % ( p 1 ) ( m o d p ) a^{n} \equiv a^{n \% (p - 1)} \pmod p ,所以我们找到了在转移乘幂时不再爆炸的方法(想起论坛里大家讨论费马小定理时,有人冒出一句:“这不是很显然吗”) 。
下面附上转移矩阵,不要问我怎么写出来的,本蒟蒻也是大佬教的,你们可以用自己灵巧的双手验证它的正确性。
[ b 1 b 2 b 3 b k 1 b k 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 ] \begin{bmatrix} b_1 &amp; b_2 &amp; b_3 &amp; \cdots &amp; b_{k - 1} &amp; b_k \\ 1 &amp; 0 &amp; 0 &amp; \cdots &amp; 0 &amp; 0 \\ 0 &amp; 1 &amp; 0 &amp; \cdots &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 1 &amp; \cdots &amp; 0 &amp; 0 \\ \vdots &amp; \vdots &amp; \vdots &amp; \ddots &amp; \vdots &amp; \vdots \\ 0 &amp; 0 &amp; 0 &amp; \cdots &amp; 0 &amp; 0 \\ \end{bmatrix}

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define ll long long
#define mod 998244353//998244353是质数(宏定义不是一般的快)
using namespace std;
ll ma[205][205] , mb[205][205] , mc[205][205] , f[1000005] , b[205];
ll n , k;
ll ans = 1;
inline void read( ll & res ) {
	res = 0;
	ll pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
inline void qt1( ll num ) {
	while ( num ) {
		if ( num & 1 ) {
			memset( mc , 0 , sizeof(mc) );
			for ( register ll i = 1 ; i <= k ; ++i ) {
				for ( register ll j = 1 ; j <= k ; ++j ) {
					for ( register ll l = 1 ; l <= k ; ++l ) {
						mc[i][j] += mb[i][l] * ma[l][j];
						mc[i][j] %= ( mod - 1 );//费马小定理
					}
				}
			}
			memcpy( mb , mc , sizeof(mc) );
		}
		memset( mc , 0 , sizeof(mc) );
		for ( register ll i = 1 ; i <= k ; ++i ) {
			for ( register ll j = 1 ; j <= k ; ++j ) {
				for ( register ll l = 1 ; l <= k ; ++l ) {
					mc[i][j] += ma[i][l] * ma[l][j];
					mc[i][j] %= ( mod - 1 );//费马小定理
				}
			}
		}
		memcpy( ma , mc , sizeof(mc) );
		num >>= 1;
	}
}
inline ll qt2( ll a , ll num ) {
	ll res = 1;
	while ( num ) {
		if ( num & 1 ) {
			res *= a;
			res %= mod;
		}
		a *= a;
		a %= mod;
		num >>= 1; 
	}
	return res;
}
int main () {
	//freopen( "seq.in" , "r" , stdin );
	//freopen( "seq.out" , "w" , stdout );
	scanf("%d%d",&n,&k);
	for ( register int i = 1 ; i <= k ; ++i ) {
		ma[i][i - 1] = 1;
		mb[i][i - 1] = 1;
		read(b[i]);
		ma[1][i] = b[i];
		mb[1][i] = b[i];
	}
	for ( register int i = 1 ; i <= k ; ++i ) {
		read(f[i]);
	}
	if ( n <= k ) {
		printf("%lld",f[n]);
		return 0;
	}
	qt1( n - k - 1 );
	for ( register int i = 1 ; i <= k ; ++i ) {
		ans *= qt2( f[k - i + 1] , mb[1][i] );
		ans %= mod;
	}
	printf("%lld",ans);
	return 0;
}

最后说一句,情人节的题真毒瘤啊qwq。

猜你喜欢

转载自blog.csdn.net/ha_ing/article/details/99728010
今日推荐