Loading

2021 辽宁省赛vp 题解

传送门

省流:没有 A H K

2021年辽宁省赛 vp

五一集训第三天的模拟赛,这套题有种区分度不是很大的感觉,就是一开场疯狂过题,接着三小时直接坐牢,不过题目还是很不错的

后来补题的时候发现有的题确实思路就差点点

B. 阿强的路

这题是赛后补题的

弗洛伊德算法

一个路径的代价是这条路径的最大点权和最大边权的积

对于边权,我们用弗洛伊德来维护

对于点权,我们将其从小到大排序,弗洛伊德枚举中间点的时候就是从小到大

做这题的时候,发现对于弗洛伊德的理解还是不够透彻,其记忆化的方式在于,第一层循环的用意是,起点 -> 中间点 -> 终点,中间点仅由前 k 个点组成

因此弗洛伊德的转移为:当路径的最大边权变小时,尝试更新路径的最小代价,点权的最大值就是 起点、中间点、终点的点权最大值,即 \(max(a[i],a[k],a[j])\),因为点根据点权从小到大排,所以中间点的点权最大就为 \(a[k]\)

为何不尝试在边权变大的时候更新?因为是由点权从小到大排,因此对于同一起点和终点,他所搜索到的路径的最大点权只会越来越大

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 510;
typedef long long ll;
const ll inf = 1e18 + 10;
ll dis[maxn][maxn], ans[maxn][maxn], num[maxn];

struct node
{
    ll val;
    int id;
}x[maxn];

bool cmp(const node& a, const node& b)
{
    return a.val < b.val;
}

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++)
    {
        scanf("%lld", &x[i].val);
        num[i] = x[i].val;
        x[i].id = i;
    }
    for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) ans[i][j] = ans[j][i] = dis[j][i] = dis[i][j] = inf;
    for(int i=0; i<m; i++)
    {
        ll a, b, c;
        scanf("%lld%lld%lld", &a, &b, &c);
        dis[a][b] = dis[b][a] = c;
        ans[a][b] = ans[b][a] = min(ans[a][b], dis[a][b] * max(x[a].val, x[b].val));
    }
    sort(x + 1, x + 1 + n, cmp);

    for(int kk=1; kk<=n; kk++)
    {
        int k = x[kk].id;
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)
            {
                if(i == j || k == i || k == j) continue;
                ll temp = max(dis[i][k], dis[k][j]);
                if(dis[i][j] > temp)
                {
                    dis[i][j] = temp;
                    ans[i][j] = min(ans[i][j], dis[i][j] * max({num[i], num[j], num[k]}));
                }
            }
        }
    }

    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            if(j != 1) printf(" ");
            if(i == j) printf("0");
            else if(ans[i][j] == inf) printf("-1");
            else printf("%lld", ans[i][j]);
        }
        printf("\n");
    }
    return 0;
}

C. 传染病统计

简单模拟

按照能被互相传染的分组,判断一下分组的最大最小人数

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <ctime>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int inf = 2147483647;
const double PI = acos(-1.0);
int num[maxn];

int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        int n;
        scanf("%d", &n);
        for(int i=0; i<n; i++) scanf("%d", &num[i]);
        sort(num, num + n);
        num[n++] = 1000;
        int maxx = 0, minn = n;
        int now = 1;
        for(int i=1; i<n; i++)
        {
            if(num[i] - num[i-1] > 2)
            {
                maxx = now > maxx ? now : maxx;
                minn = now < minn ? now : minn;
                now  = 1;
            }
            else
                now++;
        }
        printf("%d %d\n", minn, maxx);
    }
    return 0;
}

D. 阿强与网格

贪心

  • 当前点不与终点在同一行或同一列的时候,可以有斜着走一次和相邻地走两次,这里判断一下 2x 和 y 的关系贪心地找最优

  • 当前点与终点在同一行或同一列的时候,可以考虑直接相邻地走或者斜着走上去,再斜着走下来,这里判断一下 x 和 y 的关系,贪心地找最优

注意:有一个边界条件,当图只有一行或一列的时候,这是的第二种情况下,只能相邻地走,不能斜着走

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <ctime>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int inf = 2147483647;
const double PI = acos(-1.0);

