Codeforces Round #683 (Div. 2, by Meet IT)

Codeforces Round #683 (Div. 2, by Meet IT)

A. Add Candies

题意: 水题

题解:

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T;

int main() {
    // freopen("in.txt", "r", stdin);
    cin >> T;
    while(T--) {
        cin >> n;
        cout << n << endl;
        for (int i = 1; i <= n; ++i) cout << i << " ";
        cout << endl;
    }
    return 0;
}

B. Numbers Box

题意: 给一个n×m的矩阵,可进行m次操作(m无限制),每次操作可以选则两个相邻的格子(相邻即为有公共边),将这两个格子内的元素都乘上-1,问最终这个矩阵内所有元素的和最大是多少。

题解: 通过把相邻的格子都乘上-1,那么就算把某个数字的符号转移到相邻位置去,因此可以尽可能地把负数集中到一起。如果负数为偶数个,那么这些负数最后都能变成正数;如果负数为奇数个,那么就把剩下的负号转移到a[i]中绝对值最小的元素上面去。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 100 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T, a[N][N];

int main() {
    // freopen("in.txt", "r", stdin);
    cin >> T;
    while(T--) {
        cin >> n >> m;
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) cin >> a[i][j];
        }

        int res_zheng = 0, res_fu = 0;
        int flg0 = 0, minv = 1e9 + 10, cnt = 0;
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                if (a[i][j] > 0) {
                    res_zheng += a[i][j];
                    minv = min(minv , a[i][j]);
                }
                if (a[i][j] == 0) flg0 = 1;
                if (a[i][j] < 0) {
                    cnt++;
                    res_fu += abs(a[i][j]);
                    minv = min(minv, abs(a[i][j]));
                }
            }
        }
        // cout << res_zheng << " " << res_fu << " " << minv << endl;
        if (cnt & 1) {
            if (flg0) res_zheng += res_fu;
            else res_zheng += res_fu - 2 * minv;
        }
        else {
            res_zheng += res_fu;
        }
        cout << res_zheng << endl;
    }
    return 0;
}

C. Knapsack

题意: 给你n个物品,问你能否选择某些个物体,使得总体积为ceil(W / 2) ~ W。

题解: 由于是W/2~ W,因此如果是2个物品都是小于W/2,那么加起来的和也必然小于W。那么可以把数据按照从大到小排序,如果当前数据是在W/2~W中,那么直接break,表示找到;如果小于W/2,那么可以不断将更小的累加,直到W/2 ~ W。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T, a[N];
LL W;

int main() {
    // cout << (int)ceil((double)7 / 2) << endl;
    // freopen("in.txt", "r", stdin);
    cin >> T;
    while(T--) {
        cin >> n >> W;
        vector<PII> v;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            v.push_back({a[i], i});
        }
        sort(v.begin(), v.end());
        reverse(v.begin(), v.end());
        vector<int> res;
        LL sum = 0;
        for (int i = 0; i < v.size(); ++i) {
            if (v[i].first > W) continue;
            sum += v[i].first;
            res.push_back(v[i].second);
            if (v[i].first >= (LL)ceil((double)W / 2) && v[i].first <= W) 
                break;
            if (sum >= (LL)ceil((double)W / 2) && sum <= W) 
                break;
        }
        if (sum >= (LL)ceil((double)W / 2) && sum <= W) {
            printf("%d\n", res.size());
            for (int i = 0; i < res.size(); ++i) printf("%d ", res[i]);
            printf("\n");
        }
        else {
            printf("-1\n");
        }
    }
    return 0;
}

D. Catching Cheaters

题意: 要求所有找到字符串A和B的公共子串的最大S函数,S(C, D) = 4 * LCS(C, D) - |C| - |D|。字符串长度为5e3

题解: 设f[i, j]表示A取前i个,B取前j个时的S函数最大值。那么可以按照lcs原理进行改编,如果当前a[i] 和 b[j]相同,那么 f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − 1 ] + 2 ) ; f[i][j] = max(f[i][j], f[i - 1][j - 1] +2); f[i][j]=max(f[i][j],f[i1][j1]+2);,其他情况,需要 m a x ( m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] ) − 1 , 0 ) ; max(max(f[i - 1][j], f[i][j - 1]) - 1, 0); max(max(f[i1][j],f[i][j1])1,0);

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 5e3 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T,f[N][N],anss=0;

char a[N],b[N];
int main() {
    cin >> n >> m;
    cin >> a + 1 >> b + 1;
    memset(f, 0, sizeof f);
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            f[i][j] = max(max(f[i - 1][j], f[i][j - 1]) - 1, 0);
            if (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] +2);
            anss=max(f[i][j],anss);
        }
    }
    
    cout << anss;
    return 0;
}

E. Xor Tree

