Codeforces Round 871 (Div. 4) (VP)

一系列编程题目,涉及字符串比较、数组处理、贪心策略、二叉树结构、图的深度优先搜索、动态规划思想、回溯法和矩阵操作。题目涵盖字符串匹配、连续区间问题、时间优化、二叉树结构分析、连续值统计、雪花结构的图和寻找最大深度的水区域、雪花结构的节点识别以及子区间AND运算计数。
摘要由CSDN通过智能技术生成

A. Love Story

#include <bits/stdc++.h>


using namespace std;
typedef long long int ll;
typedef vector<int> vi;
typedef vector<ll> vll;


#define lowbit(S) ((S) & -(S))
#define pb push_back





int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int TC;
    cin >> TC;
    string r = "codeforces";
    while (TC--){
        string s;
        cin >> s;
        int cnt = 0;
        for (int i = 0; i < (int)s.size(); ++i){
            if (s[i] != r[i]) ++cnt;
        }
        cout << cnt << endl;

    }
    return 0;
}

B. Blank Space

#include <bits/stdc++.h>


using namespace std;
typedef long long int ll;
typedef vector<int> vi;
typedef vector<ll> vll;


#define lowbit(S) ((S) & -(S))
#define pb push_back




int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int TC;
    cin >> TC;
    while (TC--){
        int n;
        cin >> n;
        vi a(n);
        for (auto& x : a) cin >> x;
        int ans = 0, cnt = 0;
        for (auto x : a){
            if (x == 0) cnt += 1;
            else cnt = 0;
            ans = max(ans, cnt);
        }
        cout << ans << endl;
    }
    return 0;
}

总结:不是简单的O(n)iteration,里面有一个很经典的greedy思路,就是选择当前最优,如果当前数字非0,那么再继续搜索下去肯定不如从0开始重新计数搜索...同类问题为找连续区间问题:找左右端点,找区间连续值,都可以理解为贪心策略

C. Mr. Perfectly Fine

#include <bits/stdc++.h>


using namespace std;
typedef long long int ll;
typedef vector<int> vi;
typedef vector<ll> vll;


#define lowbit(S) ((S) & -(S))
#define pb push_back







int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int TC;
    cin >> TC;
    while (TC--){
        int n, m;
        string s;
        cin >> n;
        int a = 1e6, b = 1e6, c = 1e6;
        for (int i = 0; i < n; ++i){
            cin >> m >> s;
            if (s[0] == '1'){b = min(m, b);}
            if (s[1] == '1'){a = min(m, a);}
            if (s[0] == '1' && s[1] == '1'){c = min(c, m);}
        }
        if (a != 1e6 && b != 1e6)
            cout << min(a + b, c) << endl;
        else cout << -1 << endl;
    }
    return 0;
}

总结:简单的统计问题,看到题目的时候脑子卡了两分钟,在想如何组织数据,要不要按时间或者什么东西排个序再去判定...思考过程中思路延申到使用变量来记录3种情况各需要消耗多少时间,最后比较一下时间即可得出结果.感觉也可以归纳为贪心问题。看了一下题目tag有一个:bitmask..暂时想不到如何用位运算写,因为读输入的时候两个01数字目前只能想到用字符串或者字符去读。

感觉题目有点像动态规划,又捋了一下,确定不是动态规划。因为dp有bottom-up和up-down两种策略,都是将问题分解成一个个sub-case,最后递推出最优解,对于这个问题,我觉得dp不行,虽然它也是一步一步的递推,但是它是制定了一个贪心的选择策略,总是选择当下case的最优解,最后得到全局解。

D. Gold Rush

题意:给定数字n和m,问n经过多次分割后能否得到m,分割只能是当前的n分成两份,一份是另一份大小的2倍。

思路:一眼看过去就是个二叉树形结构,左子树是自己的3分之1,右子树是自己的3分之2,直到该节点不能被3整除。就dfs遍历看看能不能到达有解的叶节点即可。

#include <bits/stdc++.h>


using namespace std;
typedef long long int ll;
typedef vector<int> vi;
typedef vector<ll> vll;


#define lowbit(S) ((S) & -(S))
#define pb push_back




























void backtrack(bool& ok, const int& target, int cur){
    if (ok == true || target == cur){ok = true; return;}
    if (cur < target || cur % 3 != 0) {return;}
    backtrack(ok, target, cur / 3 * 2);
    backtrack(ok, target, cur / 3);
}








