大一下第十四周学习笔记

周二 6.1(dp)

这周开始刷kuangbin基础dp专题。昨天写高数作业去了没训练

A - Max Sum Plus Plus(dp空间与时间优化)

这题的dp方程我写出来了,但是感觉空间也炸时间也炸,就懵逼了

要同时对空间和时间优化

dp[i][j]表示前j一定选,前j个数分成i段的最大和

那么根据第j个数为新的一段以及和j之前的数为一段两种情况可以写一个dp方程

dp[i][j]=a[j]+max(dp[i][j-1]+dp[i-1][k]) (k<j)

我当时就懵逼了,m的范围都没给,而且n到1e6,必然超时啊

突破口在max(dp[i-1][k]) 发现这只是需要上一行的j之前所有dp值的最大值

那么我们显然可以开一个数组记录这个最大值,这样就不用枚举k了,大大节省了时间

假设这个数组是pre[j-1]

那么dp方程dp[i][j]=a[j]+max(dp[i][j-1]+pre[j-1])

发现i这一维度可以直接省掉,也就是dp[j]=a[j]+max(dp[j-1]+pre[j-1])

这样也解决了空间的问题 最后答案为max(dp[1-n])

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e6 + 10;
int dp[N], pre[N], a[N], n, m, mx;

int main()
{
    while(~scanf("%d%d", &m, &n))
    {
        memset(dp, 0, sizeof(dp));
        memset(pre, 0, sizeof(pre));

        _for(i, 1, n) scanf("%d", &a[i]);
        _for(i, 1, m)
        {
            mx = -1e9;
            _for(j, i, n) //枚举从i开始,因为分成i段至少要i个元素
            {
                dp[j] = a[j] + max(dp[j - 1], pre[j - 1]);
                pre[j - 1] = mx; //这里先赋值再更新mx,因为pre[j-1]是不包括j的
                mx = max(mx, dp[j]);
            }
        }
        printf("%d\n", mx); 
    }

	return 0;
}

B - Ignatius and the Princess IV(STL)

这题和dp有什么关系吗……

直接unordered_map秒杀

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

unordered_map<int, int> mp;
int n;

int main()
{
    while(~scanf("%d", &n))
    {
       mp.clear();
       _for(i, 1, n)
       {
           int x; scanf("%d", &x);
           mp[x]++;
       }

       for(auto x: mp)
            if(x.second >= (n + 1) / 2)
            {
                printf("%d\n", x.first);
                break;
            }
    }

	return 0;
}

C - Monkey and Banana

这题就有点类似最大上升子序列,但是做一些处理

虽然每一个积木可以用无数次,但是如果这个积木的高度确定了话,只可能用一次

而且长和宽可以均处理成一个min 一个max,也就是长比宽大

所以可以一个积木化成三个积木,每个积木至多用一次

之后排个序,保证dp的无后效性

然后用最长上升子序列的dp思路就好

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 100;
int dp[N], n;
struct node { int x, y, h; };

bool cmp(node a, node b)
{
    if(a.x == b.x)
        return a.y < b.y;
    return a.x < b.x;
}

int main()
{
    int kase = 0;
    while(scanf("%d", &n) && n)
    {
        vector<node> ve;
        _for(i, 1, n)
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            ve.push_back(node{min(a, b), max(a, b), c});
            ve.push_back(node{min(b, c), max(b, c), a});
            ve.push_back(node{min(a, c), max(a, c), b});
        }
        sort(ve.begin(), ve.end(), cmp);

        int ans = 0;
        rep(i, 0, ve.size())
        {
            dp[i] = ve[i].h;
            _for(j, 0, i - 1)
                if(ve[j].x < ve[i].x && ve[j].y < ve[i].y)
                    dp[i] = max(dp[i], ve[i].h + dp[j]);
            ans = max(ans, dp[i]);
        }
        printf("Case %d: maximum height = %d\n", ++kase, ans);
    }

	return 0;
}

周四 6.2

D - Doing Homework(状压dp + 方案记录)

这道题自己独立做没做出来

看题解原来是状压dp

其实看到n <= 15就要意识到了

这么小的范围很可能是状压dp

既然这样,dp[S]表示状态为S时,1表示选了,0表示没选,的最优结果

那么转移方程就很好写了,枚举当前状态是由哪一个状态转移过来就行了

这道题还要记录方案,所以要记录一下dp转移时的选择以及前驱,用结构体记录一下就好

因为输出的时候是从后往前递归,之后再输出的,所以枚举课程的时候需要逆序枚举,按照题目要求的顺序

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 20;
struct node
{
    string name;
    int d, t;
}a[N];
struct node2
{
    int t, pre, choose;
}record[1 << 15];
int dp[1 << 15], n;

