AtCoder Beginner Contest 217 A B C D E G 题解

在这里插入图片描述
第 一 次 6 题 纪 念 第一次6题纪念 6

A - Lexicographic Order

题意
s 字 符 串 的 字 典 序 是 否 小 于 t s字符串的字典序是否小于t st
思路
模 拟 模拟
时间复杂度 O 1 O1 O1

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int N = 1e6 + 10 , M = 2010 , inf = 0x3f3f3f3f , mod = 1e9 + 7 ;
 
signed main()
{
    string s , t ;
    cin >> s >> t ;
    if(s < t) puts("Yes") ;
    else puts("No") ;
    return 0;
}

B - AtCoder Quiz

题意
给 定 4 个 不 同 的 字 符 串 给定4个不同的字符串 4
输 入 三 个 不 同 字 符 串 , 求 没 有 出 现 过 的 那 个 输入三个不同字符串,求没有出现过的那个 ,
思路
模 拟 模拟
时间复杂度 O 1 O1 O1

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int N = 1e6 + 10 , M = 2010 , inf = 0x3f3f3f3f , mod = 1e9 + 7 ;
map<string,int> q ;
signed main()
{
    string x ;
    
    q["ABC"] ++ ;
    q["ARC"] ++ ;
    q["AGC"] ++ ;
    q["AHC"] ++ ;
    
    for(int i = 1 ; i <= 3 ; i ++)
    {
        cin >> x ;
        q[x] ++ ;
    }
    
    for(auto i : q)
    {
        if(i.y == 1) 
        {
            cout << i.x << "\n" ;
            break ;
        }
    }
    return 0;
}

C - Inverse of Permutation

题意
给 定 一 个 n 的 排 列 给定一个n的排列 n
求 对 所 有 的 [ 1 < = i < = n ] 等 于 i 的 下 标 求对所有的[1 <= i <= n ] 等于i的下标 [1<=i<=n]i
思路
模 拟 模拟
时间复杂度 O n On On

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int N = 1e6 + 10 , M = 2010 , inf = 0x3f3f3f3f , mod = 1e9 + 7 ;
int n ;
int a[N] ;
int q[N] ;
signed main()
{
    cin >> n ;
    
    fer(i,1,n) sf(a[i]) ;
    
    fer(i,1,n)
    {
        q[a[i]] = i ;
    }
    
    
    fer(i,1,n)
    {
        cout << q[i] << " " ;
    }
    return 0;
}

D - Cutting Woods

题意
一 开 始 有 一 根 木 棒 一开始有一根木棒
长 度 为 n 长度为n n
从 1 到 n 编 号 从1到n编号 1n
输 入 q 个 操 作 输入q个操作 q

操 作 1 操作1 1
断 开 x 所 在 的 木 棒 断开x所在的木棒 x
[ 1 , n ] 断 开 x 变 成 了 [ 1 , x ] [ x + 1 , n ] [1,n] 断开x变成了 [1,x] [x+1,n] [1,n]x[1,x][x+1,n]

操 作 2 操作2 2
询 问 x 所 在 的 区 间 长 度 询问x所在的区间长度 x

q < = 1 e 5 q <= 1e5 q<=1e5
n < = 1 e 9 n <= 1e9 n<=1e9
1 < = x < = 1 e 9 1 <= x <= 1e9 1<=x<=1e9
思路
有 个 很 重 要 的 性 质 有个很重要的性质
分 割 之 后 左 边 以 及 右 边 这 个 区 间 的 答 案 是 固 定 的 分割之后左边以及右边这个区间的答案是固定的
也 就 是 说 答 案 只 跟 分 割 点 有 关 也就是说答案只跟分割点有关
比 如 区 间 [ l , r ] 分 割 x 变 成 了 [ l , x ] , [ x + 1 , r ] 比如区间[l,r] 分割x变成了[l,x] , [x+1,r] [l,r]x[l,x],[x+1,r]
可 以 发 现 可以发现
l 到 x 这 个 区 间 的 询 问 答 案 都 是 x − l + 1 l到x这个区间的询问答案都是x-l+1 lxxl+1
x + 1 到 r 这 个 区 间 的 询 问 答 案 都 是 r − ( x + 1 ) − 1 x+1到r这个区间的询问答案都是r-(x + 1) - 1 x+1rr(x+1)1

所 以 可 以 考 虑 二 分 找 到 所 在 区 间 相 邻 2 个 的 分 割 点 下 标 所以可以考虑二分找到所在区间相邻2个的分割点下标 2
先 考 虑 边 界 问 题 先考虑边界问题
边 界 无 非 是 0 − n + 1 / 0 − n / 1 − n / 1 − n + 1 边界无非是0 - n + 1 / 0 - n / 1 - n / 1 - n + 1 0n+1/0n/1n/1n+1
这 4 种 的 其 中 一 种 这4种的其中一种 4