int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int TC;
    cin >> TC;
    while (TC--){
        int n, m;
        cin >> n >> m;
        bool ok = false;
        backtrack(ok, m, n);
        cout << (ok ? "YES" : "NO") << endl;
    }
    return 0;
}

总结:感觉batrack和dfs的区别还不是理的很清楚,一般来说dfs是往深了走,遍历所有的节点找解析。 backtrack是根据当前的状态往下一个状态传导,complete search,有一个计算下一步的状态和一个恢复状态的过程,不过大体结构都差不多,感觉这个题目应该算dfs。

E. The Lakes

题意:给一个n*m的矩阵,找出深度最大的块,块的深度是该块每个点的深度的和,并且该块内所有的点是连通的(即不存在深度为0的点)

思路:dfs,固定参数是一个water数组和一个vis数组(记录该点有没有访问),可变参数是当前点的坐标和一个计数器。然后对每个点进行dfs并计数,找出计数最大的water区域即可

#include <bits/stdc++.h>


using namespace std;
typedef long long int ll;
typedef vector<int> vi;
typedef vector<ll> vll;


#define lowbit(S) ((S) & -(S))
#define pb push_back

















vi dx{-1, 1, 0, 0};
vi dy{0, 0, -1, 1};


bool isVaild(int n, int m, int i, int j){
    if (i >= 0 && i < n && j >= 0 && j < m) return true;
    return false;
}
void backtrack(vector<vi>& water, vector<vector<bool>>& vis, int n, int m, int i, int j, int& cnt){
    if (isVaild(n, m, i, j) == false || vis[i][j] == true || water[i][j] == 0) return;
    vis[i][j] = true;
    cnt += water[i][j];
    for (int k = 0; k < 4; ++k){
        int px = i + dx[k], py = j + dy[k];
        backtrack(water, vis, n, m, px, py, cnt);
    }
}





int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int TC;
    cin >> TC;
    while (TC--){
        int n, m;
        cin >> n >> m;
        vector<vi> a(n, vector<int>(m));
        for (int i = 0; i < n; ++i){
            for (int j = 0; j < m; ++j) cin >> a[i][j];
        }
        vector<vector<bool>> vis(n, vector<bool> (m, false));
        int ans = 0;
        for (int i = 0; i < n; ++i){
            for (int j = 0; j < m; ++j) if (vis[i][j] == false){
                int cnt = 0;
                backtrack(a, vis, n, m, i, j, cnt);
                ans = max(ans, cnt);
            }
        }
        cout << ans << endl;

    }
    return 0;
}

总结:其实这个应该还是dfs,不应该写成backtrack,不过上个题是树形结构的dfs,这个题是图结构的dfs。

F. Forever Winter

 题意:给定n,m代表节点数和边数,其中这些节点是一个雪花形的结构,即中间有一个中心节点c,跟c相连的有x个节点,然后这x各节点每个又连接了y个节点。给出n和m,求x和y。题目保证x和y greater than 1。 2 <= n <= 200

思路:图的结构是固定的,而且x,y大于1,所以对于每个叶子节点来说,它们只有一条边。采用邻接表的数据结构,然后统计每个点边的数量,可以将叶子节点筛选出来,剩下的节点就是中心节点C和与C相连的第二层节点。

有两种情况:

如果C和第一层节点的边的数量不相等,说明出现频次为1的点代表的边数,就是x的数量,剩下的频次-1就是第一层节点的边的数量(减一是因为在统计时第一层节点与点C也有一条边,要求出Y的数值需要减去这个边)。

如果C与第一层节点的边的数量的统计值相等,为t,说明x = t, y = t - 1。

#include <bits/stdc++.h>


using namespace std;
typedef long long int ll;
typedef vector<int> vi;
typedef vector<ll> vll;
typedef pair<int, int> pii;
typedef vector<pii> vii;


#define lowbit(S) ((S) & -(S))
#define pb push_back









int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int TC;
    cin >> TC;
    while (TC--){
        int n, m;
        cin >> n >> m;
        vector<vector<int>> AL(n + 1);
        for (int i = 0; i < m; ++i){
            int a, b;
            cin >> a >> b;
            AL[a].pb(b);
            AL[b].pb(a);
        }
        map<int, int> mapp;
        for (int i = 1; i <= n; ++i){
            mapp[(int)AL[i].size()] += 1;
        }
        pair<int, int> ans{0, 0};
        if (mapp.size() == 3){
            for (auto x : mapp){
                if (x.first == 1) continue;
                else if (x.second == 1) ans.first = x.first;
                else ans.second = x.first - 1;
            }
        }
        if (mapp.size() == 2){
            for (auto x : mapp){
                if (x.first > 1 && x.second > 1){ans.first = x.first; ans.second = x.first - 1;}
            }
        }
        cout << ans.first << " " << ans.second << endl;
    }
    return 0;
}

