简单的组合数学问题一例 —— CodeForces - 1823A

1f603f95a8b242f0a9067a17fdc30365.png

这是笔者在第一学期参加SCUACM 2023级 新生赛初赛写的一道题目。也是我写出来的第一道算法题目。

题目 构造

构建一个长度为eq?n的数组,数组元素只能为 1 或者 −1。
定义数组eq?a每满足一对eq?a_i%20%5Ctimes%20a_j%20%3D%201为有1个特征A。求数组是否能构成出满足刚好k个特征A。
如果能构成该数组输出 YES 并给出构造数组。反之只输出 NO。

输入

每个测试包含多个测试用例。第一行包含测试用例的数量eq?t%281%5Cleq%20t%5Cleq%20100%29。测试用例说明如下。每个测试用例的唯一一行包含两个整数eq?neq?keq?%282%5Cleq%20n%5Cleq%20100%3B0%20%5Cleq%20k%5Cleq%20%5Cfrac%7B%28n-1%29n%7D%7B2%7D%29--所需的数组长度和所需的A字符。

输出

对于每个测试用例,如果没有数组eq?a具有给定的A特征k,则打印 NO。

否则,打印 YES 和eq?n数字1和-1,它们组成所需的数组eq?a。如果有多个答案,则打印任意一个。

题目给出的特征A的表象之下,结合数组的特性,实际上可以得出:每两个相同的元素可以生成一个特征A。故只需要将数组分为两部分——一部分为p个1,另一部分为q个-1,则特征A的个数k的计算公式为:

eq?k%3DC_p%5E2+C_q%5E2%2Cp+q%3Dn

代码思路就是去枚举不同的p,q来判断是否存在解,若枚举完之后仍然找不到符合条件的p和q,则打印 NO,否则打印 YES 之后先连续打印p个1,后连续打印q个-1即可。

笔者当时只浅学了C,并未涉及C++的知识,给出的代码如下:

#include <stdio.h>
#include <stdlib.h>

long long int comb(int m,int n)
{
	long long int result;
	if(m<=n) result = n*(n-1)/2;
	else result = 0;
	return result;
}

int judge(int n,int k){
	for(int i=0;i<=n;i++){
		if(k==(comb(2,i)+comb(2,n-i))){
			printf("YES\n");
			for(int j=1;j<=i;j++){
				printf("1");
				if(j!=n){
					printf(" ");
				}else printf("\n");
			}
			for(int j=i+1;j<=n;j++){
				printf("-1");
				if(j!=n){
					printf(" ");
				}else printf("\n");
			}
			return 0;
		}
	}
	printf("NO\n");
	return 0;
}

int main()
{
	int t;
	scanf("%d",&t);
	int* n = (int*)malloc(t*sizeof(int));
	int* k = (int*)malloc(t*sizeof(int));
	for(int i=0;i<t;i++){
		scanf("%d %d",&n[i],&k[i]);
	}
	for(int i=0;i<t;i++){
		judge(n[i],k[i]);
	}
	free(n);
	free(k);
	return 0;
}

当时的代码水平很烂,尽管知道思路也还是试了三个小时才码对,由于C语言蹩脚的不可变数组,我研究了好一段时间才把n和k表示出来。

学习了C++并辅以不断的练习,这一次我只用了几分钟就码完了。

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

struct ans{
    bool found;
    int p;
    ans(bool iptB,int iptI){
        found = iptB; p = iptI;
    }
};

int Combination(int n){
    return (n*(n-1)/2);
}

ans enumerate(int n,int k){
    for(int i=0;i<=n;i++){
        if(k == Combination(i) + Combination(n-i)) return ans(true,i);
    }
    return ans(false,0);
}

void generateNums(int n,int p){
    for(int i=0;i<p;i++){
        cout << "1 ";
    }
    for(int i=p;i<n;i++){
        cout << "-1 ";
    }
    cout << endl;
}

int main(){
    int t;
    cin >> t;
    vector<vector<int>> test(t,vector<int>(2));
    for(int i=0;i<t;i++){
        for(int j=0;j<2;j++){
            cin >> test[i][j];
        }
    }
    for(int i=0;i<t;i++){
        ans answer = enumerate(test[i][0],test[i][1]);
        if(answer.found){
            cout << "YES" << endl;
            generateNums(test[i][0],answer.p);
        }
        else cout << "NO" << endl;
    }
    return 0;
}

C++中的vector容器很实用,结构体也非常有用。

运用有意义的函数名和打包封装函数的方式能提升代码的可读性和层次性,使人一目了然。

同时我也确实感受到了在这三个月之间个人码力的提升,希望我能在今年四月份的转专业中如愿以偿。

感谢你能看到这里。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值