Codeforces Round #661 (Div. 3)

Codeforces Round #661 (Div. 3)

A. Remove Smallest

题意: 给定t个测试样例,每个测试样例给定n个数字,每次操作选择两个差值绝对值小于等于1的数字,删除较小的那个。问是否能够通过多次删除操作,使得n个数字最后只剩下一个。1 <= t <= 100, 1 <= n <= 50, 1 <= a[i] <= 100
题解: 由于每次都能选择两个差值绝对值小于等于1的数字,因此最好要是能够删除到只剩下一个那么绝对最相近的两个数字的差值小于等于1.因此,只需要,先排完序后,只需要顺着扫描一遍,是否相邻两个数字的差值绝对值小于等于1即可。
代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 110;
int a[N];
int n, t;

int main() {
    cin >> t;
    while (t--) {
        int flg = true;
        cin >> n;
        for (int i = 1, x; i <= n; ++i) 
            cin >> a[i];
        sort(a + 1, a + 1 + n);
        for (int i = 2; i <= n; ++i) {
            if (a[i] - a[i - 1] > 1) flg = false;
        }
        if (n == 1) flg = true;
        if (flg) cout << "YES\n";
        else cout << "NO\n";
    }
    
    return 0;
}

B. Gifts Fixing

题意: n份物品,每份都有a,b两种值。现在每次操作可以选择将其中一份的a减一或者b减一或者同时减一。问至少几次操作可以使得所有物品的a值相同,且所有物品的b值相同。
题解: 把a[i]减到a[i]的最小值,把b[i]减到b[i]的最小值,贪心即可。
代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 110, INF = 1e9 + 10;
LL a[N], b[N];
LL n, t;

int main() {
    cin >> t;
    while (t--) {
        cin >> n;
        LL aminv = INF, bminv = INF;
        for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]), aminv = min(aminv, a[i]);
        for (int i = 1; i <= n; ++i) scanf("%lld", &b[i]), bminv = min(bminv, b[i]);
        long long res = 0;
        for (int i = 1; i <= n; ++i) {
            res += max(a[i] - aminv, b[i] - bminv);
        }
        cout << res << endl;
    }
    
    return 0;
}

C. Boats Competition

题意: 给定t个测试样例,每个测试样例给定n个数字w[i],求把n个数字两两组合,每组的和值相同,最多能够组合成为多少组? 1 <= t <= 1000, 1 <= n <= 50, 1 <= wi <= n
题解: 本题由于规模比较小,直接枚举和值,然后枚举左端点位置,再二分枚举右端点位置即可。这样其实比较麻烦,可以枚举和值,然后枚举第一个数字即可,判断权值和s-a[i]是否存在,然后加上计数,完全不需要二分法。
代码:
懒得写了,送上二分代码

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 100;
int a[N], n, cnt[N], t;  // cnt[1]:1的数目

int main() {
    scanf("%d", &t);
    while (t--) {
        memset(cnt, 0, sizeof cnt);
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            cnt[a[i]]++;
        }
        sort(a + 1, a + 1 + n);
        int m = unique(a + 1, a + 1 + n) - a - 1;
        // cout << "m:" << m << endl;
        int res = -1;
        for (int s = 2; s <= 2 * n; ++s) {
            // cout << "cur s:" << s << endl;
            int tmp = 0;
            for (int i = 1; i <= m; ++i) {
                int l = i, r = m;
                while (l < r) {
                    int mid = (l + r) >> 1;
                    if (a[i] + a[mid] >= s) r = mid;
                    else l = mid + 1;
                } 
                if (a[l] + a[i] == s) {
                    // cout << "l: " << i << " " << "r: " << l << endl; 
                    if (a[l] == a[i] && cnt[a[l]] == 1) continue;
                    if (a[l] == a[i]) tmp += cnt[a[l]] / 2;
                    else tmp += min(cnt[a[l]], cnt[a[i]]); 
                }
            }
            res = max(res, tmp);
        }
        printf("%d\n", res);
    }
    return 0;
}

D. Binary String To Subsequences

题意: 给定T个测试样例,每个测试样例给定一个字符串,长度为n,要求把字符串中的每个字符分成多个01子序列,求最少能够划分为多少个01子序列,打印出每个字符属于哪个子序列。 ∑ n \sum_{}n n <= 2*105
题解: 只需要维护两个队列,一个队列存放结尾为0的字符串,一个队列存放结尾为1的字符串,然后每次判断当前字符s[i]是0还是1,如果是1,那么看是否能够插入结尾为0的队列中;如果是0则反之。
代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
int T, n, idx, res[N];
string s;
queue<int> q1, q0;

