20200715模拟赛1题解

A. 数列

题目描述

  • 下面数列的第 n 项:

输入格式

  • 包含 1 行,共 8 个整数:

输出格式

  • 输出 f(n) 的后 18 位(后 18 位的前缀 0 需要输出,不足 18 位用 0 补齐)。

样例输入

1 2 3 4 5 6 7 3

样例输出

000000000000000035	

数据范围与提示

  • 对于 30% 的数据,
  • 对于 100% 的数据,

Solve

  • 一看题目就能想到矩阵快速幂,可是我没想到要用高精度(主要是好长时间不写了),后来看到了林sir的压位高精,把18位的数拆成两个9位的进行运算,好写而且快。

Code

#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
const ll M = 1e18, N = 1e9;
void write(ll x) {
    char c[20];
    int tot = 0;
    for (int i = 1; i <= 18; ++i)
        c[i] = '0';
    while (x) c[++tot] = x % 10 + '0', x /= 10;
    for (int i = 18; i; --i)
        putchar(c[i]);
    puts("");
}
struct Lint {
    ll a;
    Lint() { 
        a = 0; 
    }
    ll operator * (const Lint &b) {
        ll x1 = a / N;
        ll x2 = a % N;
        ll y1 = b.a / N;
        ll y2 = b.a % N;
        return ((x1 * y2 + x2 * y1) % N * N + x2 * y2) % M;
    }
};
struct Matrix {
    Lint a[5][5];
    Matrix() { 
        memset(a, 0, sizeof(a));
    }
    Matrix operator * (const Matrix &b) {
        Matrix c;
        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].a += a[i][k] * b.a[k][j],
                    c.a[i][j].a %= M;
        return c;
    }
}a;
Lint a0, a1, a2, b[5];
ll n, ans;
Matrix Qpow(Matrix a, ll x) {
    Matrix ans = a; --x;
    while (x) {
        if (x & 1) ans = ans * a;
        a = a * a;
        x >>= 1;
    }
    return ans;
}
int main() {
    scanf("%lld%lld%lld%lld%lld%lld%lld%lld", &b[3].a, &b[2].a, &b[1].a, &a.a[1][1].a, &a.a[2][1].a, &a.a[3][1].a, &a.a[4][1].a, &n);
    a.a[1][2].a = a.a[2][3].a = a.a[4][4].a = 1;
    b[4].a = 1;
    a = Qpow(a, n - 2);
    for (int k = 1; k <= 4; ++k)
        ans += b[k] * a.a[k][1], ans % M;
    write(ans);
    return 0;
}

B. 旗木双翼

题目描述

  • 菲菲和牛牛在一块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<=10
  • 对于 30%的数据n,m<=20
  • 另有 20%的数据n<=5
  • 另有 20%的数据m<=5
  • 对于100%的数据n,m<=100000

Solve

  • 考试时用了个神奇的方法得了70,原来原理这么简单,就是求\(C_{m+n}^{n}\)
    其中做除法的时候要用到乘法逆元。

Code

#include <cstdio>
#define int long long
using namespace std;
const int M = 998244353;
int n, m, jnm, b = 1;
int qpow(int a, int x) {
    int ans = 1;
    while (x) {
        if (x & 1) ans = ans * a % M;
        a = a * a % M;
        x >>= 1;
    }
    return ans;
}
signed main() {
    scanf("%lld%lld", &n, &m);
    for (int i = 2, a = 1; i <= m + n; ++i) {
        a = a * i % M;
        if (i == n) b = b * a % M;
        if (i == m) b = b * a % M;
        if (i == m + n) jnm = a;
    }
    printf("%lld\n", jnm * qpow(b, M - 2) % M);
    return 0;
}

C. 乌龟棋

题目描述

  • 小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
  • 乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数)。棋盘第1格是唯一的起点,第N格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。 ……
  • 1 2 3 4 5……N
  • 乌龟棋中M张爬行卡片,分成4种不同的类型(M张卡片中不一定包含所有4种类型的卡片,见样例),每种类型的卡片上分别标有1、2、3、4四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
  • 游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
  • 很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
  • 现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?

输入格式

  • 第1行2个正整数N和M,分别表示棋盘格子数和爬行卡片数。
  • 第2行N个非负整数,a1, a2,……, an,其中ai表示棋盘第i个格子上的分数。
  • 第3行M个整数,b1,b2,……, bm,表示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≤N≤30,1≤M≤12。
  • 对于50%的数据有1≤N≤120,1≤M≤50,且4种爬行卡片,每种卡片的张数不会超过20。
  • 对于100%的数据有1≤N≤350,1≤M≤120,且4种爬行卡片,每种卡片的张数不会超过40;0≤ai≤100,1≤i≤N;1≤bi≤4,1≤i≤M。输入数据保证N−1=\(\sum_{i = 0}^{n}M_i\times bi\)

Sove

  • 考试的时候本来想到正确的DP了,可是忘记加第一个数,一直没调过去,就打了个暴力

Code