void print(int x)
{
    if(!x) return;
    print(record[x].pre);
    cout << a[record[x].choose].name << endl;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        memset(record, 0, sizeof(record));
        memset(dp, 0x3f, sizeof(dp));

        scanf("%d", &n);
        _for(i, 1, n) cin >> a[i].name >> a[i].d >> a[i].t;

        dp[0] = 0;
        rep(S, 1, 1 << n)
            for(int j = n; j >= 1; j--)
                if(S & (1 << (j - 1)))
                {
                    int P = S ^ (1 << (j - 1));
                    int val = max(record[P].t + a[j].t - a[j].d, 0);
                    if(dp[P] + val < dp[S])
                    {
                        dp[S] = dp[P] + val;
                        record[S].t = record[P].t + a[j].t;
                        record[S].choose = j;
                        record[S].pre = P;
                    }
                }

        printf("%d\n", dp[(1 << n) - 1]);
        print((1 << n) - 1);
    }

	return 0;
}

E - Super Jumping! Jumping! Jumping!(最长上升子序列)

 水题秒了

类似最长上升子序列的思路

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e3 + 10;
int a[N], n;
ll dp[N];

int main()
{
    while(scanf("%d", &n) && n)
    {
        _for(i, 1, n) scanf("%d", &a[i]);

        ll ans = 0;
        _for(i, 1, n)
        {
            dp[i] = a[i];
            _for(j, 1, i - 1)
                if(a[j] < a[i])
                    dp[i] = max(dp[i], dp[j] + a[i]);
            ans = max(ans, dp[i]);
        }
        printf("%lld\n", ans);
    }

	return 0;
}

F - Piggy-Bank(完全背包)

裸的完全背包,秒了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 500 + 10;
const int M = 1e4 + 10;
int dp[M], w[N], v[N], n, m;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
       int a, b;
       scanf("%d%d%d", &a, &b, &n);
       m = b - a;
       _for(i, 1, n) scanf("%d%d", &v[i], &w[i]);

       memset(dp, 0x3f, sizeof(dp));
       dp[0] = 0;
       _for(i, 1, n)
            _for(j, w[i], m)
                dp[j] = min(dp[j], dp[j - w[i]] + v[i]);

        if(dp[m] > 1e9) puts("This is impossible.");
        else printf("The minimum amount of money in the piggy-bank is %d.\n", dp[m]);
    }

	return 0;
}

G - 免费馅饼(简单dp)

dp[i][j]表示前i秒在第j个位置的最多馅饼

dp[i][j] = a[i][j] + max(dp[i - 1][j - 1],dp[i - 1][j], dp[i - 1][j + 1 ])

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
int dp[N][15], a[N][15], n, T;

int main()
{
    while(scanf("%d", &n) && n)
    {
       T = 0;
       memset(a, 0, sizeof a);
       memset(dp, 0, sizeof dp);

       _for(i, 1, n)
       {
           int x, t;
           scanf("%d%d", &x, &t);
           T = max(T, t);
           a[t][x]++;
       }

       _for(i, 4, 6) dp[1][i] = a[1][i];
       _for(i, 2, T)
            _for(j, 0, 10)
                dp[i][j] = max(dp[i][j], a[i][j] + max(dp[i - 1][max(j - 1, 0)], max(dp[i - 1][j], dp[i - 1][min(j + 1, 10)])));

        int ans = 0;
        _for(j, 0, 10) ans = max(ans, dp[T][j]);
        printf("%d\n", ans);
    }

	return 0;
}

H - Tickets(简单dp)

比较水的dp

这里秒的转化可以模仿高精度进位

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e3 + 10;
int dp[N], a[N], b[N], n;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
       scanf("%d", &n);
       _for(i, 1, n) scanf("%d", &a[i]);
       _for(i, 1, n - 1) scanf("%d", &b[i]);

       _for(i, 1, n)
       {
           dp[i] = dp[i - 1] + a[i];
           if(i - 2 >= 0) dp[i] = min(dp[i], dp[i - 2] + b[i - 1]); //不等式这么写不容易错
       }

       int t = dp[n], ans1, ans2, ans3;
       ans3 = t;
       ans2 = ans3 / 60; ans3 %= 60;
       ans1 = ans2 / 60; ans2 %= 60;

       int op = 1;
       if(8 + ans1 >= 12) ans1 -= 12, op = 2;
       printf("%02d:%02d:%02d ", 8 + ans1, ans2, ans3);
       puts(op == 1 ? "am" : "pm");
    }

	return 0;
}

I - 最少拦截系统(最长上升子序列)

做过,知道结论即答案是最长上升子序列

用的是nlogn的做法,之前整理过模板,直接抄模板

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 3e4 + 10;
int dp[N], d[N], a[N], n, len;

