cf908(div2)题解(补题)

第一次akdiv2,赛后ak怎么不算是ak呢

比赛链接cf908div2

A

这题是个骗人题,整个比赛会停下来就是一个人赢够了回合数,那么在谁这停下来就是谁赢了整个比赛,不用管每回合赢得规则。

#include<iostream>
using namespace std;
#include<string>
int main()
{
	int t;
	cin >> t;

	while (t--)
	{
		int n;
		cin >> n;
		string s;
		cin >> s;

		int A = 0, B = 0;
		for (int i = 0; i < s.size(); i++)
		{
			if (s[i] == 'A')
			{
				A++;
			}
			else
			{
				B++;
			}
		}
		if (s[s.size() - 1] == 'A')cout << "A" << endl;
		else cout << "B" << endl;

	}
	return 0;

}

B

先从最简单的情况考虑,或者说分析一下给的三个条件必须正好满足两个是什么意思
如果序列为一个数a重复三次,那么要满足任意两个条件,都不可避免的同时满足了三个条件
那么自然想到,要将两个条件分配给两个数,让他们分别去实现其中一个条件,比如,有两个5,两个6,那么就可以给两个5分别赋值为1,2,给两个6分别赋值为1 3
那么,我们可以先明确不满足的情况是这样的:出现次数大于等于2的数字小于两个。
而如果出现次数大于等于2的数字大于两个,我们就可以任意找两个数字,找到他们的两次出现位置,分别赋值为1,2,和1,3,然后其他所有位置都是1,就一定满足。

#include<iostream>
#include<unordered_map>
#include<queue>
using namespace std;
const int maxn = 100020;
#include<vector>

int main()
{
	int t;
	cin >> t;

	while (t--)
	{
		int a[maxn] = { 0 };
		int b[maxn] = { 0 };
		
		int n;
		cin >> n;
		
		unordered_map<int, int>mp;                //记录每个数出现了多少次
		int count_2 = 0;                          //记录有多少个数出现次数大于等于2
		unordered_map<int, int>asdf;
		for (int i = 1; i <= n; i++)
		{
			cin >> a[i];
			mp[a[i]]++;
			
		}
		for (int i = 1; i <= n; i++)
		{
			if (mp[a[i]] >= 2&&asdf.count(a[i])==0)
			{
				count_2++;
			
			}
			asdf[a[i]]++;
		}

		if (count_2 < 2)
		{
			cout << -1 << endl;
		}
		else
		{
			int firstnum = -1, secondnum = -1;
			int index = -1;
			for (int i = 1; i <= n; i++)
			{
				if (mp[a[i]] >= 2)
				{
					firstnum = a[i];
					index = i;
					break;
				}
			}
			for (int i = index + 1; i <= n; i++)
			{
				if (mp[a[i]] >= 2&&a[i]!=firstnum)
				{
					secondnum = a[i];
					break;
				}
			}

			int countfirst = 0, countsecond = 0;

			for (int i = 1; i <= n; i++)
			{
				if (a[i] == firstnum)
				{
					countfirst++;
					b[i] = countfirst;
					if (countfirst == 2)
					{
						break;
					}
				}
			}
			for (int i = 1; i <= n; i++)
			{
				if (a[i] == secondnum)
				{
					countsecond++;
					b[i] = countsecond;
					if (countsecond == 2)
					{
						b[i] = 3;
						break;
					}
				}
			}

			for (int i = 1; i <= n; i++)
			{
				if (b[i] == 0)
				{
					b[i] = 1;
				}

			}
			for (int i = 1; i <= n; i++)
			{
				cout << b[i] << " ";

			}
			cout << endl;
		}
		

		
	}

	return 0;

}

C

我在赛后过题挑战中取得了比赛一结束立刻就通过的好成绩,你也来试试吧~

这题手动模拟几次可以发现,如果给的那个最终序列最后一个位置是x,那么它的上一个序列其实就是这个序列的后x位移动到序列前面,给定最终序列,那么如此反向操作k次,结果是唯一的。
如果中间发现这个最后一个数x>n,那么就说明不可能。
如果我们想模拟这个反向操作,可以把序列复制很多次,也可以用模运算,具体见代码:

