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;
}
上一篇:Allegro skill


下一篇:allegro 批量替换过孔96