8.22CF训练题解

这几天比较忙可能组织不了视频直播讲解,希望同学们自己抽时间订正,有什么困难可以直接联系我
初赛的题目一定要去认真手算,用编译器做题不过是自欺欺人的把戏

A题题解
合法密码只能是数字+字母拼接分为两块组成
考虑到小写字母的ASCII码都是大于数字字符的
所以直接判断题目给定的串是不是非递减即可

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

string solve() {
    int n;
    string s;
    cin >> n >> s;
    for(int i = 0; i + 1 < n; i ++) {
        if(s[i] > s[i + 1]) return "NO";
    }
    return "YES";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int T;
    cin >> T;
    while(T --)
        cout << solve() << "\n";
}

B题题解
简单分类讨论题 ,A B数组只相差一个元素,所以操作可以分为两块
1: 把前N个元素变为一样
2:复制一个元素

显然如果第N+1号元素值能在操作1过程中产生的话,操作2就不需要额外修改
否则只能取操作1过程中出现的最接近 第N+1号元素的值再去做加减法

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

using i64 = long long;

i64 solve() {
    int n, x;
    cin >> n;
    vector<int> a(n), b(n);
    for(int i = 0; i < n; i ++) cin >> a[i];
    for(int i = 0; i < n; i ++) cin >> b[i];
    cin >> x;

    i64 ans = 1;
    int mind = INT_MAX;
    for(int i = 0; i < n; i ++) {
        ans += abs(a[i] - b[i]);
        if(x >= min(a[i], b[i]) && x <= max(a[i], b[i])) mind = 0;
        else mind = min({mind, abs(x - a[i]), abs(x - b[i])});
    }
    return ans + mind;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int T;
    cin >> T;
    while(T --)
        cout << solve() << "\n";
}

C题题解
枚举第 i i i个人,那么他前面的人放哪个组是确定的,二分往后面查找什么位置会让编程组或者测试组放满,放满以后只能全部丢到另一个组去了

#include<bits/stdc++.h>
#define ll long long
#define pii pair<long long,long long>
#define pb push_back
#define eb emplace_back 
#define debug(x) cerr<<x<<'\n'; 
using namespace std;
void solve()
{
	int n,m;cin>>n>>m;
	vector<int>a(n+2+m),b(n+2+m);
	for(int i=1;i<=n+m+1;i++)
		cin>>a[i];
	for(int i=1;i<=n+m+1;i++)
		cin>>b[i];
	int N = n+m+1; 
	vector<ll>preb(N+1),prea(N+1),sb(N+1),sa(N+1),sufa(N+2),sufb(N+2);
	for(int i=1;i<=N;i++){
		sa[i]=sa[i-1]+(a[i]>b[i]?a[i]:0);
		sb[i]=sb[i-1]+(b[i]>a[i]?b[i]:0);
		prea[i]=prea[i-1]+(a[i]>b[i]);
		preb[i]=preb[i-1]+(a[i]<b[i]);
	} 
	for(int i=N;i;i--)
		sufa[i]=sufa[i+1]+a[i],sufb[i]=sufb[i+1]+b[i];
	int nown=0,nowm=0;ll sum=0;
	for(int i=1;i<=N;i++) {
		int l1=i,r1=N;
		while(l1<r1) {
			int mid=l1+r1>>1;
			if(nown+prea[mid]-prea[i]>=n) r1=mid;
			else l1=mid+1;
 		}
 		if(nown+prea[l1]-prea[i]<n) l1++;
 		int l2=i,r2=N;
		while(l2<r2) {
			int mid=l2+r2>>1;
			if(nowm+preb[mid]-preb[i]>=m) r2=mid;
			else l2=mid+1;
 		}
 		int l=min(l1,l2);
 		ll ans = sum+sa[l]-sa[i]+sb[l]-sb[i];
 		if(l1<l2) ans+=sufb[l+1];
 		else ans+=sufa[l+1];
 		cout<<ans<<' ';
 		if(a[i]>b[i]) {
 			if(nown<n) nown++,sum+=a[i];
 			else nowm++,sum+=b[i];
		 }else  {
		 	if(nowm<m) nowm++,sum+=b[i];
		 	else nown++,sum+=a[i];
		 }
	}
	cout<<'\n';
}
main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cerr.tie(0);
	int _ = 1;cin>>_;
	while(_--) solve();
}

D题题解
简单讨论题,要么2个人互为好朋友,要么就是三元组
如果两个人互相是好友,那只用两人,否则三个人

#include<bits/stdc++.h>
using namespace std;
int A[55];
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%d",&A[i]);
		}
		int ok=1;
		for(int i=1;i<=n;i++){
			if(A[A[i]]==i){
				ok--;
				break;
			}
		}
		printf("%d\n",2+ok);
	}
	return 0;
}