#include<iostream>
using namespace std;
const int maxn = 200200;
#include<unordered_map>
#include<string>
#include<cstring>
long long a[maxn];
int main()
{
	int t;
	cin >> t;
	int tt = t;
	while (t--)
	{
		memset(a, 0, sizeof a);
		long long n, k;
		cin >> n >> k;
		
		for (int i = 0; i <n; i++)
		{
			cin >> a[i];
		}

		int flag = 1;
	    long long count = 0;
		unordered_map<long long, long long >num;
		for (long long i = n - 1; count <k;i=((i-a[i])+n)%n)           //count<k而不是<=k,因为要求是正好k次
		{
			if (a[i]>n)
			{
				
				flag = 0;
				break;
			}
			count++;
			if (num.count(i) != 0)                                     //遇到了重复下标,说明进入一个循环的过程了,就一定可以
			{
				flag = 1;
				break;
			}
			num[i]++;
		}
		if (flag == 1)cout << "Yes" << endl;
		else if(flag==0)cout << "No" << endl;

	}

	return 0;
}

分割线,赛时就想出来了这三道题,C题还因为<=k这个愚蠢bug没调出来,后面是补题,待更新

D

首先,最长上升子序列的长度一定不会变小,因为无论怎么加,原本的序列相对位置不变,所以至少可以取和原本一样的最长上升子序列
写成式子就是:LIS(C)>=LIS(A)
然后是LIS(C)<=LIS(A)+1,因为将b序列从大到小插入到A序列中的某一个位置,b序列对于A的LIS的增长贡献不会超过1,因为新加入的这些元素两两不能位于同一个上升子序列,所以说这样加入的B序列里面最多有一个数会使得A的某个上升子序列加1
下面考虑是否可以让LIS(C)==LIS(A),是可以的,和刚才的考虑类似,即按照不能位于同一上升子序列来考虑,假设只插入一个数x,那么要么这个数小于所有的数,把它放到最后,就不会让LIS增加,要么这个数不是最小的,那么可以找到一个比他小的数y,把x插入到y的前一个位置,这样x和y不可能出现在同一个出现在同一个上升子序列中,那么插入之后所有的包含x的上升子序列都可以把x用y来代替,即x的插入不会改变上升子序列的长度。那么对于B序列的所有元素都这么做,为了方便并且防止b序列完整进入A序列自身产生上升子序列,我们让每个数插入到第一个比他小的数前面,那么最终的结果就是把B倒序排序,然后和A序列进行一种类似归并排序的过程(C++甚至有merge函数可以完成)
代码如下:

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int t;
    cin >> t;

    while (t--)
    {
        int n, m;
        cin >> n >> m;

        vector<int> a(n), b(m);

        for (int i = 0; i < n; i++)
            cin >> a[i];
        for (int i = 0; i < m; i++)
            cin >> b[i];

        sort(b.begin(), b.end(), greater<int>());
        int i = 0, j = 0;
        vector<int> c(n + m);
        int k = 0;
        while (i < n && j < m)
        {
            if (a[i] > b[j])
            {
                c[k] = a[i++];
                k++;
            }
            else
            {
                c[k] = b[j++];
                k++;
            }
        }

        while (i < n)
        {
            c[k++] = a[i++];
        }
        while (j < m)
        {
            c[k++] = b[j++];
        }

        for (int jk = 0; jk < n + m; jk++)
        {
            cout << c[jk] << " ";
        }
        cout << endl;
    }

    return 0;
}

E

应该算是一种贪心做法,假定最后的集合大小是len,从题意中可以想到肯定有len的集合要少取,没有len的集合要多取,对于每个len可以用这样的一个大致的策略求得一个值,然后取最小。那么len一共有多少个呢,定义sumn,suml,sumr为n,l,r的求和,那么lne的范围应该是suml~sumr,也就是一共有sumr-suml+1种len,而X集合里面可能出现的元素种类只有sumn,如果len的种类数多余集合X中的可能的元素种类数,那么根据抽屉原理,一定可以在len的若干个可能取值中找到至少一个不会出现在X中的值,那么这种情况答案就是0,也就是说,如果sumr-suml+1>sumn,那么答案就是0
那如果sumr-suml+1<=sumn,就得按照贪心策略来取,首先是,对于不含len的集合,必须取满上限r,然后对于含有len的集合,我们可以根据题给的c数组确定len在这个集合有多少个,然后能够得出在这个集合中有多少个‘非len’,如果‘非len’的个数很少,甚至不足以选够l,那么我们为了满足l这个限制就不得不取l-not_len个len,如果‘非len’的个数足够取l,那么我么尽可能多的取‘非len’.这样取完之后,看此时填充到X中的个数,如果不够len,就意味着必须再从那些‘非len’不够的集合继续取一些len
代码如下

