A:牛牛的DRB迷宫I
思路:设dp[i][j]为起点在(i,j)时的方案数
那么dp转移式就显然可以得到:

if(s[i][j] == 'R'){
    dp[i][j] = dp[i][j + 1];
}
if(s[i][j] == 'D'){
    dp[i][j] = dp[i + 1][j];
}
if(s[i][j] == 'B'){
    dp[i][j] = dp[i][j + 1] + dp[i + 1][j];
}

考虑到此dp转移式是由后面状态更新,所以用递归写比较简单

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
int n, m;
ll dp[55][55];
char s[55][55];
ll dfs(int i, int j){

    if(i <= 0 || i > n || j <= 0 || j > m) return 0;
    if(dp[i][j]) return dp[i][j];
    if(s[i][j] == 'R'){
        dp[i][j] = dfs(i, j + 1) % mod;
    }
    if(s[i][j] == 'D'){
        dp[i][j] = dfs(i + 1, j) % mod;
    }
    if(s[i][j] == 'B'){
        dp[i][j] = (dfs(i, j + 1) + dfs(i + 1, j))% mod;
    }
    return dp[i][j];
}
void print(){
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            printf("%c", s[i][j]);
        }
        putchar('\n');
    }
}
int main(){
    while(scanf("%d%d", &n, &m) != EOF){
        memset(dp, 0, sizeof(dp));
        dp[n][m] = 1;
        for(int i = 1; i <= n; i++){
            scanf("%s", s[i] + 1);
        }
        //print();
        printf("%lld\n", dfs(1, 1));
    }
    return 0;
}

B:牛牛的DRB迷宫II
感受:这个题目真得需要多观察,不然感觉不好想
思路:

例子
BDXX
RBDX
XRBD
XXRB
XXXX
这是5 * 4的矩形,'X'代表未知字母,'B','D','R'为题目所给
观察第一行到第四行,起点为(1,1)----目前考虑的路径不经过'X'
到第一行的B(1,1),方案数为1
到第二行的B(2,2),方案数为2
到第三行的B(3,3),方案数为4
到第四行的B(4,4),方案数为8
...
显然按照这种构造方法,到第i行的B(i,i),方案数为;
有没有发现,这与二进制非常相符,那我们怎么构造3,5,...呢?
例如我想构造11
11 = 8 + 2 + 1
BDXX
RBDX
XRBD
XXRB
XXXF
'F'表示终点,那么我们可以知道所有到达F(不经过X)的路径一共8条,那我们就要思考怎样构造+2
BDXX
RBDX
XBBD
XDRB
XRXF
我们把(3,2)由'R'改成'B',(4,2)由'X'改成'D',(5,2)由'X'改成'R';
有没有发现新增一系列路径,可以到达(5,2)点,其方案数为2
那我们就可以把(5,3)改成'R',从而贡献F点,使得方案数变为8 + 2


有没有发现,其实第5行就是加法器(或者认为是二进制)
11 = 1011
整体思路已经说得很清楚了,剩下地大家可以自己思考,毕竟自己思考才能提升自己的能力


#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const ll mod = 1e9 + 7;
char s[50][50];
vector<int>pos;
ll k;
int n;
void solve(ll k){
    pos.clear();
    int cnt = 1;
    while(k){
        if(k % 2){
            pos.push_back(cnt);
        }
        cnt++;
        k /= 2;
    }
}
void print(){
    int Size = pos.size();
    printf("digit = %d\n", Size);
    for(int i = 0; i < Size; i++){
        printf("%d\n", pos[i]);
    }
}
void print1(){
    printf("%d %d\n", n, n);
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            printf("%c", s[i][j]);
        }
        putchar('\n');
    }
}
int main(){
    while(scanf("%lld", &k) != EOF){
        k %= mod;
        if(k == 0){
            printf("2 3\n");
            printf("DDD\n");
            printf("DDD\n");
            continue;
        }
        solve(k);
        int Size = pos.size();
        n = pos[Size - 1];
        for(int i = 1; i <= n; i++){
            s[i][i] = 'B';
            if(i != n){
                s[i + 1][i] = 'R';
                s[i][i + 1] = 'D';
            }
            s[n + 1][i] = 'B';
        }

        for(int j = 1; j <= n; j++){
            for(int i = j + 2; i <= n; i++){
                s[i][j] = 'D';
            }
        }

        for(int j = n; j >= 1; j--){
            for(int i = j - 2; i >= 1; i--){
                s[i][j] = 'R';
            }
        }

        for(int i = 0; i < Size - 1; i++){
            int tmp = pos[i];
            s[tmp + 1][tmp] = 'B';
        }
        printf("%d %d\n", n + 1, n);

        for(int i = 1; i <= n + 1; i++){
            for(int j = 1; j <= n; j++){
                printf("%c", s[i][j]);
            }
            putchar('\n');
        }
    }
    return 0;
}

