先上代码:

#define MX 10
#define NX 200
using namespace std;
int a[MX + 10],p[MX + 10][NX + 10];
long long t[1000000 + 10];
int k,M;
long long ans,cnt,b;
int check(int x)
{
    int L = 1,R = cnt,mid;
    while(L < R)
    {
        mid = (L + R) >> 1;
        if (t[mid] > x)
        {
            R = mid;
        }
        else
        {
            L = mid + 1;
        }
    }
    if (t[L] <= x)
    {
        return 0;
    }
    return cnt - L + 1;
}
void dfs1(int n,int sum)
{
    if (n > 3)
    {
        if (sum > M)
        {
            ans += b;
        }
        else
        {
            int q = check(M - sum);
            ans += q;
        }
        return ;
    }
    for (int i = 1;i <= a[n];i++)
    {
        dfs1(n + 1,sum + p[n][i]);
    }
    dfs1(n + 1,sum);
}
void dfs2(int n,int sum)
{
    if (n > k)
    {
        t[++cnt] = sum;
        return ;
    }
    for (int i = 1;i <= a[n];i++)
    {
        dfs2(n + 1,sum + p[n][i]);
    }
    dfs2(n + 1,sum);
}
int main()
{
    while(cin >> k >> M)
    {
        ans = cnt = 0;
        b = 1;
        int i,j;
        for (i = 1;i <= MX;i++)
        {
            a[i] = 0;
            for (j = 1;j <= NX;j++)
            {
                p[i][j] = 0;
            }
        }
        for (i = 1;i <= k;i++)
        {
            cin >> a[i];
            for (j = 1;j <= a[i];j++)
            {
                cin >> p[i][j];
            }
            if (i > 3)
            {
                b *= (a[i] + 1);
            }
        }
        if (k <= 3)
        {
            dfs2(1,0);
            for (i = 1;i <= cnt;i++)
            {
                if (t[i] > M)
                {
                    ans++;
                }
            }
        }
        else
        {
            dfs2(4,0);
            sort(t + 1,t + cnt + 1);
            dfs1(1,0);
        }
        cout << ans << endl;
    }
    return 0;
}
 

这道题难度不高(大概是黄题左右,CF大概1400)。显然,这道题是DFS,但是很不幸,如果按照传统的DFS函数来写,那么每次分叉的“树枝”个数就是100 + 1 = 101(100表示ai种任选一个的最大值,1表示整个ai一类都不选),而深度是k <= 6。取最大值6,不难发现搜索状态空间的大小远远超出一般DFS算法题目的时间限制(100)^6 = 10^12次。

如果说普通计算机1s能算1e8次,那么10^12就得算将近3个小时。。。。

其他题解(不管是蒟蒻还是大佬们)的大佬们一般都是用的DFS+剪枝,这确实难度不高,很容易写。虽然剪枝确实能够大量减少状态空间,但是不能对时间复杂度的降低有任何贡献(换句话说,你一旦碰上了刁钻的数据,容易暴毙;甚至是被卡常爆零qwq。。。)。

本人蒟蒻提供一种个人感觉很不错的算法(一种新颖的分治搜索):

首先,我们将k分解成3 + (k - 3),那么在k > 3时,我们就可以先对后3个种类物品进行搜索,用一个t[]来存储对后三种物品搜索后好感度之和(这大概是100^3 = 1e6次,对计算机来说很轻松)。然后再对t[]进行从小到大排序(为了使得t[]具有单调性,以保证后续二分查找的有效性)。

然后,我们再对前三种物品进行搜索(效率同上),搜索途中(已经搜索过了了3种物品)可以剪枝(比如说当当前选中物品的好感度之和已经>M,满足条件,那么就可以用ans += a[4] * a[5] * a[6],不过要注意的是,a[5],a[6]可能是0,所以建议最好在读入时就用b记录下)。搜索最后,我们可以二分查找M - sum,找到在t[]中第一个>M - sum的位置,那么此时该位置包括该位置后面的数+sum就一定>M满足条件了,所以二分查找check()函数的返回值就是当前sum加上后三种物品后的可行解的个数!最后累加到ans输出即可。

要注意的是,二分查找每个人有每个人喜欢的写法,这里我不多BB了。

PS:还有一种非常高妙的搜索算法(分治+分块+二分+DFS+模拟+双指针(+快速幂)),速度极快,题目中k <= 6若改为k <= 10,估计大部分人的代码都卡掉了(包括上述我提供的代码),但是这种“神奇”的算法却不会。这里献上:

#include <bits/stdc++.h>
#define MX 10
#define NX 100
#define SEX 10000
using namespace std;
int k,M;
int a[MX + 10][NX + 10];
void dfs(int s,int e,vector<long long> &v,long long sum) 
{
    if (s > e)
	{
		v.push_back(sum); 
		return;
	}  
    dfs(s + 1,e,v,sum);
    for (int i = 1;i <= a[s][0];i++)
	{
		dfs(s + 1,e,v,sum + a[s][i]);
	} 
}
long long fun(vector<long long> &m,vector<long long> &t, long long R) 
{
    long long cnt = 0;
    int j = t.size() - 1;
    for (int i = 0;i < m.size();i += SEX) 
	{
		int len = (int)(m.size());
        int end = min(len,i + SEX);
        while (j >= 0 && m[i] + t[j] > R) 
		{
			j--;
		}
        for (int k = i;k < end;k++) 
		{
            while (j >= 0 && m[k] + t[j] > R) 
			{
				j--;
			}
            cnt += t.size() - j - 1;
        }
    }
    return cnt;
}
long long solve() 
{
    vector<long long> front,mid,tail;
    int sp1 = min(3,k);
    dfs(1,sp1,front,0);
    if (k > 3) 
	{
        int sp2 = min(6,k);
        dfs(4,sp2,mid,0);
        if (k > 6) 
		{
			dfs(7,k,tail,0);
		}
    }
    sort(mid.begin(),mid.end());
    sort(tail.begin(),tail.end());
    long long ans = 0;
    for (int i = 0;i < front.size();i++)
	{
		long long q = front[i];
        long long r = M - q;
        if (k <= 3) 
		{
			ans += (q > M);
		}
        else if (k <= 6) 
		{
            ans += mid.end() - upper_bound(mid.begin(),mid.end(),r);
        } 
		else 
		{
            ans += fun(mid,tail,r);
        }
    }
    return ans;
}
int main() 
{
	int i,j;
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    while (cin >> k >> M) 
	{
        for (i = 1;i <= k;i++)
	 	{
            cin >> a[i][0];
            for (j = 1;j <= a[i][0];j++)
            {
            	cin >> a[i][j];
			}
        }
        cout << solve() << '\n';
    }
    return 0;
}

该代码配上快速幂和位运算效率(上面没搭配)可以缩小到约为6e7(k == 9,ai均为1000时).过于神奇就不多说了。没准可以拿个专利什么的,呵呵呵,给我高兴一下午。。。。