第二届齐鲁工业大学(山东省科学院)与山东师范大学ICPC大学生程序设计竞赛联赛 [官方题解]


若发现错误或疑问,联系QQ:1098509291 指正/说明。

A. A+B Problem

题意: 找一个最小的整数 K ( 2 ≤ K ≤ 9 ) K(2\leq K \leq 9) K(2K9)满足 ( a + b ) 10 = ( n ) K (a+b)_{10}=(n)_K (a+b)10=(n)K,找不到则输出 − 1 -1 1

思路: 模拟。枚举 2 2 2 9 9 9进制,判断 n n n是否可以是这个进制的数,如果可以就把 n n n转化成 10 10 10进制数判断是否与 a + b a+b a+b相等。

//A+B Problem Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
int a,b;
string n;
int main(){
    
    
	cin>>a>>b>>n;
	for(int i=2;i<=9;i++){
    
    
		int sum=0;
		for(int j=n.length()-1,k=1;j>=0;j--,k*=i){
    
    
			if(n[j]-'0'>=i){
    
    sum=-1;break;}
			sum+=k*(n[j]-'0');
		}
		if(sum==a+b){
    
    
			cout<<i;
			return 0;
		}
	}
	cout<<-1;
}

B. 77777

题意: 问所给的小写串 S S S中是否能找到一个子串,其中至少有一个字母的出现次数恰好等于 7 7 7

思路: 贪心。遍历一遍串 S S S,只要整个串里有一个字母出现的次数 ≥ 7 \geq 7 7,就一定能找到一个合法的子串使得其中至少有一个字母的出现次数恰好等于 7 7 7

//77777 Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
int n,cnt[26];
string s;
int main(){
    
    
	cin>>n>>s;
	for(int i=0;i<n;i++)cnt[s[i]-'A']++;
	for(int i=0;i<26;i++)
		if(cnt[i]>=7){
    
    
			printf("YES");
			return 0;
		}
	printf("NO");
}

C. Merge

题意: n n n张带权值的卡牌,每次可执行一个操作:选一张卡牌,让它相邻的一张卡牌加或减所选卡牌的权值,并把所选卡牌删掉,求最后剩余的一张卡牌的最大权值。

思路: 贪心。

1° 若至少有一张卡牌权值 ≥ 0 \geq0 0,我们可以让这张牌加上所有正权值卡牌,减去所有负权值卡牌,那么答案应该是所有卡牌的权值绝对值之和

2° 若所有卡牌权值都 < 0 <0 <0,此时至少有一张卡牌的权值需要取负的贡献,其余卡牌可以取正的贡献(即绝对值)。也就是说我们应该找到一张卡牌,让它去减其他所有卡牌。所以要最大化最终权值,应该找权值最大的卡牌,去减其他所有卡牌。答案也就是所有卡牌的权值绝对值之和 - 最大的卡牌权值*2

//Merge Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e6+7;
int n;
LL ans,x,k=-1e9-1;
int main(){
    
    
	cin>>n;
	for(int i=1;i<=n;i++){
    
    
		scanf("%lld",&x);
		ans+=(x>0?x:-x);
		k=max(k,x);
	}	
	if(n==1)cout<<k;
	else if(k>=0)cout<<ans;
	else cout<<ans+k+k;
}

D. 魔法少女:承

题意: 给定每颗高、低纯度结晶的回复理性值和纯粹度,找一种使用高、低纯度结晶的个数,使得理性值回满,平均纯粹度 ≥ 60 % \geq60\% 60%,尽可能节约高纯度结晶,其次节约低纯度结晶。

扫描二维码关注公众号,回复: 12108120 查看本文章

思路: 两层for循环枚举使用两种结晶的个数,判断是否合法,更新最小值。

//魔法少女:承 Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
int T,a1,b1,a2,b2;
int main(){
    
    
	cin>>T;
	while(T--){
    
    
		cin>>a1>>b1>>a2>>b2;
		int ans1=INT_MAX,ans2=INT_MAX;
		for(int i=0;i<=100;i++)
		for(int j=0;j<=100;j++)
			if(i*a1+j*a2>=100&&(i*b1+j*b2)/(i+j)>=60){
    
    
				if(i<ans1||(i==ans1&&j<ans2)){
    
    
					ans1=i;
					ans2=j;
				}
			}
		printf("%d %d\n",ans1,ans2);
	}
}

E.学妹的任务

题意: 依次拼接字符串,对当前已拼接的字符串的后缀下一个待拼接的字符串的前缀去重再拼接。

