上海市计算机学会竞赛平台2023年3月月赛乙组

T1 卡片游戏

题目描述
小爱拿到了n张卡片,每张卡片的正反面均写有一个数字,其中第ii张卡片的正面的数字为 a i a_i ai,反面的数字为 b i b_i bi
他想把每张卡片选取合适的一面后,放入下列算式中,卡片之间顺序可以交换,但每张卡片只能用一次。
在这里插入图片描述
请问,小爱通过以上操作,能得到的最大值是多少?
输入格式
第一行,一个正整数n
接下来n行,每行两个正整数 a i a_i ai, b i b_i bi
输出格式
输出共一行,一个整数,表示填入算式后,所能获得的最大值
数据范围
对于 30% 的数据,1≤n≤10
对于 60% 的数据,1≤n≤103
对于 100% 的数据,1≤n≤105,−104 a i a_i ai ≤104且数据保证 n 是偶数
样例数据
输入:
6
10 -12
-17 -7
-7 5
-17 2
-4 3
-10 -8
输出:
62
说明:
10 - (-17) + 5 -(-17) + 3 -(-10) = 62

该题第一想法是爆搜,逐个选择卡片正反,初测30分。

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

int ans=-1,n,a[100010],b[100010];

void dfs(int zheng,int fu,int s,int pos){
	if(pos>n*2){
		ans=max(ans,s);
		return;
	}
	if(zheng!=n){
		dfs(zheng+1,fu,s+a[pos],pos+1);
		dfs(zheng+1,fu,s+b[pos],pos+1);
	}
	if(fu!=n){
		dfs(zheng,fu+1,s-a[pos],pos+1);
		dfs(zheng,fu+1,s-b[pos],pos+1);
	}
	
}

signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;++i)
		scanf("%lld%lld",&a[i],&b[i]);
	n/=2;
	dfs(0,0,0,1);
	printf("%lld\n",ans);
	return 0;
}

题意可以改为:将n张卡片加起来,再改为减掉一半。
1.将n张卡片加起来,一张卡片只能用一次,肯定是

+=max(a[i],b[i])

2.减去一半。每减一个肯定要减小的。上面已经把大的摆在正面了,那么减去的肯定是是反面,每减一张,总量都会减少 a [ i ] + b [ i ] a[i]+b[i] a[i]+b[i],为了最大要减去 a [ i ] + b [ i ] a[i]+b[i] a[i]+b[i]最小的卡片。

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

int n,b[100010],a[100010],m[100010],ans;

signed main(){
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>a[i]>>b[i];
		m[i]=a[i]+b[i];
		ans+=max(a[i],b[i]);
	}
	sort(m+1,m+1+n);
	for(int i=1;i<=n/2;++i)
		ans-=m[i];
	cout<<ans<<endl;
}

T2 邀请的方案数

题目描述
你非常仰慕高年级的小爱学长和小艾学姐,于是他想要邀请小爱学长和小艾学姐同他出去游玩,但是小爱和小艾都不认识你,他们不会同不认识的人出去游玩。因此你决定在他所认识的n名同学中选一些人一并邀请,这样只要这些人中有人认识小爱和小艾,他就可以将学长和学姐请来。
这n名同学中,其中a名同学既不和小爱学长相识也不和小艾学姐相识,b名同学只和小爱相识,c名同学只和小艾相识,d名同学既和小爱也和小艾相识。
现在你想知道有多少种不同的选择(从n名同学中任选一些人,可以只选一个)使得能够邀请到小爱和小艾。只要邀请的人有一个不同就视为不同的选择。由于方案数可能非常地大,所以输出答案对998244353取模。
输入格式
输入的第一行,为一个正整数T,表示一共有T组测试数据。
接下来T行,每一行包括四个整数,分别为a,b,c,d,含义见题面。
输出格式
输出T行,每行一个整数,表示小C可以选择的方案数。
数据范围
对于 30% 的数据,1≤a+b+c+d≤10
对于 60% 的数据,0≤a,b,c,d≤1000
对于 100% 的数据,1≤T≤1,000;0≤a,b,c,d≤10,000,000;1≤a+b+c+d
样例数据
输入:
3
1 1 1 1
2 2 2 2
3 4 5 0
输出:
10
228
3720

