2022牛客暑期多校训练营6(ABGIJM)

题集链接
视频题解

A Array

构造

题意

给定序列 a i a_i ai ,满足 ∑ 1 a i ⩽ 1 2 \sum\frac 1{a_i}\leqslant\frac 12 ai121 ,构造长度不超过1e6的序列 c i c_i ci,满足在 c 序列首尾相接形成的序列 b 中,任意长度为 a i a_i ai 的子串都含有数字 i i i

思路

对于 a i a_i ai 的限制,我们可以理解为“填充率”,即数字 i 至少占所求序列的 1 a i \frac 1{a_i} ai1

然后我们考虑构造,考虑序列 c 的长度为 2 max ⁡ ( a i ) 2\max(a_i) 2max(ai) ,对于每一个 i ,按 a i a_i ai 升序排列,依次构造,从起始处开始每 a i a_i ai 位填入一个 i ,如果对应位被占,则向左找最近的空位;

在结尾处,我们考虑可能存在由于被占位造成的左移,使得c序列最后一个 i 和下一个c序列的第一个i之间的距离不满足要求,则我们在结尾处重新寻找空位插入;

由于保证有解,所以不存在数组前 a i a_i ai 位均被占找不到空位等情况;

代码
#include <bits/stdc++.h>

using namespace std;

int ans[1000006];

int main()
{
    int n, mxg = 0;
    pair<int, int> g[100005];
    cin >> n;
    for (int i = 1; i <= n; i++)
        scanf("%d", &g[i].first), g[i].second = i, mxg = max(mxg, g[i].first);
    sort(g + 1, g + n + 1);
    int c = 1;
    for (int i = 1; i <= n; i++)
    {
		int j=c;
		while(ans[j])j++,c++;
        for (; j <= 2*mxg; j += g[i].first)
        {
			while(ans[j])j--;
            ans[j] = g[i].second;
        }
		if(j-2*mxg<c)
		{
			while(j>2*mxg||ans[j])j--;
			ans[j] = g[i].second;
		}
        c++;
    }
    printf("%d\n", 2*mxg);
    for (int i = 1; i <= 2*mxg; i++)
        printf("%d ", (ans[i])?ans[i]:1);
    return 0;
}

B Eezie and Pie

树上差分

题意

在一棵以 1 为根的树中,每一个点有一个属性 d i d_i di ,表示该点可以覆盖自己和向根方向的 d i d_i di 个点( d i d_i di不含自身),输出每个点可以被几个点覆盖;

思路

区间加问题,我们考虑差分来解决;

在dfs形成的每一条链上,我们对末端点的 d i d_i di 进行差分,在dfs离开该点时,将该点的差分值加到父亲上,以此统计每个点的被覆盖次数;

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<int> rod[2000006];
ll ans[2000006], d[2000006];
vector<pair<int, int>> tmp;//这里的.first实际上无意义

void dfs(int x, int f)
{
    tmp.push_back({x, 1});
    if ((int)tmp.size() - 1 - d[x] - 1 >= 0)
        tmp[(int)tmp.size() - 1 - d[x] - 1].second--;

    for (auto u : rod[x])
    {
        if (u == f)
            continue;
        dfs(u, x);
    }

    ans[x] = tmp[tmp.size() - 1].second;
    if (tmp.size() > 1)
    {
        tmp[tmp.size() - 2].second += tmp[tmp.size() - 1].second;
    }
    tmp.pop_back();
}
int main()
{
    int n, u, v;
    cin >> n;
    for (int i = 1; i < n; i++)
    {
        scanf("%d%d", &u, &v);
        rod[u].push_back(v);
        rod[v].push_back(u);
    }
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld", &d[i]);
    }
	// tmp.push_back({0,0});
    dfs(1, 0);
    for (int i = 1; i <= n; i++)
    {
        printf("%lld ", ans[i]);
    }
    return 0;
}

G Icon Design

字符串

题意

按要求打印字符串

代码

队友代码如下

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