C:牛牛的数组越位
思路:就是暴力模拟题,没什么难度。注意数组越界范围

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1005;
ll n, m, p, ma;
int ok;
ll a[maxn][maxn];
void check(ll x, ll y, ll val){
    if(x < 0 || x >= n || y < 0 || y >= m) ok = 1;
    ll dis = m * x + y;
    if(dis < 0 || dis > ma){
        ok = 2;
        return ;
    }
    ll res = dis % m;
    ll tp = dis / m;
    a[tp][res] = val;
}
int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        memset(a, 0, sizeof(a));
        scanf("%lld%lld%lld", &n, &m, &p);
        ma = m * n - 1;
        ok = 0;///ok = 1 B       = 2 RE
        ll x, y, val;
        while(p--){
            scanf("%lld%lld%lld", &x, &y, &val);
            if(ok != 2 )check(x, y, val);
        }
        if(ok == 2){
            printf("Runtime error\n");
        }
        else{
            for(int i = 0; i < n; i++){
                for(int j = 0; j < m; j++){
                    printf("%lld%c", a[i][j], j == m - 1 ? '\n' : ' ');
                }
            }
            if(ok == 1){
                printf("Undefined Behaviour\n");
            }
            else{
                printf("Accepted\n");
            }
        }
    }
    return 0;
}

D:牛牛与二叉树的数组存储
感受:这场两道暴力模拟题,见到就很难受。
思路:就直接按照题目给出的定义模拟即可

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
struct Node{
    int fa;
    int lson, rson;
    Node(){
        fa = -1;
        lson = rson = -1;
    }
}ans[maxn];
int a[maxn];
int main(){
    int n, Size = 0, root = -1;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
        if(a[i] != -1 && root == -1) root = a[i];
        Size = max(Size, a[i]);
    }
    printf("The size of the tree is %d\n", Size);
    printf("Node %d is the root node of the tree\n", root);
    for(int i = 1; i <= n; i++){
        if(a[i] != -1){
            int tp = i;
            int fa, lson, rson;
            fa = lson = rson = -1;
            if(tp * 2 <= n) lson = a[tp * 2];
            if(tp * 2 + 1 <= n) rson = a[tp * 2 + 1];
            if(tp / 2 > 0) fa = a[tp / 2];
            ans[a[i]].fa = fa;
            ans[a[i]].lson = lson;
            ans[a[i]].rson = rson;
        }
    }
    for(int i = 1; i <= Size; i++){
        printf("The father of node %d is %d, the left child is %d, and the right child is %d\n", i, ans[i].fa, ans[i].lson, ans[i].rson);
    }
    return 0;
}

E:牛牛的随机数
感受:代码量多一点,利用贡献量计算在本题集中极为常见
思路:多观察

例子[1,1] [3,5]
000
001
010
011---3
100
101---5
我们用1 ^ 3 + 1 ^ 4 + 1 ^ 5。
考虑每一位(bit)对答案的恭喜,从右向左观察第一位(1(3) 0(4) 1(5)),1的第一位也是1,那么只有4对答案有贡献。这就让我们得出一个思路,我们只要能快速统计一个区间的数在某一位上有多少个1,多少个0,就可以求出该二进制位对答案的贡献。