这 个 时 候 先 假 设 区 间 是 [ 1 , 2 ] , [ 3 , 4 ] , [ 5 ] 这个时候先假设区间是[1,2] , [3,4] , [5] [1,2],[3,4],[5]

分割点是 0/1 2 4 5/5+1

对于查询2
找到第一个大于等于2的数是2
第一个小于2的数应该是 0/1
答案是2
所以应该是 2 - 0 = 2
左边界应该是0

在考虑查询5
答案是1
找到第一个大于等于5的数是5/6
第一个小于5的数应该是4
所以应该是 5 - 4 = 1
右边界是n

在考虑二分操作的时候 分割点数组保持有序
所以可以用set动态维护

时间复杂度 O n l o g n Onlogn Onlogn

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int N = 1e6 + 10 , M = 2010 , inf = 0x3f3f3f3f , mod = 1e9 + 7 ;
set<int> q ;
int n , t ;
signed main()
{
    cin >> n >> t ;
    q.insert(0) ;
    q.insert(n) ;
    
    while(t--)
    {
        int c , x ;
        sf(c) , sf(x) ;
        if(c == 1)
        {
            q.insert(x) ;
        }
        else
        {
            cout << *q.lower_bound(x) - *--q.lower_bound(x) << "\n" ;
        }
    }
    return 0;
}

E - Sorting Queries

题意
一 开 始 有 一 个 空 的 序 列 一开始有一个空的序列
q 个 操 作 q个操作 q

操 作 1 操作1 1
在 序 列 末 尾 添 加 一 个 数 x 在序列末尾添加一个数x x

操 作 2 操作2 2
输 出 序 列 第 一 个 数 并 且 删 除 输出序列第一个数并且删除

操 作 3 操作3 3
给 当 前 序 列 排 序 给当前序列排序

q < = 1 e 5 q <= 1e5 q<=1e5
x < = 1 e 9 x <= 1e9 x<=1e9
思路
假 设 不 考 虑 操 作 3 假设不考虑操作3 3
可 以 用 数 组 模 拟 队 列 o n 解 决 可以用数组模拟队列on解决 on

那 么 队 列 是 一 定 要 使 用 的 那么队列是一定要使用的 使
现 在 考 虑 一 下 怎 么 维 护 操 作 3 对 队 列 的 影 响 现在考虑一下怎么维护操作3对队列的影响 3

队 列 的 2 个 元 素 队列的2个元素 2
h h 代 表 对 头 hh代表对头 hh
t t 代 表 队 尾 tt代表队尾 tt

排 序 如 果 暴 力 的 话 O n 2 l o g n 排序如果暴力的话On^2logn On2logn
考 虑 如 何 优 化 考虑如何优化
有 一 个 很 重 要 的 性 质 是 排 序 的 时 候 有一个很重要的性质是排序的时候
是 边 插 入 边 排 序 是边插入边排序
所 以 可 以 用 s e t 动 态 维 护 所以可以用set动态维护 set

然 后 把 对 头 指 向 s e t 后 面 的 第 一 个 下 标 然后把对头指向set后面的第一个下标 set
时间复杂度 O n l o g n Onlogn Onlogn

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int N = 1e6 + 10 , M = 2010 , inf = 0x3f3f3f3f , mod = 1e9 + 7 ;
 
int a[N] ;
multiset<int> q ;
// 带重复元素的set
bool st[N] ;
signed main()
{
    int hh = 0 , tt = 0 ;
    
    int t ;
    cin >> t ;
    
    int k = 1; // k = 1 表示队列第一个数的下标是1
    int op ;
    
    while(t--)
    {
        sf(op) ;
        if(op == 1)
        {
            int x ;
            sf(x) ;
            a[++ tt] = x ;
            // 队尾加入x
        }
        else if(op == 2)
        {
        	// 如果排序过
            if(q.size())
            {
                cout << *q.begin() << "\n" ;
                q.erase(q.begin()) ;
                // 输入对头并且删除
            }
            else 
            {
                cout << a[k] << "\n" ;
                st[k] = 1 ;
                k ++ ;
                // 没有排序过就输出第一个数
                // 然后标记这个数没有了
                // 对头 ++ 
            }
        }
        else 
        {
            for(int i = k ; i <= tt ; i ++) 
            {
                if(!st[i]) q.insert(a[i]) ;
            }
            k = tt + 1 ;
            // 插入之后 对头指向队尾的下一个下标
        }
    }
    return 0;
}

G - Groups

题意
有 1 , 2 , 3...... n 有1,2,3......n 1,2,3......n
n 个 数 n个数 n
问 这 n 个 数 可 以 分 为 多 少 组 问这n个数可以分为多少组 n
并 且 保 证 每 一 组 的 数 % m 的 值 没 有 相 等 的 并且保证每一组的数\%m的值没有相等的 %m
输 出 每 个 组 的 方 案 输出每个组的方案