int main()
{
    int n;
    scanf("%d", &n);
    for(int i=0; i<n; i++)
    {
        ll n, m, x, y;
        scanf("%lld%lld%lld%lld", &n, &m, &x, &y);
        if(n == 1 || m == 1)
        {
            printf("%lld\n", (max(n, m) - 1) * x);
            continue;
        }
        n--, m--;
        ll ans = 0;
        ll temp = min(n, m);
        if(y < 2 * x)
            ans = temp * y;
        else
            ans = temp * x * 2;
        n -= temp, m -= temp;
        ll nex = max(n, m);
        if(nex)
        {
            if(x <= y)
                ans += nex * x;
            else
            {
                if(nex & 1) ans += x;
                nex /= 2;
                nex *= 2;
                ans += nex * y;
            }
        }
        printf("%lld\n", ans);
    }
    return 0;
}

E. 生活大爆炸

组合数学

直接组合数学求解,即枚举要选取的男生数量 i,则女生数量为 t - i,这时候的组合数为 \(C(i, n) * C(t - i, m)\)

在枚举的时候注意上边界和下边界,例如男生的数量至少为4,并且要保证这样选取的话,女生是够人数组成队伍的,即男生至少为 max(4, t - m)

女生同男生一样,设置好上边界即可

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 110;
typedef long long ll;
ll c[maxn][maxn];

ll dps(ll up, ll down)
{
    if(c[up][down]) return c[up][down];
    if(up == down || up == 0) return c[up][down] = 1;
    return c[up][down] = dps(up - 1, down - 1) + dps(up, down - 1);
}

int main()
{
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);
    ll ans = 0;
    for(int i=max(4, k-m); k-i>=max(1, k-n); i++)
        ans += dps(i, n) * dps(k - i, m);
    printf("%lld\n", ans);
    return 0;
}

F. Capslock

简单模拟

判断是否要修改,然后修改就好

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s;
    cin >> s;
    int fbig = 1, f = 1;
    int len = s.length();
    for(int i=0; i<len && fbig; i++)
        if(s[i] >= 'a' && s[i] <= 'z') fbig = 0;
    for(int i=1; i<len && f; i++)
        if(s[i] >= 'a' && s[i] <= 'z') f = 0;
    if(fbig || (s[0] >= 'a' && s[0] <= 'z' && f))
    {
        for(int i=0; i<len; i++)
        {
            if(s[i] >= 'a' && s[i] <= 'z') s[i] = s[i] - 'a' + 'A';
            else s[i] = s[i] - 'A' + 'a';
        }
    }
    cout << s << endl;
    return 0;
}

G. 字节类型

简单分支语句

用字符串判断就行,但是不能直接判断字典序

要先判断长度,再判断字典序

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s;
    cin >> s;
    int len = s.length();
    if(len < 3 || (len == 3 && s <= "127"))
        cout << "byte" << endl;
    else if(len < 4 || (len == 5 && s <= "32767"))
        cout << "short" << endl;
    else if(len < 10 || (len == 10 && s <= "2147483647"))
        cout << "int" << endl;
    else if(len < 19 || (len == 19 && s <= "9223372036854775807"))
        cout << "long" << endl;
    else
        cout << "BigInteger" << endl;
    return 0;
}

I. 完美主义

差分+set 或者 树状数组

我们知道差分的时候如果出现小于 0 的情况即为不满足,因此我们可以将差分中小于 0 的情况加入到 set 中

然后每次通过区间起点,二分查找离区间起点最近的 不满足条件的下标在哪里,判断它与区间终点的关系

修改值的时候,对 set 进行删除或插入操作即可

吐槽:这题数据是真的弱,集训队里有个队伍居然暴力直接过了,这数据居然没放边界数据

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
const int maxn = 3e5 + 10;
set<int>vis;
int num[maxn];
int dif[maxn];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++) scanf("%d", &num[i]);
    for(int i=1; i<n; i++)
    {
        dif[i] = num[i+1] - num[i];
        if(dif[i] < 0) vis.insert(i);
    }
    for(int i=0; i<m; i++)
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        if(a == 1)
        {
            num[b] = c;
            if(b > 1)
            {
                dif[b-1] = c - num[b-1];
                if(dif[b-1] >= 0)
                {
                    vis.erase(b-1);
                }
                else vis.insert(b-1);
            }
            if(b < n)
            {
                dif[b] = num[b+1] - c;
                if(dif[b] >= 0)
                {
                    vis.erase(b);
                }
                else vis.insert(b);
            }
        }
        else
        {
            auto it = vis.lower_bound(b);
            if(it == vis.end() || *it >= c) printf("Yes\n");
            else printf("No\n");
        }
    }
    return 0;
}