int main()
{
    while(~scanf("%d", &n))
    {
        _for(i, 1, n) scanf("%d", &a[i]);
        d[1] = a[1]; len = 1;
        _for(i, 2, n)
        {
            if(a[i] > d[len]) d[++len] = a[i];
            else d[lower_bound(d + 1, d + len + 1, a[i]) - d] = a[i];
        }
        printf("%d\n", len);
    }

	return 0;
}

 J - FatMouse's Speed(最长上升子序列+记录方案)

先排序一下保证dp后效性

然后依然是类似最长上升子序列的思路

记录一下方案就好了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int dp[N], pre[N];
struct node
{
    int w, s, id;
}a[N];
int n;

bool cmp(node x, node y)
{
    if(x.w != y.w) return x.w < y.w;
    return x.s > y.s;
}

void print(int x)
{
    if(!x) return;
    print(pre[x]);
    printf("%d\n", a[x].id);
}

int main()
{
    int w, s;
    while(~scanf("%d%d", &w, &s))
        a[++n] = node{w, s, n};
    sort(a + 1, a + n + 1, cmp);

    int ans = 0, pos;
    _for(i, 1, n)
    {
        dp[i] = 1;
        _for(j, 1, i - 1)
            if(a[j].w < a[i].w && a[j].s > a[i].s && dp[i] < dp[j] + 1)
            {
                dp[i] =  dp[j] + 1;
                pre[i] = j;
            }
        if(ans < dp[i])
        {
            ans = dp[i];
            pos = i;
        }
    }

    printf("%d\n", ans);
    print(pos);

	return 0;
}

周五 6.4(dp)

L - Common Subsequence

最长公共子序列

#include <cstdio>
#include <cstring>
#include <algorithm>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int dp[N][N];
char a[N], b[N];

int main()
{
    while(~scanf("%s%s", a + 1, b + 1))
    {
        int lena = strlen(a + 1), lenb = strlen(b + 1);
        _for(i, 1, lena)
            _for(j, 1, lenb)
            {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                if(a[i] == b[j]) dp[i][j] = max(dp[i][j], 1 + dp[i - 1][j - 1]);
            }
        printf("%d\n", dp[lena][lenb]);
    }

    return 0;
}

 N - Longest Ordered Subsequence

最长上升子序列

#include <cstdio>
#include <iostream>
#include <algorithm>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int dp[N], a[N], n;

int main()
{
    cin >> n;
    _for(i, 1, n) cin >> a[i];

    int ans = 0;
    _for(i, 1, n)
    {
        dp[i] = 1;
        _for(j, 1, i - 1)
            if(a[i] > a[j])
                dp[i] = max(dp[i], dp[j] + 1);
        ans = max(ans, dp[i]);
    }
    printf("%d\n", ans);

    return 0;
}

M - Help Jimmy

首先是dp[i]表示前i个,我发现状态转移还和位置有关,而一个平台上的位置由很多,就卡了一下

借鉴前几天一道cf树形dp的经验,一个区间直接取左右端点

这道题也是一样的,只要用左右端点代替这个区间就好,中间的点不理

dp[i][0]表示落到第i个平台到左端点的最少时间,dp[i][1]右端点

转移的时候模仿跳的过程,往后面的状态转移

注意这里只能转移一次,也就是转移到最近的平台

如果一直没有符合的平台就是落在地上了,更新一下答案

#include <cstdio>
#include <cstring>
#include <algorithm>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int dp[N][2], n, x, y, hmax;
struct node
{
    int l, r, h;
}a[N];

bool cmp(node x, node y)
{
    return x.h > y.h;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d%d%d", &n, &x, &y, &hmax);
        _for(i, 1, n) scanf("%d%d%d", &a[i].l, &a[i].r, &a[i].h);
        sort(a + 1, a + n + 1, cmp);

        int st, mi = 1e9;
        _for(i, 1, n)
            if(a[i].l <= x && x <= a[i].r && y - a[i].h >= 0 && mi > y - a[i].h)
            {
                mi = y - a[i].h;
                st = i;
            }

        if(mi == 1e9)
        {
            printf("%d\n", y);
            continue;
        }

        int ans = 1e9;
        memset(dp, 0x3f, sizeof dp);
        dp[st][0] = abs(x - a[st].l);
        dp[st][1] = abs(x - a[st].r);


        _for(i, st, n)
        {
            _for(j, i + 1, n) //左边
            {
                if(abs(a[j].h - a[i].h) > hmax) break;
                if(a[j].l <= a[i].l && a[i].l <= a[j].r)
                {
                    dp[j][0] = min(dp[j][0], dp[i][0] + abs(a[i].l - a[j].l));
                    dp[j][1] = min(dp[j][1], dp[i][0] + abs(a[i].l - a[j].r));
                    break;
                }
                if(j == n) ans = min(ans, dp[i][0]);
            }
            _for(j, i + 1, n) //右边
            {
                if(abs(a[j].h - a[i].h) > hmax) break;
                if(a[j].l <= a[i].r && a[i].r <= a[j].r)
                {
                    dp[j][0] = min(dp[j][0], dp[i][1] + abs(a[i].r - a[j].l));
                    dp[j][1] = min(dp[j][1], dp[i][1] + abs(a[i].r - a[j].r));
                    break;
                }
                if(j == n) ans = min(ans, dp[i][1]);
            }
        }
        printf("%d\n", min(ans, min(dp[n][0], dp[n][1])) + y);
    }

    return 0;
}

