#10249. 「一本通 1.3 例 5」weight

文章描述了一道编程题目,要求根据给定的前缀和与后缀和信息,以及一个数的集合,找出可能的数列,并确保在存在多组解的情况下,输出字典序最小的那个。解决方案涉及到深度优先搜索(DFS)和桶标记法,通过从两端搜索并判断当前数的合法性进行剪枝,最终找到并输出满足条件的最小数列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【题目描述】

原题来自:USACO

已知原数列 a1​,a2​,⋯,an​ 中的前 1 项,前 2 项,前 3 项, 前 n 项的和,以及后 1 项,后 2 项,后 3 项,后 n 项的和,但是所有的数都被打乱了顺序。此外,我们还知道数列中的数存在于集合 S 中。试求原数列。当存在多组可能的数列时,求字典序最小的数列。

【输入格式】

第 1 行,一个整数 n 。
第 2 行,2×n 个整数,注意:数据已被打乱。
第 3 行,一个整数 m ,表示 S 集合的大小。
第 4 行, m 个整数,表示 S 集合中的元素。

【输出格式】

输出满足条件的最小数列。

【样例输入】

5
1 2 5 7 7 9 12 13 14 14
4
1 2 4 5

【样例输出】

1 1 5 2 5

【数据范围与提示】

数据范围

对于 100% 的数据, 1≤n≤1000,1≤m≤500 ,且 S∈{1,2,⋯,500} 。

数据解释

从左往右求和 ----------------从右往左求和
____1=1 ---------------------------------5=5
___2=1+1---------------------------- 7=2+5
___7=1+1+5 --------------------12=5+2+5
___9=1+1+5+2 -------------13=1+5+2+5
___14=1+1+5+2+5----- 14=1+1+5+2+5

吐槽

惨烈的调试区(本蒟蒻不会用自带的调试orz)
在这里插入图片描述

if(l==r){
//			cout<<cur<<" "<<l<<" "<<r<<" "<<suml<<" "<<sumr<<endl;
//			for(int i=1;i<=n*2;i++){
//			cout<<ans[i]<<" ";
//		}
//		cout<<endl<<"--------------------------"<<endl;
		if(tong[a[cur]-suml]==0 &&tong[a[cur]-sumr]==0) 
			return;
//			for(int i=1;i<=n*2;i++){
//			cout<<ans[i]<<" ";
//		}
	//	cout<<endl<<"222222222222222222222222222222222222"<<endl;
		if(a[2*n]-suml-sumr<1||a[2*n]-suml-sumr>500)return;
//		for(int i=1;i<=n;i++){
//			if(ans[i]!=ans[i+n])return;
//		}
	//	cout<<endl<<"================================"<<endl;
		ans[l]=a[2*n]-suml-sumr;
		for(int i=1;i<=n;i++){
			cout<<ans[i]<<" ";
		}
		cout<<endl;
		exit(0);
	}

不做评价

大致思路

  • 根据题意, a 1 a_1 a1~ a n a_n an中既有前缀和又有后缀和,最直接的思路就是将缀和从小到大排完序后从集合S中一个一个尝试,从左搜到右,这显然很慢。
  • 搜索顺序: 既然一个是前缀和一个是后缀和,那么我们可以两头搜。
  • 如何确定原序列的数呢?首先,无论在前缀和还是后缀和中, s u m i sum_i sumi= s u m i − 1 + a i sum_{i-1}+a_i sumi1+ai,那么 a i a_i ai就等于 s u m i sum_i sumi- s u m i − 1 sum_{i-1} sumi1,我们只需要判断当前搜索的 s u m i sum_i sumi与上一个已确定的 s u m i − 1 sum_{i-1} sumi1做差,判断此差是否属于集合S即可。
  • 由以上我们可以很简单的理清dfs所需的参数,当前 a 1 a_1 a1~ a n a_n an的下标,左端点,右端点, s u m L sum_L sumL, s u m R sum_R sumR