总结:题目类型没怎么见过,一眼看过去有点生疏,不知道怎么解,就采用了这种simulation式的解法。然后感觉AL数组下次可以开大一点,题目虽然说了n的范围上限是200,但是并没有说每个点是从1开始往上计数的,也就是说有的题目可能会出现大数的情况,如果想数组开稍微小一点最好开一个discrete映射一下。 最后用了map,是因为感觉不同的频次最多只有3个,如果再开个数组去记录的话要浪费很多的空间。

G. Hits Different

思路:先预处理出来了一个这种三角形的数组,然后对于每个输入n,找到所处的行和列下标,然后dfs访问记录数值。

TLE了,晚点更新

TLE代码

#include <bits/stdc++.h>


using namespace std;
typedef long long int ll;
typedef vector<int> vi;
typedef vector<ll> vll;
typedef pair<int, int> pii;
typedef vector<pii> vii;


#define lowbit(S) ((S) & -(S))
#define pb push_back

class UnionFind{
private:
    vi fa, setSize, d;
    int numSets;
public:
    UnionFind(int m){
        fa.assign(m, 0); for (int i = 0; i < m; ++i) fa[i] = i;
        setSize.assign(m, 1);
        d.assign(m, 0);
        numSets = m;
    }
    int findSet(int x){
        if (fa[x] == x) return x;
        int root = findSet(root);
        d[x] += d[fa[x]];
        return fa[x] = root;
    }
    bool isSameset(int x, int y){return findSet(x) == findSet(y);}
    int numDisjointSets(){return numSets;}
    int numOFSets(int x){return setSize[findSet(x)];}
    void unionSet(int x, int y){
        x = findSet(x), y = findSet(y);
        if (x == y) return;
        d[x] = setSize[y];
        setSize[y] += setSize[x];
        fa[x] = y;
        numSets -= 1;
    }
};

//0-indexed or 1-indexed?
class FenwickTree{
private:
    vll ft;
    void build(const vll& f){
        int m = f.size() - 1;
        ft.assign(m + 1, 0);
        for (int i = 1; i <= m; ++i){
            ft[i] += f[i];
            if (i + lowbit(i) <= m) ft[i + lowbit(i)] += ft[i];
        }
    }
public:
    FenwickTree(int m){ft.assign(m + 1, 0);}
    FenwickTree(const vll& f){build(f);}
    FenwickTree(const int& maxn, const vi& s){
        vll f(maxn + 1, 0);
        for (int i = 0; i < (int)s.size(); ++i) f[s[i]] += 1;
        build(f);
    }
    int rsq(int j){ ll sum = 0; while(j > 0){sum += ft[j]; j -= lowbit(j);} return sum;}
    int rsq(int i, int j){return rsq(j) - rsq(i - 1);}
    void update(int p, ll v){
        while(p < (int)ft.size()){ft[p] += v; p += lowbit(p);}
    }
};



class SegmentTree{
private:
    vi A, st, lazy;
    int n;
    int left(const int& p){return p << 1;}
    int right(const int& p){return (p << 1) + 1;}
    int conquer(int a, int b){return a == -1 ? b : b == -1 ? a : min(a, b);}
    void build(int p, int l, int r){
        if (l == r) st[p] = A[r];
        else{
            int m = (l + r) >> 1;
            build(left(p), l, m);
            build(right(p), m + 1, r);
            st[p] = conquer(st[left(p)], st[right(p)]);
        }
    }
    void propagate(int p, int l, int r){
        if (lazy[p] != -1){
            st[p] = lazy[p];
            if (l != r) lazy[left(p)] = lazy[right(p)] = lazy[p];
            else A[l] = lazy[p];
            lazy[p] = -1;
        }
    }
    void update(int p, int l, int r, int i, int j, int val){
        propagate(p, l, r);
        if (i > j) return;
        if (l >= i && r <= j) {lazy[p] = val; propagate(p, l, r);}
        else{
            int m = (l + r) >> 1;
            update(left(p), l, m, i, min(m, j), val);
            update(right(p), m + 1, r, max(m + 1, i), j, val);
            int lsubtree = (lazy[left(p)] == -1) ? st[left(p)] : lazy[left(p)];
            int rsubtree = (lazy[right(p)] == -1) ? st[right(p)] : lazy[right(p)];
            st[p] = (lsubtree <= rsubtree) ?  st[left(p)] : st[right(p)];
        }
    }
    int rmq(int p, int l, int r, int i, int j){
        propagate(p, l, r);
        if (i > j) return -1;
        if (l >= i && r <= j) return st[p];
        else{
            int m = (l + r) >> 1;
            return conquer(rmq(left(p), l, m, i, min(m, j)), rmq(right(p), m + 1, r, max(m + 1, i), j));
        }
    }
public:
    SegmentTree(const int& m): A(m), st(4 * m), lazy(4 * m, -1), n(m){}
    SegmentTree(const vi& initialA):SegmentTree((int)initialA.size()){

    }
    void update(int l, int r, int val){update(1, 0, n - 1, l, r, val);}
    int rmq(int i, int j){return rmq(1, 0, n - 1, i, j);}
};


