题目
【问题描述】
请考虑一个由1到 N (N=3, 4, 5 … 9)的数字组成的递增数列:1 2 3 … N。现在请在数列中插入“+”表示加,或者“-”表示减,“ ”表示空白(例如1-2 3就等于1-23),来将每一对数字组合在一起(请不要在第一个数字前插入符号)。计算该表达式的结果并判断其值是否为0。请你写一个程序找出所有产生和为零的长度为N的数列。
【输入格式】
单独的一行表示整数N 。
【输出格式】
按照ASCII码的顺序,输出所有在每对数字间插入“+”, “-”, 或 “ ”后能得到结果为零的数列。
【输入样例】
7
【输出样例】
1+2-3+4-5-6+7
1+2-3-4+5+6-7
1-2 3+4+5+6+7
1-2 3-4 5+6 7
1-2+3+4-5+6-7
1-2-3-4-5+6+7
【数据范围】
3 <= N <= 9
作者:这次不写略了。。懒得各位再去找
分析
这道题时间复杂度只有3^9,没有什么难的
主要是考一个隔板实现 ,是个子集生成
剪枝的话应该只能加一个最优预测,要用后缀数组预处理,意义不是很大
而且不能逆序,剪枝效果又会减少
这个还是直接生成测试吧,回溯不是很好弄
代码
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn=12;
int n,a[maxn],x[maxn];//ASCLL码顺序:' ','+','-'
//x[i]表示在第i个元素后面放什么,1,2,3对应 ' ','+','-'
void Initial(int n)
{
for(int i=n;i>=1;i--)
{
a[i]=i;
}
x[0]=2;//手动加隔板
x[n]=2;
}
char mp(int x)
{
if(x==2)
return '+';
if(x==3)
return '-';
return ' ';
}
void out()//判断+输出
{
int s=0,tot;
//两重循环同参数模型
for(int i=0;i<n;i++)
{
if(x[i]==2)
{
tot=a[i+1];
i++;
while(x[i]==1)
{
tot=tot*10+a[i+1];
i++;
}
s+=tot;
i--;//退位
}
if(x[i]==3)
{
tot=a[i+1];
i++;
while(x[i]==1)
{
tot=tot*10+a[i+1];
i++;
}
s-=tot;
i--;//退位
}
}
if(s==0)
{
for(int i=1;i<n;i++)
{
printf("%d%c",a[i],mp(x[i]));
}
printf("%d\n",a[n]);
}
}
void run(int k)//准备考虑第k个元素后面放什么
{
if(k>=n)
{
out();
return;
}
for(int i=1;i<=3;i++)
{
x[k]=i;
run(k+1);
}
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d",&n);
Initial(n);
//printf("%d %d %d",'+','-',' ');
run(1);
return 0;
}
USA实现依旧不那么容易
这道题和基础隔板的延伸在于隔板是有意义的,而且这个隔板的意义范围是对后面的元素,因此生成测试的方法里面就出现了一个很重要的模型——两重循环同参数模型
(是我瞎BB的)
这个模型时间复杂度是o(n),最常见的操作就是退位操作
(还是我瞎BB的)
,有障碍了要退回去让新的操作来再次读入这个障碍,提炼出这个模型之后就不要因为觉得这个实现诡异而害怕了
番外
根据我的性格我依旧是不会允许写这么基础的代码的,因此我又实现了回溯算法加上预测剪枝,剪枝大概减少了百分之二十的时间
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn=12;
int n,a[maxn];//ASCLL码顺序:' ','+','-'
char x[maxn];
int sum[maxn][maxn];
void Initial(int n)
{
for(int i=1;i<=n;i++)
{
a[i]=i;
for(int j=i;j<=n;j++)
{
sum[i][j]=sum[i][j-1]*10+j;
}
}
}
void run(int k,int s)//从第k个元素开始考虑,已经得到的和为1
{
if(k>=n+1)
{
if(s==0)
{
cout<<1;
for(int i=2;i<=n;i++)
{
cout<<x[i]<<i;
}
cout<<endl;
}
return ;
}
x[k]='+';
for(int i=n;i>=k+1;i--)
{
for(int j=k+1;j<=i;j++)
x[j]=' ';
run(i+1,s+sum[k][i]);
}
run(k+1,s+k);
if(k==1)
return;
x[k]='-';
for(int i=n;i>=k+1;i--)
{
for(int j=k+1;j<=i;j++)
x[j]=' ';
run(i+1,s-sum[k][i]);
}
run(k+1,s-k);
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d",&n);
Initial(n);
run(1,0);
return 0;
}
这次实现稍微把x意义改了,改成i元素前面那个元素的范围,也没有用a数组和映射函数,比较贴合这道题目了,效率比之前高了些,但其实都是几十毫秒的事情了。
为了实现字典序采用逆序枚举的方法
实现中有个问题就是特殊考虑问题,本来想统一化的但是一直都没有办法规避,原因是因为n个数字只有n-1个符号,因为之前的习惯一直没有进行过特殊考虑(行末空格自动忽略的原因),想了一个办法利用行末空格,但是和意义不符合,还是特殊考虑了
再分析
这道题用的子集生成算法,根据题意我们再看看
我们可以把数分成加或减的数,必须要求加的数为tot/2,但是根据这个并没有想出更好的算法,根据原理其实也是子集生成,欢迎补充其它思路
收获与知识点
- 两重循环单参数模型->退位操作
- 预测剪枝
- 分析题目合理设置函数、数组意义,不要拘泥
- 排序的有序性
- 不要害怕特殊考虑
-
USACO的题不卡时间复杂度考实现很刁钻