突然想到可以把起点也看作在一个平台上,这样写起来不用特判,代码更加简洁

#include <cstdio>
#include <cstring>
#include <algorithm>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int dp[N][2], n, x, y, hmax;
struct node
{
    int l, r, h;
}a[N];

bool cmp(node x, node y)
{
    return x.h > y.h;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d%d%d", &n, &x, &y, &hmax);
        _for(i, 1, n) scanf("%d%d%d", &a[i].l, &a[i].r, &a[i].h);
        sort(a + 1, a + n + 1, cmp);

        a[0].l = a[0].r = x; a[0].h = y;
        memset(dp, 0x3f, sizeof dp);
        dp[0][0] = dp[0][1] = 0;

        int ans = 1e9;
        _for(i, 0, n)
        {
            _for(j, i + 1, n) //左边
            {
                if(abs(a[j].h - a[i].h) > hmax) break;
                if(a[j].l <= a[i].l && a[i].l <= a[j].r)
                {
                    dp[j][0] = min(dp[j][0], dp[i][0] + abs(a[i].l - a[j].l));
                    dp[j][1] = min(dp[j][1], dp[i][0] + abs(a[i].l - a[j].r));
                    break;
                }
                if(j == n) ans = min(ans, dp[i][0]);
            }
            _for(j, i + 1, n) //右边
            {
                if(abs(a[j].h - a[i].h) > hmax) break;
                if(a[j].l <= a[i].r && a[i].r <= a[j].r)
                {
                    dp[j][0] = min(dp[j][0], dp[i][1] + abs(a[i].r - a[j].l));
                    dp[j][1] = min(dp[j][1], dp[i][1] + abs(a[i].r - a[j].r));
                    break;
                }
                if(j == n) ans = min(ans, dp[i][1]);
            }
        }
        printf("%d\n", min(ans, min(dp[n][0], dp[n][1])) + y);
    }

    return 0;
}

O - Treats for the Cows

dp[l][r]表示左端选1~l 右端选r~n时的最大值

#include <cstdio>
#include <algorithm>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e3 + 10;
int dp[N][N], a[N], n;

int main()
{
    scanf("%d", &n);
    _for(i, 1, n) scanf("%d", &a[i]);

    int ans = 0;
    _for(l, 0, n)
        for(int r = n + 1; r > l; r--)
        {
            int t = l - 0 + n + 1 - r;
            dp[l][r] = t * a[r] + dp[l][r + 1];
            if(l - 1 >= 0) dp[l][r] = max(dp[l][r], t * a[l] + dp[l - 1][r]);
            ans = max(ans, dp[l][r]);
        }
    printf("%d\n", ans);

    return 0;
}

hdu 1058

本来要搜1078的,不知道为啥打错了打成1058……笑死

这道用stl搞一搞就行了。貌似也可以用dp

输出好坑

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
typedef pair<ll, ll> pa;
const int p[] = {2, 3, 5, 7};
map<int, int> id = {{2, 0}, {3, 1}, {5, 2}, {7, 3}};

int main()
{
    vector<int> ans;
    priority_queue<pa, vector<pa>, greater<pa>> q;

    ans.push_back(0); ans.push_back(1);
    rep(i, 0, 4) q.push(make_pair(p[i], p[i]));
    _for(i, 1, 5842)
    {
        pa x = q.top(); q.pop();
        ans.push_back(x.first);
        rep(i, id[x.second], 4)
            q.push(make_pair(x.first * p[i], p[i]));
    }

    int n;
    while(scanf("%d", &n) && n)
    {
        printf("The %d", n);
        int t = n % 10;
        if(t == 1 && n % 100 != 11) printf("st");
        else if(t == 2 && n % 100 != 12) printf("nd");
        else if(t == 3 && n % 100 != 13) printf("rd");
        else printf("th");
        printf(" humble number is %d.\n", ans[n]);
    }

    return 0;
}

周六 6.5(cf + dp)

昨天晚上打了一场cf

第一个小时做了三题。剩下一个小时做第四题,但是比赛时比较紧张,导致能做的题没把一些细节想清楚

赛后静下心想很快就AC了

心理素质还要锻炼

A. Fair Playoff

水题,反过来想方便一点

