2021 年百度之星·程序设计大赛 - 初赛一

久违一更,距离上次更新已经半年了。

这次题目竟然不是按照难度排序

A. 迷失 (Hdu 6996)

题目大意

给定一张无向图,边的边权为\(0\)\(1\),在图上随机游走,问从1号点经过k条边到达n号点,经过的边的异或和为1的概率。

解题思路

第一眼,图上随机游走,循环概率,点数不超过100,高斯消元,不想写,跳了。

做完后面的回来看第二眼,哦,恰好经过k条边,设\(dp[i][j][k]\)表示当前第\(i\)号点,当前边权异或和为\(k\),经过\(j\)条边到达\(n\)号点且边权异或和为\(1\)的概率。

忽然看到\(k \leq 10^6\),空间爆了。注意到\(dp[i][j][k]\)总是从\(dp[?][j-1][?]\)转移过来的,于是可以用循环队列压成两维,即\(dp[cur][i][k]\)表示当前状态\((j)\)\(dp[cur^1][i][k]\)表示上一个状态\((j - 1)\),空间问题解决了,但时间复杂度\(O(mk)\)\(O(n^2k)\)还是爆了时间。

忽然发现给定一张图,转移方程式是确定且线性的,矩阵快速幂优化就可以了。时间复杂度为\(O(n^3\log k)\)

具体而言,矩阵规模是\(200 \times 200\),根据转移方程式\(dp[i][j][k] = \frac{1}{du[i]}\sum\limits_{v \in (i, v)}dp[v][j-1][k \oplus cost[(i,v)]\)构造即可。

下面代码中是根据方程式两边乘以了\(du[i]\)构造的。\(du[i]\)表示\(i\)号点的度。

结果卡常,中途运算从\(long\ long\)改成\(int\),矩阵乘法减少不必要的取模才过了。

神奇的代码
#include <bits/stdc++.h>
#include <vector>
using namespace std;
typedef long long LL;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int MO = 998244353;
const int N = 1e2 + 8;
const int M = 2e4 + 8;

int head[N], nxt[M], ty[M], cnt[N], to[M];

int inv[N];

int n, m, k, num;

inline int qpow(int a, int b) {
    int ans = 1;
    while(b) {
        if(b & 1) {
            ans = 1ll * ans * a % MO;
        }
        a = 1ll * a * a % MO;
        b = b >> 1;
    }
    return ans;
}

struct matrix {
    int n, m;
    int data[N * 2][N * 2];
    inline void clear() {
        for (int i = 0; i <= n; i++)
            for (int j = 0; j <= m; j++) data[i][j] = 0;
    }
}ff, eye;

inline matrix operator *(matrix a, matrix b) {
    matrix qwq;
    qwq.n = a.n;
    qwq.m = b.m;
    qwq.clear();
    LL tmp = 0;
    for (int i = 1; i <= a.n; i++)
        for (int j = 1; j <= b.m; j++){
            tmp = qwq.data[i][j];
            for (int k = 1; k <= a.m; k++){
                tmp += ((1ll * a.data[i][k] * b.data[k][j]));
                if (k % 15 == 0)
                    tmp %= MO;
            }
            if (tmp >= MO)
                tmp %= MO;
            qwq.data[i][j] = tmp;
        }
    return qwq;
}

inline void add(int u, int v, int w){
    num ++;
    nxt[num] = head[u];
    to[num] = v;
    ty[num] = w;
    head[u] = num;
    num ++;
    nxt[num] = head[v];
    to[num] = u;
    ty[num] = w;
    head[v] = num;
    cnt[u] ++;
    cnt[v] ++;
}


int main(void) {
    for(int i = 1; i <= 105; ++ i)
        inv[i] = qpow(i, MO - 2);
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        read(n);
        read(m);
        read(k);
        num = 0;
        for(int i = 1; i <= n; ++ i){
            head[i] = 0;
            cnt[i] = 0;
        }
        for(int u, v, w, i = 1; i <= m; ++ i){
            read(u);
            read(v);
            read(w);
            add(u, v, w);
        }
        ff.n = ff.m = 2 * n;
        ff.clear();
        for(int i = 1; i <= n; ++ i)
            for(int j = head[i]; j; j = nxt[j]){
                int v = to[j];
                int kin = ty[j];
                for(int s = 0; s <= 1; ++ s){
                    int yuan = i;
                    if (s)
                        yuan += n;
                    int nxt = v;
                    if (s ^ kin)
                        nxt += n;
                    ff.data[yuan][nxt] = inv[cnt[v]];
                }
            }
        eye.n = eye.m = 2 * n;
        eye.clear();
        for(int i = 1; i <= 2 * n; ++ i)
            eye.data[i][i] = 1;
        while(k){
            if (k & 1)
                eye = eye * ff;
            ff = ff * ff;
            k >>= 1;
        }
        int ans = eye.data[1][n + n] * 1ll * cnt[n] % MO * inv[cnt[1]] % MO;
        write(ans, '\n');
    }
    return 0;
}