E题题解
最优的方式其实是一个一个移动0

#include<bits/stdc++.h>
using namespace std;
char s[200005];
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int cnt=0;
		long long int ans=0;
		scanf("%s",s+1);
		int n=strlen(s+1);
		for(int i=1;i<=n;i++){
			if(s[i]=='1')cnt++;
			else{
				if(cnt!=0)ans=ans+cnt+1;
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

F题题解
考虑 d p dp dp,对于前 i i i个数操作 0 到 k 0到k 0k次得到的最优解
如果一段区间被操作了,那么最终的数组一定是这个区间中的最小值
操作的区间与区间之间不会有交叉部分,那么本题就基本上推导出来了,线性的
因为 k k k很小,每次可以暴力转移附近的数字

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n, k;
        cin >> n >> k;
        vector<LL> a(n + 1), s(n + 1);
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        vector<vector<LL>> dp(n + 1, vector<LL>(k + 1, 1e16));
        dp[0][0] = 0;
        for (int i = 1; i <= n; i++) {
            for (int x = 0; x <= k; x++) {
                for (int l = i; l >= i - x && l >= 1; l--) {
                    int r = l + x;
                    if (r > n)
                        continue;
                    for (int t = 0; t <= k - x; t++) {
                        dp[r][t + x] = min(dp[r][t + x], dp[l - 1][t] + (a[i] * (x + 1)));
                    }
                }
            }
        }
        LL ans = 1e16;
        for (int i = 0; i <= k; i++) {
            ans = min(dp[n][i], ans);
        }
        cout << ans << endl;
    }
    return 0;
}

临时再加一道G题题解吧 ,感觉大家也是可以做的,只不过对于大家来说思维比较难

我们按照 b i bi bi的值从大到小排序。枚举边界 i i i,从 1 到 i 1到i 1i的部分选 k k k个, i + 1 到 n i + 1 到 n i+1n的部分任意选择。 1 到 i 1到i 1i部分选的 k k k个一定是这次选择里最大的 k k k b b b,这部分选的某个位置对答案的贡献是 − a i -ai ai 负的 。将问题转化成选择 k k k个最小的 a i ai ai,因此维护单调队列。
考虑到 i + 1 到 n i+1到n i+1n的部分任意选,由于最大的 k k k b b b已经选了,这部分选的某个位置对答案的贡献是 b i − a i bi-ai biai 因为这部分是任意选的,为了让答案更大,对答案的贡献就是 b i − a i bi-ai biai 结果为正值的和。

#include<bits/stdc++.h>
using namespace std;
struct pe{
	int a;
	int b;
}A[200005];
bool cmp(pe x,pe y){
//	if(x.b==y.b)return (x.b-x.a >y.b-y.a);
	return x.b<y.b;
}
//            b-1    b b b  b
//            a      a 
priority_queue<int>q;
long long int sum[200005];
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n,k;
		scanf("%d%d",&n,&k);
		for(int i=1;i<=n;i++){
			scanf("%d",&A[i].a);
		}
		for(int i=1;i<=n;i++){
			scanf("%d",&A[i].b);
		}
		sort(A+1,A+1+n,cmp);
		for(int i=1;i<=n;i++){
			if(A[i].a<A[i].b){
				sum[i]=sum[i-1]+(A[i].b-A[i].a);
			}
			else{
				sum[i]=sum[i-1];
			}
		}
		if(k==0){
			printf("%lld\n",sum[n]);
			continue;
		}
		//按照B排序  枚举划分点
	//	拿小于等于k  全部亏  
	//	拿大于K个  最大的K个Bi全部亏  剩下的全部拿 ai<bi的  
	//  在最大的B里面选择最小的K个ai  亏  剩下的算利润  
	//	M个里面选K个  亏的最少的 ()      i   (利润) 
		
	//	K个 
	//4	K+1个丢掉最大的ai     
		long long int s=0;
		long long int ans=0;//啥也不买  买啥亏啥 
		for(int i=n;i>=1;i--){
			if(q.size()<k){
				q.push(A[i].a);
				s=s-A[i].a; //亏  
				continue;
			}
			else{
				ans=max(ans,s+sum[i]);
				q.push(A[i].a);
				if(A[i].a!=q.top()){
				s=s-A[i].a+q.top();
				ans=max(ans,s+sum[i-1]);
				}
				else{
				ans=max(ans,s+sum[i]);	
				}
				q.pop();//扔掉当前最大的Ai  反正也是要亏  就少亏一点
			}
		} 
		printf("%lld\n",ans);
		while(!q.empty())q.pop();
	}
	return 0;
}  




  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值