判断一下什么时候输,而不是判什么时候赢

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        int a[5];
        _for(i, 1, 4) scanf("%d", &a[i]);

        if(min(a[1], a[2]) > max(a[3], a[4]) || min(a[3], a[4]) > max(a[1], a[2])) puts("NO");
        else puts("YES");
    }

    return 0;
}

B. Array Reodering

发现会gcd后面的数多一个2,利用这个条件,偶数放前面,奇数放后面

用partition实现很方便

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e3 + 10;
int a[N], n;

int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &a[i]);
        partition(a + 1, a + n + 1, [](int x) { return x % 2 == 0; });

        int ans = 0;
        _for(i, 1, n)
            _for(j, i + 1, n)
                ans += gcd(a[i], a[j] * 2) > 1;
        printf("%d\n", ans);
    }

    return 0;
}

C. Unstable String

注意题目说连续的,所以可以维护一个区间

首先求一个最大的满足条件的区间,区间长度为n,答案增加n * (n + 1) / 2

此后如果这个区间的末尾是连续的问号,则左端点就移到末端连续问号的第一个,继续下去

否则就整个区间不要,从新开始

当末端有问号时,注意这部分会重复算,所以要减掉

减的时候要再下一次更新的时候减掉,因为不确定还有没有下一次更新,我因为这里WA了

 

之后重判断WA了……

要开long long 

我的统计答案的ans开了long long

但是我还用了一个变量用于统计ans,这个变量忘记开long long了……

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
 
typedef long long ll;
 
ll f(ll n)
{
    return 1ll * n * (n + 1) / 2;
}
 
int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
       string s, t;
       cin >> s;
       t = s;
 
       ll ans = 0;
       int len = s.size(), l = 0, r = 0;
	   ll last = 0;
       while(l < len && r < len)
       {
           while(r + 1 < len && (s[r + 1] != s[r] || s[r] == '?' && s[r + 1] == '?'))
           {
               if(s[r + 1] == '?')
               {
                   if(s[r] == '1') s[r + 1] = '0';
                   if(s[r] == '0') s[r + 1] = '1';
               }
               r++;
           }
           ans += f(r - l + 1) - last;
 
           if(t[r] == '?')
           {
               int p = r;
               while(p - 1 >= 0 && t[p - 1] == '?') p--;
               last = f(r - p + 1);
               l = p; r++;
           }
           else
           {
                last = 0;
                l = r + 1;
                r = l;
           }
       }
 
       printf("%lld\n", ans);
    }
 
    return 0;
}

D. Playoff Tournament

挺裸的一道树形dp,但是考试时因为点的标号没想清楚最终没A出来

首先要建树,按照题目的的标号建一颗树

然后就树形dp统计

对于每次询问,从当前节点向上更新就好了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e6 + 10;
int dp[N], l[N], r[N], f[N], fa[N], k, root;
string s;

void dfs(int u)
{
    if(!l[u])
    {
        if(s[u] == '?') dp[u] = 2;
        else dp[u] = 1;
        return;
    }

    dfs(l[u]);
    dfs(r[u]);
    if(s[u] == '0') dp[u] = dp[l[u]];
    if(s[u] == '1') dp[u] = dp[r[u]];
    if(s[u] == '?') dp[u] = dp[l[u]] + dp[r[u]];
}

void query(int u)
{
    if(!l[u])
    {
        if(s[u] == '?') dp[u] = 2;
        else dp[u] = 1;
    }
    else
    {
        if(s[u] == '0') dp[u] = dp[l[u]];
        if(s[u] == '1') dp[u] = dp[r[u]];
        if(s[u] == '?') dp[u] = dp[l[u]] + dp[r[u]];
    }
    if(u != root) query(fa[u]);
}

void build()
{
    int t = k - 1, cnt = 0;
    while(t)
    {
        vector<int> ve; ve.push_back(0);
        _for(i, 1, f[t]) ve.push_back(++cnt);

        t--;
        _for(i, 1, f[t])
        {
            int now = ++cnt;
            l[now] = ve[i * 2 - 1];
            r[now] = ve[i * 2];
            fa[l[now]] = now;
            fa[r[now]] = now;
        }
        cnt -= f[t];
    }
}

int main()
{
    f[0] = 1;
    _for(i, 1, 19) f[i] = f[i - 1] * 2;

    cin >> k >> s;
    s = " " + s;
    build();

    root = f[k] - 1;
    dfs(root);

    int m; cin >> m;
    while(m--)
    {
        int x; string op;
        cin >> x >> op;
        s[x] = op[0];
        query(x);
        printf("%d\n", dp[root]);
    }

    return 0;
}

P - FatMouse and Cheese(记忆化搜索)

好久没写记忆化搜索,竟然卡住了

这题倒过来想,dp[x][y]表示以(x, y)为起点的最大值

