CF1700板刷

CF1700板刷

前言:
vp edu的时候逐渐力不从心,于是滚回来板刷1700 qwq
https://codeforces.com/problemset?order=BY_RATING_ASC&tags=1700-

log

3.5 466C
3.6 474D
3.7 339D,126B
3.8 118D,1365D,977F,467C,349B

466C - Number of Ways

前缀和 + STL
小贪心

#include <bits/stdc++.h>
#define ll long long

using namespace std;
const int N = 5e5 + 5;
ll a[N], pre[N], suf[N], sum, n, ans;

int main () {
    cin >> n;
    for (int i = 1; i <= n; i++)    cin >> a[i], sum += a[i];
    if (sum % 3) {
        cout << 0;
        return 0;
    }
    sum /= 3;
    for (int i = 1; i <= n; i++)    pre[i] = pre[i-1] + a[i];
    set<int> s;
    for (int i = n; i >= 1; i--) {
        suf[i] = suf[i+1] + a[i];
        if (suf[i] == sum)  s.insert (i);
    }
    for (int i = 2; i < n; i++) {
        if (pre[i-1] != sum)      continue;
        while (s.size() && *s.begin () <= i) s.erase (s.begin());
        ans += s.size ();
        s.erase (i);
        // cout << i << ": ";
        // for (auto j : s)    cout << j << ' ';
        // cout << endl;
    }
    cout << ans << endl;
}

474D - Flowers

dp + 前缀和
比较浅显的线性dp,转移式子也非常明示。但是最开始做的时候下意识往组合数学推式子方面去想,最终自然是推出了错误的式子。

#include <bits/stdc++.h>
#define ll long long

using namespace std;
const int N = 1e5 + 5, mod = 1000000007;
int t, k;
ll f[N], sum[N];

int main () {
    scanf ("%d%d", &t, &k);
    f[0] = 1;
    for (int i = 1; i <= 1e5; i++) {
        if (i >= k)     f[i] = (f[i-1] + f[i-k]) % mod;
        else    f[i] = f[i-1];
    }
    for (int i = 1; i <= 1e5; i++)  sum[i] = (sum[i-1] + f[i]) % mod;
    while (t --) {
        int a, b;
        scanf ("%d%d", &a, &b);
        printf ("%lld\n", (sum[b] - sum[a-1] + mod) % mod);
    }
}

339D - Xenia and Bit Operations

线段树基础
啊啊主要是读题读不懂
这个式子大概是逐层合并向上(两两合成),类似线段树的分层过程,只需记录深度单点修改就可。关于记录深度这里:先递归!想不通就类比树的记录深度的dfs过程。

#include <bits/stdc++.h>
#define ll long long

using namespace std;
const int N = 131072 + 5;
ll a[N * 4], dep[N * 4], n, q, sum;

struct Node {
    int l, r;
    ll sum;
}st[N * 4]; //千万别忘了 * 4

void pushup (int u){
    if (dep[u] & 1)     st[u].sum = (st[u<<1].sum | st[u<<1|1].sum);
    else    st[u].sum = (st[u<<1].sum ^ st[u<<1|1].sum);
}

void modify (int u, int x, int v){
    //从u结点开始查询,在x处插入,插入的值为v
    if (st[u].l == st[u].r){
        st[u].sum = v;
        return;
    }
    int mid = st[u].l + st[u].r >> 1;
    if (x <= mid)    modify (u << 1, x, v);
    else    modify (u << 1 | 1, x, v);
    pushup (u);//以子更新父      
}

void build (int u, int l, int r){
    if (l == r) {
        st[u] = {l, r, a[r]};
        return;
    }
    st[u] = {l, r};
    int mid = l + r >> 1;
    build (u << 1, l, mid);
    build (u << 1 | 1, mid + 1, r);
    dep[u] = dep[u<<1] + 1; //深度更新的顺序: 先建树再更新深度,与dfs跟更新类似
    pushup (u);
}

int main () {
    //cout << pow (2, 17);
    cin >> n >> q;
    n = pow (2, n);
    for (int i = 1; i <= n; i++)    cin >> a[i];
    build (1, 1, n);
    //cout << sum << endl;
    while (q--) {
        ll id, x;
        cin >> id >> x;
        //a[id]修改为x
        modify (1, id, x);
        cout << st[1].sum << endl;
    }
}

//啊啊原来改变是会保留的。。麻了
//线段树性质

126B - Password

字符串基础
加深对于kmp next数组的理解,参考博客
\(next\) 数组一直往前跳(具体看注释)
字符串哈希也可以搞,二分一下

#include <bits/stdc++.h>
#define vi vector<int>

using namespace std;
const int N = 1e6 + 5;
int n, ne[N];

void Next(string &p){
    for (int i = 1, j = 0; i < n; i ++){
        while (j && p[i] != p[j])    j = ne[j - 1];
        if (p[i] == p[j])    j ++;
        ne[i] = j;
    }
}

