POJ1722 算法竞赛进阶指南 SUBSTRACT减操作

原题连接

题目描述

给定一个整数数组\(a_1,a_2,…,a_n\)

定义数组第 i 位上的减操作:把\(a_i\)\(a_{i+1}\)换成\(a_i - a_{i+1}\)

用con(a,i)表示减操作,可以表示为:

\[con(a,i)=[a_1,a_2,…,a_{i-1},a_i-a_{i+1},a_{i+2},…,a_n] \]

长度为 n 的数组,经过 n-1 次减操作后,就可以得到一个整数t。

例如数组[12,10,4,3,5]经过如下操作可得到整数4:

\[con([12,10,4,3,5],2) = [12,6,3,5] \\ con([12,6,3,5] ,3) = [12,6,-2] \\ con([12,6,-2] ,2) = [12,8] \\ con([12,8] ,1) = [4] \]

现在给定数组以及目标整数,求完整操作过程。

输入格式

第1行包含两个整数n和t。

第2..n+1行:第i行包含数组中的第 i 个整数\(a_i\)

输出格式

输出共n-1行,每行包含一个整数,第 i 行的整数表示第 i 次减操作的操作位置。

数据范围

\[1 \le n \le 100 \\ -10000 \le t \le 10000 \\ 1 \le a_i \le 100 \\ \]

输入样例:

5 4
12
10
4
3
5

输出样例:

2
3
2
1

解题报告

题意理解

就是说,有一种操作,名为减操作,可以将合并相邻的两个数,比如说原来的数字是.

\[3,4,那么合并后变成-1 \]

也就是,

\[合并后的数字=前面一个数字-后面一个数字. \\ a[i]=a[i]-a[i-1] \\ 然后删除a[i+1] \]


思路解析

性质分析

我们发现,每一次减操作都会使得序列长度减少一个.

\[即原本长度len,然后一次减操作后就会变成len-1 \]

所以说,我们发现其实对于序列的最终结果\(t\),可以变成这种形式.

\[a[1]-a[2] \pm a[3] \pm a[4] \pm a[5]=t \]

举个例子表示一下

\[a[1] \quad a[2] \quad a[3] \quad a[4] \quad a[5] \qquad 原序列\\ a[1] \quad a[2]-a[3] \quad a[4] \quad [5] \qquad 此时cut(2) \\ a[1] \quad a[2]-a[3] \quad a[4]-a[5] \qquad 此时cut(3) \\ a[1] \quad a[2]-a[3]-(a[4]-a[5]) \qquad 此时cut(2) \\ a[1]-(a[2]-a[3]-(a[4]-a[5])) \qquad 最后cut(1) \\ a[1]-a[2]+a[3]+a[4]-a[5] \qquad 处理后的答案序列 \]

我们发现

\[a[1]必须是+号,a[2]必须是-号 \]

对于

\[a[1]必须是+号 \]

因为我们发现,\(1\)的前面没有数,可以去进行减操作.

最后一次执行的必然是\(cut(1)\)操作

\(a[1]\)表示,我真的想要减操作,但是我就是没有数可以和我一起减操作.

然后我们再来康康为什么一定是

\[a[2]必须为-号 \]

其实道理和之前一样,

最后一次执行的必然是\(cut(1)\)操作.

\(a[2]\)表示,我真的是被迫的,\(cut(1)\)使得\(a[1]-a[2]\).


状态设置

这样我们将题目转换成了
一个数列,对于数组中的数,将一些正整数变为负数,使整个数组的和为t,最后输出将哪些数变为负数.

我们发现这道题目的数据范围

\[n \le 100 \\ -10000 \le t \le 10000 \\ \]

数据范围真的好小啊,开一个\(n*t\)的数据范围丝毫没有问题.

所以说我们不妨这么设置一个状态数组.

\[f[i][cnt] \quad 表示前i个数字的和为cnt \\ f[i][cnt]=1 \quad 表示第i个数前面是+号 \\ f[i][cnt]=-1 \quad 表示第i个数前面是-号 \\ \]

不过我们要注意一下,C++负数下标有可能性挂掉了,所以我们不得不让所有下标加上一个固定的大数字,保证最后的下标是一个正数.

此时最大的问题就是,如何反推出我们的cut操作?


反推路径
  1. 为什么有些数可以是正数?也就是前面是+号?

这是一个非常重要的问题,我们发现.

\[一个数字前面是+号,只有在它这一位进行cut操作. \]

假如说我们第\(i\)位不进行\(cut\)操作,那么它前面一定不是\(+\)号.

一个数,前面不是加号,就是减号.

\[cut(i) \qquad a[i]-a[i+1] \\ cut(i-1) \qquad a[i-1]-a[i] \\ \]

只有当\(i-1\)位进行\(cut\)操作的时候,这个第\(i\)位才可以是减号.

这就让我们证明了.

\[一个数字前面是+号,只有在它这一位进行cut操作. \]

所以找到每一个\(+\)号的位置,然后输出当前位置.

不过你要注意一下,输出应该是.

\[i-tot-1 \\ tot表示为当前有几个cut操作了 \\ \]


代码解析

#include<bits/stdc++.h>
using namespace std;
#define init() ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);//读入优化
const int maxn=105,maxt=20086,hh=10000;//hh是我们的下标转移常数
int n,t,f[maxn][maxt],a[maxn],ans[maxn];
void dp()
{
    f[1][a[1]+hh] = 1;//a[1]必然是正数
	f[2][a[1]-a[2]+hh]=-1;//a[2]必然是
	for(int i=3; i<=n; i++)
		for(int j=-10000+hh; j<=10000+hh; j++)
		{
			if(f[i-1][j])//可以转移
			{
				f[i][a[i]+j]=1;//+号
				f[i][j-a[i]]=-1;//-号
			}
		}
}
void out()
{
    int s=hh+t;
	for(int i=n; i>=2; i--)//回溯走路径,确定+,-号
	{
		ans[i]=f[i][s];
		if(ans[i]==1)
			s-=a[i];
		else if(ans[i]==-1)
			s+=a[i];
	}
	int cnt=0;
	for(int i=2; i<=n; i++)
		if(ans[i]==1)//是时候减操作了.
		{
			cout<<i-cnt-1<<endl;
			cnt++;
		}
	for(int i=2; i<=n; i++)
		if(ans[i]==-1)//寻找
			cout<<1<<endl;
}
int main()
{
	init();
	cin>>n>>t;
	for(int i=1; i<=n; i++)
		cin>>a[i];
	dp();
    out();
	return 0;
}
posted @ 2019-06-25 20:52  秦淮岸灯火阑珊  阅读(766)  评论(1编辑  收藏  举报