我一开始想的是为终点的最大值,发现很难搞

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 110;
int a[N][N], dp[N][N], n, k;

int dfs(int x, int y)
{
    if(dp[x][y] != -1) return dp[x][y];
    int mx = 0;
    _for(xx, max(x - k, 1), min(x + k, n))
        if(a[x][y] < a[xx][y])
            mx = max(mx, dfs(xx, y));
    _for(yy, max(y - k, 1), min(y + k, n))
        if(a[x][y] < a[x][yy])
            mx = max(mx, dfs(x, yy));
     return dp[x][y] = a[x][y] + mx;
}

int main()
{
   while(~scanf("%d%d", &n, &k))
   {
       if(n == -1 && k == -1) break;
       _for(i, 1, n)
            _for(j, 1, n)
                scanf("%d", &a[i][j]);

        memset(dp, -1, sizeof(dp));
        printf("%d\n", dfs(1, 1));
   }

    return 0;
}

Q - Phalanx

先翻转一下矩阵变成左上到右下

 dp[i][j]表示以(i, j)为右下角的最大对称矩阵

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int dp[N][N], n;
char a[N][N];

int main()
{
   while(scanf("%d", &n) && n)
   {
       _for(i, 1, n)
       {
           scanf("%s", a[i] + 1);
           reverse(a[i] + 1, a[i] + n + 1);
       }

       int ans = 1;
       _for(i, 1, n)
            _for(j, 1, n)
            {
                dp[i][j] = 1;
                if(i == 1 || j == 1) continue;
                int x = j - 1, y = i - 1, cnt = 0;

                while(x >= 1 && y >= 1)
                {
                    if(a[i][x] != a[y][j]) break;
                    x--; y--;
                    cnt++;
                }
                dp[i][j] = min(dp[i - 1][j - 1], cnt) + 1;
                ans = max(ans, dp[i][j]);
            }

        printf("%d\n", ans);
   }

    return 0;
}

R - Milking Time

类似最长上升子序列的思路

水题

#include <cstdio>
#include <algorithm>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int dp[N], n, m, R;
struct segment
{
    int l, r, v;
}a[N];

bool cmp(segment x, segment y)
{
    return x.l < y.l;
}

int main()
{
    scanf("%d%d%d", &n, &m, &R);
    _for(i, 1, m) scanf("%d%d%d", &a[i].l, &a[i].r, &a[i].v);
    sort(a + 1, a + m + 1, cmp);

    int ans = 0;
    _for(i, 1, m)
    {
        dp[i] = a[i].v;
        _for(j, 1, i - 1)
            if(a[j].r + R <= a[i].l)
                dp[i] = max(dp[i], dp[j] + a[i].v);
        ans = max(ans, dp[i]);
    }
    printf("%d\n", ans);

    return 0;
}

今晚训练赛,打得并不是很好,补它一波题,好好学一波

CodeForces - 1526B

这道题卡了很久……

主要是没意识到不需要考虑那么多1111

其实可以简化为11 和 111 

因为11和111和转化出所有的1111 11111……

所以就是看11x + 111y = n

存不存在一组解使得x >=0 y >= 0

考试的时候算一下,可以直接枚举因子暴力

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        int x; scanf("%d", &x);

        bool ok = false;
        _for(i, 0, 1e6)
        {
            int t = 111 * i;
            int now = x - t;
            if(now < 0) break;

            if(now % 11 == 0)
            {
                ok = true;
                break;
            }
        }
        puts(ok ? "YES" : "NO");
    }

    return 0;
}

考完后想了想,可以用exgcd解不定方程的思路来弄

这样复杂度是O(1)的

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        int n; scanf("%d", &n);
        int d = 1, x = -10, y = 1;
        x *= n / d, y *= n / d;

        int k = (-x + 110) / 111;
        x += k * 111;
        y -= k * 11;

        if(x >= 0 && y >= 0) puts("YES");
        else puts("NO");
    }

    return 0;
}

看了官方题解,发现可以推出111的倍数是0到10,太秀了,其实111的倍数是很小的

C2. Potions (Hard Version)

这题也卡了一会儿

用优先队列贪心

每次把人加入队列,如果小于0,就弹出最小的,直到大于等于0

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
priority_queue<int, vector<int>, greater<int>> q;
int n, x;
ll sum;

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        scanf("%d", &x);
        sum += x;
        q.push(x);
        while(sum < 0) sum -= q.top(), q.pop();
    }
    printf("%d\n", q.size());

    return 0;
}

1528B - Kavi on Pairing Duty

比赛时已经想到了dp[i] = d[i - 1] + dp[i - 2] ……dp[i] + S

S就是全部长度相等有多少种,这部分我想错了导致没做出来

实际上,这部分的个数就是n的因子个数

这部分暴力的话n根号n

有类似筛法的方法可以做到nlogn

