Codeforces Round-#373 (Div. 2 && Div. 1) [Codeforces719 && 718]

28 篇文章 0 订阅
8 篇文章 0 订阅

题目链接
Div. 1
Div. 2
官方题解

719 - A - Vitya in the Countryside

题目大意

一个月有 31 天,每天月亮的尺寸为 0,1,,14,15,14,,1, ,第二月又重复上面循环,现给出连续 n 天的月亮尺寸求判断下一天月亮尺寸的大小变化,不能确定则输出1

思路 - 模拟

按照题意判断即可,注意坑点有最后一天是 0 15需要特判。

代码

#include <cstdio>

using namespace std;

int n, pre, cur;

int main() {
    while(1 == scanf("%d", &n)) {
        scanf("%d", &cur);
        for(int i = 1; i < n; ++i) {
            pre = cur;
            scanf("%d", &cur);
        }
        if(cur == 0) {
            printf("UP\n");
        }
        else if(cur == 15) {
            printf("DOWN\n");
        }
        else if(n == 1) {
            printf("-1\n");
        }
        else {
            printf("%s\n", pre < cur ? "UP" : "DOWN");
        }
    }
    return 0;
}

719 - B - Anatoly and Cockroaches

题目大意

有一个 01 串,现要求 0 1交替排列,有两种操作,①交换任意两个字符;②将其中一个字符变成相反的字符,求使这个串满足要求时最少的操作次数?

思路 - 贪心

按照题目要求,再枚举奇数位放 0 1,则可以确定 0 1的个数。然后通过统计奇数位和偶数位的 0 1的个数先确定要替换字符,然后贪心地先替换不在正确位置的字符,不够时再替换在正确位置的字符,最后再加上不在正确位置的字符个数即可。答案是两个的较小值。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int n;

int getMin(int cnt_b[3], int cnt_r[3]) {
    int num = n / 2 + (n & 1), tmp;//num为r字符的应有值
    //令第一个字符为r字符
    if(cnt_r[0] + cnt_r[1] <= num) {//若r的字符个数小于等于应有值,则需要将b字符转换成r字符
        tmp = num - cnt_r[0] - cnt_r[1];//需要将tmp个b字符转换成r字符
        //可以确定在0,2,4...的b字符一定大于等于tmp,否则不满足字符数等于应有值
        cnt_b[0] -= tmp;//将所有需要转换的字符都在正确位置转换
        tmp += cnt_b[0];//剩余的不在正确位置的b字符需要交换操作
    }
    else {//需要将r字符转换成b字符
        tmp = cnt_r[0] + cnt_r[1] - num;//需要将tmp个r字符转换成b字符
        if(cnt_r[1] >= tmp) {
            cnt_r[1] -= tmp;
        }
        else {
            cnt_r[1] -= tmp - cnt_r[0];
            cnt_r[1] = 0;
        }
        tmp += cnt_b[0];//剩余的不在正确位置的b字符需要交换操作
    }
    return tmp;
}

int cnt_b[3][3], cnt_r[3][3], ans, tmp, num;
char s[100003];

int main() {
    while(1 == scanf("%d", &n)) {
        scanf("%s", s);
        cnt_b[0][0] = cnt_b[0][1] = cnt_r[0][0] = cnt_r[0][1] = 0;
        cnt_b[1][0] = cnt_b[1][1] = cnt_r[1][0] = cnt_r[1][1] = 0;
        for(int i = 0; i < n; ++i) {//统计每个字符在奇数位和偶数位出现的次数
            if(s[i] == 'b') {
                ++cnt_b[0][i & 1];
                ++cnt_b[1][i & 1];
            }
            else {
                ++cnt_r[0][i & 1];
                ++cnt_r[1][i & 1];
            }
        }
        printf("%d\n", min(getMin(cnt_b[0], cnt_r[0]), getMin(cnt_r[1], cnt_b[1])));
    }
    return 0;
}

618 - A - Efim and Strange Grade

题目大意

给定一个小数,可以对小数部分最多进行 t 次四舍五入,求能得到的数的最大值?

思路 - 模拟

从小数点后第一个大于等于5的数开始往前四舍五入,注意进位即可。