#include <bits/stdc++.h>
#define ls o << 1
#define rs o << 1 | 1
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const int maxn = 1e5 + 5;
struct Node{
    ll num1;
    ll num0;
    Node(){
        num1 = num0 = 0;
    }
};
ll l1, r1, l2, r2;
ll quick_mod(ll a, ll b, ll mod){
    ll sum = 1;
    while(b){
        if(b & 1) sum = sum * a % mod;
        a = a * a % mod;
        b /= 2;
    }
    return sum;
}
Node solve(ll x, ll y, ll bit){
    Node ans;
    ll len = (ll)1 << (bit + 1);///[1, len]
    ll t1, t2;
    t1 = x / len + 1; t2 = y / len + 1;
    x %= len; y %= len;
    ll num = (t2 - t1 - 1) < 0 ? 0 : t2 - t1 - 1; num %= mod;
    ll dif = len / 2;///[0, dif - 1] 0  [dif, len - 1] 1
    if(t1 == t2){
        if(x < dif && y < dif){
            ans.num0 = y - x + 1;
        }
        else if(x >= dif && y >= dif){
            ans.num1 = y - x + 1;
        }
        else{
            ans.num0 = dif - x;
            ans.num1 = y - dif + 1;
        }
        ans.num0 %= mod;
        ans.num1 %= mod;
        return ans;
    }


    ans.num0 = ans.num1 = (len / 2) % mod * num % mod;

    //printf("num0 = %lld   num1 = %lld\n", ans.num0, ans.num1);
    ll num0 = 0, num1 = 0;
    if(x >= dif){
        num1 = len - x;
        num1 %= mod;
    }
    else{
        num0 = dif - x; num0 %= mod;
        num1 = dif; num1 %= mod;
    }
    //printf("add: num0 = %lld  num1 = %lld\n", num0, num1);
    if(y >= dif){
        num1 = num1 + y - dif + 1; num1 %= mod;
        num0 = num0 + dif; num0 %= mod;
    }
    else{
        num0 = num0 + y + 1; num0 %= mod;
    }
    ans.num0 = (ans.num0 + num0) % mod;
    ans.num1 = (ans.num1 + num1) % mod;
    return ans;
}
void print(Node c, ll i){
    printf("***************************\n");
    printf("i = %lld\n", i);
    printf("num0 = %lld num1 = %lld\n", c.num0, c.num1);
    printf("**************************\n");
}
int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        scanf("%lld%lld%lld%lld", &l1, &r1, &l2, &r2);
        ll ans = 0; ll tmp = quick_mod(((r1 - l1 + 1) % mod * ((r2 - l2 + 1) % mod)) % mod, mod - 2, mod);
        //Node tt = solve(8, 12, 0);
        //print(tt, 0);
        for(ll i = 0; i < 62; i++){
            Node a = solve(l1, r1, i);
            Node b = solve(l2, r2, i);
            //print(a, i);
            //print(b, i);
            ll cnt = (((a.num1 % mod) * (b.num0 % mod) % mod) + ((a.num0 % mod) * (b.num1 % mod) % mod)) % mod;
            ll val = (((ll)1 << i) % mod) % mod;
            ans = ans + val * cnt % mod;
            ans %= mod;
        }
        ans = ans * tmp % mod;
        printf("%lld\n", ans);
    }
    return 0;
}

F:牛牛的Link Power I
感受:同样可以用贡献来算
思路:看下面例子分析