int main(){
	cin >> T;
	while (T--) {
		memset(res, 0, sizeof res);
		idx = 0;
		while (q1.size()) q1.pop();
		while (q0.size()) q0.pop();
		cin >> n;
		cin >> s;	
		for (int i = 0; i < s.size(); ++i) {
			if (s[i] == '0') {
				if (q1.empty()) {
					idx++;
					q0.push(idx);
					res[i] = idx;
				}
				else {
					auto t = q1.front();
					q1.pop();
					q0.push(t);
					res[i] = t;	
				}
			}
			else {
				if (q0.empty()) {
					idx++;
					q1.push(idx);
					res[i] = idx;
				}
				else {
					auto t = q0.front();
					q0.pop();
					q1.push(t);
					res[i] = t;
				}
			}
		}
		cout << idx << endl;
		for (int i = 0; i < n; ++i) printf("%d ", res[i]);
		cout << endl;
}
	return 0;
}

E1. Weights Division (easy version)

题意: 给定T个测试样例,每个测试样例给定n个点和一个S,这n个点组成一颗根为1的树。规定每次操作能够把一条边的权值w变化为floor(w / 2)。问最少需要多少次操作能够把 ∑ l e n ( r o o t , l e a v e ) \sum_{}len(root, leave) len(root,leave) <= S。leave为叶节点,root为根节点。1≤t≤2*104, 2≤n≤105;1≤S≤1016, 1≤vi,ui≤n;1≤wi≤106, ∑ n \sum_{}n n <= 105
题解: 求出每条跟到叶节点的距离,然后动态修改,这样很难处理。可以转化思路,求出每天边权值对于总答案的贡献,然后变化的适合只要改变每条边权值即可。每条边被经过的次数是固定的,只有权值是变化的。为了操作的次数最少,那么只需要按照每次操作能够减少最多的差值的按照从大到小排序即可。因此,维护一个优先队列,优先队列按照(t.w-t.w/2)*t.cnt从大到小排序即可。
代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 1e5 + 10;
int e[N * 2], w[N * 2], ne[N * 2], h[N], idx, n, m, T;
LL times[N * 2];
struct Edge {
    LL ti, wi;
    bool operator<(const Edge &e) const {
        return (wi - wi / 2) * ti < (e.wi - e.wi / 2) * e.ti;
    }
};
priority_queue<Edge, vector<Edge>, less<Edge> > q;
LL sum, S;

void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; 
}

void dfs(int u, int fa) {
    int wi = 0;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) {
            wi = w[i];
            continue;
        }
        dfs(j, u);
        times[u] += times[j];
    }
    if (!wi) return;
    if (e[h[u]] == fa && ne[h[u]] == -1) times[u] = 1;
    q.push({times[u], wi});
    sum += times[u] * wi;
    return;
}

int main(){
    cin >> T;
    while (T--) {
        while (q.size()) q.pop();
        sum = 0;
        memset(h, -1, sizeof h);
        memset(times, 0, sizeof times);
        idx = 0;
        scanf("%lld%lld", &n, &S);
        for(int i = 1, a, b, c; i <= n - 1; ++i) {
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c), add(b, a, c);
        }
        dfs(1, -1);
        
        LL res = 0;
        while (sum > S) {
            auto t = q.top();
            q.pop();
            sum -= t.ti * t.wi;
            t.wi /= 2;
            sum += t.ti * t.wi;
            res ++;
            q.push(t);
        }
        printf("%lld\n", res);
    }
    return 0;
}

E2. Weights Division (hard version)

题意: 给定大小为n的树,树有边权w和花费c,c=1或者c=2。点1是树的根,定义树的权值为所有叶子到根的距离和。给定S,一次操作:你可以将一条边的边权除以2,花费为该边权的c。问最少花费多少代价,使得树的权值<=S。数据范围:n<=1e5

题解: 使用堆分别维护花费为1和花费为2的边,然后枚举1,二分2,就能保证所有情况被遍历。需要对堆维护前缀和。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
int const N = 1e5 + 10;
int e[N * 2], w[N * 2], ne[N * 2], h[N], idx, n, m, T, c[N * 2];
LL times[N * 2];
struct Edge {
    LL ti, wi;
    bool operator<(const Edge &e) const {
        return (wi - wi / 2) * ti < (e.wi - e.wi / 2) * e.ti;
    }
};
LL suma[N * 100], sumb[N * 100];
priority_queue<Edge, vector<Edge>, less<Edge> > qa, qb;
LL sum, S;