int main() {
   int n;
   cin >> n;

   set<pair<int, int>> s;

   // N

   int L = n + 1, U = n;

   for (int i = 1; i <= 2 * n + 3; i++) {
      s.insert({L + i, U + i});
      s.insert({L + 1, U + i});
      s.insert({L + 2 * n + 3, U + i});
   }

   // F

   L += 2 * n + 3 + n + 1;

   for (int i = 1; i <= 2 * n + 3; i++) {
      s.insert({L + 1, U + i});
      s.insert({L + i, U + 1});
      s.insert({L + i, U + n + 2});
   }

   // L

   L += 2 * n + 3 + n + 1;

   for (int i = 1; i <= 2 * n + 3; i++) {
      s.insert({L + 1, U + i});
      s.insert({L + i, U + 2 * n + 3});
   }

   // S
   L += 2 * n + 3 + n + 1;

   for (int i = 1; i <= 2 * n + 3; i++) {
      s.insert({L + i, U + 1});
      s.insert({L + i, U + n + 2});
      s.insert({L + i, U + 2 * n + 3});
      if (i <= n + 2) {
         s.insert({L + 1, U + i});
      }
      if (i >= n + 2) {
         s.insert({L + 2 * n + 3, U + i});
      }
   }

   for (int i = 0; i < 4 * n + 5; i++) {
      for (int j = 0; j < 13 * n + 19; j++) {
         if (i == 0 || j == 0 || i == 4 * n + 5 - 1 ||
             j == 13 * n + 19 - 1) {
            cout << '*';
         } else if (s.find({j, i}) != s.end()) {
            cout << "@";
         } else {
            cout << ".";
         }
      }
      cout << '\n';
   }

   return 0;
}

I Line

构造

题意

给定向量集 v 和数字 d ,要求生成点集 p ,要求 p 中的每个点在向量集 v 的每个方向上(共线即可)都经过恰好 d 个 p 中的点(含自身);

思路

我们首先在 p 中构造原点,再对 p 中的每个点依次沿着 v 中的每个方向构造 d-1 个点;

为了保证不发生意外共线,我们可以将延长的距离乘一个对 v 中元素两两不同的系数,经测试,取 3 7 i , 2 3 i 37^i,23^i 37i,23i 均可;
经测试,某些质数也会发生意外共线,具体原因待确定;

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
pair<ll, ll> vv[10];
vector<pair<pair<ll, ll>, int>> pp;
vector<pair<ll, ll>> p;
bool operator==(pair<pair<ll, ll>, int> a, pair<pair<ll, ll>, int> b)
{
    return a.first.first == b.first.first && a.first.second == b.first.second;
}
int main()
{
    int n, u, v, d;
    cin >> n >> d;
    for (int i = 0; i < n; i++)
    {
        scanf("%lld%lld", &vv[i].first, &vv[i].second);
        if (vv[i].first * vv[i].second)
        {
            int g = __gcd(abs(vv[i].first), abs(vv[i].second));
            vv[i].first /= g;
            vv[i].second /= g;
        }
        else
        {
            if (vv[i].first + vv[i].second)
            {
                if (vv[i].second)
                    vv[i] = {0, 1};
                else if (vv[i].first)
                    vv[i] = {1, 0};
            }
        }
    }
    sort(vv, vv + n);
    n = unique(vv, vv + n) - vv;
    pp.push_back({{0, 0}, -1});
    ll bas = 1;
    for (int i = 0; i < n; i++, bas *= 37)
    {
        for (int k = 0; k < pp.size(); k++)
        {
            if (pp[k].second == i)
                break;
            auto tt = pp[k].first;
            for (int j = 2; j <= d; j++)
            {
                pp.push_back(
                    {{tt.first + vv[i].first * bas * j, tt.second + vv[i].second * bas * j}, i});
            }
        }
    }

    printf("%d\n", pp.size());
    for (auto i : pp)
        printf("%lld %lld\n", (i.first), (i.second));
    return 0;
}

J Number Game

数学

题意

给定数字 A,B,C ,定义两种操作,问能否通过若干操作使得第三个位置的数字变为 x ;

思路

对于一组相同的值,进行若干次操作,这组值只会有两种不同的结果。其中第二个位置(B)的值只有可能是 B 和 A-B ;

那么对于第三个位置,其生成方式也有两种,即 先与B运算,再与A-B运算 和 先与A-B运算,再与B运算;

