暑期集训模拟赛1
前言
由于自己的不认真丢了不少分……
NO.1 数列
题目描述
下面数列的第 \(n\) 项:
\(f(0)=a_0,f(1)=a_1,f(2)=a_2\)
\(f(n)=b\times f(n−1)+c\times f(n−2)+d×f(n−3)+e(3\ge n)\)
输入格式
包含 \(1\) 行,共 \(8\) 个整数:\(a_0、a_1、a_2、b、c、d、e、n\)。
输出格式
输出 \(f(n)\) 的后 \(18\) 位(后 \(18\) 位的前缀 \(0\) 需要输出,不足 \(18\) 位用 \(0\) 补齐)。
样例输入
1 2 3 4 5 6 7 3
样例输出
000000000000000035
数据范围
对于 \(30\%\) 的数据,\(0\le a_0,a_1,a_2,b,c,d,e,n\le 10^6\)
对于 \(100\%\) 的数据,\(0\le a_0,a_1,a_2,b,c,d,e,n\le 10^{18}\)
分析
看到这样的类似于菲波那契的一个递推,很容易就能想到是矩阵快速幂(当时想到了,但是不会写……),分析一下,每一次乘以一个矩阵就能得到\(f_i\)下一个\(f_{i+1}\)的大小,输入的时候一直到\(a_2\),所以我们可以得到要求出来\(f_n\),那么只需要让矩阵乘\(n-2\)次方,然后再对应相应的位置乘以一个\(a_0,a_1,a_2\)即可。
也就是这样的矩阵:
和
这个题真是毒瘤……不光矩阵快速幂,还有高精度……高精度只需要计算18位即可,因为最后只是让输出18位,然后放代码。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod = 1e18;//取模防止爆long long
ll a0,a1,a2,b,c,d,e,n;
ll gj18(ll x,ll y){//求最后18位高精乘
int a[25]={0};
int b[25]={0};
int c[25]={0};
while(x || a[0]==0){
a[++a[0]] = x%10;
x/=10;
}
while(y || b[0]==0){
b[++b[0]] = y%10;
y/=10;
}
for (int i = 1; i <= a[0]; ++i) {
for (int j = 1; j <= b[0]; ++j) {
if (i + j - 1 <= 18) {
c[i + j - 1] += a[i] * b[j];
c[i + j] += c[i + j - 1] / 10;
c[i + j - 1] %= 10;
}
}
}
ll ret = 0;
for (int i = 18; i; --i) {
ret = ret * 10 + c[i];
}
return ret;
}
struct jz{//矩阵结构题
ll a[5][5];
void First(){//初始化答案矩阵
memset(a,0,sizeof(a));
for(int i=1;i<=4;++i){
a[i][i] = 1;
}
}
void Init(){//初始化开始的需要乘方的矩阵
memset(a,0,sizeof(a));
a[1][1] = b;
a[1][2] = c;
a[1][3] = d;
a[1][4] = e;
a[2][1] = 1;
a[3][2] = 1;
a[4][4] = 1;
}
void cheng(jz &A){//矩阵乘法
jz C;
memset(C.a,0,sizeof(C.a));
for(int i=1;i<=4;++i){
for(int j=1;j<=4;++j){
for(int k=1;k<=4;++k){
C.a[i][j] = C.a[i][j] + gj18(a[i][k],A.a[k][j]);
if(C.a[i][j]>mod)C.a[i][j]-=mod;
}
}
}
memcpy(a,C.a,sizeof(a));
}
};
jz jzqpow(ll a){//矩阵快速幂
jz ret,base;
ret.First();
base.Init();
while(a){
if(a&1)ret.cheng(base);
base.cheng(base);
a>>=1;
}
return ret;
}
void Print(ll x){//输出后18位
ll a[20];
for(int i=0;i<18;++i){
a[i] = x%10;
x/=10;
}
for(int i=17;i>=0;--i){
printf("%lld",a[i]);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>a0>>a1>>a2>>b>>c>>d>>e>>n;
jz JZ;
if(n>=3){//大于三才行
JZ=jzqpow(n-2);
}
ll ans = gj18(JZ.a[1][1],a2)+gj18(JZ.a[1][2],a1)+gj18(JZ.a[1][3],a0)+JZ.a[1][4];//因为开始的答案矩阵为四个位置的1,所以最后都需要乘以递推式子里的a2,a1,a0。
Print(ans);//输出
return 0;
}
NO.2 旗木双翼
题目描述
菲菲和牛牛在一块\(n\)行\(m\)列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手。
棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落子,直到填满棋盘时结束。落子的规则是:一个格子可以落子当且仅当这个格子内没有棋子且这个格子的左侧及上方的所有格子内都有棋子。
\(Itachi\)听说有不少学弟在省选现场\(AC\)了\(D1T1\),解决了菲菲和牛牛的问题,但是\(Itachi\)听说有的人认为复杂度玄学,\(Itachi\)并不想难为学弟学妹,他想为大家节约时间做剩下的题,所以将简化版的\(D1T1\)带给大家。
\(Itachi\)也在一块\(n\)行\(m\)列的棋盘上下棋,不幸的是\(Itachi\)只有黑棋,不过幸好只有他一个人玩。现在,\(Itachi\)想知道,一共有多少种可能的棋局(不考虑落子顺序,只考虑棋子位置)。
\(Itachi\)也不会为难学弟学妹们去写高精度,所以只需要告诉\(Itachi\)答案\(mod\) \(998244353\)(一个质数)的结果。
输入格式
第一行包括两个整数\(n\),\(m\)表示棋盘为\(n\)行\(m\)列。
输出格式
一个整数表示可能的棋局种数。
样例
样例输入#1
1 1
样例输出#1
2
样例输入#2
2 3
样例输出#2
10
样例输入#3
10 10
样例输出#3
184756
数据范围与提示
对于 \(20\%\)的数据\(n,m\le 10\)
对于 \(30\%\)的数据\(n,m\le 20\)
另有 \(20\%\)的数据\(n\le 5\)
另有 \(20\%\)的数据\(m\le 5\)
对于\(100\%\)的数据\(n,m\le 100000\)
分析
首先看到这个矩阵,其实就是题目给出的矩阵,其中\(n\)行\(m\)列的格子填的数字就是\(n\)行\(m\)列的矩阵的最多方案数。把它歪过来看,让最小的\(2\)为一个顶点,我们可以发现如果在每个的外边加一个\(1\),这不就是杨辉三角吗!根据这个我们就可以知道\(n\)行\(m\)列的数字应该就是一个组合数,即\(C_{m+n}^m\)或者\(C_{m+n}^n\)(其实都一样),把这个组合数化简开,就是:
因为最后需要\(mod\ 998244353\),而这个数又刚刚好是一个质数,所以我们就可以求出来\((m+n)!\)然后求一下下边需要除的那个阶乘的逆元(因为除法没办法取模),然后乘起来。我在处理的时候直接先除以了一个\(m!\),所以求阶乘的是从\(m+1\)开始。完事!
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int Mod=998244353;
int qpow(int x,int cnt){//快速幂
int ans=1;
while(cnt){
if(cnt & 1) ans=(ans*x)%Mod;
x=(x*x)%Mod; cnt>>=1;
}
return ans;
}
int jc=1,ans=1;
signed main(){
int n,m;
scanf("%lld%lld",&n,&m);
for(int i=m+1;i<=m+n;++i)
jc=(jc*i)%Mod;//求出(m+n)!/m!
for(int i=1;i<=n;++i) ans=(ans*i)%Mod;//求出n!的逆元
ans=qpow(ans,Mod-2);//费马小定理求逆元
printf("%lld\n",ans*jc%Mod);
return 0;
}
NO.3 乌龟棋
题目描述
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘是一行\(N\)个格子,每个格子上一个分数(非负整数)。棋盘第\(1\)格是唯一的起点,第\(N\)格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。\(1\ 2\ 3\ 4\ 5……N\)
乌龟棋中\(M\)张爬行卡片,分成\(4\)种不同的类型(\(M\)张卡片中不一定包含所有\(4\)种类型的卡片,见样例),每种类型的卡片上分别标有\(1、2、3、4\)四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
输入格式
第\(1\)行\(2\)个正整数\(N\)和\(M\),分别表示棋盘格子数和爬行卡片数。
第\(2\)行\(N\)个非负整数,\(a_1, a_2,……, a_n\),其中\(a_i\)表示棋盘第\(i\)个格子上的分数。
第\(3\)行\(M\)个整数,\(b_1,b_2,……, b_m\),表示\(M\)张爬行卡片上的数字。
输入数据保证到达终点时刚好用光\(M\)张爬行卡片,即\(N−1=\)所有卡片上的数字之和。
输出格式
输出只有\(1\)行,\(1\)个整数,表示小明最多能得到的分数。
样例
样例输入#1
9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1
样例输出1
73
样例1解释
小明使用爬行卡片顺序为\(1,1,3,1,2\),得到的分数为\(6+10+14+8+18+17=73\)。注意,由于起点是\(1\),所以自动获得第\(1\)格的分数\(6\)。
样例输入#2
13 8
4 96 10 64 55 13 94 53 5 24 89 8 30
1 1 1 1 1 2 4 1
样例输出#2
455
数据范围与提示
对于\(30\%\)的数据有\(1\le N\le 30,1\le M\le 12\)。
对于\(50\%\)的数据有\(1\le N\le 120,1\le M\le 50\),且\(4\)种爬行卡片,每种卡片的张数不会超过\(20\)。
对于\(100\%\)的数据有\(1\le N\le 350,1\le M\le 120\),且\(4\)种爬行卡片,每种卡片的张数不会超过\(40\);\(0\le a_i\le 100,1\le i\le N;1\le b_i\le 4,1\le i\le M\)。输入数据保证\(N−1=\sum_{i=1}^Mb_i\)。
分析
一个比较简单的\(dp\)(数组开小了,结果就挂了……)。因为只有四种卡片,并且保证全部用完,所以我们就可以开四维数组进行\(dp\),每一维枚举走几步使用的卡片。即定义\(f[a][b][c][d]\),表示走一步\(a\)张,走两步\(b\)张……依次类推。在状态转移的时候我们只需要从使用上一种当前卡片少用一张转移而来,最后取最大值,并且加上当前使用了这么多卡片以后那个位置的价值,定义\(nn[i]\)来存储也就是\(nn[a+2\times b+3\times c+4\times d+1]\),最后只需要输出每个卡片全部都用了的值就行,状态转移方程如下:
这里\(jl\)是为了方便下边取最大值,不要忘了判定减一之后是否小于\(0\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 45;
int f[maxn][maxn][maxn][maxn];
const int N = 400;
int nn[N],mm[N];//这里千万不要开成maxn,我就是这么错的……
int n,m;
int sum[maxn];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&nn[i]);//记录每个点的值
}
for(int i=1;i<=m;++i){
scanf("%d",&mm[i]);
sum[mm[i]]++;//记录每一种的卡片有多少个
}
for(int a=0;a<=sum[1];++a){//依次枚举每种卡片个数
for(int b=0;b<=sum[2];++b){
for(int c=0;c<=sum[3];++c){
for(int d=0;d<=sum[4];++d){
int jl1=0,jl2=0,jl3=0,jl4=0;
if(a)jl1 = f[a-1][b][c][d];//状态转移
if(b)jl2 = f[a][b-1][c][d];
if(c)jl3 = f[a][b][c-1][d];
if(d)jl4 = f[a][b][c][d-1];
f[a][b][c][d] = max(max(jl1,jl2),max(jl3,jl4))+nn[a+2*b+3*c+4*d+1];//取最大值,加上到达后的那个点的值
}
}
}
}
cout<<f[sum[1]][sum[2]][sum[3]][sum[4]]<<endl;//输出结果
return 0;
}
NO.4
题目描述
一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会。
今年的面具都是主办方特别定制的。每个参加舞会的人都可以在入场时选择一 个自己喜欢的面具。每个面具都有一个编号,主办方会把此编号告诉拿该面具的人。为了使舞会更有神秘感,主办方把面具分为\(k (k\ge 3)\)类,并使用特殊的技术将每个面具的编号标在了面具上,只有戴第\(i\) 类面具的人才能看到戴第\(i+1\) 类面具的人的编号,戴第\(k\) 类面具的人能看到戴第\(1\) 类面具的人的编号。 参加舞会的人并不知道有多少类面具,但是栋栋对此却特别好奇,他想自己算出有多少类面具,于是他开始在人群中收集信息。 栋栋收集的信息都是戴第几号面具的人看到了第几号面具的编号。如戴第\(2\)号面具的人看到了第\(5\) 号面具的编号。栋栋自己也会看到一些编号,他也会根据自己的面具编号把信息补充进去。由于并不是每个人都能记住自己所看到的全部编号,因此,栋栋收集的信 息不能保证其完整性。现在请你计算,按照栋栋目前得到的信息,至多和至少有多少类面具。由于主办方已经声明了\(k\ge 3\),所以你必须将这条信息也考虑进去。
输入格式
第一行包含两个整数\(n,m\),用一个空格分隔,\(n\) 表示主办方总共准备了多少个面具,\(m\) 表示栋栋收集了多少条信息。
接下来\(m\) 行,每行为两个用空格分开的整数\(a,b\),表示戴第\(a\) 号面具的人看到了第\(b\) 号面具的编号。相同的数对\(a,b\) 在输入文件中可能出现多次。
输出格式
包含两个数,第一个数为最大可能的面具类数,第二个数为最小可能的面具类数。
如果无法将所有的面具分为至少\(3\) 类,使得这些信息都满足,则认为栋栋收集的信息有错误,输出两个\(-1\)。
样例输出#1
6 5
1 2
2 3
3 4
4 1
3 5
样例输出#1
4 4
样例输入#2
3 3
1 2
2 1
2 3
样例输出#2
-1 -1
数据范围与提示
\(50\%\)的数据,满足\(n\le 300, m\le 1000\);
\(100\%\)的数据,满足\(n\le 100000, m\le 1000000\)。
分析
分析一下题目的意思,也就是两条性质:
1、所有看到这个点的是一类。
2、这个点看到的所有是一类。
根据这两个结论,我们就可以将这个图进行缩点操作,把同类的点先都缩成一个点,然后进行下一步。
现在只剩下了可能不同种类的点,那么这个图还是有两种情况:
1、图中有链,这种比较好分析,因为只要链的长度大于\(3\),那么任意的种类数都是可以满足的。所以链的最大就是链的长度,最小就是\(3\)。
2、有环,那么假如图中有好多环,那么种类的最大数一定是环大小的最大公约数,因为如果比最大公约数大,那么小一点的环是无法构成的,也就不满足了。而最小数就是最小的一个大于\(3\)的约数。
为了寻找环和链,我们在建边的时候正常的边赋值\(1\),反过来就是\(-1\)。最后到某个点的\(dis\),也就是距离,就是当前环或者链的大小。最后统计答案即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
struct Node{
int v,next,val;
}e[maxn<<1];
int n,m;
int ans,flag[maxn];
int Max,Min;
int tot=-1,head[maxn],vis[maxn],dis[maxn];
void Add(int x,int y,int z){//建边
e[++tot].v = y;
e[tot].next = head[x];
head[x] = tot;
e[tot].val = z;
}
void dfs1(int x){
vis[x] = 1;
for(int i=head[x];~i;i=e[i].next){
int v = e[i].v;
if(!vis[v]){//没访问过
dis[v] = dis[x] + e[i].val;
dfs1(v);
}
else{
ans = __gcd(ans,abs(dis[x]+e[i].val-dis[v]));//求出最大公约数
}
}
}
void dfs2(int x){
Max = max(Max,dis[x]);//求出到链的最远端的最大和最小距离,计算链长度
Min = min(Min,dis[x]);
vis[x] = 1;
for(int i=head[x];~i;i=e[i].next){
if(!flag[i]){//没经过这个点
flag[i] = flag[i^1] = 1;//正向边和反向边都标记,不让他回来
int v = e[i].v;
dis[v] = dis[x] + e[i].val;//记录长度
dfs2(v);
}
}
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i){
int x,y;
scanf("%d%d",&x,&y);
Add(x,y,1);//正向权值1
Add(y,x,-1);//反向-1
}
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;++i){
if(!vis[i]){//没有访问过就开始搜
dfs1(i);
}
}
if(ans){
if(ans<3)printf("-1 -1\n");//小于三不成立
else{//成立的情况
int i;
for(i=3;i<=ans;++i){
if(ans%i==0)break;//找到最小的因子
}
printf("%d %d\n",ans,i);
}
return 0;
}
memset(vis,0,sizeof(vis));//清空
for(int i=1;i<=n;++i){
if(!vis[i]){
Max = Min = dis[i] = 0;//初始化开始点
dfs2(i);
ans += Max-Min+1;//最大减最小加一就是链的长度
}
}
if(ans>=3){
printf("%d %d\n",ans,3);//种类大于等于3才行
}
else printf("-1 -1\n");//否则输出-1
return 0;
}