2024 寒假模拟赛题解

Day1

T1

送分题,分解质因数,分别加入两个桶里,然后随便怎么乱搞比对一下就行。

代码:

#include <bits/stdc++.h>
#define int long long
#define mod 1000000000
using namespace std;
int n, m;
unordered_map<int, signed>mpa;
unordered_map<int, signed>mpb;
void solve(int x, bool flg) {
    for (int i = 2; i * i <= x; i++) {
        if (x % i == 0) {
            if (flg)
                while (x % i == 0)
                    mpa[i]++, x /= i;
            else
                while (x % i == 0)
                    mpb[i]++, x /= i;
        }
    }
    if (x > 1) {
        if (flg)
            mpa[x]++;
        else
            mpb[x]++;
    }
}
int qpow(int x, int y) {
    int ans = 1;
    while (y) {
        if (y & 1)
            ans = ans * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return ans;
}
signed main() {
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++) {
        int x;
        scanf("%lld", &x);
        solve(x, 1);
    }
    scanf("%lld", &m);
    for (int i = 1; i <= m; i++) {
        int x;
        scanf("%lld", &x);
        solve(x, 0);
    }
    int ans = 1;
    for (unordered_map<int, signed>::iterator it = mpa.begin(); it != mpa.end(); it++) {
        int key = (*it).first, num = (*it).second;
        if (mpb.count(key))
            ans = (ans * qpow(key, min(num, (int)mpb[key])) % mod);
    }
    cout << ans << "\n";
    return 0;
}

T2

做法貌似很多,说一下我知道的几种。

  1. 预处理,算出每个横纵坐标各有多少个点,动态移动机器人的时候计算。

  2. 预处理,将横纵坐标分别排序,二分查找个数更新。

  3. 用数据结构(动态开点线段树 or 树状数组)分别维护横纵坐标,计算个数。

复杂度 O(n)O(n log(n))不等。

放一个法三的树状数组代码:

#include <bits/stdc++.h> 
#define N 500005
#define int long long
const int inf = 0x7f7f7f7f;
using namespace std;
int n, m;
int lowbit(int x) {
    return x & (-x);
}
struct BIT {
    unordered_map<int, int>tree;
    void point_add(int x, int val) {
        while (x <= inf * 2) {
            tree[x] += val;
            x += lowbit(x);
        }
    }
    int ask(int x) {
        int ans_ask = 0;
        while (x) {
            ans_ask += tree[x];
            x -= lowbit(x);
        }
        return ans_ask;
    }
}X, Y;
int dis;
char s[300005];
signed main() {
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= n; i++) {
        int x, y;
        scanf("%lld%lld", &x, &y);
        dis += abs(x) + abs(y);
        X.point_add(x + inf, 1);
        Y.point_add(y + inf, 1);
    }
    scanf("%s", s);
    int sx = inf, sy = inf;
    for (int i = 0; i < m; i++) {
        if (s[i] == 'I') {
            int sl = X.ask(sx);
            dis += sl;
            dis -= n - sl;
            ++sx;
        }
        else if(s[i] == 'Z') {
            int sr = X.ask(sx - 1);
            dis -= sr;
            dis += n - sr;
            --sx;
        }
        else if(s[i] == 'S') {
            int sd = Y.ask(sy);
            dis += sd;
            dis -= n - sd;
            ++sy;
        }
        else if(s[i] == 'J') {
            int su = Y.ask(sy - 1);
            dis -= su;
            dis += n - su;
            --sy;
        }
        cout << dis << "\n";
    }
    return 0;
}

T3

极其不好想的题目。

从暴力做法开始考虑。暴力做法显然是暴力 dfs 展开所求的字符串,再 乱搞 Hash  or  kmp 解决。这样显然会炸,原因是字符串的长度最大是约 1052级别的。

考虑优化空间是什么。由于字符串的个数是一定的 n100,因此极限情况中一定有多个字符串的重复展开,因此在 dfs 的基础上优化尝试改为记忆化搜索。

考虑每一次转移中变化了什么量。发现匹配到哪一个字符串会改变,匹配到模式串的位数也会改变。