我们可以打表找规律:对于非负整数 k ,第三个位置的值为
( − 1 ) k + 1 k + 1 2 A + ( − 1 ) k k B + ( − 1 ) k C ( − 1 ) k k 2 A + ( − 1 ) k + 1 k B + ( − 1 ) k C (-1)^{k+1}\frac {k+1}2A+(-1)^kkB+(-1)^kC\\ (-1)^{k}\frac {k}2A+(-1)^{k+1}kB+(-1)^kC (1)k+12k+1A+(1)kkB+(1)kC(1)k2kA+(1)k+1kB+(1)kC
将k按奇偶讨论,换元为 2 n 2n 2n 2 n + 1 2n+1 2n+1 ,如果能求出 n 的非负整数解,即题求情况存在;

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e4;
const ll M = 1e9 + 7;
typedef struct
{
    int x, y, z;
} ppp;
int main()
{
    ll t, a, b, c, x;
    cin >> t;
    while (t--)
    {
        cin >> a >> b >> c >> x;
        if (c == x)
        {
            printf("Yes\n");
            continue;
        }
        if (2 * b - a)
        {
            if ((x + c + b - a) % (a - 2 * b) == 0 && (x + c + b - a) / (a - 2 * b) >= 0)
            {
                printf("Yes\n");
                continue;
            }
            if ((x + c - b) % (2 * b - a) == 0 && (x + c - b) / (2 * b - a) >= 0)
            {
                printf("Yes\n");
                continue;
            }
            if ((x - c) % (2 * b - a) == 0 && (x - c) / (2 * b - a) >= 0)
            {
                printf("Yes\n");
                continue;
            }
            if ((x - c) % (a - 2 * b) == 0 && (x - c) / (a - 2 * b) >= 0)
            {
                printf("Yes\n");
                continue;
            }
            printf("No\n");
            continue;
        }
        else if (a - b - c == x || b - c == x)
        {
            printf("Yes\n");
            continue;
        }
        else
        {
            printf("No\n");
            continue;
        }
    }
}

M Game on grid

DP

题意

给定n*m的网格,一些格点上标有字母A或B ,棋子初始在(0,0)点上,每次移动只能移动到 (i+1,j) 或 (i,j+1) 。若某时刻旗子在A上,则先手赢,若在B上则后手赢,若走到 (n-1,m-1) 点仍未划分胜负,则平局;
对于给定网格,请问先手是否有必赢、必平、必输机会;

思路

我们发现,对于 (i+j)%2=0 的点,接下来一定是先手进行操作,反之为后手先;

我们调整胜负判定,将先手必输的情况调整为以B判先手胜的先手赢,平局调整为以平局为判定的先手赢
考虑DP:

有状态表示 d p [ i ] [ j ] dp[i][j] dp[i][j] 为走到 (i,j) 点时先手的输赢情况,若为1,则走到该点时,接下来一定会先手赢,若为-1,则一定会先手输,若为0则不定;

考虑状态转移方程
d p [ i ] [ j ] = { c h e c k ( m p [ i ] [ j ] )   m p [ i ] [ j ] ! = . max ⁡ ( d p [ i + 1 ] [ j ] , d p [ i ] [ j + 1 ] )   ( i + j ) % 2 = = 0 min ⁡ ( d p [ i + 1 ] [ j ] , d p [ i ] [ j + 1 ] )   ( i + j ) % 2 = = 1 dp[i][j]=\begin{cases} check(mp[i][j])\ mp[i][j]!=.\\ \max(dp[i+1][j],dp[i][j+1])\ (i+j)\%2==0\\ \min(dp[i+1][j],dp[i][j+1])\ (i+j)\%2==1\\ \end{cases} dp[i][j]= check(mp[i][j]) mp[i][j]!=.max(dp[i+1][j],dp[i][j+1]) (i+j)%2==0min(dp[i+1][j],dp[i][j+1]) (i+j)%2==1
即先手倾向于走向先手赢,后手倾向于走向先手输;

至于将字符映射到1,0,-1的过程,则通过check函数实现;

代码

队友代码如下

#include <bits/stdc++.h>
using namespace std;
const int N = 502;

int dp[N][N], n, m;
char s[N][N];

void solve()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++)
        scanf("%s", s[i]);

    auto dpf = [&](auto ck, int final = 1)
    {
        for (int i = n - 1; i >= 0; i--)
        {
            for (int j = m - 1; j >= 0; j--)
            {
                dp[i][j] = ck(i, j);
                if (dp[i][j] != 0)
                    continue;
                if (i == n - 1 && j == m - 1)
                    continue;
                vector<int> nxt;
                if (i < n - 1)
                    nxt.push_back(dp[i + 1][j]);
                if (j < m - 1)
                    nxt.push_back(dp[i][j + 1]);
                if ((i + j) % 2 == 0)
                {
                    dp[i][j] = *max_element(nxt.begin(), nxt.end());
                }
                else
                {
                    dp[i][j] = *min_element(nxt.begin(), nxt.end());
                }
            }
        }
        return dp[0][0] == final;
    };

    if (dpf(
            [](int i, int j) -> int
            {
                if (s[i][j] == 'B')
                    return -1;
                return s[i][j] == 'A';
            }))
        printf("yes ");
    else
        printf("no ");

    if (dpf([](int i, int j) -> int { return s[i][j] == '.' ? 0 : -1; }, 0))
        printf("yes ");
    else
        printf("no ");

    if (dpf(
            [](int i, int j) -> int
            {
                if (s[i][j] == 'A')
                    return -1;
                return s[i][j] == 'B';
            }))
        puts("yes");
    else
        puts("no");
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        solve();
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值