B. 愿望幽灵 (Hdu 6997)

题目大意

咕咕咕

解题思路

神奇的代码
咕咕咕


C. 鸽子 (Hdu 6998)

题目大意

\(n\)台电脑,第\(k\)台坏了。\(m\)次操作,每次操作交换第\(u_i\)和第\(v_i\)台电脑,你可以跳过若干次操作。

问对于每个\(j \in [1,n]\),最终坏的电脑的位置是第\(j\)台的最小跳过次数是多少。无法实现输出-1。

解题思路

\(dp[i][j]\)表示前\(i\)次操作后,坏的电脑是第\(j\)台的最小跳过次数。容易发现每次操作只会更改两个\(dp\)值,即\(dp[i]\)\(dp[i-1]\)只有\(dp[i][u_i]\)\(dp[i][v_i]\)会发生变化,所以复用\(dp\)数组转移其实是\(O(1)\)的,总时间复杂度为\(O(n)\)

具体而言,转移时考虑是否跳过本次操作,于是\(dp[u_i] = \min(dp[v_i], dp[u_i] + 1)\)\(dp[v_i] = \min(dp[u_i], dp[v_i] + 1)\),注意等式右边的\(dp\)值是原来的\(dp\)值,不是本次更新后的\(dp\)值。注意无法实现的情况。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N = 1e5 + 8;

int n, m, k;

int dp[N];

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        read(n);
        read(m);
        read(k);
        for(int i = 1; i <= n; ++ i)
            dp[i] = -1;
        dp[k] = 0;
        for(int u, v, i = 1; i <= m; ++ i){
            read(u);
            read(v);
            int su = dp[u];
            int sv = dp[v];
            if (su == -1 && sv == -1)
                continue;
            if (sv == -1){
                dp[u] = su + 1;
                dp[v] = su;
            }
            else if (su == -1){
                dp[v] = sv + 1;
                dp[u] = sv;
            }
            else{
                dp[u] = min(su + 1, sv);
                dp[v] = min(sv + 1, su);
            }
        }
        for(int i = 1; i <= n; ++ i)
            printf("%d%c", dp[i], i == n ? '\n' : ' ');
    }
    return 0;
}


D. 萌新 (Hdu 6999)

题目大意

给定两个正整数\(a,b\),求最小和最大的\(c\)满足\(a \mod c = b \mod c\),且\(2 \le c \le \max(a,b)\)

不存在则输出\(-1\)

解题思路

假设\(a\geq b\),题意即为\(a\equiv b \mod c\)\((a-b) \equiv 0 \mod c\)\(c|(a-b)\),所以最小\(c\)即为\((a-b)\)的最小质因数,最大\(c\)即为\((a-b)\)

\(a-b=1\)\(c\)无解$。

\(a=b\)时特判\(a=1\)时无解,否则最小\(c\)\(2\),最大\(c\)\(a\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        int a, b;
        read(a);
        read(b);
        int dis = max({a,b}) - min({a, b});
        if (dis == 1){
            puts("-1 -1");
        }else if (dis == 0){
            if (a == 1){
                puts("-1 -1");
            }
            else
                printf("2 %d\n", a);
        }else{
            int ans = 2;
            int up = sqrt(dis);
            while(dis % ans != 0 && ans <= up)
                ++ ans;
            if (dis % ans != 0)
                ans = dis;
            printf("%d %d\n", ans, dis);
        }
    }
    return 0;
}