题解用的是 DP dp[i] 表示让第 i 位进位到第i1位所需的最小进位次数。然后对 a[i]>=5 a[i]==4 a[i]<4 分别进行状态转移即可,最后找到最小的 i 使得dp[i]<=t,对 a[i]+1 即可。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int n, t, pos, ed, carry;
char s[200003];

int main() {
    while(2 == scanf("%d%d", &n, &t)) {
        scanf("%s", s + 1);
        s[0] = '0';
        for(pos = 1; pos <= n; ++pos) {
            if(s[pos] == '.') {
                break;
            }
        }
        int i;
        for(i = 1; i <= n; ++i) {
            if(i > pos && '5' <= s[i] && s[i] <= '9') {
                if(i - 1 == pos) {
                    ++s[i - 2];
                    s[i - 1] = '\0';
                    i -= 2;
                }
                else {
                    ++s[i - 1];
                    if(i > pos) {
                        s[i] = '\0';
                    }
                    --i;
                }
                carry = (s[i] == '0' + 10) ? 1 : 0;
                --t;
                break;
            }
        }
        if(i <= n) {
            while(i > pos && t > 0 && '5' <= s[i]) {
                if(i - 1 == pos) {
                    ++s[i - 2];
                    s[i - 1] = '\0';
                    i -= 2;
                }
                else {
                    ++s[i - 1];
                    if(i > pos) {
                        s[i] = '\0';
                    }
                    --i;
                }
                carry = (s[i] == '0' + 10) ? 1 : 0;
                --t;
            }
        }
        while(carry == 1) {
            s[i] = '0';
            if(i - 1 == pos) {
                ++s[i - 2];
                s[i - 1] = '\0';
                i -= 2;
            }
            else {
                ++s[i - 1];
                if(i > pos) {
                    s[i] = '\0';
                }
                --i;
            }
            carry = (s[i] == '0' + 10) ? 1 : 0;
        }
        printf("%s\n", s[0] == '0' ? s + 1 : s);
    }
    return 0;
}

718 - C - Sasha and Array

题目大意

给定一个数列 a[1],a[2],,a[n] ,有两个操作:
1 l r x :对 a[l],a[l+1],,a[r] ,分别加上 x
2 l r:查询 sumri=lf(a[i]) f(x) 表示斐波那契数列的第 i 项。

思路 - 线段树 && 矩阵快速幂

肯定能想到用矩阵快速幂求斐波那契数列,但是没有想到利用矩阵快速幂的方法,用线段树维护矩阵。
叶子结点维护f(a[i]),f(a[i]+1),然后矩阵支持分配律,所以非叶子结点是其所有叶子结点的对应项之和,用 lazy 标记一下即可。
lazy 应该也存成矩阵,不能存成指数,否则会 TLE 。刚开始存成指数,各种优化,还闯到 51 组数据。。。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

#define lson (i << 1)
#define rson ((i << 1) | 1)

using namespace std;

const int MAXN = 100003;
const int MAX_MAT = 2;
const long long MOD = 1e9+7;

struct Matrix {
    int m[MAX_MAT][MAX_MAT];

    bool operator == (const Matrix& b) const {
        for(int i = 0; i < MAX_MAT; ++i) {
            for(int j = 0; j < MAX_MAT; ++j) {
                if(m[i][j] != b.m[i][j]) {
                    return false;
                }
            }
        }
        return true;
    }

    Matrix operator + (const Matrix& b) const {
        Matrix c;
        for(int i = 0; i < MAX_MAT; ++i) {
            for(int j = 0; j < MAX_MAT; ++j) {
                c.m[i][j] = (m[i][j] + b.m[i][j]) % MOD;
            }
        }
        return c;
    }

    Matrix operator - (const Matrix& b) const {
        Matrix c;
        for(int i = 0; i < MAX_MAT; ++i) {
            for(int j = 0; j < MAX_MAT; ++j) {
                c.m[i][j] = (m[i][j] - b.m[i][j] + MOD) % MOD;
            }
        }
        return c;
    }