思路: 每次拼接时,枚举下一个待拼接的字符串的所有前缀,用string中的substr函数判断这个前缀是否是已拼接的字符串的后缀,如果是,更新最大重叠长度。然后暴力拼接。

//学妹的任务 Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
int n;
string s[107],ans;
int main(){
    
    
	cin>>n;
	for(int i=1;i<=n;i++)cin>>s[i];
	ans=s[1];
	for(int i=2;i<=n;i++){
    
    
		int l0=ans.length(),l1=s[i].length(),k=0;
		for(int j=l1;j;j--){
    
    
			if(l0-j<0)continue;
			if(s[i].substr(0,j)==ans.substr(l0-j,j)){
    
    
				k=j;
				break;
			}
		}
		for(int j=k;j<l1;j++)ans+=s[i][j];
	}
	cout<<ans;
}

F. AKIE’s Penalty

题意: 给定N个题的提交次数和通过时间,若通过时间 T i Ti Ti为正,则最后一次提交正确,若通过时间 T i Ti Ti为负,则本题未通过。每次错误提交的罚时为20,计算所有通过题目的总罚时。

思路: T i Ti Ti为正,此题罚时为 ( K i − 1 ) ∗ 20 + T i (Ki-1)*20+Ti (Ki1)20+Ti,否则罚时为0。累加即可。

//AKIE's Penalty Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=107;
int n,ans,k[N],t;
int main(){
    
    
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%d",&k[i]);
	for(int i=1;i<=n;i++){
    
    
		scanf("%d",&t);
		if(t>=0)ans+=20*(k[i]-1)+t;
	}
	printf("%d",ans);
}

G. A-B Problem

题意: 输入两个数,输出A-B。

思路: …[组织语言中.jpg]

//A-B Problem Standard Code [C++]
#include<iostream>
using namespace std;
int main(){
    
    
	int a,b;
	cin>>a>>b;
	cout<<a<<"-"<<b;
}

H. 翻转队列

题意: 给两个只包含 0 / 1 0/1 0/1的串 A , B A,B A,B,每次可以执行操作:从A串中选一个 1 1 1作为中心,将其相邻的前一个位置的数后一个位置的数取反( 0 0 0变成 1 1 1 1 1 1变成 0 0 0),但开头和结尾位置上的 1 1 1不能被作为中心。问是否能由 A A A串经过若干次操作(或零次)变为 B B B串。

思路:

假设 A A A串是 1101 1101 1101 B B B串是 0111 0111 0111,我们对它们分别取异或前缀和,

可以得到前缀和数组 S U M A SUM_A SUMA: 1001 1001 1001 S U M B : 0101 SUM_B: 0101 SUMB:0101

我们选定 A A A串中第二个 1 1 1为中心,进行相邻取反操作,是将其前面第一个数和其后面第一个数取反, A A A会变为 0111 0111 0111,此时 S U M A SUM_A SUMA会变为 0101 0101 0101

可以看出来,如果我们以位置 i i i为中心,对 i − 1 i-1 i1 i + 1 i+1 i+1取反,对前缀和数组的影响是:SUM[i-1]取反,SUM[i]取反,对前缀和数组中的其余值其实没有影响。由于 A A A串中 i i i 位置是选择的中心,所以它这一位肯定是 1 1 1,所以 S U M A SUM_A SUMA数组中 S U M A [ i − 1 ] SUM_A[i-1] SUMA[i1] S U M A [ i ] SUM_A[i] SUMA[i]一定不同,所以一定会有一个 1 1 1变成 0 0 0,一个 0 0 0变成 1 1 1

所以,在串 A A A中以位置 i i i为中心, i − 1 i-1 i1 i + 1 i+1 i+1取反的操作对于前缀和数组的影响,只是造成了 S U M A [ i − 1 ] SUM_A[i-1] SUMA[i1]取反, S U M A [ i ] SUM_A[i] SUMA[i]取反,并且它们两个中,一定是有一个 1 1 1变成了 0 0 0,有一个 0 0 0变成了 1 1 1

所以可以得到,经过若干次操作后,前缀和数组中的 0 0 0 1 1 1的数量不会改变。

并且,只要两个前缀和数组中 0 0 0 1 1 1的数量都对应一致,一定可以通过某种方案使 A A A串转变为 B B B串。

所以只需要判断两个串的异或前缀和数组 0 0 0 1 1 1的数量是否对应一致即可。

注意: 由于内存限制为1.5M,所以不可以开2×106长度的int数组,但好像可以开一个相同长度的bitset(未测试)。题解代码未使用数组。