因此考虑设 dp[i][j] 为当前在第 i 个字符串,上一次匹配模板串匹配到了第 j 位的匹配次数。

转移过程是如果是小写字母,就继续 kmp 匹配,更新匹配位置;如果是大写字母,就 dfs 更新答案。因此我们需要一个东西记录大写字母匹配后模式串的位置,于是定义 pos[i][j] 为当前在第 i 个字符串,上一次模式串匹配到了第 j 位,匹配后模式串会在哪一位。 dfs 后更新这个位置即可。

代码的实现或许会有一些难度,详细见代码。

#include <bits/stdc++.h>
#define N 28
#define L 105
#define mod 10000
using namespace std;
int n;
char name;
char s[N][L];
int dp[N][L]; 
int pos[N][L]; 
int nxt[L];
char g[L], lg; //模式串 
void get_nxt(char *t) {
    lg = strlen(t + 1);
    for (int i = 2, j = 0; i <= lg; i++) {
        while (j && t[i] != t[j + 1])
            j = nxt[j];
        if (t[i] == t[j + 1])
            j++;
        nxt[i] = j;
    }
}
int dfs(int num, int w) {
    if (~dp[num][w]) //未经过
        return dp[num][w];
//  printf("%d %d\n", num, w);
    dp[num][w] = 0;//归零
    int l = strlen(s[num] + 1); 
    int p = w; //记录实时匹配位置 
    for (int t = 1; t <= l; t++) {
        char ch = s[num][t];
        if (ch >= 'a' && ch <= 'z') {
            int j = p; //跳指针匹配
            while (j && ch != g[j + 1])
                j = nxt[j];
            if (ch == g[j + 1])
                j++;
            if (j == lg) { //匹配成功 
                dp[num][w]++;
                dp[num][w] %= mod;
            } 
            p = j; //实时记录模式串匹配到哪一位 
        } 
        else {
            dp[num][w] += dfs(ch - 'A' + 1, p);
            dp[num][w] %= mod;
            p = pos[ch - 'A' + 1][p];
        }
    } 
    pos[num][w] = p; //到最终再记录! 
    return dp[num][w];
} 
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> name;
    for (int i = 1; i <= n; i++) {
        char ept;
        cin >> ept >> ept;
        cin >> (s[i] + 1);
    }
    memset(dp, -1, sizeof dp);
    cin >> (g + 1);
    get_nxt(g);
    cout << dfs((int)(name - 'A' + 1), 0) << "\n";
    return 0;
}

T4

显然这个合法情况是不好算的。考虑到合法情况 = 总情况 非法情况,非法情况就是所有分别为 (2, 3, 5, ...) 的倍数的四元组。显然不能直接减去,需要容斥原理一下。

具体的实现上,先预处理出 210000 的每个数的倍数在 10000 范围内的个数,每个四元组的查询转化为了在个数集合中选四个的集合,即 Cnum4。最后判断一下每个数的质因数个数,奇减偶加容斥原理计算答案即可。

详见代码:

#include <bits/stdc++.h>
#define N 10005
using namespace std;
int n;
int a[N];
long long c[N];
int num[N];
long long ans;
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        num[a[i]] = 1;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 2; j * j <= a[i]; j++) {
            if (a[i] % j == 0) {
                num[j]++;
                if (j * j != a[i])
                    num[a[i] / j]++;    
            }
        }
    }
    for (int i = 4; i <= 10000; i++)
        c[i] = (long long)i * (i - 1) * (i - 2) * (i - 3) / 24;
    ans = c[n];
    for (int i = 2; i <= 10000; i++) {
        int cnt = 0, x = i; 
        for (int j = 2; j * j <= i; j++)
            if (x % j == 0) {
                if ((x / j) % j == 0)
                    goto FS;
                ++cnt;
                while (x % j == 0)
                    x /= j;
            }
        if (x > 1)
            cnt++;
        if (cnt % 2 == 1)   
            ans -= c[num[i]];
        else
            ans += c[num[i]];
        FS:;
    }
    cout << ans << "\n";
    return 0;

}

Day2

T1

还是送分题,无论怎么乱搞预处理出素数,前缀和差分预处理一下可以做到 O(1) 的查询。

