【题目描述】
原题来自: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 sumi−1+ai,那么 a i a_i ai就等于 s u m i sum_i sumi- s u m i − 1 sum_{i-1} sumi−1,我们只需要判断当前搜索的 s u m i sum_i sumi与上一个已确定的 s u m i − 1 sum_{i-1} sumi−1做差,判断此差是否属于集合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;
}