//翻转队列 Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
int n,sum1,cnt1,sum2,cnt2;
int main(){
    
    
	cin>>n;
	char x;
	for(int i=1;i<=n;i++){
    
    
		while(x=getchar(),x!='0'&&x!='1');
		sum1^=(x=='1');
		cnt1+=(sum1==1);
	}
	for(int i=1,x;i<=n;i++){
    
    
		while(x=getchar(),x!='0'&&x!='1');
		sum2^=(x=='1');
		cnt2+=(sum2==1);
	}
	if(cnt1==cnt2)printf("yes");
	else printf("no");
}

I. 魔法少女:转

题意: 找一种五个亡者之怒的使用顺序并指定它们的打击魔兽,使得所有魔兽都被消灭。

思路: 深度优先搜索(dfs),搜索每个魔兽被使用的亡者之怒,若已经被消灭就搜索下一个魔兽,找到答案就输出即可。

//魔法少女:转 Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
int T,hp[4],a[6],ans[6],flag;
void dfs(int k,int damage){
    
    
	if(flag)return;
	if(k>3){
    
    
		for(int i=1;i<=5;i++)printf("%d%c",ans[i]," \n"[i==5]);
		flag=1;
		return;
	}
	if(damage>=hp[k]){
    
    
		dfs(k+1,0);
		return;
	}
	for(int i=1;i<=5;i++)
		if(!ans[i]){
    
    
			ans[i]=k;
			dfs(k,damage+a[i]);
			ans[i]=0;
		}
}
int main(){
    
    
	cin>>T;
	while(T--){
    
    
		flag=0;
		for(int i=1;i<=3;i++)cin>>hp[i];
		for(int i=1;i<=5;i++)cin>>a[i];
		dfs(1,0);
	}
}

J. 挑选队列

题意及思路: Provided By 杨熠辰
本题大意为在 1 − n 1-n 1n 中挑选出 3 3 3 个数使得两两互质或两两不互质的方案数。

我们考虑一个含有 n n n 个点的无向图,如果两个数互质,那么就在这两个数之间连边。这样的话,这题本质上就是求图上三个点的个数,并且要求要么两两连边,要么两两不连边。

由于直接做不是很好做,所以我们考虑容斥,用所有的三元组减掉非法的三元组来求答案。

非法的三元组无非只有两种情况,一种是三个点之间只有一条边,另一种是三个点之间有两条边,如下图:
在这里插入图片描述

对于图 1 1 1,我们考虑点 1 1 1,我们可以发现它与其他的两个点连接且仅连接了一条边,即点 1 1 1 与点 2 2 2 有连边却与点 3 3 3 没有连边,对于点 2 2 2 也是。

我们发现图 2 2 2 也有这个性质,即三元组中恰好存在两个点,这两个点与除了它自己之外的点恰好连了一条边,另一条没有连边。

于是,我们可以对于每个点,统计一下它的度数,对于点 i i i,求所有点的 d [ i ] × ( n − d [ i ] − 1 ) d[i] \times (n - d[i] - 1) d[i]×(nd[i]1) 之和,然后把结果除 2 2 2 即是不合法的三元组数。

所以我们下一步只需要求出每个点的度数即可,根据题意,对于点 i i i,有

d [ i ] = ∑ j = 1 n [ gcd ⁡ ( i , j ) = 1 ] = ∑ d ∣ i μ ( d ) ⌊ n d ⌋ d[i] = \sum_{j = 1}^n[\gcd(i, j) = 1] = \sum_{d|i}\mu(d)\lfloor\frac nd\rfloor d[i]=j=1n[gcd(i,j)=1]=diμ(d)dn
但是如果采用枚举因数的方式求 d [ i ] d[i] d[i] 其最终时间复杂度为 O ( n n ) O(n\sqrt n) O(nn ),所以我们更改枚举方式,对于一个因数 d d d,我们将 μ ( d ) ⌊ n d ⌋ \mu(d)\lfloor\frac nd\rfloor μ(d)dn 加到其倍数上,这样可以在 n log ⁡ n n\log n nlogn 的时间复杂度内解决此问题。

//挑选队列 Standard Code [C++]
#include <bits/stdc++.h>

const int MAXN = 2e6 + 5;

int mu[MAXN], prime[MAXN], notprime[MAXN], n, cnt;
long long f[MAXN];