int main () {
    string s;
    cin >> s;
    n = s.size ();
    Next (s);
    //for (int i = 0; i < n; i++)     cout << ne[i] << ' ';cout << endl;
    int len = ne[n-1], maxn = 0;
    for (int i = 1; i < n - 1; i++)     maxn = max (maxn, ne[i]); //中间的最长公共前后缀
    while (len > maxn)  len = ne[len-1]; //后缀出发,找到中间第一个最长公共子串
    if (len)    cout << s.substr (0, len); //确保该子串不是前缀
    else    cout << "Just a legend";
}

//既是前缀又是后缀又是中缀的最长子串
//最长公共前后缀next[]\neq答案, 再去check中间是否存在相应串(hash??)
//深刻理解next数组含义!!


//找到的第二个(特判是否在结尾)
//hash: [1,a],[b,c],[d,n] 且 a-1=c-b=n-d 且 a,c < n; b,d > 1
//根据len存值?

118D - Caesar's Legions

线性dp
总结:
转移式子想了很久,究其根本是递推的边界没想明白。最开始写的版本里面都是只继承了相同的数字,并没有考虑新开一段要怎么转移,所以总是少一些数字。
还有就是状态的设计,最开始的状态设计是 \(dp_{i,j,ki,kj}\) 表示填了 \(i\) 个0,\(j\) 个1,当前有连续 \(ki\) 个0,\(kj\) 个1。然而这样设计状态的后果就是,最后两维总有一个是0,而这其实可以优化成这样一个状态 \(dp_{i,j,k,0/1}\):当前有连续 \(k\) 个0/1。
还有就是初始化问题!!考虑一种一个都不放时另一个种的状态(直接转移转移不到)

// LUOGU_RID: 104014869
#include <bits/stdc++.h>

using namespace std;
const int N = 105, M = 15, mod = 1e8;
int f[N][N][M][2]; //f[i][j][k][0/1]: 0放了i个,1放了j个,连续k个0/1

int main () {
    int n1, n2, k1, k2;
    cin >> n1 >> n2 >> k1 >> k2;
    //初始化也非常关键
    for (int i = 0; i <= k1; i++)   f[i][0][i][0] = 1;
    for (int i = 0; i <= k2; i++)   f[0][i][i][1] = 1;
    for (int i = 1; i <= n1; i++) {
        for (int j = 1; j <= n2; j++) {
            //init: 开新的要从上一个不同的继承
            for (int x = 1; x <= min (j, k2); x++)  (f[i][j][1][0] += f[i-1][j][x][1]) %= mod;
            for (int x = 1; x <= min (i, k1); x++)  (f[i][j][1][1] += f[i][j-1][x][0]) %= mod;
            //连续
            for (int k = 2; k <= min (i, k1); k++)  (f[i][j][k][0] += f[i-1][j][k-1][0]) %= mod;
            for (int k = 2; k <= min (j, k2); k++)  (f[i][j][k][1] += f[i][j-1][k-1][1]) %= mod;
        }
    }
    int ans = 0;
    for (int i = 0; i <= k1; i++)    (ans += f[n1][n2][i][0]) %= mod;
    for (int i = 0; i <= k2; i++)    (ans += f[n1][n2][i][1]) %= mod;
    cout << ans << endl;
}

1365D - Solve The Maze

小贪心 + bfs + 一丢丢分类讨论
为了让所有的坏人不能到达,所以直接在坏人四周放上墙,看看从终点走能否走到所有好人。
注意特判一些边界(比如没有坏人,好人原本就走不到之类的,可以看看代码)

#include <bits/stdc++.h>

using namespace std;
typedef pair<int,int> pii;
const int N = 55;
char a[N][N];
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
int n, m;
bool vis[N][N];

bool Range (int x, int y) {
    if (x < 1 || x > n || y < 1 || y > m)   return false;
    return true;
}

void bfs (int x, int y) {
    memset (vis, false, sizeof vis);
    queue <pii> q;
    q.push ({x, y});
    while (!q.empty ()) {
        auto t = q.front ();
        q.pop ();
        int x = t.first, y = t.second;
        for (int i = 0; i < 4; i++) {
            int xx = x + dx[i], yy = y + dy[i];
            if (!Range (xx, yy) || vis[xx][yy] || a[xx][yy] == '#')    continue;
            q.push ({xx, yy});
            vis[xx][yy] = true;
        }
    }
}