E. 二分 (Hdu 7000)

题目大意

\(n\)个格子,你现在在第\(x\)个格子,想跳到第\(y\)个格子。

每次,如果你当前的格子编号小于\(y\),你会等概率跳到比当前编号更大的一个格子中,反之则等概率跳到比当前编号更小的一个格子中。

求期望跳的次数。

解题思路

看上去挺有趣的题,结果被A卡常卡得莫得时间思考,咕咕咕。

神奇的代码
咕咕咕


F. 毒瘤数据结构题 (Hdu 7001)

题目大意

一个长为\(n\),初始全为\(0\)的序列,有\(n\)次操作,每次可以:

  • \(1 x\) 表示把\(x\)位置修改为\(1\)

  • \(2 x\) 表示查询,如果将\(x\)位置修改为\(1\),求最大的\(i\)满足序列位置\(1\)\(i-1\)上的值均为\(1\)。注意只是如果,实际并不做修改。

解题思路

上来先写的题

其实这并不毒瘤呀,用并查集维护连通性就可以了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N = 5e6 + 8;

int f[N], n;

int findfa(int x){
    return f[x] == x ? x : f[x] = findfa(f[x]);
}

int main(void) {
    read(n);
    for(int i = 1; i <= n; ++ i){
        int x, y;
        read(x);
        read(y);
        if (x == 1){
            if (f[y] == 0){
                f[y] = y;
                int nxt = findfa(y + 1);
                if (nxt != 0)
                    f[y] = nxt;
                int la = findfa(y - 1);
                if (la != 0)
                    f[la] = f[y];
            }
        }else{
            int ans = findfa(1);
            if (ans == y - 1)
                ++ ans;
            int nxt = findfa(y + 1);
            if (nxt != 0 && ans >= y)
                ans = nxt;
            ++ ans;
            write(ans, '\n');
        }
    }    
    return 0;
}


G. 流年烹茶 (Hdu 7002)

题目大意

咕咕咕

解题思路

咕咕咕

神奇的代码
咕咕咕


H. 猎人杀 (Hdu 7003)

题目大意

\(n\)个人,其中一个是狼人,其余是猎人。

每个人都会有一个想干掉的人的编号列表,是一个\(1-n\)的全排列。

首先狼人干掉自己编号列表的第一人,记为\(target\)

随后,\(target\)会干掉自己列表中,从左到右第一个活着的人,这人会成为新的\(target\),重复此步骤。

如果期间狼人被干掉了,则猎人胜利。

若某一个猎人被干掉后,场上除了狼人还有另一个猎人活着,则狼人胜利。

给定一个局面,问你最终谁胜。

解题思路

就是个模拟题,不过题意写得晦涩难懂。一开始在疑惑就一晚狼人干掉人吗?不过注意到每个人想干掉的列表是个全排列,那么白天就一定会游戏结束了,狼人不会再干掉第二个人。

看样例时看到有狼人自刀的情况出现,嗯???

神奇的代码
#include <bits/stdc++.h>
#include <exception>
#include <vector>
using namespace std;
typedef long long LL;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N = 55;

bool dead[N];

int cur[N];

int fav[N][N];

int n, lang;

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        read(n);
        for(int i = 1; i <= n; ++ i){
            int a;
            read(a);
            if (a == 1)
                lang = i;
        }
        for(int i = 1; i <= n; ++ i)
            for(int j = 1; j <= n; ++ j){
                read(fav[i][j]);
            }
        for(int i = 1; i <= n; ++ i){
            dead[i] = false;
            cur[i] = 1;
        }
        int cnt = n;
        int tar = fav[lang][1];
        dead[tar] = true;
        -- cnt;
        while(cnt > 2 && !dead[lang]){
            while(dead[fav[tar][cur[tar]]])
                ++ cur[tar];
            tar = fav[tar][cur[tar]];
            dead[tar] = true;
            -- cnt;
        }
        if (dead[lang])
            puts("lieren");
        else 
            puts("langren");
    }
    return 0;
}


posted @ 2021-07-31 19:26  ~Lanly~  阅读(2112)  评论(5编辑  收藏  举报