1. 程式人生 > 實用技巧 >20200715模擬賽1題解

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;
}