void Shaker() {
    
    
    notprime[1] = mu[1] = 1;
    for(int i = 2; i <= n; i++) {
    
    
        if(!notprime[i]) prime[cnt++] = i, mu[i] = -1;
        for(int j = 0; j < cnt && i * prime[j] <= n; j++) {
    
    
            notprime[i * prime[j]] = 1;
            if(i % prime[j] == 0) {
    
    
                mu[i * prime[j]] = 0;
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
    }
}

long long cn3(int n) {
    
    
    return 1LL * n * (n - 1) * (n - 2) / 6;
}

int main() {
    
    
    long long ans = 0;
    scanf("%d", &n);
    Shaker();
    for(int i = 1; i <= n; i++) 
        for(int j = i; j <= n; j += i) 
            f[j] += n / i * mu[i];
    f[1]--;
    for(int i = 1; i <= n; i++) ans += 1LL * f[i] * (n - f[i] - 1);
    ans = cn3(n) - ans / 2;
    printf("%lld\n", ans);
    return 0;
}

K. 魔法少女:合

题意: 分别给定两棵树的所有连边,判断两棵树是否完全相同(节点、连边都相同)。

思路: 分别对两棵树每条边的两个点升序排序,对所有边按照两端点双关键字排序,然后判断两棵树的边对应相等即可。

//魔法少女:合 Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+7;
int n;
pair<int,int>a[N],b[N];
int main(){
    
    
	cin>>n;
	for(int i=1,x,y;i<n;i++){
    
    
		scanf("%d%d",&x,&y);
		if(x>y)swap(x,y);
		a[i]=make_pair(x,y);
	}
	for(int i=1,x,y;i<n;i++){
    
    
		scanf("%d%d",&x,&y);
		if(x>y)swap(x,y);
		b[i]=make_pair(x,y);
	}
	sort(a+1,a+n);
	sort(b+1,b+n);
	int flag=1;
	for(int i=1;i<n&&flag;i++){
    
    
		if(a[i]!=b[i])flag=0;
	}
	if(flag)cout<<"RED PILL";
	else cout<<"BLUE PILL";
}

L. 神奇的转盘

题意: 交互题。有一个 2 ∗ 2 2*2 22的转盘乱序填入了 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4四个数,每次可以询问 x x x n n n,代表位于 x x x位置上的数是否为 n n n,系统会回答。转盘会在每次询问前顺时针旋转 90 ° 90° 90° 。要求在 6 6 6次询问以内,确定当前转盘状态并输出。

思路:

假设起始转盘是:[数字代表转盘中存的数字]

2 2 2 3 3 3

1 1 1 4 4 4

(1) 第一次询问前变为:

1 1 1 2 2 2

4 4 4 3 3 3

(2) 第二次询问前变为:

4 4 4 1 1 1

3 3 3 2 2 2

(3) 第三次询问前变为:

3 3 3 4 4 4

2 2 2 1 1 1

对于这个起始转盘,我们可以通过前三次询问本转盘中数字 1 1 1 (标红色的位置)所出现的位置,分别询问它是否是 1 / 2 / 3 1/2/3 1/2/3,从而确定标红位置是几。

可以看出(1)、(2)、(3)中数字 1 1 1所在的位置分别是位置1,位置2,位置3

所以我们第一次询问 1 1 1 1 1 1,第二次询问 2 2 2 2 2 2,第三次询问 3 3 3 3 3 3,就可以确定标红色的位置存的数是几。

同理,确定了第一个数,在用剩下的三个数中的任意两个去测试 2 2 2次**本转盘中数字 2 2 2接下来出现的两个位置,可以确定本转盘中数字 2 2 2**的位置存的数是几。

最后再用剩下的两个数去测试本转盘中数字3接下来出现的一个位置,确定本转盘中数字3的位置存的是几。

所以询问恰好 3 + 2 + 1 3+2+1 3+2+1次即可确定转盘内容,并输出当前转盘内容。

以上过程可以用一个函数实现询问。

//神奇的转盘 Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
int flag[5],a[3][3];
void query(int x,int y,int start,int end){
    
    
	int sum=0;
	for(int i=1;i<=4;i++)
		if(!flag[i])sum+=i;
	for(int i=start,j=1,k;i<=end;i++,j++){
    
    
		while(flag[j])j++;
		cout<<i<<" "<<j<<endl;
		sum-=j;
		cin>>k;
		if(k==1)a[x][y]=j,flag[j]=1;
	}
	if(!a[x][y])a[x][y]=sum,flag[sum]=1;
}
int main(){
    
    
	query(1,2,1,3);
	query(2,2,1,2);
	query(2,1,4,4);
	query(1,1,2020,1024);//后两个参数任意填的,只要进函数之后不执行循环即可
	cout<<0<<endl;
	cout<<a[1][1]<<" "<<a[1][2]<<"\n"<<a[2][1]<<" "<<a[2][2];
}

M. AAO

题意: 新创语言。有 n n n个数 [ 1 , 2 , . . . , n ] [1,2,...,n] [1,2,...,n],但输入缺失了一个数(只输入 n − 1 n-1 n1个数),要求找出缺失的数。

思路: 做法很简单,就是计算 1 + 2 + . . . + n 1+2+...+n 1+2+...+n的值,减去所有输入的数,即是缺失的数。难点在于要用新创语言给定的语句做,建议用C++11自带的

cout<<R"(...)";

语句输出所有新语言的句子,具体写法如下:

//AAO Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
int main(){
    
    
	cout<<R"(
		Initial n
		Yes, BangDream
		
		Hey, Y.O.L.O!!!!! n
		
		Initial sum
			ROKIROKIROLL n
		Yes, BangDream
			
		Initial n
			Please don't say "You are lazy" 1
		Yes, BangDream
		
		Initial x
		Yes, BangDream
		
		A to Z! A to Z! n
			Hey, Y.O.L.O!!!!! x
			
			Initial sum
				ROKIROKIROLL n
				Please don't say "You are lazy" x
			Yes, BangDream
			
			Initial n
				Please don't say "You are lazy" 1
			Yes, BangDream
		tenkao
		
		go go go for it! sum
	)";
}

N. 来自异世界的巧克力

题意: 给一个数 n n n,要求把它分成 x x x个正奇数 + y y y个正偶数,使得这 x x x个奇数和 y y y个偶数的和等于 n n n。注意,没有要求所分的数各不相同。

思路:

1° 偶数不会改变奇偶性,奇数个奇数的和仍然是奇数偶数个奇数的和是偶数,所以我们可以先根据 n n n的奇偶性和 x x x的奇偶性判断是否可以分。

x x x个正奇数 + y y y个正偶数的和的最小值应该是 x x x 1 1 1 y y y 2 2 2相加,所以我们可以把 x + 2 ∗ y x+2*y x+2y 的值与 n n n比较,若 x + 2 ∗ y > n x+2*y > n x+2y>n,显然没法分。

所以,判断完可以分之后,我们可以先把 n n n分出来 x x x 1 1 1 y y y 2 2 2,剩下的值肯定是一个偶数,我们可以把剩余值加到任意分出来的数上,输出即可。

//来自异世界的巧克力 Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int t;
LL n,x,y;
void solve(){
    
    
    scanf("%lld%lld%lld",&n,&x,&y);
    if((n%2==0&&x%2==1)||(n%2==1&&x%2==0)||x+2LL*y>n){
    
    
        printf("NO\n");
        return;
    }
    printf("YES\n");
    n-=x+2LL*y;
    for(int i=1;i<=x;i++)printf("1%c"," \n"[i==x]);
    for(int i=1;i<y;i++)printf("2 ");
    printf("%lld\n",n+2);
}
int main(){
    
    
    cin>>t;
    while(t--)solve();
}

O. hina的迷宫

题意: 给一个字符矩阵的迷宫,其中有已存在的墙壁(不能通过),人物可以向 8 8 8个相邻的方向走(不可以走到迷宫外),问是否有方案设置不多于 k k k个墙壁,使得人物不能从左上角走到右下角。

思路: 在起点的右方、下方、右下方设置字符串即可。设置的时候判断一下原来是否存在墙壁。注意,如果n * m=2 * 2,无论如何设置墙壁,都无法阻挡人物到达右下角(因为终点不能设置墙壁,直接从起点往右下方走一步即可到达)。

//hina的迷宫 Standard Code [C++]
#include<bits/stdc++.h>
using namespace std;
int T,n,m,k;
char s[57][47];
void solve(){
    
    
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	if(n*m==4)return (void)puts("NO");
	puts("YES");
	int cnt=(s[1][2]=='.')+(s[2][1]=='.')+(s[2][2]=='.');
	cout<<cnt<<"\n";
	if(s[1][2]=='.')cout<<"1 2\n";
	if(s[2][1]=='.')cout<<"2 1\n";
	if(s[2][2]=='.')cout<<"2 2\n";
}
int main(){
    
    
	cin>>T;
	while(T--)solve();
}

猜你喜欢

转载自blog.csdn.net/qq_45530271/article/details/109957130