感谢 B 题与 J 题的出题人0x3ea 与参与验题的小伙伴,同时感谢老师给予的出题机会以及各位同学的参与。

A:Misaki 与 奇偶数

思路:对于奇偶数的定义是前五位加起来为奇数,后五位加起来为偶数,前5位的取值范围为 ,后五位的取值范围为 枚举统计一下符合要求的数量,取二者之积即可。

标程:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

bool check(ll x){
    int sum = 0;
    while (x) {
        sum += x % 10;
        x /= 10;
    }
    return (sum & 1 ? 1 : 0);//判断奇数偶数
}

void solve(){
    ll odd = 0, even = 0;
    for(int i = 10000; i <= 99999; i ++){
        if(check(i)) even ++;
    }
    for(int i = 0; i <= 99999; i ++){
        if(!check(i)) odd ++;
    }
    cout << even * odd << '\n';
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    // cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

当然本题是填空题,直接输出答案也可以。

#include<bits/stdc++.h>
using namespace std;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cout << 2250000000 << '\n';
    return 0;
}

B:Misaki 与 密码

思路:按照题意模拟枚举子串的头部尾部即可,注意检验给出的几个条件

标程:

#include<iostream>
using namespace std;

int main(){
    cout << 1212 << '\n';
    return 0;
}

C:Misaki 与 无限斯达

思路:按照题意模拟即可

标程:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