J. 放棋子

类似于背包 dp

这题是赛后补的,感觉很不应该,前几天才做了一场 div2 的有个 dp 题和这个状态转移一模一样:https://codeforces.com/contest/1673/problem/C

如果我们能确定前 n 列中每一列所放的棋子的个数,那么第 n + 1 列的棋子个数,必然等于第 1 列的棋子个数

因此我们可以考虑只用 dp 算前 n 列的方案数,而后面相同的由于是一样的,则用快速幂来维护其代价即可

例如如果我们选定了第 1 列,那么 n + 1,2n + 1,都是一样的,则第 1 列放大到整个棋盘的方案数就是 pow(第 1 列的方案数, 对 n 取模结果为 1 的列的数量)

对于前 n 列的状态转移,设 \(dp[i][j] = x\) 为前 i 列中放了 j 个棋子的方案数为 x

状态转移为 \(dp[i][j] = \sum^{min(n, j)}_{k=0} dp[i-1][j-k] * g[k][i\leq tx]\)

tx = n % m,k 可以理解为当前列放入 k 个,g 数组为维护的快速幂的值,如果看不懂 g 数组建议按照思路自己敲一遍代码就知道了为啥这样写了

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 110;
const ll mod = 1e9 + 7;
ll dp[maxn][maxn * maxn], c[maxn][maxn], g[maxn][2];

ll dps(ll up, ll down)
{
    if(c[up][down]) return c[up][down];
    if(up == down || up == 0) return c[up][down] = 1;
    return c[up][down] = (dps(up - 1, down - 1) + dps(up, down - 1)) % mod;
}

ll qpow(ll x, ll n)
{
    ll ans = 1;
    while(n)
    {
        if(n & 1) ans = x * ans % mod;
        n >>= 1;
        x = x * x % mod;
    }
    return ans % mod;
}

int main()
{
    ll n, m, k;
    cin >> n >> m >> k;
    ll t = m / n, tx = m % n;
    for(int i=0; i<=n; i++)
    {
        g[i][0] = qpow(dps(i, n), t);
        g[i][1] = (g[i][0] * dps(i, n)) % mod;
    }
    dp[0][0] = 1;
    for(int i=1; i<=n; i++)
    {
        ll e = min(i * n, k);
        for(int j=0; j<=e; j++)
        {
            ll y = min(n, (ll)j);
            for(int x=0; x<=y; x++)
            {
                dp[i][j] = (dp[i][j] + dp[i-1][j - x] * g[x][i <= tx]) % mod;
                // cout << i << " " << j << " " << dp[i][j] << endl;
            }
        }
    }
    printf("%lld\n", dp[n][k]);
    return 0;
}

L. 神奇的回答

签到

#include <iostream>
using namespace std;

int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        int n;
        cin >> n;
        n = n > 18 ? 18 : n;
        cout << n << endl;
    }
}

M. 比赛!

拓扑板子

一个字符串两个拓扑,直接看能不能生成拓扑排序就行

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <ctime>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int inf = 2147483647;
const double PI = acos(-1.0);
vector<char>gra[maxn];
int du[maxn], vis[maxn];
char last[maxn];
int tp = 0;

bool topu()
{
    queue<char>q;
    int ans = 0;
    for(int i=0; i<300; i++)
        if(vis[i] && du[i] == 0)
            q.push(i);
    for(int i=0; i<300; i++) if(vis[i]) ans++;
    while(q.size())
    {
        char now = q.front();
        q.pop();
        last[tp++] = now;
        for(int i=0; i<gra[now].size(); i++)
        {
            char nex = gra[now][i];
            if(--du[nex] == 0)
                q.push(nex);
        }
    }
    return tp == ans;
}

int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        string s;
        cin >> s;
        du[s[0]]++;
        du[s[3]]++;
        gra[s[2]].push_back(s[0]);
        gra[s[0]].push_back(s[3]);
        vis[s[0]] = 1;
        vis[s[2]] = 1;
        vis[s[3]] = 1;
    }
    if(topu())
    {
        for(int i=0; i<tp; i++)
            cout << last[i];
        cout << endl;
    }
    else
        cout << "No Answer" << endl;
}
posted @ 2022-05-05 21:57  dgsvygd  阅读(59)  评论(0编辑  收藏  举报