因此递推就好了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e6 + 10;
const int mod = 998244353;
int dp[N], sz[N], n;

int add(int a, int b) { return (a + b) % mod; }

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
        for(int j = i; j <= n; j += i)
            sz[j]++;

    int sum = 1;
    dp[1] = 1;
    _for(i, 2, n)
    {
        dp[i] = add(sz[i], sum);
        sum = add(sum, dp[i]);
    }
    printf("%d\n", dp[n]);

    return 0;
}

周日 6.6

B. Monopole Magnets

这道2000的cf题做出来了

但是有时我也会1700 1800的做不出来

所以不一定了,有些题比较符合我的思维,有些不符合,不好说

这道题就是一道思维题

首先一个显然的想法,可以黑格全部放S,这样在一个联通块里面,只要有N,就可以去到所有地方

但是注意,对于一个联通块,不可以走到白色区域

这么处理呢,可以判断每一个行和每一个列,看是否存在一行或一列中有两段以上的连续黑块

而不是dfs当前联通块时判断

还有一种不可行的情况,就是不能每行每列都放S

首先处理出联通块占了所有的行和列

看有多少行和多少列没被。只有其中一个数不为0,另一个数为0的情况才不可行

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int dir[4][2] = {1, 0, -1, 0, 0, 1, 0, -1};
int vis[N][N], X[N], Y[N], n, m;
char s[N][N];

void dfs(int x, int y)
{
    X[x] = Y[y] = 1;
    rep(i, 0, 4)
    {
        int xx = x + dir[i][0], yy = y + dir[i][1];
        if(s[xx][yy] == '#' && !vis[xx][yy])
        {
            vis[xx][yy] = 1;
            dfs(xx, yy);
        }
    }
}

int main()
{
   scanf("%d%d", &n, &m);
   _for(i, 1, n) scanf("%s", s[i] + 1);

    _for(i, 1, n)
    {
        int cnt = 0;
        _for(j, 1, m)
            if(s[i][j] == '#' && s[i][j - 1] != s[i][j])
                cnt++;
        if(cnt > 1)
        {
            puts("-1");
            return 0;
        }
    }
    _for(j, 1, m)
    {
        int cnt = 0;
        _for(i, 1, n)
            if(s[i][j] == '#' && s[i - 1][j] != s[i][j])
                cnt++;
        if(cnt > 1)
        {
            puts("-1");
            return 0;
        }
    }

   int ans = 0;
   _for(i, 1, n)
        _for(j, 1, m)
            if(s[i][j] == '#' && !vis[i][j])
            {
                dfs(i, j);
                ans++;
            }

    int cnt1 = 0, cnt2 = 0;
    _for(i, 1, n) cnt1 += !X[i];
    _for(i, 1, m) cnt2 += !Y[i];
    if(cnt1 && !cnt2 || !cnt1 && cnt2) puts("-1");
    else printf("%d\n", ans);

    return 0;
}

B - Array Reodering

之前写的一道题,发现官方题解有个代码写的很秀

把一个数组偶数放前面,奇数放后面

我当时是用partition

还有一种方法,就是用sort

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e3 + 10;
int a[N], n;

int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &a[i]);
        sort(a + 1, a + n + 1, [](int x, int y) { return x % 2 == 0 && y % 2 == 1; } );

        int ans = 0;
        _for(i, 1, n)
            _for(j, i + 1, n)
                ans += gcd(a[i], a[j] * 2) > 1;
        printf("%d\n", ans);
    }

    return 0;
}

D. Playoff Tournament

旧题新写

发现自己写的非常非常麻烦,虽然AC了

其实可以简化很多

(1) 2的k次方直接1 << k

(2)转化一下下标发现类型线段树,只不过左边是+1右边不加1.处理一下就好了

(3)不用dfs,直接循环就好

#include <bits/stdc++.h>
#define l(k) (k << 1 | 1)
#define r(k) (k << 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e6 + 10;
int dp[N], k, n;
string s;

void update(int u)
{
    if(l(u) > n) dp[u] = 1 + (s[u] == '?'); //注意这里要加括号
    else
    {
        if(s[u] == '0') dp[u] = dp[l(u)];
        if(s[u] == '1') dp[u] = dp[r(u)];
        if(s[u] == '?') dp[u] = dp[l(u)] + dp[r(u)];
    }
}

int main()
{
    cin >> k >> s;
    n = (1 << k) - 1; //直接1 << k
    reverse(s.begin(), s.end()); //转化下标
    s = " " + s;

    for(int i = n; i >= 1; i--) update(i);

    int m; cin >> m;
    while(m--)
    {
        int x; string op;
        cin >> x >> op;
        x = n - x + 1; //转化一下
        s[x] = op[0];
        for(int i = x; i >= 1; i >>= 1) update(i);
        cout << dp[1] << endl;
    }

    return 0;
}

