Codeforces Round 967 (Div. 2)

A. Make All Equal
题意:给定n个数每次可以选2个相邻的数,并且前面的数不能比后面的数大,并且删除其中的一个。这个数组是循环数组,最后一个数是第一个数的前一个数。问最少操作多少次,可以让剩下的数全都相等。

思路:红黑树+一次遍历,记录出现次数最多的数,剩下的数全部删掉即可。

总结:看了一下示例很快就A了,但是不太理解这里要求选数的时候 前面的数不能比后面的数大 这个条件有什么影响。 思考一下,假设有相邻的i,j。i出现的次数最多,i > j,则不能直接删除j,需要考虑j和j + 1,而j + 1和j的大小关系不确定,如果j + 1是要保留的数,那么可以直接删掉j,如果不是要保留的数,那么不在乎大小关系,随便删一个即可。所以到了最后,j的后面的数一定是i,另一种情况当i <= j,直接删掉j。

void solve(){
	int n;
	cin >> n;

	map<int, int> mapp;
	int ans = n + 1;
	for (int i = 0; i < n; ++i) {
		int t;
		cin >> t; 
		mapp[t] ++;
		ans = min(ans, n - mapp[t]);
	}

	cout << ans << '\n';
}

B. Generate Permutation

题意:给定两个指针l和r,初始时分别在数组首末。每次操作可以将指针往对应的方向移动1,或者在当前的下标上将初始值-1改为一个数组未出现最小的正数,或者将指针回溯到起始点。 问是否有一个permutation,不管用左指针构造还是右指针构造,他们回溯到起始点的操作数是相等的。

思路:偶数时无解。 奇数时把1放到数组最中间,然后维护两个指针,一个往右加数,一个往左加数,并在每次移动一个距离后将两个指针的值互换,这样保证左右两个指针在构造时,回溯次数相等。
比如 n = 5
-1 -1 1 -1 -1
-1 3 1 2 -1
5 3 1 2 4
左指针构造要回溯2次,右指针也是两次。

总结:先看测试示例,大概能看出来偶数长度无解。考虑奇数长度,为了让左右指针的回溯次数相等,肯定是中间有一个中位数,剩下的数往两边补,补的过程中要保证左右指针回溯次数相等。而为了保证增加数时,保证指针的回溯次数相等,那肯定是一次考虑增加两个数,如果只增加一个数,必然有一个指针不用回溯。 有了这个思路,直接拿n = 3和n = 5试一下,就知道如何写代码了。

void solve(){
	int n;
	cin >> n;

	if (n % 2 == 0) {
		cout << "-1\n";
	}
	else {
		int m = n / 2;
		int cnt = 1;
		int i = m + 1, j = m - 1;
		vector<int> ans(n);
		ans[m] = 1;
		while (j >= 0) {
			if (j & 1) {
				ans[i] = ++cnt;
				ans[j] = ++cnt;
			}
			else {
				ans[j] = ++cnt;
				ans[i] = ++cnt;
			}
			i ++;
			j --;
		}
		for (int i = 0; i < n; ++i) {
			cout << ans[i] << " \n"[i == n - 1];
		}
	}
}

赛时很快A完2道题,然后开始坐牢,交互题是真不想做啊,看了一遍题目,就没继续了。

D题看错了,把数值范围看成了1~9(我寻思最大最小字典序数字都不超过10啊),然后调试了半个多小时,发现读错题了
直接关机睡觉。

哎 又要掉大分了

D. Longest Max Min Subsequence

题意:给定n个数,求最长的包含不相同元素的满足特定条件的子序列。特定条件:最长子序列中的奇数位*-1后,字典序最小

思路:维护每个数出现的次数一个一个ans数组,然后依次遍历n个数。当遍历到第i个数时,如果ans为空,则加入数到ans,并且标记该数已在ans中出现过,否则循环的考虑ans中的倒数第一个数和倒数第二个数,看当前数在替代某一个数的情况下,是否可以不改变最终ans的长度(通过查看后续数字中是否出现过ans中的这两个数来判定),以及是否会让字典序更小(通过判定奇数偶数位来决定是否当前数字更优,奇数位应该找大数,偶数位找小数),如果更优,则pop出对应的数,否则加当前数加入到数组末尾。

总结:赛时看错题了,以为只有19,于是维护了每个位置下一个19数字出现的次数,并判断当前位是奇数还是偶数位,然后在不改变最终子序列长度的情况下,选个最优的数,结果在调试的时候发现。。范围搞错了。
这个题目的类型,应该属于最长子序列的变种题。最长子序列的经典问题包括最长非递增,非递减,递增递减子序列,要用特有的算法去求解。
对于当前的题目,也可以归结为一种特别的算法,最长不相同元素子序列(好像直接set去重就行,太简单了),那就加个限制,最长递增不相同元素子序列(差不多可以认为是个模板题吧)。

reserve后代码评测使用内存从1400k降到了0,神奇。

void solve(){
    int n;
    cin >> n;
    
    vector<int> a(n);
    vector<int> ans;
    ans.reserve(n);
    for (auto& x : a) {
        cin >> x;
    }

    int s = *max_element(a.begin(), a.end());
    vector<int> cnt(s + 1);
    vector<bool> has(s + 1);
    for (const auto&x : a) {
        cnt[x] ++;
    }

    auto better = [](int x, int y, bool odd) {
        return odd ? x > y : x < y;
    };
    for (int i = 0; i < n; ++i) {
        cnt[a[i]] --;
        if (ans.empty()) {
            ans.push_back(a[i]);
            has[a[i]] = true;
            continue;
        }
        if (has[a[i]]) {
            continue;
        }
        while (!ans.empty()) {
            int m = (int)ans.size();
            if (cnt[ans[m - 1]] && better(a[i], ans[m - 1], m & 1)) {
                has[ans[m - 1]] = false;
                ans.pop_back();
            }
            else if (m > 1 && cnt[ans[m - 2]] && cnt[ans[m - 1]] && better(a[i], ans[m - 2], (m - 1) & 1)) {
                has[ans[m - 2]] = false;
                has[ans[m - 1]] = false;
                ans.pop_back();
                ans.pop_back();
            }
            else {
                break;
            }
        }
        ans.push_back(a[i]);
        has[a[i]] = true;
    }
    cout << ans.size() << '\n';
    for (int i = 0; i < ans.size(); ++i) {
        cout << ans[i] << " \n"[i == ans.size() - 1];
    }
}
posted @   _Yxc  阅读(369)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示