Codeforces #Round 321 Div2

@(K ACMer)


A. Kefa and First Steps

题意:
求连续最长非递减子序列的长度.
分析:
从左到右扫描即可.这里差点理解成经典DP题LIS.
这里复习一下LIS:
LIS有两种方法,一种是比较直观地 O(n2) 的方法.

定义:dp[i]为以i结尾的LIS的长度,有状态转移方程:

dp[i] = max(dp[j]+1 | a[j]<a[i],j= 0....i1)

然后是 O(nlogn) 的做法.
定义:dp[i]为长度为i的LIS的结尾的数的最小值.每次加一个数都二分的到DP数组中去查找它大于的第一个数并更新.

//LIS的代码
int dp[M], a[M], n;

int LIS(void) {
    fill(dp, dp + n + 1, INF);
    for (int i = 0; i < n; i++) {
        *lower_bound(dp, dp + n, a[i]) = a[i];
    }
    return lower_bound(dp, dp + n, INF) - dp;
}
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int M = int(1e5) + 9,mod = int(1e9) + 7, INF = 0x3fffffff;

int main(void) {
    int n, a[M];
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    int key = 1, sum = 1;
    for (int i = 1; i < n; i++) {
        if (a[i] < a[i - 1]) {
            key = 1;
        } else key++;
        sum = max(sum, key);
    }
    cout << sum << endl;
    return 0;
}

B. efa and Company

题意:每一个人都有它的身价和友谊度,要求一个人的集合让他们的最大身价差小于m,且友谊度和最大.
分析:
典型的尺取法.排序,然后用尺取法即可,然后求和的时候用前缀数组, O(1) 的查询任意区间和即可.
Code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int M = int(1e5) + 9,mod = int(1e9) + 7, INF = 0x3fffffff;
struct people{int m, f;}v[M];
long long pre[M];

bool cmp(const people & a, const people & b) {
    return a.m < b.m;
}

int main(void) {
    int n, d;
    scanf("%d%d", &n, &d);
    for (int i = 0; i < n; i++) {
        scanf("%d%d", &v[i].m, &v[i].f);
    }
    sort(v, v + n, cmp);
    for (int i = 0; i < n; i++) {
        if (i) pre[i] += pre[i - 1];
        pre[i] += v[i].f;
    }
    long long sum = 0;
    for (int i = 0, j = 0; j < n && i < n;) {
        if (v[j].m - v[i].m < d) {
            sum = max(sum, pre[j] - pre[i - 1]);
            j++;
        } else i++;
    }
    cout << sum << endl;
    return 0;
}
C. Kefa and Park

题意:给你一颗数,家在编号为1的根节点,所有的饭店在叶子节点,每个节点有猫或者没有猫,如果从家走到饭店的连续猫的数量不超过m就可以到该饭店,求可到达的饭店个数.
分析:
DFS来遍历树即可.
注意一个wa点,就是判断该顶点是否是叶子节点的时候用的数度数为1,这样有可能把根也当做叶子节点…比赛的时候就是wa在这里,找了好久的错=_=.

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int M = int(1e5) + 9,mod = int(1e9) + 7, INF = 0x3fffffff;
vector<int> v[M];
int a[M], n, m, sum;

void dfs(int cur, int par, int cd) {
    if (a[cur]) cd++;
    else cd = 0;
    if (cd > m) return;
    if (v[cur].size() == 1 && cur != 1) {
        sum++;
        return;
    }
    for (int i = 0; i < v[cur].size(); i++) {
        if (v[cur][i] == par) continue;
        dfs(v[cur][i], cur, cd);
    }
    return;
}

int main(void) {
    scanf("%d%d", &n, &m);
    sum = 0;
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for (int i = 0; i < n - 1; i++) {
        int x, y;
        scanf("%d%d", &x, &y);
        v[x].push_back(y);
        v[y].push_back(x);
    }
    dfs(1, -1, 0);
    printf("%d\n", sum);
    return 0;
}

D. Kefa and Dishes

题意:
总共有n道菜,每一道菜有其相应的愉悦度,而你要点m道菜,让其愉悦度最大.这里有k个限制条件形如: x y c 表示在点完x后点y就可以增加额外的c个愉悦度.
分析
根据题目数据范围很容易猜测到状压DP,然而事实也是比较裸的状态压缩DP.
定义: dp[s][j] 为当前已经选的菜的集合为s,最后一个选的菜为j所拥有的愉悦度.容易得到状态转移方程:

dp[s][j]=max(dp[sv][u]+d[u][v] | us)
:svsv

在打出所有的DP表之后,需要统计含有元素为个数为m的所有集合s中的愉悦度最大值.
知识补充:
状态压缩DP其实就是,对有一个DP维度的压缩,这个维度十分复杂,不能直观地表式,我们就把它要缩到一个二进制数的表达里.通过这个二进制数的位表示,来表示这个困难的状态.
然后注意状态压缩的标志就是数据范围,在18左右.

code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <queue>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int M =  int(1e0) + 17,mod = int(1e9) + 7, INF = 0x3fffffff;
long long d[M][M], dp[1 << M][M], n, m, k;

bool cal(int x) {  //检测是否恰好选择了m个顶点
    int ret = 0;
    while (x) {
        if (x & 1) ret++;
        x >>= 1;
    }
    return ret == m;
}