邀请方案大致分为两种:
1.邀请了d中的人,其余人可邀请可不邀请。
2.邀请了c和b,其余人可邀请可不邀请。
但是1 2有重复部分----就是邀请b,c,d其余人可邀请可不邀请的部分。所以改为:
邀请方案大致分为两种:
1.邀请了d中的人,其余人可邀请可不邀请。
2.邀请了c和b,a可邀请可不邀请。
选d中的人有几种可能?
每个人都有两种情况,选与不选。那么共有2d种方案,但是,必须要保证至少选一个,所以要减去一个都不邀请的空方案。其余的也是如此。
但是有些情况可以为空,如:a可选可不选,所以不需要-1

#include <bits/stdc++.h>
#define int long long
#define mod 998244353
using namespace std;

int f(int step,int a){
	if(step==0)return 1;
	if(step==1)return a;
	int i=f(step/2,a)%mod;
	return step%2?i*i%mod*a%mod:i*i%mod;
}

signed main(){
	int T;
	cin>>T;
	while(T--){
		int b,a,c,d,ans=0;
		cin>>a>>b>>c>>d;
		ans+=((f(d,2)-1+mod)%mod*f(c,2)%mod*f(b,2)%mod*f(a,2)%mod+(f(b,2)-1+mod)*(f(c,2)-1+mod)%mod*f(a,2)%mod)%mod;
		cout<<ans<<endl;
	}
	return 0;
}

T3 神秘的信号

题目描述
现在有一种神秘的机器,它每次只会发出一种确定的信号aa,aa是一个字符串,且只可能包含“L”,“C“,“R”,“&”,“O”,“I”这六种字符,并且每个字符至多出现一次,这种机器可以不断重复发送这一信号a。然后给你一个字符串s,表示接收器所收到的信号,它是由不同的神秘机器发出的信号组合而成,所以s中可能混合着多个a串(保持原来的相对前后顺序)。
就比如当a=“LCR”,那么一个有效的s串可以是“LCLCRR”,s中混有了2个位置没有重复且与a串相同的子序列,而“LRLCCR”就不是一个有效的s串。同时机器发出信号时每个字符之间的间隔时间是不确定的,而且每台机器一定会依次发送出完整的信号,也就是只有当一台机器发送出了完整的信号它才会发送下一次信号或者停止发送信号。所以这个s串只能由两台机器发送得到。
现在请你计算模拟字符串中所有神秘信号,所需不同的神秘机器的最少数目,也就是最少需要多少台这种神秘机器才能发出s这样的信号。如果这个串ss无法由若干个有效的a串字符混合而成,输出”error“。
假如a=“LCR&OI”,那么要想发出信号“LCR&OI“,神秘机器必须依序发出“L”,“C“,”R”,“&”,“O”,“I”这6个字符。机器每次发出信号一定会发出全部的六个字母,也就是不会出现只发送了“LC“这种情况。如果此时的串s=“LC”,则输出”error“。
输入格式
输入两行,第一行,一个字符串表示神秘机器所发出的信号a
第二行,一个字符串表示接收器收到的字符串s。
输出格式
输出一行,如果s合法,输出一个正整数表示至少需要几台神秘机器才能发出ss这样的信号,否则输出”error“。
数据范围
对于 30% 的数据,a串和s串只可能包括O,I两种字符,字符串s的长度不超过100
接下来的 30% 的数据,a串和s串只可能包括L,C,R三种字符,字符串s的长度不超过1500
对于 100% 的数据,a串和s串都只可能包括L,C,R,&,O,I这六种字符,字符串s的长度不超过120,000
样例数据
输入:
LCR&OI
LCR&OILCR&OI
输出:
1
说明:
一台机器发出了两次信号