vi sievePrimes(const int& x){
    vi result;
    bitset<40000> bs;
    bs.set();
    if (x > 4e4) {exit(111);}
    for (int i = 2; i <= x; ++i){if (bs[i])
        for (int j = i * i; j <= x; j += i)
            bs[j] = 0;
        result.push_back(i);
    }
    return result;
}

vi sievePrimeFactors(const int& x){
    vi result(x + 1, 0);
    for (int i = 2; i <= x; ++i) if (result[i] == 0){
        for (int j = i; j <= x; j += i){
            result[j] += 1;
        }
    }
    return result;
}

template<typename T>
bool isPrime(const T& x){
    if (x % 2 == 0) return false;
    for (T i = 3; i * i <= x;  i += 2) if (x % i == 0)  return false;
    return true;
}

template<typename T>
bool isPrime(const T& x, const vi& prime){
    for (int i = 0; i < (int)prime.size() && prime[i] * prime[i] <= x; ++i) if (x % prime[i] == 0) return false;
    return true;
}

template<typename T>
vi primeFactors(const T& x){
    vi result;
    while(x % 2 == 0){x /= 2; result.push_back(2);}
    for (T i = 3; i * i <= x; i += 2){
        while(x % i == 0){x /= i; result.push_back(i);}
    }
    if (x > 1) result.push_back(x);
    return result;
}

template<typename T>
vi primeFactors(const T& x, const vi& prime){
    vi result;
    for (int i = 0; i < (int)prime.size() && prime[i] * prime[i] <= x; ++i){
        while (x % prime[i] == 0){
            x /= prime[i];
            result.push_back(prime[i]);
        }
    }
    if (x > 1) result.push_back(x);
    return result;
}


template <typename T>
T gcd(T a, T b){return b == 0 ? a : gcd(b, a % b);}

template<typename T>
T lcm(T a, T b){return a / gcd(a, b) * b;}

template<typename T>
vi getFactors(const T& x){
    vi result;
    for (T i = 2; i * i <= x; ++i){
        if (x % i == 0){
            result.push_back(i);
            if (i != x / i) result.push_back(x / i);
        }
    }
    return result;
}

template<typename T>
int discrete(const T& x, map<T, int>& mapp, int& cnt){
    if (!mapp.count(x)) mapp[x] = cnt++;
    return mapp[x];
}

























bitset<1000009> bs;
void backtrack(const vector<vi>& a, pii pos, ll& sum){
    if (pos.first < 0 || pos.second < 0 || pos.second >= (int)a[pos.first].size() || bs[a[pos.first][pos.second]] == 0) return;
    sum += ( 1ll * a[pos.first][pos.second] * a[pos.first][pos.second]);
    bs[a[pos.first][pos.second]] = 0;
    backtrack(a, {pos.first - 1, pos.second},sum);
    backtrack(a, {pos.first - 1, pos.second - 1}, sum);
}

pii get_level(const vector<vi>& a, int n){
    int l = 0, r = a.size() - 1;
    while (l < r){
        int mid = (l + r) >> 1;
        int index = a[mid].size() - 1;
        if (a[mid][index] >= n) r = mid;
        else l = mid + 1;
    }
    for (int i = 0, j = (int)a[l].size() - 1; i <= j;){
        int mid = (i + j) >> 1;
        if (a[l][mid] > n) j = mid;
        else if (a[l][mid] < n) i = mid + 1;
        else if (a[l][mid] == n) return {l, mid};

    }
    return {0, 0};
}