E. Gold Transfer(树上倍增)

E题没想的那么难,只是开始涉及到一些算法了

这道题就是树上倍增

显然应该找一个深度最浅的点,然后把这个点到根节点全部买了,这样是最优的

那么怎么找这个点呢,可以发现从根节点往给节点v找挺麻烦,要判断v在左子树还是右子树,想了想可以用dfs序。不过这题是动态的不能用dfs序

但是这样是O(q)的,会超时

考虑反过来,从节点v往根节点找,一直往上找,这样方便,也不要判断左右子树啥的

但是一样会超时。这个向下的过程不好优化,但向上的过程可以。

我一开始想到了二分,但是不知道怎么把这个路径给提取出来二分

但还可以联想到树上倍增,一次跳很多格,这样就可以在logq的时间内跳到那个点,然后统计答案

但是更新答案之后要改变a数组的值,这个时候我就懵逼了,不知道怎么一次性把找到的点到根节点的点全部赋值为0

显然不能暴力,暴力超时,然后我就卡住了

之后看了题解,我的思路已经有点接近题解了

题解是把购买分成一个节点一个节点的买,而我想的是一次性买

按照题解的话,就每次倍增找到一个最浅的a数组大于0的点,购买,修改a数组,然后继续倍增找下一个点

显然找到一个点后,下一个点就是它的儿子,这个儿子的子树包含了v

但是用程序不知道怎么实现,干脆就再找一次

这样看起来比较暴力,但实际上是不会超时的

因为一个节点最多被删除一次。总的复杂度是O(qlogq)的

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 3e5 + 10;
int a[N], c[N], up[N][25], q;

int main()
{
    scanf("%d%d%d", &q, &a[0], &c[0]);
    _for(u, 1, q)
    {
        int op, v, w;
        scanf("%d", &op);
        if(op == 1)
        {
            scanf("%d%d%d", &up[u][0], &a[u], &c[u]);
            _for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];
        }
        else
        {
            scanf("%d%d", &v, &w);
            ll ansa = 0, ansc = 0;
            while(w && a[v])
            {
                int p = v;
                for(int j = 20; j >= 0; j--)
                    if(a[up[p][j]])
                        p = up[p][j];
                int t = min(w, a[p]);
                w -= t; a[p] -= t;
                ansa += t; ansc += 1ll * t * c[p];
            }
            printf("%lld %lld\n", ansa, ansc);
            fflush(stdout);
        }
    }

    return 0;
}

Mean Inequality

挺简单的

直接最高的放后面,然后一高一低一高一低

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 100;
int a[N], b[N], n;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n); n *= 2;
        _for(i, 1, n) scanf("%d", &a[i]);
        sort(a + 1, a + n + 1);

        b[n] = a[n];
        int l = 1, r = n - 1;
        for(int i = n - 1; i >= 1; i--)
        {
            if(i & 1) b[i] = a[l++];
            else b[i] = a[r--];
        }
        _for(i, 1, n) printf("%d ", b[i]); puts("");
    }

    return 0;
}

E. Gold Transfer(逆序对)

这道题的翻转其实是迷惑你的

其实就是问一个字符串,每次操作可以交换相邻的两个字符,问最少交换多少次可以转化为另一个字符串

将目标字符串附上编号1 2 3 4 ……n

然后把这个编号赋给第二个字符串,如abc转化为cba
那么第二个字符串就是321

注意这里会有相同的字符串比较麻烦,比如aab为123, 那么原始字符串中有a,到底赋值1还是2呢?

顺序赋值,也就是第一a是1,第二个a是2,这样是最优的。这个可以用队列来实现,因为每次都尾部加入元素,然后取出头部头部出队

然后我们转为为数字后,问题转化为一个排列,最少经过多次交换使其有序,也就是变成123456……

这个答案就是逆序对,因为一次交换可以消灭一个逆序对,而有序时是没有逆序对的,所以就需要交换逆序对的个数

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
queue<int> q[30];
int f[N], a[N], n;
string s, t;

int lowbit(int x) { return x & (-x); }

void add(int x)
{
    for(; x <= n; x += lowbit(x))
        f[x]++;
}

int sum(int x)
{
    int res = 0;
    for(; x; x -= lowbit(x))
        res += f[x];
    return res;
}

int main()
{
    cin >> n >> s;
    t = s;
    reverse(t.begin(), t.end());

    rep(i, 0, n) q[t[i] - 'a'].push(i + 1);
    rep(i, 0, n)
    {
        a[i] = q[s[i] - 'a'].front();
        q[s[i] - 'a'].pop();
    }

    long long ans = 0;
    rep(i, 0, n)
    {
        ans += sum(n) - sum(a[i]);
        add(a[i]);
    }
    printf("%lld\n", ans);

    return 0;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值