用一个数组表示目前的机器状况,如果有一个信号正好可以接上那么就更改状态(如果有状态一样的机器改哪一个不会影响最终结果,因为我们在意的是机器个数而不是是谁发出了信号)。
如果不能接上呢?
如果是a串第一个字符那么再开一台机器,否则“error”。
但是这样 O ( n 2 ) O(n^2) O(n2)会超时。

“如果有状态一样的机器改哪一个不会影响最终结果,因为我们在意的是机器个数而不是是谁发出了信号”

所以我们没有必要将这么多机器分开记录,完全可以用一个数组,其中, a [ i ] a[i] a[i]表示状态为匹配到第 i i i个信号的机器个数。
但是注意!如果出现了没有匹配完的信号,那也属于错误

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

int vh[10],pos,pl[300],ans;
string a,s;

signed main(){
	cin>>a>>s;
	pos=a.size();
	for(int i=0;i<pos;++i)
		pl[a[i]]=i;
	int pos=s.size();
	for(int i=0;i<pos;++i){
		if(vh[pl[s[i]]])--vh[pl[s[i]]];
		else{
			if(!pl[s[i]])++ans;
			else{
				cout<<"error"<<endl;
				return 0;
			}
		}
		++vh[(pl[s[i]]+1)%(::pos)];
	}
	for(int i=1;i<(::pos);++i)
		if(vh[i]){
			cout<<"error"<<endl;
			return 0;
		} 
	cout<<ans<<endl;
	return 0;
}

T4 平整序列(四)

题目描述
给定一个整数序列 a 1 , … , a n a_1,\dots,a_n a1,,an,小爱希望通过一系列调整操作使所有数字均相等,但可惜他只有 m m m次机会,每次机会可以将序列中任意一个整数更改成他想要的值。
尽管他没有办法让所有的数字在操作后均相等,但他还是想让整个序列看上去更平整一些。于是小爱定义了不平整指数,所谓一个序列的不平整指数,指该序列中任意相邻两元素差值的绝对值的最大值。不平整指数越大,证明存在相邻两项差值绝对值越大,则序列越不平整。
请你帮他计算如何更改 m m m次数字,才能使修改后序列的不平整指数最小?
输入格式
输入共两行,
第一行,两个正整数 n , m n,m n,m
第二行, n n n个正整数 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an
输出格式
输出修改后序列的最小不平整指数
数据范围
对于30%的数据, 1 ≤ n ≤ 10 1≤n≤10 1n10
对于60%的数据, 1 ≤ n ≤ 22 1≤n≤22 1n22
对于100%的数据, 1 ≤ n ≤ 1000 1≤n≤1000 1n1000 0 ≤ m ≤ n 0≤m≤n 0mn − 1 0 9 ≤ a i ≤ 1 0 9 -10^9 \leq a_i \leq 10^9 109ai109
样例数据
输入:
4 2
1 3 5 7
输出:
2
输入:
6 2
5 5 2 5 2 5
输出:
0
说明:
两次修改都2换成5

这题的暴力分甚至都很难得。
因为不能直接枚举更改的数(原因是如果连续2次更改,则前面的更改会对后面的更改造成影响),所以应当枚举不更改的数。
枚举不更改的数,用除法直接算出两个选定不更改数之间的距离,最后取最大值。

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

int n,m,a[1010],vh[1010],nn,ans=1e9;