    Matrix operator * (const Matrix& b) const {
        Matrix c;
        for(int i = 0; i < MAX_MAT; ++i) {
            for(int j = 0; j < MAX_MAT; ++j) {
                long long res = 0;
                for(int k = 0; k < MAX_MAT; ++k) {
                    res += ((long long) m[i][k]) * b.m[k][j];
                }
                c.m[i][j] = res % MOD;
            }
        }
        return c;
    }

    Matrix operator ^ (long long n) const;
};

const Matrix FIB = {1, 1,
                    0, 0
};

const Matrix P = {0, 1,
                  1, 1,
};

const Matrix I = {1, 0,
                  0, 1,
};

//由于使用了结构体本身的常量,所以需要定义成外部成员函数
Matrix Matrix :: operator ^ (long long n) const {
    Matrix m = *this, b = I;
    while(n > 0) {
        if((n & 1) == 1) {
            b = b * m;
        }
        n = n >> 1;
        m = m * m;
    }
    return b;
}

struct Node {
    int l, r;
    Matrix sum, lazy;//刚开始一直把lazy存成指数,导致花式TLE
}tr[MAXN << 2];

int n, m;
int ope, L, R, X;

inline void pushDown(int i) {
    if(!(tr[i].lazy == I) && tr[i].l != tr[i].r) {//叶子结点不下放,否则会越界
        tr[lson].sum = tr[lson].sum * tr[i].lazy;
        tr[lson].lazy = tr[lson].lazy * tr[i].lazy;

        tr[rson].sum = tr[rson].sum * tr[i].lazy;
        tr[rson].lazy = tr[rson].lazy * tr[i].lazy;
        tr[i].lazy = I;
    }
}

inline void pushUp(int i) {
    tr[i].sum = tr[lson].sum + tr[rson].sum;
}

void build(int i, int l, int r) {
    tr[i].l = l;
    tr[i].r = r;
    tr[i].lazy = I;
    if(l == r) {
        scanf("%d", &X);
        tr[i].sum = FIB;
        tr[i].sum = tr[i].sum * (P ^ (X - 1));
        return ;
    }

    int mid = (l + r) >> 1;
    build(lson, l, mid);
    build(rson, mid + 1, r);
    pushUp(i);
}

void modify(int i, const Matrix& temp) {
    if(L <= tr[i].l && tr[i].r <= R) {
        tr[i].sum = tr[i].sum * temp;
        tr[i].lazy = tr[i].lazy * temp;
        return ;
    }

    pushDown(i);
    int mid = tr[lson].r;
    if(mid >= L) {
        modify(lson, temp);
    }
    if(mid < R) {
        modify(rson, temp);
    }
    pushUp(i);
}

int query(int i) {
    if(L <= tr[i].l && tr[i].r <= R) {
        return tr[i].sum.m[0][0];
    }

    pushDown(i);
    int mid = tr[lson].r;
    int res = 0;
    if(mid >= L) {
        res = query(lson);
    }
    if(mid < R) {
        res += query(rson);
    }
    pushUp(i);
    return res % MOD;
}

int main() {
    while(2 == scanf("%d%d", &n, &m)) {
        build(1, 1, n);
        while(m-- >0) {
            scanf("%d%d%d", &ope, &L, &R);
            if(ope == 1) {
                scanf("%d", &X);
                modify(1, P ^ X);
            }
            else {
                printf("%d\n", query(1));
            }
        }
    }
    return 0;
}

718 - D - Andrew and Chemistry

题目大意

给定一颗无根树,现可以添加一个结点,使得添加后形成不同构的树有多少?保证添加前后每个结点的度不能超过 4

思路 - 树形DP

题解的方法好巧妙,由于每个结点i至多只和四个结点相连,所以可以用这四个结点(不够时补上虚拟结点)的状态描述结点 i 的状态。
可以想到O(n2logn)的做法,枚举每个点作为根,然后用 DP ,从下往上更新每个点的状态值。所有根状态值的不同个数即为答案。
可以不枚举,再从上往下更新状态值就能得到所有点作为根时的状态值。大致是递归前,令当前结点 i 的以前的子结点v作为其父亲结点,得到结点 i 以其上部分作为子结点时的状态值。然后就能在O(nlogn)内求得以每个店作为根时的状态值。

