AtCoder Beginner Contest 235 (C-F)

C - The Kth Time Query (模拟)

题意:给定长度为n的数组,然后有m个询问,每个询问给出一个二元组(x, k),即询问值为x的数在数组里面从左到右第k次出现的下标是多少,如果不存在就输出-1.

题解:
可以将值相同的下标都放到一个数组里面去,查询的时候直接访问到这个x数组的第k个下标即可,但是注意到这里的数达到了1e9级别,因此需要用到离散化

AC代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <string>
#include <vector>

using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int g[N], idx[N];
vector<int> ve[N];

int main()
{    
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &g[i]), idx[i] = g[i];
    // 离散化
    sort(idx + 1, idx + n + 1);
    int cnt = unique(idx + 1, idx + n + 1) - idx - 1;
    
    for (int i = 1; i <= n; i ++ )
    {
        int id = lower_bound(idx + 1, idx + cnt + 1, g[i]) - idx;
        ve[id].push_back(i);
    }

    for (int i = 1; i <= m; i ++ )
    {
        int x, y;
        scanf("%d%d", &x, &y);
        int id = lower_bound(idx + 1, idx + cnt + 1, x) - idx;
        if (ve[id].size() == 0 || y > ve[id].size() || idx[id] != x) puts("-1");
        else printf("%d\n", ve[id][y - 1]);
    }
    return 0;
}

D - Multiply and Rotate(bfs)

题意:给定一个x、a、n,有两种操作,操作一将x替换为a*x,操作二将x的最后一个数字放到最前面去(如123->312),只有在x模10不等于0的情况下才能进行操作二

题解: bfs硬模拟就行,然后开long long。

AC代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <string>
#include <vector>

using namespace std;
typedef long long ll;
int a, n;
int f[9];   // f[i] 表示10^i
bool st[1000010];
int len;    // n的位数
string To_string(ll x)
{
    string res;
    while (x) res += (x % 10 + '0'), x /= 10;
    return res;
}

int bfs()
{
    queue<pair<int, int> > q;
    q.push({1, 0});
    st[1] = true;
    while (q.size())
    {
        int x = q.front().first, d = q.front().second;
        q.pop();
        if (x == n) return d;
        if (To_string(1ll * a * x).size() <= len) q.push({x * a, d + 1});     // 相当于剪枝,当x大于答案的位数时,后续的操作一定不会使x变成n
        if (x % 10 != 0)                           // 模10不等于才可进行操作二
        {
            int len = To_string(x).size();
            int y = x % 10 * f[len - 1] + x / 10;  // 将最后一位数字放到最前面去
            if (!st[y]) q.push({y, d + 1});
            st[y] = true;
        }
    }
    return -1;
}

int main()
{    
    f[0] = 1;
    for (int i = 1; i <= 8; i ++ ) f[i] = f[i - 1] * 10;
    cin >> a >> n;
    len = To_string(n).size();
    cout << bfs() << endl;
    return 0;
}