void solve(){
    ll n, m, k, t;
    cin >> n >> m >> k >> t;
    ll ans = 0;
    if(k >= m){
        ans += k / m;
        k %= m;
    }
    t = t / n;
    for(int i = 1; i <= t; i ++){
        k *= 2;
        ans += k / m;
        k %= m;
    }
    cout << ans << '\n';
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

D:Misak 与 阶乘

思路:能想到,一个数的末尾 0 都是由因子 2 与因子 5 所提供的,N 的阶乘中因子 2 的数量肯定是足够多的,所以就统计因子 5 的数量即可,最后因为是平方,所以 0 的数量是 5 的因子数量乘以 2 。

标程:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

void solve(){
    ll n, m;
    cin >> n >> m;
    ll ans = 0;
    while(n){
        ans += n / 5;
        n /= 5;
    }
    ans *= 2;
    cout << (ans == m ? "YES\n" : "NO\n");    
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

E:Misaki 与 空竞集训

思路:根据题意,只有在同一个队伍并且性别相同的队员才能住在一起,所有先判断一个队伍是否能住一个双人间,分别用sum1, sum2记录能住的双人间数量和单人间数量。最后要考虑单人全住双人间或者单人间,以及能住双人间的全住双人间,能住单人间的在双人间和单人间选便宜的住。

标程:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

void solve(){
    ll n, c1, c2;
    cin >> n >> c1 >> c2;
    ll sum1 = 0, sum2 = 0;
    for(int i = 1; i <= n; i ++){
        string s;
        cin >> s;
        if(s[0] == s[1] || s[0] == s[2] || s[1] == s[2]){
            sum1 ++;
            sum2 ++;
        }
        else sum2 += 3;
    }
    ll ans = min(n * 3 * c1, n * 3 * c2);
    ans = min(ans, sum1 * c2 + sum2 * min(c1, c2));
    cout << ans << '\n';
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

F:Misaki 与 十一进制

思路:一个数是否是 5 的倍数其实只和它的个位数是不是 0 或者 5,可以发现,11 的末尾固定是1,所以说答案其实就是 对 5 取模是不是0。

标程:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

void solve(){
    ll n;
    cin >> n;
    ll ans = 0;
    for(int i = 1; i <= n; i ++){
        ll a1, a2;
        char b;
        cin >> a1 >> b;
        if(b != 'A') a2 = (b - '0');
        else a2 = 10;
        ans = (ans + a1 * a2) % 5;
    }
    if(ans == 0) cout << "YES\n";
    else cout << "NO\n";
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

G:Misaki 与 贪吃蛇

思路:枚举删除的左端点,二分右端点位置,关键是二分的check方式,很容易想到这个是满足二分性的。假设字符串长度为n,删除的子串是 s[l, r] ,那么 如果这个删除方案是合法的时候,s[1, l - 1] + s[r + 1, n] 的字符种类数大于 2 ,那么这个可以用 26 个前缀和维护一下,每次 check 检查 26 种字母在 s[1, l - 1] 和 s[r + 1, n] 是否出现过,如果出现至少两种字母,那么这次的 check 就是成功的。

标程:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

ll cal(char c){
    return c - 'a' + 1;
}

void solve(){
    string s;
    cin >> s;
    ll n = s.size();
    vector<ll> sum(28);
    vector<vector<ll>> pre(28, vector<ll>(n + 1));
    for(int i = 1; i <= n; i ++){
        ll x = cal(s[i - 1]);
        sum[x] ++;
        for(int j = 1; j <= 26; j ++){
            pre[j][i] = sum[j];
        }
    }
    ll ans = 0;
    set<ll> st;
    for(int i = 1; i <= n; i ++){
        ll x = cal(s[i - 1]);
        st.insert(x);
        auto check =[&](ll mid) -> bool{
            set<ll> sp;
            for(int j = 1; j <= 26; j ++){
                ll yu1 = pre[j][i - 1];
                ll yu2 = pre[j][n] - pre[j][mid];
                if(yu1 || yu2) sp.insert(j); 
            }
            return (sp.size() >= 2 ? true : false);
        };
        ll l = i, r = n;
        while(l < r){
            ll mid = l + r + 1 >> 1ll;
            if(check(mid)) l = mid;
            else r = mid - 1;
        }
        if(check(l)) ans += l - i + 1;
        if(st.size() == 2){
            ll yu = n - i;
            ans += yu * (yu + 1) / 2;
            break;
        }
    }
    cout << ans << '\n';
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

H:Misaki 与 魔大大

题意:这里需要转换一下思路,考虑从最终状态往前推,先记录所有的操作,对于每一行/列的操作,只有最后一次操作是有效的,从后往前推,记录n, m等于初始的行数和列数,如果该行/列的最后一次操作为火焰攻击,那么火焰地块加上 n/m 。但是如果第 i 行的最后一次操作之后,列的有效数量就要 - 1,第 i 列的操作同理。最后初始的 n * m 减去火焰地块数量即可。

标程:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

void solve(){
    ll n, m, q;
    cin >> n >> m >> q;
    ll sum = n * m;
    vector<ll> r(m + 1), l(n + 1);
    vector<array<ll, 3>> op(q + 1);
    for(int i = 1; i <= q; i ++){
        string s1, s2;
        ll x;
        cin >> s1 >> s2 >> x;
        if(s2 == "row"){
            if(s1 == "flame") op[i] = {1, x, 1};
            else op[i] = {1, x, 0};
        }
        else{
            if(s1 == "flame") op[i] = {0, x, 1};
            else op[i] = {0, x, 0};
        }
    }
    ll ans = 0;
    for(int i = q; i >= 1; i --){
        auto [x, y, z] = op[i];
        if(x == 0){
            if(r[y]) continue;
            r[y] = 1;
            if(z) ans += n;
            m --;
        }
        else{
            if(l[y]) continue;
            l[y] = 1;
            if(z) ans += m;
            n --;
        }
    }
    cout << sum - ans << '\n';
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

I:Misaki 与 空中回弹

思路:其实本体就是稍微变化一下的LIS问题,假设 ans[i] 是以第 i 个数开头的最长上升子序列,这里可以从后往前维护,初始化 ans[n] 为1,假设当前在第 i 个节点,那么 ans[i] 就是所有满足 的 ans[j] 的最大值,那么这个查询该怎么维护呢?

办法就是线段树维护,先开一棵有 n 个叶子节点的空树,每次查询到 ans[i] 之后把 segmenttree[h[i]] 修改为 ans[i] 即可。

标程:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

struct segtree{
    ll n;
    vector<ll> a;
    segtree(ll _n) : n(_n * 4 + 10), a(n) {}

    void pushup(ll u){
        a[u] = max(a[u * 2], a[u * 2 + 1]);
    }

    void modify(ll u, ll l, ll r, ll idx, ll val){
        if(l == idx && r == idx){
            a[u] = val;
        }
        else{
            ll mid = l + r >> 1ll;
            if(idx <= mid) modify(u * 2, l, mid, idx, val);
            else modify(u * 2 + 1, mid + 1, r, idx, val);
            pushup(u);
        }
    }

    ll query(ll u, ll l, ll r, ll ql, ll qr){
        if(ql <= l && r <= qr){
            return a[u];
        }
        else{
            ll mid = l + r >> 1ll;
            ll ans = 0;
            if(ql <= mid) ans = max(ans, query(u * 2, l, mid, ql, qr));
            if(r > mid) ans = max(ans, query(u * 2 + 1, mid + 1, r, ql, qr));
            return ans;
        }
    }
};

void solve(){
    ll n;
    cin >> n;
    vector<ll> h(n + 1), ans(n + 1);
    for(int i = 1; i <= n; i ++) cin >> h[i];
    segtree seg(n);
    ans[n] = 1;
    seg.modify(1, 1, n, h[n], 1);
    for(int i = n - 1; i >= 1; i --){
        ans[i] = seg.query(1, 1, n, h[i], n) + 1;
        seg.modify(1, 1, n, h[i], ans[i]);
    }
    for(int i = 1; i <= n; i ++) cout << ans[i] - 1 << ' ';
    cout << '\n';
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

J:Masaya 与 赛前改造

思路:首先,如果 那么成功的概率必定是0。

否则的话, 次内改造成功的概率 = 恰好1次改造成功的概率 + ... + 恰好次改造成功的概率

考虑怎样求恰好 次改造成功的概率。

恰好 次改造成功的概率,就是恰好 次改造成功案数 * 发生这种情况的概率

因为改造成功后立刻停止,所以第 次改造一定是成功的

在前 次改造中,有 次改造成功, 次改造失败,恰好 次改造成功的方案数就是:

每一种方案成功的概率就是:

那么总的成功的概率就是:

累加求和即可,注意需要特判改造次数小于 的情况。

标程:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000010, mod = 1e9 + 7;
const int Mod = 1e9 + 7, INF = 0x3f3f3f3f;

ll fac[N], inv[N];

ll fastpow(ll a, ll n){
    ll base = a, res = 1;
    while(n){
        if(n & 1) res = (res * base) % mod;
        base = (base * base) % mod;
        n >>= 1;
    }
    return res;
}

ll Inv(ll a){
    return fastpow(a, mod - 2);
}

ll C(ll n, ll m){
    if(n < 0 || m < 0 || n < m) return 0;
    return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

void init(ll n){
    fac[0] = 1;
    for(int i = 1; i <= n; i ++) fac[i] = (i * fac[i - 1]) % mod;
    inv[n] = Inv(fac[n]);
    for(int i = n - 1; i >= 0; i --) inv[i] = inv[i + 1] * (i + 1) % mod;
}

void solve(){
    ll n, x;
    cin >> n >> x;
    if(n < x) cout << 0 << '\n';
    else{
        ll ans = 0, INV = Inv(x);
        for(int i = x; i <= n; i ++){
            ll now = 1;
            now = now * fastpow(INV, x - 1) % mod;
            now = now * fastpow(x - 1, i - x) % mod * fastpow(INV, i - x) % mod;
            now = now * C(i - 1, i - x) % mod;
            now = (now * INV) % mod;
            ans = (ans + now) % mod;
        }
        cout << ans << '\n';
    }
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    init(1000000);
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}