代码

#include <cstdio>
#include <cstring>
#include <map>
#include <algorithm>

#define lson (i << 1)
#define rson ((i << 1) | 1)

using namespace std;

const int MAXN = 100003;
const int INF = 0x3f3f3f3f;

struct Vect {
    int a[4];

    Vect() {
        memset(a, 0x3f, sizeof(a));
    }

    bool operator < (const Vect& x) const {
        for(int i = 0; i < 4; ++i) {
            if(a[i] != x.a[i]) {
                return a[i] < x.a[i];
            }
        }
        return false;
    }
};

struct Node {
    int v, nxt;
}edge[MAXN << 1];

int fir[MAXN], tot;

void init() {
    memset(fir, -1, sizeof(fir));
    tot = 0;
}

void addEdge(int u, int v) {
    edge[tot].v = v;
    edge[tot].nxt = fir[u];
    fir[u] = tot++;

    edge[tot].v = u;
    edge[tot].nxt = fir[v];
    fir[v] = tot++;
}

int n, ans, cnt;
//a[i]表示只看子结点时的状态值,b[i]表示看所有结点时的状态值
int a[MAXN], b[MAXN], deg[MAXN];//deg[i]表示i点的度
bool vis[MAXN * 3];//【注意】由于dfsFromDown中cnt至多+n次,dfsFromUp中cnt至多+(n+n)次,所以要开三倍空间
map<Vect, int> mp;

void dfsFromDown(int u, int p) {//自底向上更新a
    Vect cur;
    int num = 0, v;
    for(int i = fir[u]; i != -1; i = edge[i].nxt) {
        v = edge[i].v;
        if(v != p) {
            dfsFromDown(v, u);
            cur.a[num++] = a[v];
        }
    }
    sort(cur.a, cur.a + 4);//按子结点状态的升序排序
    if(mp[cur] == 0) {//如果当前状态未出现过,则标记为一个新的状态
        mp[cur] = ++cnt;
    }
    a[u] = mp[cur];
}

//status表示p结点以u结点为父亲结点时应有的状态值a[p]
void dfsFromUp(int u, int p, int status) {//自顶向下更新b
    Vect cur;
    int num = 0, v;
    for(int i = fir[u]; i != -1; i = edge[i].nxt) {
        v = edge[i].v;
        if(v == p) {
            cur.a[num++] = status;
        }
        else {
            cur.a[num++] = a[v];
        }
    }
    sort(cur.a, cur.a + 4);//按子结点状态的升序排序
    if(mp[cur] == 0) {//如果当前状态未出现过,则标记为一个新的状态
        mp[cur] = ++cnt;
    }
    b[u] = mp[cur];

    for(int i = fir[u]; i != -1; i = edge[i].nxt) {
        v = edge[i].v;
        if(v != p) {
            for(int i = 0; i < 4; ++i) {
                if(cur.a[i] == a[v]) {
                    cur.a[i] = INF;
                    break;
                }
            }
            sort(cur.a, cur.a + 4);//按子结点状态的升序排序
            if(mp[cur] == 0) {//如果当前状态未出现过,则标记为一个新的状态
                mp[cur] = ++cnt;
            }
            dfsFromUp(v, u, mp[cur]);
            cur.a[3] = a[v];
        }
    }
}

int u, v;

int main() {
    while(1 == scanf("%d", &n)) {
        init();
        mp.clear();
        ans = cnt = 0;
        memset(deg, 0, sizeof(deg));
        memset(vis, false, sizeof(vis));
        for(int i = 1; i < n; ++i) {
            scanf("%d%d", &u, &v);
            addEdge(u, v);
            ++deg[u];
            ++deg[v];
        }
        dfsFromDown(1, 0);
        dfsFromUp(1, 0, 0);
        for(int i = 1; i <= n; ++i) {
            if(deg[i] < 4 && !vis[b[i]]) {//当前结点的度小于4且状态未出现过,则可以添一个结点形成新的不同的树
                vis[b[i]] = true;
                ++ans;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

718 - E -

题目大意

思路 -

代码


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值