给自己出题的小X
Problem Description
小X学习了dfs,为了练习搜索,开始给自己出题玩。
玩着玩着,一会把自己难住了,一会又被自己难倒了,真是有趣诶!
小X出的题:
现在有N个不同的正整数,求它们可以组成多少个这样的集合,满足:
- 集合内的元素数量S>1
- 集合内任意两个数的差的绝对值都大于集合内的元素数量。
Input
第一行,一个正整数T(T<=20)表示数据组数。
对于每组数据,有两行。第一行为一个正整数N(3≤N≤25),第二行为N个用空格隔开的正整数xi(xi≤200)。
Output
对于每组数据,输出一行一个整数表示题中所描述的集合的个数。
Sample Input
1
5
2 3 5 8 1
Sample Output
6
题意:
给出N(N≤25)个不同的正整数,求有多少个这样的集合,集合内存在至少两个元素,且任意两个元素差值都大于集合内的元素数量。
解题思路:
以下是出题人的题解
正如题面中所描述的,出题人的意图是出一个搜索题。
该题,是按一定顺序枚举子集,然后检查子集是否合法。
时间上,子集数目最多为2N2N 个,判断是否合法可以在搜索过程中检查,因此时间复杂度为O(T*2N2N),T为数据组数。
不过根据题目数据范围xi≤200,合理地搜索一定可以剪枝,因此实际上搜索用时会低于极限复杂度,时间上可以接受。
具体来说:第一步还是先排个序,这样,“任意两个元素差值都大于集合内的元素数量”就等价于“排序后间隔最小的两个相邻元素差值大于集合内的元素数量”;
然后,可以从2开始枚举集合里的元素数目,在确定了元素数目之后dfs依次搜索第i个数,dfs过程中保证第i个数合法。
另外,不枚举集合内元素数目也是可以的,直接dfs依次枚举第i个数,当确定下一个数是否合法时,需要检查截止目前的最小相邻间隔是否大于目前集合内的元素数量。这一过程最好在dfs时记录并传递一个相邻间隔最小值。
值得一提的是,此题可以不用搜索完成,使用动态规划————事实上,在许多时候,指数级复杂度的搜索问题都可以尝试着用动态规划来解决。
其中一种动态规划方法是,定义dp[i][j][k] 表示前i个数里选j个数,并且第i个数要选,相邻最小间隔大于k的方案数。
初始化为 dp[i][1][k]=1(1 ≤ i ≤ N, 1 ≤ k ≤ N)
状态转移方程是:
dp[i][j][k]=∑p=1idp[p][j−1][k](a[i]−a[p]>k)
答案为 ∑Ni=1∑Nk=2dp[i][k][k]
时间复杂度为O(N^4 )。
我的解法:DFS。先排序。用last记录上一个加入集合的下标,i记录当前准备用来加入集合的下标,s表示当前集合中元素数量,m表示当前集合中差的绝对值的最小值。如果当前元素可以加入,ans++,继续更新last,i,s和m后继续搜下一层。不管当前元素能不能加入,都要考虑不加入的情况,只有i+1,其他不变继续搜下一层。DP的方法有空再写写吧
Code:
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int INF=1e8;
int a[30];
int ans=0;
int n;
void DFS(int last,int i,int s,int m)
{
if(i>=n||s<=0||m<=s)
return;
if(a[i]-a[last]>s+1&&m>s+1)
{
ans++;
DFS(i,i+1,s+1,min(m,a[i]-a[last]));
}
DFS(last,i+1,s,m);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",a+i);
sort(a,a+n);
ans=0;
for(int i=0;i<n-1;i++)
{
DFS(i,i+1,1,INF);
}
printf("%d\n",ans);
}
return 0;
}
/**********************************************************************
Problem: 1973
User: HN0017
Language: C++
Result: AC
Time:112 ms
Memory:2020 kb
**********************************************************************/