代码:

#include <bits/stdc++.h>
#define N 32780
using namespace std;
long long prm[N], tot;
bool vis[N];
void solve() {
    for (int i = 2; i <= 32767; i++) {
        if (!vis[i])
            prm[++tot] = i;
        for (int j = 1; j <= tot; j++)
            if (i * prm[j] <= 32767)
                vis[i * prm[j]] = true;
    }
}
long long sum[N];
long long mp[N];
int main() {
    solve();
    for (int i = 1; i <= tot; i++)
        sum[i] = sum[i - 1] + prm[i];
    for (int i = 1; i <= tot; i++)
        for (int j = i; j <= tot; j++)
            if (sum[j] - sum[i - 1] <= 32767)
                mp[sum[j] - sum[i - 1]]++;
    int x;
    while (scanf("%d", &x) && x) 
        printf("%d\n", mp[x]);
    return 0;
}

T2

基础 dp,状态好想,但转移式子的细节有些小麻烦。(我也不知道我是怎么挂的)

可以滚动数组压缩空间,但是小心会炸

代码:

// dp ?
// 变量:min,km,type,lazy
// min可以滚动数组成两维,type不用管 
// 定义dp(i)[j][2]为yyc第 i 分钟后疲劳度为 j 时,当前状态是休息(0)/启动(1)时最多能走多少千米 
#include <bits/stdc++.h>
#define D 1005
#define M 505
using namespace std;
int n, m;
int d[10005];
int dp[2][M][2];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d", &d[i]);
    int t = 1;
    for (int i = 1; i <= n; i++, t ^= 1) { 
        for (int j = 0; j <= min(i, m); j++) {
            if (j == 0) 
                dp[t][j][0] = max(dp[t ^ 1][j][0], max(dp[t ^ 1][j + 1][0], dp[t ^ 1][j + 1][1]));
            else {
                dp[t][j][0] = max(dp[t ^ 1][j + 1][0], dp[t ^ 1][j + 1][1]);
                dp[t][j][1] = dp[t ^ 1][j - 1][1];
                if (j == 1)
                    dp[t][j][1] = max(dp[t][j][1], dp[t ^ 1][0][0]);
                dp[t][j][1] += d[i];
            }
        }
    }
    printf("%d\n", dp[t ^ 1][0][0]);
    return 0;
}

T3

容易发现对于每一列 i ,若 i1=i2(mod n),则 i1, i2两列所放 的点个数是相同的。证明?等式的性质。

于是我们可以只处理一个 n×n 的矩阵来推广到整个图,具体对于矩阵的处理,本质上是一个 dp,因为所填的每一列对后面列是有后效性的。考场上怎么就没想到 dp 呢qwq

具体地,设 dp[i][j] 为当前在第 i 列,已经填了 j 个点的方案数。

转移式子显然: f[i][j]=f[i][j]+f[i1][jp(0pj)]×Cnp(nmim mod n)

预处理!一切都要预处理!dp的转移必须严格O(1)!!!

代码:

#include <bits/stdc++.h>
#define int long long
#define N 10005
#define mod 1000000007
using namespace std;
int n, m, k;
int qpow(int x, int y) {
    int ans = 1;
    while (y) {
        if (y & 1)
            ans = ans * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return ans;
}
int fac[N];
int ans;
int C(int n, int m) {
    if (n < m || n < 0 || m < 0)
        return 1;
    return fac[n] * qpow(fac[n - m], mod - 2) % mod * qpow(fac[m], mod - 2) % mod;
}
int dp[105][10005];
// 定义dp[i][j]为第i列,已经放了j个点的方案数 
int c[105] = {1};
int pw[105][2];
signed main() {
    fac[0] = 1;
    for (int i = 1; i < N; i++) 
        fac[i] = fac[i - 1] * i % mod;
    cin >> n >> m >> k;
    int t = m / n;
    int w = m % n;
    for (int i = 0; i <= n; i++) {
        c[i] = C(n, i);
        pw[i][0] = qpow(c[i], t);
        pw[i][1] = qpow(c[i], t + 1);
    }
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++) 
        for (int j = 0; j <= k; j++)
            for (int p = 0; p <= min(n, j); p++)
                dp[i][j] = (dp[i][j] + (dp[i - 1][j - p] * ((i <= w) ? (pw[p][1]) : (pw[p][0])) % mod + mod) % mod) % mod;//,cout<<p<<" "<<c[p]<<" "<<C(n,p)<<"\n";
    cout << dp[n][k] << "\n";
    return 0;
}