#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 355, M = 45;
int n, m, w[N], a[5], f[M][M][M][M];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &w[i]);
    for (int i = 1, x; i <= m; ++i)
        scanf("%d", &x), ++a[x];
    f[0][0][0][0] = w[1];
    for (int i = 0; i <= a[1]; ++i)
        for (int j = 0; j <= a[2]; ++j)
            for (int k = 0; k <= a[3]; ++k)
                for (int l = 0; l <= a[4]; ++l) {
                    int y = 1 + i + j * 2 + k * 3 + l * 4;
                    if (i) f[i][j][k][l] = max(f[i][j][k][l], f[i-1][j][k][l] + w[y]);
                    if (j) f[i][j][k][l] = max(f[i][j][k][l], f[i][j-1][k][l] + w[y]);
                    if (k) f[i][j][k][l] = max(f[i][j][k][l], f[i][j][k-1][l] + w[y]);
                    if (l) f[i][j][k][l] = max(f[i][j][k][l], f[i][j][k][l-1] + w[y]);
                }
    printf("%d\n", f[a[1]][a[2]][a[3]][a[4]]);
    return 0;
}

D. 假面舞会

题目描述

  • 一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会。

  • 今年的面具都是主办方特别定制的。每个参加舞会的人都可以在入场时选择一 个自己喜欢的面具。每个面具都有一个编号,主办方会把此编号告诉拿该面具的人。为了使舞会更有神秘感,主办方把面具分为k (k≥3)类,并使用特殊的技术将每个面具的编号标在了面具上,只有戴第i 类面具的人才能看到戴第i+1 类面具的人的编号,戴第k 类面具的人能看到戴第1 类面具的人的编号。 参加舞会的人并不知道有多少类面具,但是栋栋对此却特别好奇,他想自己算出有多少类面具,于是他开始在人群中收集信息。 栋栋收集的信息都是戴第几号面具的人看到了第几号面具的编号。如戴第2号面具的人看到了第5 号面具的编号。栋栋自己也会看到一些编号,他也会根据自己的面具编号把信息补充进去。由于并不是每个人都能记住自己所看到的全部编号,因此,栋栋收集的信 息不能保证其完整性。现在请你计算,按照栋栋目前得到的信息,至多和至少有多少类面具。由于主办方已经声明了k≥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 ≤ 300, m ≤ 1000;
  • 100%的数据,满足n ≤ 100000, m ≤ 1000000。

Solve

  • 对于这些条件可以建出一张图,里面可能有链或有环
    • 对于链来说,多少种都可以
    • 对于环种类数是每个环大小的公约数
  • 所以可以得到
    • 最大值:如果有环就是每个环大小的最大公约数,反之就是所有链的长度
    • 最大值:如果有环就是每个环大小不小于3的最小公约数,反之答案为3

Code

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e7+10;
int cnt = 1,head[maxn];
int vis[maxn],dis[maxn];
int flag[maxn];
int ans;
int Max,Min;

struct node{
    int next,to,dis;
}a[maxn];

int gcd(int x,int y){return y == 0 ? x : gcd(y , x % y);}

void add(int x,int y,int z){
    a[++cnt].to = y;
    a[cnt].next = head[x];
    a[cnt].dis = z;
    head[x] = cnt;
}

void Dfs(int u){
    vis[u] = 1;
    for(int i = head[u];i;i = a[i].next){
        int v = a[i].to;
        if(!vis[v]){
            dis[v] = dis[u] + a[i].dis;
            Dfs(v);
        }
        else{
            ans = gcd(ans,abs(dis[u] + a[i].dis - dis[v]));
        }
    }
}

void dfs(int u){
    Max = max(Max,dis[u]);
    Min = min(Min,dis[u]);
    vis[u] = 1;
    for(int i = head[u];i;i = a[i].next){
        if(!flag[i]){
            flag[i] = flag[i ^ 1] = 1;
            int v = a[i].to;
            dis[v] = dis[u] + a[i].dis;
            dfs(v);
        }
    }
}

int main(){
    int n,m;scanf("%d%d",&n,&m);
    //memset(head,-1,sizeof(head));
    for(int i = 1;i <= m;++i){
        int x,y;scanf("%d%d",&x,&y);
        add(x,y,1);add(y,x,-1);
    }
    for(int i = 1;i <= n;++i){
        if(!vis[i])Dfs(i);
    }
    if(ans){
        if(ans < 3){printf("-1 -1\n");return 0;}
        int i;
        for(i = 3;i <= n;++i)if(ans % i == 0)break;
        printf("%d %d",ans,i);
        return 0;
    }
    memset(vis,0,sizeof(vis));
    for(int i = 1;i <= n;++i){
        if(!vis[i]){
            Max = Min = 0;
            dfs(i);
            ans += Max - Min + 1;
        }
    }
    if(ans < 3){printf("-1 -1");return 0;}
    printf("%d 3",ans);
    return 0;
}
posted @ 2020-07-16 21:38  Shawk  阅读(203)  评论(0)    收藏  举报