n < = 5000 n <= 5000 n<=5000
思路
假 设 先 不 考 虑 保 证 每 一 组 的 数 % m 的 值 没 有 相 等 的 假设先不考虑保证每一组的数\%m的值没有相等的 %m

那 么 就 是 一 个 很 经 典 的 第 二 类 斯 特 林 数 那么就是一个很经典的第二类斯特林数
f [ i ] [ j ] 表 示 前 i 个 数 分 成 j 组 的 方 案 数 f[i][j]表示前i个数分成j组的方案数 f[i][j]ij
f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + j ∗ f [ i − 1 ] [ j ] f[i][j] = f[i-1][j-1] +j*f[i-1][j] f[i][j]=f[i1][j1]+jf[i1][j]

第 i 个 数 单 独 分 成 一 组 是 f [ i − 1 ] [ j − 1 ] 第i个数单独分成一组是f[i-1][j-1] if[i1][j1]
或 者 第 i 个 数 分 成 前 面 已 经 有 的 j 组 中 的 其 中 一 组 j ∗ f [ i − 1 ] [ j ] 或者第i个数分成前面已经有的j组中的其中一组j * f[i-1][j] ijjf[i1][j]

那 么 考 虑 一 下 加 上 题 目 给 的 限 制 条 件 那么考虑一下加上题目给的限制条件
每 一 组 的 数 % m 的 值 没 有 相 等 的 每一组的数\%m的值没有相等的 %m
也 就 是 说 第 i 个 数 分 成 前 面 已 经 有 的 j 组 中 的 其 中 一 组 也就是说第i个数分成前面已经有的j组中的其中一组 ij
必 须 保 证 分 到 的 那 个 组 中 没 有 当 前 这 个 数 % m 的 值 出 现 必须保证分到的那个组中没有当前这个数\%m的值出现 %m

所 以 先 维 护 一 个 c n t 数 组 所以先维护一个cnt数组 cnt
表 示 a [ i ] % m 的 个 数 表示a[i] \% m的个数 a[i]%m

在 放 第 i 个 数 的 时 候 如 果 已 经 放 过 了 这 个 数 了 在放第i个数的时候如果已经放过了这个数了 i
放 下 一 个 数 的 时 候 就 不 能 在 放 这 个 数 放下一个数的时候就不能在放这个数
所 以 状 态 方 程 就 变 成 了 所以状态方程就变成了
f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + k ∗ f [ i − 1 ] [ j ] f[i][j] = f[i-1][j-1] +k*f[i-1][j] f[i][j]=f[i1][j1]+kf[i1][j]

但 是 这 样 还 是 有 一 个 问 题 但是这样还是有一个问题
第 一 层 循 环 枚 举 的 是 i 也 就 是 这 个 数 第一层循环枚举的是i也就是这个数 i
第 二 层 循 环 枚 举 的 是 j 组 数 第二层循环枚举的是j组数 j
我 们 发 现 不 能 不 重 不 漏 的 进 行 转 移 我们发现不能不重不漏的进行转移
因 为 必 须 保 证 每 个 数 是 按 照 顺 序 放 的 因为必须保证每个数是按照顺序放的

所 以 先 枚 举 组 数 j 所以先枚举组数j j
在 枚 举 i 在枚举i i
在 组 数 固 定 情 况 下 在组数固定情况下
依 次 放 入 每 个 数 即 可 依次放入每个数即可
k 的 求 法 具 体 细 节 可 以 看 代 码 k的求法具体细节可以看代码 k
时间复杂度 O n 2 On^2 On2

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int N = 1e6 + 10 , M = 5010 , inf = 0x3f3f3f3f , mod = 998244353 ;
int f[M][M] ;
// 前i个数分成j组的方案数
int n , m ;
int a[N] ;
// 1到i的每个数
int cnt[M] ;
// 每个数 % m的个数
int q[M] ;
// 工具数组
signed main()
{
    cin >> n >> m ;
    
    fer(i,1,n)
    {
        a[i] = i ;
        cnt[i % m] ++ ;
    }
    
    f[0][0] = 1 ;  // 初始化
    for(int j = 1 ; j <= n ; j ++)
    {
    	//  每次固定j组数 
        memcpy(q,cnt,sizeof cnt) ;
        // 把cnt数组给q数组
        for(int i = 1 ; i <= n ; i ++)
        {
            f[i][j] = (f[i-1][j-1] + f[i-1][j] * (j - ( cnt[a[i] % m] - q[a[i] % m] ) )) % mod;
            // k = j - ( cnt[a[i] % m] - q[a[i] % m] )
            // 表示用j所有组减去前面有多少个组放了这个数 % m
            q[a[i] % m] -- ;
            // 放了这个数之后 这个数 % m -- 
            // 表示j之前的所有组中放了a[i] % m 的组 ++
        }
    }
    
    for(int i = 1 ; i <= n ; i ++) cout << f[n][i] % mod << "\n" ;
 
    return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值