E - MST + 1(Kruskal + 离线处理

题意:给定n个点m条有边权的边,然后有q个询问,对于每个询问给出一条边(u->v)及边权,问在原图中加入这条边后这条边是否是最小生成树里面的边,若是则输出yes,否则no

题解:
把原图中的m条边和询问中的q条边放入到一个数组中按边权从小到大进行排序,然后依次加边到生成图中去,对于当前的边(u,v),如果加入这条边之后图是不会形成环且这条边是询问的边,那么说明这条边存在最小生成树中。如果加入这条边之后图是会形成环且这条边是询问的边,那么说明这条边不存在最小生成树中。要注意的点是我们寻常进行Kruskal算法是如果这条边加入不会形成环的时候会继续两个生成图的合并即并查集的操作,而在这如果这条边是询问的边的我们不能进行合并操作,否则会影响后续的判断

AC代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <string>
#include <vector>

using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
bool ans[N];    // 存储答案,1即为yes
int f[N];
struct node
{
    int x, y, val, idx;  // x, y边的两个端点,val该边权值,idx该边的询问的编号
    bool flag;          // 为true则说明该边为询问的边,否则就是原图中的边
    friend bool operator <(node a, node b)   
    {
        return a.val < b.val;
    }
}g[N << 1];

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

int main()
{    
    int n, m, q;
    cin >> n >> m >> q;
    int cnt = 0;
    for (int i = 1; i <= m; i ++ )
    {
        int x, y, z;
        cin >> x >> y >> z;
        g[++cnt] = {x, y, z, i, 0};
    }

    for (int i = 1; i <= q; i ++ )
    {
        int x, y, z;
        cin >> x >> y >> z;
        g[++cnt] = {x, y, z, i, 1};
    }

    sort(g + 1, g + cnt + 1);
    for (int i = 1; i <= n; i ++ ) f[i] = i;

    for (int i = 1; i <= cnt; i ++ )
    {
        int fx = find(g[i].x), fy = find(g[i].y);
        if (fx != fy)
        {
            if (g[i].flag) ans[g[i].idx] = true;
            else f[fx] = fy;
        }
    }
    for (int i = 1; i <= q; i ++ )
        if (ans[i]) puts("Yes");
        else puts("No");
    return 0;
}

F - Variety of Digits(数位dp,听着好高级啊,就是个记忆化搜索

题意:找到1~n中的数满足其数中包括c1、c2、…、cm的和(ci <= 9 && m <= 9)

题解:
这是一道比较明显的数位dp题目,从高位到低位一位一位的考虑。 f[i][j]表示前i位的状态为j的数的和(这里的状态是指前i位里面含有c1,c2,…,cm的情况如果这个数含有ci,则二进制表示下的ci + 1位为1,比如c1 = 2, c2 = 4, c3 = 8,则数字1234的状态为20(二进制表示下的10100),数字248的状态为276(二进制表示下的100010100)),num[i][j]表示表示前i位的状态为j的数的个数。

AC代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <string>
#include <vector>

using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII;
const int N = 1e4 + 10, M = 20, mod = 998244353;
ll p[N];
int g[N], cnt, res;
ll f[N][1 << 12], num[N][1 << 12];
bool st[M];

// pos表示当前枚举的是第几位,val是状态,flag用于保证枚举出来的数一定是小于n的,zero保证不含前导零
PII dfs(int pos, int val, bool flag, bool zero)
{
    if (pos > cnt) return {res == val, 0};  // 若等于res说明该数是包含所有的ci

    if (!flag && !zero && f[pos][val] != -1) return {num[pos][val], f[pos][val]};

    int up = flag ? g[pos] : 9;   // 此处即是控制枚举出来的数一定不会大于n的主要操作
    PII sum = {0, 0};

    for (int i = 0; i <= up; i ++ )
    {
        PII xx;
        if (zero && !i) xx = dfs(pos + 1, val, flag && i == up, true);               // 有前导0
        else if (st[i]) xx = dfs(pos + 1, val | (1 << i), flag && i == up, false);   //状态val可能会发生变化 
        else xx = dfs(pos + 1, val, flag && i == up, false);                        // 状态val一定不会发生变化
        sum.first = (sum.first + xx.first) % mod;
        sum.second = (sum.second + (i * p[cnt - pos] % mod * xx.first % mod + xx.second) % mod) % mod;
    }

    if (!zero && !flag) f[pos][val] = sum.second, num[pos][val] = sum.first;      
    return sum;
}

int main()
{

    string s;
    cin >> s;
    memset(f, -1, sizeof f);
    for (int i = 0; i < s.size(); i ++ )
        g[++cnt] = s[i] - '0';
    p[0] = 1;
    for (int i = 1; i <= cnt; i ++ ) p[i] = (p[i - 1] * 10) % mod;
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++ )
    {
        int x;
        cin >> x;
        st[x] = true;
        res |= 1 << x;         //用于搜索过程中快速判断一个数是不是符合要求的
    }

    PII ans = dfs(1, 0,true, true);
    printf("%lld\n", ans.second);
    return 0;
}

完结,如有错误,还请指出,感谢!!!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值