#include <bits/stdc++.h>
using namespace std;
#define int long long // 数据很大,会爆int
signed main()
{
    int t;
    cin >> t;

    while (t--)
    {
        int m;
        cin >> m;
        long long sumn = 0, suml = 0, sumr = 0;
        vector<int> n(m), l(m), r(m);
        vector<vector<int>> a(m);
        vector<vector<int>> c(m);
        vector<int> sumc(m); // 记录每个集合的元素总个数
        for (int i = 0; i < m; i++)
        {

            cin >> n[i] >> l[i] >> r[i];
            sumn += n[i];
            suml += l[i];
            sumr += r[i];

            a[i].resize(n[i]); // 第i个集合的大小为n[i]
            c[i].resize(n[i]);
            for (int j = 0; j < n[i]; j++)
            {
                cin >> a[i][j];
            }
            for (int j = 0; j < n[i]; j++)
            {
                cin >> c[i][j];
                sumc[i] += c[i][j];
            }
        }

        if (sumr - suml > sumn)
        {
            cout << 0 << endl;
            /*
                X集合的元素个数的范围是suml~sumr,它有sumr-suml+1种取值,
                而所有集合的元素种类数最多sumn种
                如果sumr-suml+1>sumn
                那么sumr-suml+1个数字里面一定至少有一个数是sumn种数里没有的
                抽屉原理
            */
            continue;
        }

        else
        {

            unordered_map<long long, long long> sum_have;            // sum_have[asdf]记录含有asdf这个值的集合如果全都选满r个,共选了多少个
            unordered_map<long long, vector<pair<int, int>>> indexs; // indexs记录的是某个值在所有集合中出现了多少次,位置在哪

            for (int i = 0; i < m; i++)
            {
                for (int j = 0; j < n[i]; j++)
                {
                    sum_have[a[i][j]] += r[i];
                    indexs[a[i][j]].push_back({i, j});
                }
            }
            // 选的策略是,含有len的,如果非len少于l就按照l选,如果非len多就尽可能多选非len,不含len的按照r来选,如果这样选之后不到len,那么只能再往里加若干个len
            long long ans = (int)2e18;
            for (long long len = suml; len <= sumr; len++)
            {
                long long xsize = 0;              // 表示的是集合X的元素个数
                long long have_to_choose_len = 0; // 不得不选的len的个数,也就是说某个集合中非len值太少,甚至不够l

                xsize = sumr - sum_have[len]; // 集合X的个数初始化为:所有不含len的集合都按照r选
                // 所有集合都选满r的值减去这个sumr_a[len]就是所有不含len的集合全部选满r的值
                for (auto &[i, j] : indexs[len]) // 便利len出现的集合
                {
                    int not_len = sumc[i] - c[i][j]; // 第i个结合中,所有元素的个数减去len在这里面的个数就是不是len的个数

                    if (not_len < l[i]) // 如果这个集合中非len元素甚至不够l,那么不得不选一些len
                    {
                        xsize += l[i];
                        have_to_choose_len += l[i] - not_len;
                    }
                    else
                    {
                        xsize += min(not_len, r[i]); // 如果非len值,尽可能多选非len值
                    }
                }
                ans = min(ans, have_to_choose_len + max(0LL, len - xsize));
                // 对于每个len的答案就是必须选的值为len的答案(某个集合不等于len的值太少了,要选满至少l个不得不在这个集合选值为len的)
                // 加上当前已经按照这个策略选的xsize和目标len之间的差值,如果当前的xsize还不够len,那么那些含有len的集合中不得不在选了l个之后继续选一些len
            }
            cout << ans << endl;
        }
    }
}
posted @   NOTHINGBUTNOTHING  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
点击右上角即可分享
微信分享提示