void dfs(int cur,int l,int r,int suml,int sumr){
  • 如何确定一个数是否存在,用遍历的方法肯定是不行的,因为这题S较小,我们可以采用桶来标记一个数是否存在,之后只需查找以这个数作为下标的桶是否为1即可
for(int i=1;i<=m;i++){
		cin>>s[i];
		tong[s[i]]=1;
	}

由以上我们得到代码一世

#include <bits/stdc++.h>
using namespace std;
int n, m, ans[99999];
int s[99999], a[99999], tong[99999];
void dfs(int cur, int l, int r, int suml, int sumr) {
    if (l == r) {
        for (int i = 1; i <= n; i++) {
            cout << ans[i] << " ";
        }
        exit(0);
    }

    if (tong[a[cur] - suml] == 1) {
        ans[l] = a[cur] - suml;
        dfs(cur + 1, l + 1, r, a[cur], sumr);
    }

    if (tong[a[cur] - sumr] == 1) {
        ans[r] = a[cur] - sumr;
        dfs(cur + 1, l, r - 1, suml, a[cur]);
    }
}
int main() {
    cin >> n;

    for (int i = 1; i <= n * 2; i++) {
        cin >> a[i];
    }

    cin >> m;

    for (int i = 1; i <= m; i++) {
        cin >> s[i];
        tong[s[i]] = 1;
    }

    sort(a + 1, a + 1 + n + n);
    dfs(1, 1, n * 2, 0, 0);
    return 0;
}
但仅仅如此还马达马达,我们只能得到30分,其他点居然不是我们预想可能的TLE,而是WA在这里插入图片描述
考虑问题所在
  • 当L等于R的时候,我们本次选取的这个数还没有进行判断,也就是嗦,我们不知道ans[L]是否是合法的
  • 因为我们让L从1开始,R从n*2开始,当从左往右与从右往左搜到的数一样的时候才说明找到了正确答案
  • 发现问题,解决问题。在代码中加入以下来判断他是否合法

if(tong[a[cur]-suml]==0 &&tong[a[cur]-sumr]==0) 
	return;
if(a[2*n]*2-suml-sumr<1||a[2*n]*2-suml-sumr>500)
	return;//判断,以不选到太大数
	 for (int i = 1; i <= n; i++) {
            if (ans[i] != ans[i + n])
                return;
        }
解决了WA的问题,TLE却又找上门来

在这里插入图片描述

暴力dfs的力量真是太弱小了,所以我们不再暴力,要开始愉(tong)快(ku)剪枝辣。
  • 既然我们最终只需要输出n个数,那么R的初值没有必要在2*n(如果你第一次就是这么做的就请当我啥也没说)更没有必要让L和R都搜完
  • 因此,ans[L]直接赋值a[n*2]-sumL-sumR即可(想一想,为什么)

由以上得出AC CODE

#include<bits/stdc++.h>
using namespace std;
int n,m,ans[99999];
int s[99999],a[99999],tong[99999];//桶
void dfs(int cur,int l,int r,int suml,int sumr){
	if(l>r)return;//当前下标,左端点,右端点sum_L,sum_R
	if(l==r){
		if(tong[a[cur]-suml]==0 &&tong[a[cur]-sumr]==0) //判断当前ans是否合法
			return;
		if(a[2*n]-suml-sumr<1||a[2*n]-suml-sumr>500)return;//不选到太大数以致ans[L]不合法
		ans[l]=a[2*n]-suml-sumr;//ans[L]赋值
		for(int i=1;i<=n;i++){
			cout<<ans[i]<<" ";
		}
		cout<<endl;
		exit(0);//找到一组答案直接结束整个程序,因为我们从小到大搜,所以第一次合法的一定是字典序最小的
	}
	if(tong[a[cur]-suml]==1){//左边找数
		ans[l]=a[cur]-suml;
		dfs(cur+1,l+1,r,a[cur],sumr); //dfs,左端点右移,下标右移,sumL更新
	}
	if(tong[a[cur]-sumr]==1){//右边找数
		ans[r]=a[cur]-sumr;
		dfs(cur+1,l,r-1,suml,a[cur]);//dfs, 右端点左移,下标右移,sumR更新
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n*2;i++){
		cin>>a[i];
	}
	cin>>m;
	for(int i=1;i<=m;i++){
		cin>>s[i];
		tong[s[i]]=1;//桶实现
	}
	sort(a+1,a+1+n+n);//sort排序
	dfs(1,1,n,0,0);
	return 0;
}
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值