void dfs(int k){
	if(k>m){
		int maxv=0,p;
		for(int i=1;i<m;++i){//根据vh数组中存的选定的不更改的数来算最小差值
			if(abs(a[vh[i+1]]-a[vh[i]])%(vh[i+1]-vh[i])==0)
				p=abs(a[vh[i+1]]-a[vh[i]])/(vh[i+1]-vh[i]);
			else
				p=abs(a[vh[i+1]]-a[vh[i]])/(vh[i+1]-vh[i])+1;
			maxv=max(maxv,p);
		}
		ans=min(ans,maxv);
		return;
	}
	if(n-vh[k-1]>m-k+1){//如果有多种选择
		for(int i=vh[k-1]+1;i<=n-(m-k);++i){
			vh[k]=i;
			dfs(k+1);
		}
	}
	else{//如果正好够选,则后面的每一个数必须都被选上
		for(int i=vh[k-1]+1;i<=n;++i)
			vh[k++]=i;
		int maxv=0,p;
		for(int i=1;i<m;++i){
			if(abs(a[vh[i+1]]-a[vh[i]])%(vh[i+1]-vh[i])==0)
				p=abs(a[vh[i+1]]-a[vh[i]])/(vh[i+1]-vh[i]);
			else
				p=abs(a[vh[i+1]]-a[vh[i]])/(vh[i+1]-vh[i])+1;
			maxv=max(maxv,p);
		}
		ans=min(ans,maxv);
		return;
	}
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;++i)
		cin>>a[i];
	m=n-m;
	dfs(1);
	cout<<ans<<endl;
	return 0;
}

深搜转递归+记忆化。
设计递归:
f ( p , q ) ; f(p,q); f(p,q);
从后向前枚举不更改的数。
其中, p p p是当前不更改的数位置, q q q是剩下需要选的不更改的数的数量。挨个枚举下一个不更改的数的位置 j j j,然后- - q q q,算出 p p p q q q之间的距离,与接下来的子问题算出的距离距离取最大值则为答案。而 j j j的枚举范围是多少?因为需要让其有数可选,不能选择越界(我的程序没有查出界的功能,应该即使 j j j的范围错了,有查出界的话费一点时间应该也可以过),所以 j j j的范围应该是 q ≤ j < p q\leq j<p qj<p
边界是当 q q q为0时,即无法选择不更改的数,范围内全可更改,所以此时应该返回0。
i f ( q > 0 ) f ( p , q ) = m i n ( m a x ( f ( q − 1 , j ) , p 与 j 的距离 ) , s ) ; e l s e f ( p , q ) = 0 ; if(q>0)f(p,q) = min(max(f(q-1,j),p与j的距离),s);\\else f(p,q) = 0; if(q>0)f(p,q)=min(max(f(q1,j),pj的距离),s);elsef(p,q)=0;

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

int n,m,a[1010],vh[1010],nn,ans=1e9;

int f(int q,int p){
	
	if(q==0)return 0;
	int s=1e9;
	for(int j=q;j<p;++j){
		int pp;
		if(abs(a[p]-a[j])%(p-j)==0)
			pp=abs(a[p]-a[j])/(p-j);	
		else
			pp=abs(a[p]-a[j])/(p-j)+1;
		s=min(max(f(q-1,j),pp),s);
	}
	//cout<<"Q:"<<q<<" P:"<<p<<" S:"<<s<<endl;
	//system("pause");
	return s;
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;++i)
		cin>>a[i];
	m=n-m;
	int p=1e9;
	for(int i=m;i<=n;++i)
		p=min(p,f(m-1,i));
	cout<<p<<endl;
	return 0;
}

依然60分,加记忆化(注意这里距离可能是0,所以记忆化应该清-1)

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

int n,m,a[1010],vh[1010][1010],nn,ans=1e9;

int f(int q,int p){
	if(vh[q][p]!=-1)return vh[q][p];
	if(q==0)return 0;
	int s=1e9;
	for(int j=q;j<p;++j){
		int pp;
		if(abs(a[p]-a[j])%(p-j)==0)
			pp=abs(a[p]-a[j])/(p-j);	
		else
			pp=abs(a[p]-a[j])/(p-j)+1;
		vh[q-1][j]=f(q-1,j);
		s=min(max(vh[q-1][j],pp),s);
	}
	return s;
}

int main(){
	memset(vh,-1,sizeof(vh));
	cin>>n>>m;
	for(int i=1;i<=n;++i)
		cin>>a[i];
	m=n-m;
	int p=1e9;
	for(int i=m;i<=n;++i)
		p=min(p,f(m-1,i));
	cout<<p<<endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GaoGuohao2022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值