【构造】【dp】POJ1722

题面

给定一个n个正整数的序列{\(a_n\)},在这个序列上我们可以执行收缩操作。一次收缩操作可指定一个i,使\(a_i\)-\(a_{i+1}\)替换\(a_i\),\(a_{i-1}\)。对于n个整数的序列,我们可以执行n-1个不同的收缩操作,每个收缩操作都会产生一个新的长度为n-1序列。现给定序列{\(a_n\)}和目标数t,求一个n-1操作序列,使最后得到t。1<=n,\(a_i\)<=100,1<=t<=1000

链接:http://poj.org/problem?id=1722

思路

设最终答案为ans,ans一定会加上\(a_1\)减去\(a_2\)。在一个操作序列进行完操作后,若\(a_k\)最终为减号时,\(a_{k+1}\)最终为加号时,对k进行操作,把\(a_{k+1}\)先减到\(a_k\)上 ,因为后面a_k一定会减,所以\(a_{k+1}\)最终就是加号了。对所有这样的一对数先进行操作,最后会只剩下为加号的\(a_1\),和一堆最终为减号的\(a_k\),这时候我们一直对1进行操作,最后\(a_k\)就都会是减号。综上只有\(a_1\),\(a_2\)的符号是固定的,\(a_k\)的符号无论加减都至少有一种操作序列满足要求。所以我们不有考虑不好维护的每一次操作的瞬时状态,而是考虑容易维护的最终状态,再结合前面的过程求得答案。具体就是把题目划为两个部分,一部分设\(f_i,_j\)来表示操作到a_i时,能否使结果为j,转移方程:

\(f_i,_j=f_{i-1},_{j+a_i}|f_{i-1},_{j-a_i}\)

并保存路径即\(a_i\)前面的加减号。一部分由加减号来构造一个符合要求的操作序列。

代码

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=110,M=1e4+10;
short path[N][M<<1],a[N],f[N][M<<1],an[N];
void dfs(int i,int j)
{
	if(i==2) return;
	if(path[i][j]) dfs(i-1,j+a[i]);
	else dfs(i-1,j-a[i]);
	an[i]=path[i][j];
}
int main()
{
//	freopen("data.in","r",stdin);
//	freopen("data.out","w",stdout);
	int n,k,last,cnt=1;
	scanf("%d%d",&n,&k);
	if(n==1) return 0;
	if(n==2)
	{
		printf("1\n");
		return 0;
	}
	for(int i=1;i<=n;i++) cin>>a[i];
	f[1][M+a[1]]=f[2][M+a[1]-a[2]]=1;
	for(int i=3;i<=n;i++)
	{
		for(int j=1;j<(M<<1);j++)
		{
			if(j-a[i]>0&&f[i-1][j-a[i]]) f[i][j]=1,path[i][j]=0;
			if(!f[i][j]&&j+a[i]<(M<<1)&&f[i-1][j+a[i]]) f[i][j]=1,path[i][j]=1;
			if(!f[i][j]) path[i][j]=-1;
		}
	}
	dfs(n,M+k);
	last=2;int in=0;
	for(int i=3;i<=n;i++)
	{
		if(!an[i]) printf("%d\n",last),in++;
		else last=i-in,cnt++;
	}
	for(int i=1;i<=cnt;i++) printf("1\n");
	return 0;
}

posted @ 2019-06-09 17:27  FlashiLizard  阅读(120)  评论(0编辑  收藏  举报