您的任务是计算n的不同分配的数量 n个学生的不同主题,每个人都得到一个 他喜欢的话题。
每个测试用例以学生数n(1 <= n <= 20)开始。接下来的n行中的每一行包含描述一个学生的偏好的n个整数。 1在第i个位置意味着这个学生喜欢第i个主题,0意味着他绝对不想接受它。
我通过定义DP [i] [mask]来解决这个问题,以表示仅使用i元素形成蒙版集的方法的数量!
这里的面具是主题的一个子集,它显示我拍摄了多少和哪些主题。
重复发生
for(i=1;i<N;i++) //Student
for(j=1;j<(1<<N);j++) //Subject Subset
{
for(k=0;k<N;k++) //Selecting subject
if( (j&(1<<k)) && A[i][k] )
DP[i][j]+=DP[i-1][j^(1<<k)];
}
即。从第i个学生最喜欢的科目中选择一个科目并递归下州!!
然而,由于解决方案的复杂性为O(2 ^ N * N ^ 2),这还不够。
我们至少需要减少一个N!
如何降低此问题的复杂性?这是我的代码:
#include<bits/stdc++.h>
using namespace std;
long long DP[20][(1<<20)+1];
int main()
{
int T;
scanf("%d",&T);
for(;T;--T)
{
int N,i,j,k;
scanf("%d",&N);
int A[N+1][N+1];
for(i=0;i<N;i++)
for(j=0;j<N;j++)
scanf("%d",&A[i][j]);
/*
First of all let's think about the state!!
DP[i][j] where i is the i th student I am considering j is a bitmask which tells me which all subjects are
Done!!
********All Right************
So what can the recurrence be..?
traverse over the array A[i][]
If We can use the k th element of i.e A[i][k].
We need to try assigning it and Get the number of ways
*********Seems Fine *********
What will be the base case??
When only one element left in the mask and i is 1 we won't traverse more down!!
**OK**
SO what is the topological order of DP states !>>>????
I dont Know!! Let's think... Let me explain ummmmmmmmmmmmmmmmmmmmmmmmmmmm
ummmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
I am like calling a smaller i with smaller subset!
for every i
go in the order of increasing subsets
I think that should work!! Let's see
*/
for(i=0;i<(1<<N);i++)
DP[0][i]=0;
for(i=0;i<N;i++)
if(A[0][i])
DP[0][1<<i]=1;
for(i=1;i<N;i++) //Student
for(j=1;j<(1<<N);j++) //Subject Subset
{
DP[i][j]=0;
for(k=0;k<N;k++) //Selecting subject
if( (j&(1<<k)) && A[i][k] )
DP[i][j]+=DP[i-1][j^(1<<k)];
}
long long ans=0;
for(i=1;i<(1<<N);i++)
ans+=DP[N-1][i];
printf("%lld\n",ans);
}
return 0;
}
问题链接以防您需要:Spoj
答案 0 :(得分:2)
您可以使用两种技术来降低时间复杂度:
在中间见面
呼吸第一次搜索
所以,我们注意到,对于第一个人,我们不需要设置所有n位,但只需设置i位,而不是遍历所有数字(0到2 ^ n) ),我们只需要遍历所有设置了i位的数字。我们可以使用BFS
来做到这一点其次,如果我们使用一个数组dp
来存储将主题分配给上半部分n / 2个人的方式,以及其他数组dp1
来存储分配主题的方式的数量下半场n / 2人,所以将受试者分配给所有n个人的方式是
int x = a number that has n/2 bit set
int result = sum (dp[x] + dp1[2^n - 1 - x]);
时间复杂度为C(n,n / 2)* n / 2 * n,n = 20~3 * 10 ^ 7次运算。
答案 1 :(得分:1)
你可以通过在内循环上进行一些黑客微观优化来提高速度(在最坏的情况下输入我的计算机上的因子为3.7)。
这个想法是,给定一个二进制数,例如10100,你可以通过操作10100&amp; amp;提取单个设置位。 -10100 = 00100。
因此我们可以通过以下代码将循环改为k以仅循环重要位:
#include<bits/stdc++.h>
using namespace std;
long long DP[20][(1<<20)+1];
int main()
{
int T;
scanf("%d",&T);
for(;T;--T)
{
int N,i,j,k;
int masks[20]; // ADDED
scanf("%d",&N);
int A[N+1][N+1];
for(i=0;i<N;i++) {
masks[i] = 0;
for(j=0;j<N;j++) {
scanf("%d",&A[i][j]);
masks[i] |= A[i][j]<<j; // ADDED
}
}
for(i=0;i<(1<<N);i++)
DP[0][i]=0;
for(i=0;i<N;i++)
if(A[0][i])
DP[0][1<<i]=1;
for(i=1;i<N;i++) //Student
for(j=1;j<(1<<N);j++) //Subject Subset
{
long long t = 0; // ADDED
int mask = j & masks[i]; // ADDED
while(mask) { // ADDED
int bit = mask & -mask; // ADDED
t += DP[i-1][j - bit]; // ADDED
mask -= bit; // ADDED
} // ADDED
DP[i][j]=t; // ADDED
}
long long ans=0;
for(i=1;i<(1<<N);i++)
ans+=DP[N-1][i];
printf("%lld\n",ans);
}
return 0;
}
答案 2 :(得分:0)
嗯, 我发现我们可以让解决方案更快!这是如何!! 我们不需要照顾i循环!为什么?? 如果一个掩码包含i个比特,那么只有它可以获取我们某些方法来做其他明智的它是零,因为一个主题将被分配给每个! 所以现在,重复发生变为
for(每个位掩码) 获取掩码的位数!(这是我唯一与掩码相关联的)。由于上述论点,每个面具只与一个i相关联!
通过向学生提供所有有利的主题,检查我们可以达到当前状态的所有方式!现在它将导致一个较低位数的掩码,它也与唯一的i相关联,特别是i-1!
所以现在,我们可以在这种情况下拥有一维DP ......
for(j=1;j<(1<<N);j++) //Subject Subset
{
DP[j]=0;
i=__builtin_popcount(j);
for(k=1;k<=N;k++) //Selecting subject
if( (j&(1<<(k-1))) && A[i][k] )
DP[j]+=DP[j^(1<<(k-1))];
}
我的AC代码(运行时间0.78),通过预处理位数而不是__builtin_popcount()来改进范围
#include<bits/stdc++.h>
using namespace std;
long long DP[(1<<20)+1];
int main()
{
int T;
scanf("%d",&T);
for(;T;--T)
{
int N,i,j,k;
scanf("%d",&N);
int A[N+1][N+1];
for(i=1;i<=N;i++)
for(j=1;j<=N;j++)
scanf("%d",&A[i][j]);
for(i=0;i<(1<<N);i++)
DP[i]=0;
DP[0]=1;
for(j=1;j<(1<<N);j++) //Subject Subset
{
DP[j]=0;
i=__builtin_popcount(j);
for(k=1;k<=N;k++) //Selecting subject
if( (j&(1<<(k-1))) && A[i][k] )
DP[j]+=DP[j^(1<<(k-1))];
}
long long ans=0;
printf("%lld\n",DP[(1<<N)-1]);
}
return 0;
}
与@Peter的优化代码相比。他能够通过spoj优化它以通过测试数据的时间限制运行时间2.60秒。
#include<bits/stdc++.h>
using namespace std;
long long DP[20][(1<<20)+1];
int main()
{
int T;
scanf("%d",&T);
for(;T;--T)
{
int N,i,j,k;
int masks[20]; // ADDED
scanf("%d",&N);
int A[N+1][N+1];
for(i=0;i<N;i++) {
masks[i] = 0;
for(j=0;j<N;j++) {
scanf("%d",&A[i][j]);
masks[i] |= A[i][j]<<j; // ADDED
}
}
for(i=0;i<(1<<N);i++)
DP[0][i]=0;
for(i=0;i<N;i++)
if(A[0][i])
DP[0][1<<i]=1;
for(i=1;i<N;i++) //Student
for(j=1;j<(1<<N);j++) //Subject Subset
{
long long t = 0; // ADDED
int mask = j & masks[i]; // ADDED
while(mask) { // ADDED
int bit = mask & -mask; // ADDED
t += DP[i-1][j - bit]; // ADDED
mask -= bit; // ADDED
} // ADDED
DP[i][j]=t; // ADDED
}
long long ans=0;
for(i=1;i<(1<<N);i++)
ans+=DP[N-1][i];
printf("%lld\n",ans);
}
return 0;
}