题意: 给定一个长度为n的数组a,对于数组a里面的每个数字a[i],他会找到一个和他异或值最小的点j,使得i到j建立一条无向边。这样建出来的图可能是不连通图,求最少删掉几个点后能够使得按照上述规则建出来的图是一张连通图。 1 < = n < = 1 0 5 , 1 < = m < = 1 0 5 1<=n<=10^5, 1<=m<=10^5 1<=n<=105,1<=m<=105

题解: 每个点都会去找使得和他异或值最小的点,可以把这个操作挂在字典树上,可以发现字典树上每个叶节点都代表一个点a[i],而与a[i]异或值最小的点处于他的兄弟节点,或者a[i]父节点的兄弟节点,或者不断往上递归父节点的兄弟节点。对于字典树上 一个点 的两棵不同子树,如果其叶子个数均大于1,这两棵子树各自的ai就会相互配对,形成多个连通块。为保证最后能够只有一个连通块,因此就需要选择左子树 或者 右子树 将其所有的叶子节点 删到 至多只剩1个 , 这样就能保证只有一个连通块。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T, a[N];
int son[N * 32][2], idx;

void insert(int num) {
    int p = 0;  // 从根节点开始
    for (int i = 30; i >= 0; --i) {  // 从第30位开始
        int u = (num >> i & 1);  // 判断num这一位上的数字
        if (!son[p][u]) son[p][u] = ++idx;  // 如果不存在,就建立一个结点
        p = son[p][u];  // 从父节点往下走
    }
}

int query(int u) {
    int l = 0, r = 0;
    if (!son[u][0] && !son[u][1]) return 1;
    if (son[u][0]) l = query(son[u][0]);
    if (son[u][1]) r = query(son[u][1]);
    if (son[u][0] && son[u][1]) return max(l, r) + 1;
    else return l + r ;
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        insert(a[i]);
    }
    cout << n - query(0);
    return 0;
}

F1/F2. Frequency Problem (Hard Version)

题意: 给定一个长度为1e5的数组,数组内的元素大小1<=ai<=1e5。要求找出一个最大长度的区间,使得区间内出现次数最多的元素种类至少为2.

题解: 首先,在整个区间内出现次数最多的元素必然包含于这个答案区间内。那么我们首先可以找出这个出现次数最多的元素,记为mx。

我们知道,如果一个元素的出现次数大于sqrt(n),那么这样的元素少于sqrt(n)种。因此如果元素的次数大于sqrt(n),我们可以去枚举元素的大小。否则,我们去枚举元素的出现次数。

对于枚举元素的大小,需要枚举sqrt(n)次。每次枚举时,我们可以使用前缀和来处理,一旦发现为mx,那么前缀和+1,发现为枚举的元素x,那么前缀和-1.如果当前的前缀和出现过,那么更新答案。

对于枚举元素的出现次数,可以使用尺取法来更新答案。

代码:

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

const int maxn = 2e5 + 10;

int n,a[maxn],cnt[maxn],mx=0,last[maxn*2],ans=0; 

// 枚举值
void solve1(int x){
	for (int i = 1; i <= n; i++) last[n - i] = last[n + i] = -1;  // 加上n防止小于0,这样不会段错误
    last[n] = 0;
    int cur = 0;
	for (int i = 1;  i <= n; i++){
		if(a[i] == x) cur ++;
        else if (a[i] == mx) cur --;
		if (last[n + cur] == -1) last[n + cur] = i;
        else ans = max(ans, i - last[n + cur]);
    }
}

// 枚举出现次数--尺取法
void solve2(int limit) {
    memset(cnt, 0, sizeof cnt);
    int equal = 0;
    for (int i = 1, j = 1; i <= n; ++i) {
        cnt[a[i]]++;
        if (cnt[a[i]] == limit) equal++;
        if (cnt[a[i]] > limit) {
            while(j <= i && cnt[a[i]] > limit) {
                cnt[a[j]]--;
                if (cnt[a[j]] == limit - 1) equal--;
                j++;
            }
        } 
        if (equal >= 2) ans = max(ans, i - j + 1);
    }
}

int main(){
    cin >> n;
    for (int i = 1; i <= n;i++){
        cin >> a[i];
        cnt[a[i]]++;  // 记录每个值的出现次数
        mx = cnt[mx] < cnt[a[i]] ? a[i] : mx;  // 记录出现次数最多的那个值
    }
	int limt=sqrt(n);  // 记录限制值
	for (int i = 1; i <= n;i++)
        if(cnt[i]>=limt && i!=mx) solve1(i);  // 如果出现次数大于等于限制值,那么直接枚举大小,因为这样的值只会有sqrt(n)个
    
	for (int i = 1; i < limt;i++)solve2(i);  // 如果出现次数次数小于限制值,直接枚举出现次数
    cout << ans << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值