1000100101,记a = 10001对答案贡献,也就是4(5- 1),b = 1001对答案的贡献,也就是3(8 - 5)c = 101对答案的贡献,也就是2(10-8)
每两个1之间设一个变量贡献值
考虑所有答案选取1的方式,发现太杂,但是我们可以发现每一次选择不同1,它都是累加a 或 b 或 c。
那我们求答案,就是考虑a被累加多少次,b被累加多少次, c被累加多少次。
比如我们要算b被累加多少次?
b = 1**10011
左边可以取index = 1 或者 index = 5 的 1
右边可以取index = 8 或者 index = 10 的 1
那么b被累加2 * 2 次

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const int maxn = 1e5 + 5;
int n;
char s[maxn];
int main(){
    while(scanf("%d", &n) != EOF){
        scanf("%s", s + 1);
        ll ans = 0;
        int num = 0, num_1 = 0;
        for(int i = 1; i <= n; i++){
            if(s[i] == '1') num++;
        }
        int pos = -1;
        for(int i = 1; i <= n; i++){
            if(s[i] == '1'){
                if(num_1){
                    ans = ans + (ll)1 * (i - pos) * (num - num_1) % mod * num_1 % mod;
                    ans %= mod;
                }
                num_1++;
                pos = i;
            }
        }
        printf("%lld\n", ans);
    }
    return 0;
}

G:牛牛的Link Power II
感受:mod太可怕,已经在这方面犯了不少由于负数%mod导致一直wa的错误
思路:考虑把1改成0对当前答案ans有什么影响,显然这个1会让ans - (1与剩下的1构成的dis和),把0改成1同理。
那我们就要思考怎样快速算某一个1与剩下的1构成dis和(记作res)?
举例子

1 0 1 1 0 1 1 0 1
我们要把第4个1改成0
res = (4 - 1) + (4 - 3) + (6 - 4) + (7 - 4) + (9 - 4)
= 4 * 2 - (1 + 3) + (6 + 7 + 9) - 4 * 3
有没有观察出规律:1 + 3 其实就是把1的下标累加得到的和(也就是区间和) 4 * 2中的2就是区间1的个数
那我们就可以用线段树维护区间1的下标和以及1的个数,从而得到更新答案的目的

#include <bits/stdc++.h>
#define ls o << 1
#define rs o << 1 | 1
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const int maxn = 1e5 + 5;
struct Node{
    ll num_one;
    ll sum;
}node[maxn << 2];
ll n, m;
char s[maxn];
void up(ll o){
    node[o].num_one = node[ls].num_one + node[rs].num_one;
    node[o].sum = (node[ls].sum + node[rs].sum) % mod;
}
void build(ll o, ll l, ll r){
    if(l == r){
        if(s[l] == '1'){
            node[o].num_one = 1;
            node[o].sum = l;
        }
        else{
            node[o].num_one = 0;
            node[o].sum = 0;
        }
        return ;
    }
    ll mid = (l + r) / 2;
    build(ls, l, mid);
    build(rs, mid + 1, r);
    up(o);
}
void update(ll o, ll l, ll r, ll x, ll opt){
    if(l == r){
        if(opt == 1){
            node[o].num_one = 1;
            node[o].sum = x;
        }
        else{
            node[o].num_one = 0;
            node[o].sum = 0;
        }
        return ;
    }
    ll mid = (l + r) / 2;
    if(mid >= x) update(ls, l, mid, x, opt);
    else update(rs, mid + 1, r, x, opt);
    up(o);
}
ll query1(ll o, ll l, ll r, ll x, ll y){
    if(l >= x && y >= r){
        return node[o].sum;
    }
    ll ans = 0;
    ll mid = (l + r) / 2;
    if(mid >= x){
        ans += query1(ls, l, mid, x, y);
        ans %= mod;
    }
    if(y > mid){
        ans += query1(rs, mid + 1, r, x, y);
        ans %= mod;
    }
    return ans;
}
ll query2(ll o, ll l, ll r, ll x, ll y){
    if(l >= x && y >= r){
        return node[o].num_one;
    }
    ll ans = 0;
    ll mid = (l + r) / 2;
    if(mid >= x){
        ans += query2(ls, l, mid, x, y);
        ans %= mod;
    }
    if(y > mid){
        ans += query2(rs, mid + 1, r, x, y);
        ans %= mod;
    }
    return ans;
}
void print(ll ans){
    printf("ans = %lld\n", ans);
}
int main(){
    while(scanf("%lld", &n) != EOF){
        scanf("%s", s + 1);
        scanf("%lld", &m);
        ll ans = 0;
        ll num = 0, num_1 = 0;
        for(ll i = 1; i <= n; i++){
            if(s[i] == '1') num++;
        }
        ll pos = -1;
        for(ll i = 1; i <= n; i++){
            if(s[i] == '1'){
                if(num_1){
                    ans = ans + (ll)1 * (i - pos) * (num - num_1) % mod * num_1 % mod;
                    ans %= mod;
                }
                num_1++;
                pos = i;
            }
        }
        build(1, 1, n);
        ll q, r_num;
        printf("%lld\n", ans);
        while(m--){
            scanf("%lld%lld", &q, &pos);
            num_1 = query2(1, 1, n, 1, pos);
            if(q == 2) num_1--;
            r_num = num - num_1;
            if(q == 2) r_num--;
            ll val = (num_1 * pos - query1(1, 1, n, 1, pos))  + (query1(1, 1, n, pos, n) - r_num * pos);
            while(val < 0) val += mod;
            val %= mod;
            if(q == 1){
                ans = (ans + val) % mod;
                num++;
            }
            else{
                ans = ans - val;
                while(ans < 0) ans += mod;
                ans = ans % mod;
                num--;
            }
            update(1, 1, n, pos, q);
            printf("%lld\n", ans);
        }
    }
    return 0;
}