void add(int a, int b, int ci, int d){
    e[idx] = b, w[idx] = ci, c[idx] = d, ne[idx] = h[a], h[a] = idx++; 
}

void dfs(int u, int fa) {
    int wi = 0, ci = 0;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) {
            wi = w[i];
            ci = c[i];
            continue;
        }
        dfs(j, u);
        times[u] += times[j];
    }
    if (!wi) return;
    if (e[h[u]] == fa && ne[h[u]] == -1) times[u] = 1;
    if (ci == 1) qa.push({times[u], wi});
    else qb.push({times[u], wi});
    sum += times[u] * wi;
    return;
}

int main(){
    cin >> T;
    while (T--) {
        sum = 0;
        memset(h, -1, sizeof h);
        memset(times, 0, sizeof times);
        idx = 0;
        scanf("%d%lld", &n, &S);
        while(qa.size()) qa.pop();
        while(qb.size()) qb.pop();
        for (int i = 0; i <= n; ++i) suma[i] = sumb[i] = 0;
        for(int i = 1, a, b, c, d; i <= n - 1; ++i) {
            scanf("%d%d%d%d", &a, &b, &c, &d);
            add(a, b, c, d), add(b, a, c, d);
        }
        dfs(1, -1);

        int c1 = 0, c2 = 0;
        while(qa.size()) {
            auto t = qa.top();
            qa.pop();
            if (!t.wi) break;
            c1++;
            suma[c1] = suma[c1 - 1] + (t.wi - t.wi / 2) * t.ti;
            if (sum - suma[c1] < S) break;
            qa.push({t.ti, t.wi / 2});
        }

        while(qb.size()) {
            auto t = qb.top();
            qb.pop();
            if (!t.wi) break;
            c2++;
            sumb[c2] = sumb[c2 - 1] + (t.wi - t.wi / 2) * t.ti;
            if (sum - sumb[c2] < S) break;
            qb.push({t.ti, t.wi / 2});
        }
        if (sum <= S) {
            cout << 0 << endl;
            continue;
        }
        LL res = 1e18;
        for (int i = 0; i <= c1; ++i) {
            int pos = lower_bound(sumb, sumb + 1 + c2, sum - S - suma[i]) - sumb;
            if (pos != c2 + 1) res = min(res, i * 1ll + 2ll * pos);
        }
        printf("%lld\n", res);
    }
    return 0;
}

F. Yet Another Segments Subset

题意: 给出n个区间,要求最多的一个区间集合,满足集合内任意两个区间,要么是互不相交,要么是完全包含。求最多能够选择出多少个区间。

题解: dp。

状态定义: f [ i ] [ j ] : 区 间 [ i , j ] 的 最 大 价 值 f[i][j]: 区间[i, j]的最大价值 f[i][j]:[i,j]

状态转移:$f[i][j] = f[i][k] + f[k + 1][j] $

优化:权值较大,需要离散化; O ( n 3 ) O(n^3) O(n3)的算法超时,需要优化为 O ( n 2 ) O(n^2) O(n2):如果当前存在某条线段和区间边界有交点,才需要考虑切割。

**代码: **

#include <bits/stdc++.h>

using namespace std;

int const MAXN = 6e3 + 10;
int l[3010], r[3010];
int n, m, T, f[MAXN][MAXN];
bool vis[MAXN][MAXN];
vector<int> vec[MAXN], tmp;

inline int get(int x) { return lower_bound(tmp.begin(), tmp.end(), x) - tmp.begin() + 1; }

int main() {
    cin >> T;
    while (T--) {
        cin >> n;
        tmp.clear();
        for (int i = 1; i <= n; i++)
            cin >> l[i] >> r[i], tmp.push_back(l[i]), tmp.push_back(r[i]);
        sort(tmp.begin(), tmp.end()), tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());
        m = tmp.size();
        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= m; j++)
                vis[i][j] = f[i][j] = 0;
        for (int i = 1; i <= m; i++)
            vec[i].clear();
        for (int i = 1; i <= n; i++)
            vis[l[i] = get(l[i])][r[i] = get(r[i])] = 1;
        for (int i = 1; i <= n; i++)
            vec[l[i]].push_back(r[i]);
        for (int L = 1; L <= m; L++) {
            for (int i = 1; i + L - 1 <= m; i++) {
                int j = i + L - 1;
                f[i][j] = max(f[i + 1][j], f[i][j - 1]);
                for (int k : vec[i])
                    if (k <= j)
                        f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j]);
                f[i][j] += vis[i][j];
            }
        }
        cout << f[1][m] << '\n';
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值