int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int TC;
    cin >> TC;
    vector<vi> a;
    for (int i = 1, cnt = 1; cnt <= 1e6 + 5; ++i){
            vi tmp(i);
            for (int k = 0; k < i; ++k){
                tmp[k] = cnt++;
            }
            a.pb(tmp);
    }
    while (TC--){
        int n;
         cin >> n;
         bs.set();
         ll ans = 0;
         pii pos = get_level(a, n);
        // cout << pos.first << " " << pos.second << endl;
         backtrack(a, pos, ans);
         cout << ans << endl;

    }
    return 0;
}

更新后的代码 求了一下前缀和dp计算

#include<bits/stdc++.h>
#define pb push_back
using namespace std;

typedef long long int ll;
typedef vector<int> vi;
typedef vector<vi> vvi;
typedef pair<int, int> pii;
typedef vector<ll> vll;
const int maxn = 1e6 + 10;

vector<vector<ll>> a;
vector<vector<ll>> prefix;
pii getLevel(const int& n){
    int l = 0, r = (int)a.size() - 1;
    pii result;
    while (l < r){
        int mid = (l + r) >> 1;
        if (a[mid][(int)a[mid].size() - 1] >= n) r = mid;
        else l = mid + 1;
    }
    result.first = l;
    r = (int)a[l].size() - 1, l = 0;
    while (l < r){
        int mid = (l + r) >> 1;
        if (a[result.first][mid] >= n) r = mid;
        else l = mid + 1;
    }
    result.second = l;
    return result;
}
int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int TC;
    cin >> TC;
    for (int i = 1, k = 1; k <= maxn; ++i){
        vll tmp(i);
        for (int j = 0; j < i; ++j) tmp[j] = k++;
        a.pb(tmp);
    }
    //for (auto x : a)for (auto y : x) cout << y << " "; cout << endl;
    prefix = a;
    for (int i = 0; i < (int)a.size(); ++i){
        prefix[i][0] *= prefix[i][0];
        for (int j = 1; j < (int)a[i].size(); ++j) prefix[i][j] = prefix[i][j]* prefix[i][j] + prefix[i][j - 1];
    }
    while (TC--){
        int n;
        cin >> n;
        ll ans = 0;
        pii pos = getLevel(n);
        //cout << pos.first << ".  " << pos.second << endl;
        int l = pos.second, r = pos.second, level = pos.first;
        while (level >= 0){
            r = min(r, (int)prefix[level].size() - 1);
            if (l > 0)ans += prefix[level][r] - prefix[level][l - 1];
            else ans += prefix[level][r];
            l -= 1;
            level -= 1;
        }
        cout << ans << endl;
    }
    return 0;
}

H. Don't Blame Me

题意:求一个数组中子区间的and运算的1的数量为k个的子区间数量

思路:昨天写题的时候看成了连续区间的数量,一时没想到解法,可能会用到一些bitmask的库,今天学习一下。感觉应该是dp的问题,先试试backtrak+memoization,不行就上dp

5.10 21:02更

思路:经典的memoization搜索问题,直接暴力dfs + 状态存储即可

#include <bits/stdc++.h>


using namespace std;

#define lowbit(S) ((S) & -(S))
#define pb push_back

typedef long long int ll;
typedef vector<int> vi;
typedef vector<ll> vll;
typedef pair<int, int> ii;
typedef vector<ii> vii;
typedef vector<bool> vb;





const int mod = 1e9 + 7;
int memoDfs(const vi& a, vector<vi>& memo, const int& k, int n, int mask){
    if (n < 0){return 0;}
    if (__builtin_popcount(mask) < k){return memo[n][mask] = 0;}
    int& ans = memo[n][mask];
    if (ans != -1) return ans;
    int n1 = memoDfs(a, memo, k, n - 1, mask) % mod;
    int n2 = memoDfs(a, memo, k, n - 1, (mask & a[n])) % mod;

    return  ans = ((__builtin_popcount(mask & a[n]) == k) + n1 + n2) % mod;
}


int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int TC;
    cin >> TC;
    while (TC--){
        int n, k;
        cin >> n >> k;
        vi a(n);
        for (auto& x :a) cin >> x;
        vector<vi> memo(n, vi(1 << 6, -1));
        int ans = memoDfs(a, memo, k, n - 1, (1 << 6) - 1);
        cout << ans << "\n";
    }
    return 0;
}

总结: 被题目搞懵了,子区间数量太多,不知道bottom-up的dp解法怎么写,就懵了,用暴力dfs会写,但是状态存储和表示用的不熟练..memoDfs就是bruteforce + memoization,无脑存状态就完事了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值