H:牛牛的k合因子数
感受:比赛时,看了好久,最后才想起可以直接暴力打表
思路:对于1 - n我们需要把所有数是几合因子数统计出来
例子


考虑K合因子的定义,指一个数的所有因子中,是合数的因子共有k个
很显然知道所有因子个数为(2 + 1) * (1 + 1) * (2 + 1)
那么容斥一下,减去非合数(2 + 1 + 2 + 1),也就是质数的种数 + 1(1是这个数)

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const int maxn = 1e5 + 5;
ll flag[maxn];
ll n;
int m;
ll Get(ll n){
    ll ans = 1;
    ll num_1 = 0;
    for(ll i = 2; i * i <= n; i++){
        if(n % i == 0){
            num_1++;
            ll num = 1;
            while(n % i == 0){
                num++;
                n /= i;
            }
            ans *= num;
        }
    }
    if(n > 1) ans *= 2, num_1++;
    return ans - (num_1 + 1);
}
int main(){
    while(scanf("%lld%d", &n, &m) != EOF){
        memset(flag, 0, sizeof(flag));
        for(ll i = 1; i <= n; i++){
            ll tmp = Get(i);
            flag[tmp]++;
        }
        int k;
        for(int i = 1; i <= m; i++){
            scanf("%d", &k);
            printf("%lld\n", flag[k]);
        }
    }
    return 0;
}

I:牛牛的汉诺塔
思路:找规律

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
ll dp[65][10];///dp[i][1] = dp[i][4]         dp[i][6] = dp[i][3]
int main(){
    int n;
    scanf("%d", &n);
    dp[1][2] = 1;
    dp[2][1] = dp[3][1] = 1; dp[2][2] = 1; dp[2][4] = 1;
    dp[4][1] = dp[5][1] = 4; dp[3][2] = 3; dp[3][3] = 1; dp[3][4] = 1; dp[3][6] = 1;
    for(int i = 6; i <= n + 1; i++){
        if(i % 2 == 0){
            dp[i][1] = 5 * dp[i - 2][1] - 4 * dp[i - 4][1] - 1;
        }
        else{
            dp[i][1] = dp[i - 1][1];
        }
    }
    for(int i = 3; i <= n + 1; i++){
        if(i % 2){
            dp[i][6] = 4 * dp[i - 2][6] + i / 2;
        }
        else{
            dp[i][6] = dp[i - 1][6];
        }
    }
    int i = n;
    if(n % 2 == 0){
        printf("A->B:%lld\n", dp[i][1]);
        printf("A->C:%lld\n", dp[i][1] - dp[i][6]);
        printf("B->A:%lld\n", dp[i][6]);
        printf("B->C:%lld\n", dp[i][1]);
        printf("C->A:%lld\n", dp[i][6] * 2);
        printf("C->B:%lld\n", dp[i][6]);
        printf("SUM:%lld\n", dp[i][1] + dp[i][1] - dp[i][6] + dp[i][6] + dp[i][1] + dp[i][6] * 2 + dp[i][6]);
    }
    else{
        printf("A->B:%lld\n", dp[i][1]);
        printf("A->C:%lld\n", dp[i + 1][1]- dp[i + 1][6]);
        printf("B->A:%lld\n", dp[i][6]);
        printf("B->C:%lld\n", dp[i][1]);
        printf("C->A:%lld\n", dp[i][6] - dp[i][1]);
        printf("C->B:%lld\n", dp[i][6]);
        printf("SUM:%lld\n", dp[i][1] + dp[i + 1][1]- dp[i + 1][6] + dp[i][6] + dp[i][1] + dp[i][6] - dp[i][1] + dp[i][6]);
    }
    return 0;
}