void solve () {
    set<pii> G, B;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
            if (a[i][j] == 'G')     G.insert ({i, j});
            if (a[i][j] == 'B')     B.insert ({i, j});
        }
    }
    if (!G.size ()) {
        cout << "Yes\n";
        return ;
    }

    //check原有G可达否(终点可达的点是否包含所有G)
    bfs (n, m);
    // for (int i = 1; i <= n; i++) {
    //     for (int j = 1; j <= m; j++) {
    //         cout << vis[i][j] << ' ';
    //     }
    //     cout << endl;
    // }
    // cout << endl;

    for (auto i : G) {
        int x = i.first, y = i.second;
        if (!vis[x][y]) {
            cout << "No\n";
            return ;
        }
    }

    if (!B.size ()) {
        cout << "Yes\n";
        return ;
    }

    // for (auto i : B) {
    //     if (!vis[i.first][i.second])    B.erase (i);
    // }

    // cout << "G: \n";
    // for (auto i : G)    cout << i.first << ' ' << i.second << endl;
    // cout << "B: \n";
    // for (auto i : B)    cout << i.first << ' ' << i.second << endl;

    //所有的B四周放上围墙
    for (auto i : B) {
        int x = i.first, y = i.second;
        for (int j = 0; j < 4; j++) {
            int xx = x + dx[j], yy = y + dy[j];
            if (!Range (xx, yy) || a[xx][yy] == 'B')    continue;
            if (a[xx][yy] == 'G' || (xx == n && yy == m)) {
                cout << "No\n";
                return ;
            }
            a[xx][yy] = '#';
        }
    }

    // for (int i = 1; i <= n; i++) {
    //     for (int j = 1; j <= m; j++) {
    //         cout << a[i][j];
    //     }
    //     cout << endl;
    // }

    //再check一遍是不是所有G都可达
    bfs (n, m);
    for (auto i : G) {
        int x = i.first, y = i.second;
        if (!vis[x][y]) {
            cout << "No\n";
            return ;
        }
    }

    cout << "Yes\n";
}

int main () {
    int t;
    cin >> t;
    while (t--) {
        solve ();
    }
}
//预处理一遍B能到的所有路径,拿墙堵上之后看G能不能到

977F - Consecutive Subsequence

线性dp + map
题意:输出最长连续上升子序列(可断)
也是非常基础的线性dp,dp的STL小应用
map记录ai上一个离他最近的ai-1所在位置
注意一定是边读入边进行!!不然就会出现1 2 3 4 3这种更新不到4的情况(类比爬山)

#include <bits/stdc++.h>

using namespace std;
typedef pair<int, int> pii;
const int N = 2e5 + 5;
int a[N], f[N], pre[N], n; //f[i]: 以i结尾的最长连续上升子序列

int main () {
    cin >> n;
    map<int, int> mp;
    //for (int i = 1; i <= n; i++)    cin >> a[i], mp[a[i]] = i;
    for (int i = 1; i <= n; i++) {
        cin >> a[i]; //一定要边读入边进行: 1 2 3 4 3
        if (mp[a[i]-1]) {
            //f[i] = max (f[i], f[mp[a[i]-1]] + 1);
            if (f[i] < f[mp[a[i]-1]] + 1) {
                pre[i] = mp[a[i]-1];
                f[i] = f[mp[a[i]-1]] + 1;
            }
        }
        else    f[i] = 1;
        mp[a[i]] = i;
    }
    int ans = 1, id = 1;
    //for (int i = 1; i <= n; i++)    cout << f[i] << ' ';cout << endl;
    for (int i = 1; i <= n; i++) {
        //cout << i << ' ' << pre[i] << endl;
        if (f[i] > ans)     ans = f[i], id = i;
    }
    cout << ans << endl;
    vector<int> v;
    while (ans--) {
        v.push_back (id);
        id = pre[id];
    }
    reverse (v.begin (), v.end ());
    for (auto i : v)    cout << i << ' ';
}

467C - C. George and Job

线性dp
也是非常裸的dp!!!状态设计出来就没了
\(dp_{i,j}\) 表示选到第 \(i\) 个数,选了 \(j\) 段;选与不选这样转移即可

// LUOGU_RID: 104045069
#include <bits/stdc++.h>
#define ll long long

using namespace std;
const int N = 5005;
ll a[N], s[N], f[N][N]; //f[i][j]: 选到i选了j段
int n, m, k;

int main () {
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++)    cin >> a[i], s[i] = s[i-1] + a[i];
    //for (int i = 1; i <= n; i++)    f[i][0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= k; j++) {
            f[i][j] = f[i-1][j]; //不选
            if (i >= m)     f[i][j] = max(f[i][j], f[i-m][j-1] + (s[i] - s[i-m]));
        }
    }
    cout << f[n][k] << endl;
}

349B - B. Color the Fence

贪心
先确定位数再贪心替换最大的

#include <bits/stdc++.h>

using namespace std;
int a[15], m, n = 9;
int minn = 1e9, id = 0;

int main () {
    cin >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        minn = min (minn, a[i]);
    }
    for (int i = 1; i <= n; i++) {
        if (a[i] == minn)   id = max (id, i);
    }
    //先确定位数
    int cnt = m / minn;
    if (cnt == 0) {
        cout << -1;
        return 0;
    }
    if (m % minn == 0) {
        while (cnt--)  cout << id; 
    }
    else {
        m -= minn * cnt;
        while (cnt-- > 0) {
            m += minn;
            //找到<=m的下标最大的
            int pos = id;
            for (int i = 1; i <= n; i++) {
                if (a[i] <= m)  pos = i;
            }
            cout << pos;
            m -= a[pos];
            if (m <= 0) break;
        }
        while (cnt-- > 0)   cout << id;
    }
}

//贪心吗...
//确定位数之后贪心替换
posted @ 2023-03-07 21:37  Sakana~  阅读(46)  评论(0编辑  收藏  举报