void solve(void) {
    for (int i = 1; i < (1 << n); i++)
        fill(dp[i], dp[i] + n, -INF);

    for (int s = 1; s < (1 << n); s++) {
        for (int v = 0; v < n; v++) {
            for (int u = 0; u < n; u++) {
                int x = (1 << v);
                x = ~x;  //为了将集合中的v点去除,而构造的x点.
                if ((s >> v) & 1 && (s >> u) & 1) {  //v和u都必须在集合中
                    dp[s][v] = max(dp[s][v], dp[s & x][u] + d[u][v]);
                }
            }
        }
    }

    long long ret = -INF;
    for (int i = 0; i < (1 << n); i++) {
        if (cal(i)) {
            for (int j = 0; j < n; j++)
                ret = max(ret, dp[i][j]);
        }
    }
    cout << ret << endl;
}

int main(void) {
    cin >> n >> m >> k;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        for (int j = 0; j < n; j++)
            d[j][i] = x;
    }
    for (int i = 0; i < k; i++) {
        int x, y, c;
        cin >> x >> y >> c;
        d[x - 1][y - 1] += c;
    }
    solve();
    return 0;
}

E. Kefa and Watch


题意:

给你一个只包含数字0到9的长度为1e6字符串.让你支持两种操作:

  • (1, l, r, x)把l到r的数字全部更新为x.
  • (2, l, r, x)l到r之间的字符串是否具有长度x的循环节?

分析:
  • 1e6的数据量,显然需要 O(nlogn) 的做法.这里比较字符串是否具有长度为x的循环节,需要借助一个错位判断周期的思想.也就是如果:
    lrxlxr,x
  • 然后我们怎么去判断两段字符串是否相同呢?显然暴力的逐个比较是不行的,那么久可以用字符串hash来解决,如果两个字符串它们的hash值相同就可以了.但是计算hash值也需要逐个计算.这里我们考虑到每次只是对其中一个区间做了改变,其它区间的字符串是不变的,这样我们就可以用一颗线段树来维护区间的hash值了,修改和查找的复杂度都是 O(nlogn) ,不是正好么!
  • 线段树来维护区间hash值的话.有一些技巧.我们用pow[i]数组来储存每种长度的字符串在hash的时候需要的权重.sum[i]来储存权重的前缀和,便于区间查寻.这样对于两个字符串区间,(l ,mid) 和(mid, r)我们在合并的时候(l, r)区间的hash值就等于: hash[(l,mid)]pow[rmid]+hash[(mid,r)]
  • 这里有必要补充一下字符串hash的思想.字符串hash相当于一个把字符串看做一个进制表达的数,每一位数给一个相应地位权,这样根据位权乘以各个字符串的ASCII码所得到的值的和就是该字符串的hash值.对于一个字符串其左边的字符权位总是比右边高,所以在合并左右区间的时候我们只在左边的hash值上乘以了右边区间长度的位权,这个类比2进制的表达很容易理解的.
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef unsigned long long ull;
const int M = int(1e5) + 17, seed = 13131, mod = 998244353;
int lazy[M << 2], n, m, k, mid;
ull pow[M << 2], sum[M << 2], d[M << 2];
char str[M];

void init() {
    pow[0] = sum[0] = 1;
    memset(lazy, -1, sizeof(lazy));
    for (int i = 1; i <= n; i++) {
        pow[i] = (pow[i - 1] * seed) % mod;
        sum[i] = (sum[i - 1] + pow[i]) % mod;
    }
}

void pushup(int k, int l, int r) {
    d[k] = (d[k << 1] * pow[r - (l + r) / 2] + d[k << 1 | 1]) % mod;
}

void build(int k, int l, int r) {
    if (l == r) d[k] = str[l] - '0';
    else {
        build(k * 2, l, (l + r) / 2);
        build(k * 2 + 1, (l + r) / 2 + 1, r);
        pushup(k, l, r);
    }
}

void pushdown(int k, int l, int r) {
    if (lazy[k] != -1) {
        lazy[k << 1] = lazy[k << 1 | 1] = lazy[k];
        lazy[k] = -1;
        d[k << 1] = lazy[k << 1] * sum[(l + r) / 2 - l ] % mod;
        d[k << 1 | 1] = lazy[k << 1 | 1] * sum[r - (l + r) / 2 - 1] % mod;
    }
}

void update(int k, int a, int b, int x, int l, int r) {
    if (a <= l && b >= r) {
        lazy[k] = x;
        d[k] = x * sum[r - l] % mod;
    } else {
        pushdown(k, l, r);
        if (a <= (l + r) / 2)update(k << 1, a, b, x, l, (l + r) / 2);
        if (b > (l + r) / 2) update(k << 1 | 1, a, b, x, (l + r) / 2 + 1, r);
        pushup(k, l, r);
    }
}

ull query(int k, int a, int b, int l, int r) {
    if (a <= l && b >= r) return d[k];
    else {
        pushdown(k, l, r);
        ull ret = 0;
        if (a > (l + r) / 2) ret = query(k << 1 | 1, a, b, (l + r) / 2 + 1, r);
        else if (b <= (l + r) / 2) ret = query(k << 1 , a, b, l, (l + r) / 2);
        else ret = (query(k << 1 , a, (l + r) / 2, l, (l + r) / 2) * pow[b - (r + l) / 2] % mod + query(k << 1 | 1, (l + r) / 2 + 1, b, (l + r) / 2 + 1, r)) %mod;
        //pushup(k, k << 1, k << 1 | 1);
        return ret ;
    }
}

int main(void) {
    scanf("%d%d%d%s", &n, &m, &k, str + 1);
    init();
    build(1, 1, n);
    for (int i = 0; i < m + k; i++) {
        int op, l, r, x;
        cin >> op >> l >> r >> x;
        if (op == 1) {
            update(1, l, r, x, 1, n);
        } else {
            if (r - l + 1 == x) puts("YES");
            else if (r - l + 1 < x) puts("NO");
            else if (query(1, l, r - x, 1, n) == query(1, l + x, r, 1, n)) puts("YES");
            else puts("NO");
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值