J:牛牛的宝可梦Go
思路:设dp[i]表示前i个时刻取走第i时刻物品的最大值
显然dp转移式为dp[i] = max(dp[0], dp[1], dp[2], ...,dp[i - 1]) + val(第i时刻)
但是这样更新答案,复杂度是炸了,所以我们需要思考怎样优化
考虑到dp[i] = max(dp[i], dp[j] + val)更行前提是(i - j >= dis[i.pos][j.pos]);
(i.pos表示i时刻所在位置,j.pos表示j时刻所在位置)---注意这个假设的前提,dp[i]表示前i个时刻取走第i时刻物品的最大值
dis[i.pos][j.pos] < n
所以dp[i] = max(dp[i - 1], dp[i - 2], dp[i - 3], ..., dp[i - n + 1], max(dp[0], dp[1], ..., dp[i - n]))
注意这里所有max里面的dp式,要保证满足i - j >= dis[i.pos][j.pos]
所以max(dp[0], dp[1], ..., dp[i - n])可以用数组维护,把复杂度降至1e7级别

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const ll mod = 1e9 + 7;
const ll inf = 1e18;
struct P{
    ll t;
    int p;
    ll val;
}in[maxn];
ll ans[maxn];
ll dis[205][205];
ll dp[maxn];
int n, m;
bool cmp(P a, P b){
    return a.t < b.t;
}
void floyd(){
    for(int k = 1; k <= n; k++){
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
            }
        }
    }

}

int main(){
    //while(scanf("%d%d", &n, &m) != EOF){
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                if(i != j)
                    dis[i][j] = inf;
                else
                    dis[i][j] = 0;
            }
        }
        int u, v, k;
        for(int i = 1;i <= m; i++){
            scanf("%d%d", &u, &v);
            dis[u][v] = 1;
            dis[v][u] = 1;
        }
        floyd();
        scanf("%d", &k);
        for(int i = 1; i <= k; i++){
            scanf("%lld%d%lld", &in[i].t, &in[i].p, &in[i].val);
        }
        sort(in + 1, in + k + 1, cmp);
        for(int i = 1; i <= k; i++) dp[i] = -inf, ans[i] = 0;
        dp[0] = 0; ans[0] = 0;
        in[0].p = 1; in[0].t = 0;
        for(int i = 1; i <= k; i++){
            for(int j = i - 1; j >= 0; j--){
                ll res = in[i].t - in[j].t;
                if(res >= n){
                    dp[i] = max(dp[i], ans[j] + in[i].val);
                    break;
                }
                int u = in[i].p, v = in[j].p;
                if(dis[u][v] <= res){
                    dp[i] = max(dp[i], dp[j] + in[i].val);
                }
            }
            ans[i] = max(dp[i], ans[i - 1]);
        }
        ll ko = 0;
        for(int i = 1; i <= k; i++) ko = max(ko, dp[i]);
        printf("%lld\n", ko);
    //}
    return 0;
}