• 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;
    }
    
  • 相关阅读:
    Easy Install详细参数
    linux.backspace乱码(转)
    RemoteFX
    netsh
    sc.exe
    WinRM和WinRS
    安全配置向导
    使用 Sconfig.cmd 配置服务器核心服务器
    FSMO
    Windows Server 2012之活动目录域服务的卸载
  • 原文地址:https://www.cnblogs.com/gzh-red/p/11086561.html
Copyright © 2020-2023  润新知