HDU [ Sequence one ]——dfs+判重+剪枝

本文介绍了一种使用深度优先搜索算法解决寻找特定数量不下降子序列的问题,包括详细的算法实现步骤和代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem Description
Search is important in the acm algorithm. When you want to solve a problem by using the search method, try to cut is very important.<br>Now give you a number sequence, include n (&lt;=1000) integers, each integer not bigger than 2^31, you want to find the first P subsequences that is not decrease (if total subsequence W is smaller than P, than just give the first W subsequences). The order of subsequences is that: first order the length of the subsequence. Second order the sequence of each integer’s position in the initial sequence. For example initial sequence 1 3 2 the total legal subsequences is 5. According to order is {1}; {3}; {2}; {1,3}; {1,2}. {1,3} is first than {1,2} because the sequence of each integer’s position in the initial sequence are {1,2} and {1,3}. {1,2} is smaller than {1,3}. If you also can not understand , please see the sample carefully. <br>
 

Input
The input contains multiple test cases.<br>Each test case include, first two integers n, P. (1<n<=1000, 1<p<=10000). <br>
 

Output
For each test case output the sequences according to the problem description. And at the end of each case follow a empty line.
 

Sample Input
3 51 3 23 61 3 24 1001 2 3 2
 

Sample Output
1321 31 21321 31 21231 21 32 32 21 2 31 2 2

================================

        题目大意:给定一组确定长度的数列,输出该数列的子集中能构成的不下降序列的子集的解,要求各个元素的原始相对位置不能改变且不能输出重复解。            

        这道题目思路倒是挺明显的,逐个枚举长度并分别dfs即可,不过有几个小细节要注意一下。

        我们先来看一下这道题目的思路:

        首先,很容易看出这是一道深搜类题目,所以最重要的就是寻找递归边界以及进入下一层状态需要满足的条件。同时我们很容易发现,在分别对不同长度递归时,我们的递归边界就是当前所需要的数列长度;其次,由于题目本身限制了输出解的个数,所以这也可以作为一个边界;两者的不同在于前者是当前长度下递归的边界,是某个解的终止条件,而后者是该组测试数据的终止条件。(当然最后别忘了后面提到的剪枝优化)

        而对于进入下一层需要满足的条件,我们可以首先想到当前的这个数据在之前一定是没有使用过的,同时它的位置也一定在上一个确定的数的位置后面;其次它还要>=上一个已经确定的数;最后也要确保在使用当前数据后不会出现重复解(及我们要有一段判重的代码)。

        分析到这里,感觉这题目的大体框架也差不多了,不过在写的过程中还是有很多小细节需要注意的,比如:

         1:判重:即当前解是否在前面出现过,我们采取的方法是从上一个确定的元素的位置开始遍历,一直到当前判断数据的位置,如果有与当前判断数据相同的数据,则代表有重复解,这段代码还是挺好实现的,所以直接看代码吧。

        2:剪枝:当某一长度下无解时,那么>=该长度的子集中也一定无解,所以这时候不应该再进入递归了,应当结束该组测试数据的运行,在这里我们用了一个flag来判断当前长度下是否有可行解(刚开始把flag的位置放错了,flag改变的条件应该是当前长度下至少有一组完整解)。

AC代码:

#include<bits/stdc++.h>
using namespace std;
int n,t,sum;
int length;
bool flag;  //用于判断当前长度下
int a[10010],position[10010];
bool same(int x,int y) //判重函数;
{
	for(int i=x+1;i<=y-1;++i)
	{
		if(a[y]==a[i])return 0;
	}
	return 1;
}
void search(int x,int start)
{
	if(sum>=t)
	{
		return ;
	}
	if(x>length)
	{
		flag=1;//注意flag的位置,开始把它放在了下面for的if里,确实挺尴尬;
		sum++;
		cout<<a[position[1]];
		for(int i=2;i<=length;++i)
		       cout<<" "<<a[position[i]];
		cout<<endl;
		return ;
	}
	for(int i=start;i<=n;++i)
	{
		if((x==1&&same(position[x-1],i)==1)||(x!=1&&a[i]>=a[position[x-1]]&&same(position[x-1],i)==1))
		{
			position[x]=i;
			search(x+1,i+1);
		}
	}
}
int main()
{
	while(cin>>n>>t)
	{
		sum=0;//更新数据;
		memset(a,0,sizeof(a));
		memset(position,0,sizeof(position));
		for(int i=1;i<=n;++i)
		               cin>>a[i];
		for(int i=1;i<=n;++i)
		{
			length=i;//开始把长度作为了参数,但写出来的代码感觉特别乱,所以函数传递的参数尽量还是要需要改变的量;
			flag=0;//用于判断当前长度下是否有解;
			search(1,1);
		        if(!flag||sum>=t)//无解或输出解的组数达到要求就没必要继续递归了;
			{
				
				break;
			}
			
			
		}
		cout<<endl;
	}
	return 0;
}

最后,贴上刚开始自己第一次写的代码,虽然能AC,但是思路太乱。

#include<iostream>
#include<cstring>
using namespace std;
int a[10010];
bool flag[10010],temp,k;
int memor[10010];
int position[10010];
int n,p,sum;
bool pc(int x,int y)//判重函数;
{
    for(int i=y+1;i<x;++i)
    {
        if(a[i]==a[x])return 0;
    }
    if(y<x)return 1;
    else return 0;
}
void search(int x,int y)
{
    if(temp==1||sum==p)return ;
    if(y==1)//特判x==1的情况;
    {
        for(int i=1;i<=n;++i)
            if(flag[a[i]]==0)
            {
                flag[a[i]]=1;
                cout<<a[i]<<endl;
                sum++;
		k++;
                if(sum==p)
		{
		    temp=1;
		    break;
		}
            }
        return ;
    }
    else
    {
       if(x>y)
       {
           //cout<<sum<<" **"<<endl;
		   k++;
           cout<<memor[1];
           for(int i=2;i<=y;++i)cout<<" "<<memor[i];
           cout<<endl;
           sum++;
           return ;
       }
       for(int i=1;i<=n;++i)
       {
          if(a[i]>=memor[x-1]&&pc(i,position[x-1])==1&&flag[i]==0)
          {
               flag[i]=1;
               position[x]=i;
               memor[x]=a[i];
               search(x+1,y);
               memor[x]=0;
               flag[i]=0;
          }
       }
    }

}
int main()
{
    while(cin>>n>>p)
    {
        sum=0;
        memset(a,0,sizeof(a));
        memset(flag,0,sizeof(flag));
        memset(position,0,sizeof(position));
        memset(memor,0,sizeof(memor));
        for(int i=1;i<=n;++i)cin>>a[i];
        for(int i=1;i<=n;++i)
        {
            memset(flag,0,sizeof(flag));
            temp=0;k=0;
			//cout<<"i="<<i<<endl;
            search(1,i);
			if(k==0)break;
            //cout<<i<<"**"<<endl;
        }
        cout<<endl;
    }
    return 0;
}


The end;



       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值