T4

思路其实并不难想。考虑一条边无法删去的情况是什么。假使一条边无法删去,它一定是 st最短路上一条必须经过的路径。于是在求最短路的时候我们可以先构建出这个图的最短路图(注意,这个图可不是 DAG!),然后把这个图扩成双向边,最后 Tarjan 找桥即可。

提醒:

  1. 数据不保证没有重边

  2. 数据不保证不卡 SPFA

代码:

#include <bits/stdc++.h>
#define N 40005
#define M 800005 
using namespace std;
int n, m;
struct Node {
    int to, nxt, dis;
    int pos;
}e[M];
int head[N], cnt;
void add(int u, int v, int w, int pos) {
    e[++cnt].to = v;
    e[cnt].pos = pos;
    e[cnt].dis = w;
    e[cnt].nxt = head[u];
    head[u] = cnt;
}
struct tode {
    int y, pos;
};
vector<tode>v[N];
bool vis[N];
struct node {
    int x, dis;
    bool operator < (const node &x) const{
        return x.dis < dis;
    } 
};
priority_queue<node>q;
int dis[N];
void Dij(int s) {
    dis[s] = 0;
    q.push((node) {
        s, 0
    });
    while (!q.empty()) {
        node tmp = q.top();
        q.pop();
        int x = tmp.x;
        if (vis[x])
            continue;
        vis[x] = true;
        for (int i = head[x]; i; i = e[i].nxt) {
            int y = e[i].to;
            if (dis[y] >= dis[x] + e[i].dis) {
                if (!vis[y])
                    q.push((node){
                        y, dis[x] + e[i].dis
                    });
                if (dis[y] > dis[x] + e[i].dis)
                    v[y].clear();
                v[y].push_back((tode) {
                    x, e[i].pos
                });
                dis[y] = dis[x] + e[i].dis;
            }
        }
    }
}
vector<tode>V[N];
bool ins[N];
bool inc[M];
void redfs(int x) {
    ins[x] = true;
    for (int i = 0; i < (int)v[x].size(); i++) {
        int y = v[x][i].y;
        inc[v[x][i].pos] = true;
        V[x].push_back((tode) {
            y, v[x][i].pos
        });
        V[y].push_back((tode) {
            x, v[x][i].pos
        });
        if (ins[y])
            continue;
        ins[y] = 1;
        redfs(y);
    }
}
int low[N], dfn[N];
int tim;
bool xycyx[M];
void Tarjan(int x, int fa) {
    low[x] = dfn[x] = ++tim;
    for (int i = 0; i < (int)V[x].size(); i++) {
        int y = V[x][i].y;
        if (!dfn[y]) {
            Tarjan(y, x);
            low[x] = min(low[x], low[y]);
            if (low[y] > dfn[x]) 
                xycyx[V[x][i].pos] = 1;
        }
        else if(dfn[y] < dfn[x] && y != fa)
            low[x] = min(low[x], dfn[y]);
    }
}
int s, t;
int main() {
    scanf("%d%d%d%d", &n, &m, &s, &t);
    for (int i = 1; i <= m; i++) {
        int x, y, w;
        scanf("%d%d%d", &x, &y, &w);
        add(x, y, w, i);
        add(y, x, w, i); 
    }
    memset(dis, 0x7f, sizeof dis);
    Dij(s);
    redfs(t);
    for (int i = 1; i <= n; i++)
        if (!dfn[i])
            Tarjan(i, 0);
    int T;
    cin >> T;
    while (T--) {
        int x;
        scanf("%d", &x);
        if (!inc[x])
            puts("Yes");
        else if (!xycyx[x])
            puts("Yes");
        else
            puts("No");
    }
    return 0;
}
posted @   长安19路  阅读(16)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示