【题目】
给一个无向图,每当对某个点操作,该点以及与该点相连的点都获得标记,问标记所有点至少需要操作多少次
输入
第一行为T,表示测试数据组数
每组测试数据第一行为n(1<=n<=20)表示有n个点,接下来n行,第i行描述与结点i所连的点,首个整数d,接下来有d个整数,表示结点i与它之间有一条边,该图没有自环
输出
每组测试数据输出一行,一个数表示至少需要操作的次数
【题解】
状态压缩+枚举
这道题数据范围好小,那么就应该不是什么特殊的算法了,可以考虑下搜索
将每个点与其他点的关系压缩,记录到数组中(比如a[i]),用一个最长20位的二进制数记录,比如结点i,当他的第j位是1,表示i与j之间有一条边,我们不妨设结点i的第i位为1。
我们设b代表当前每个点是否被标记的状态,第i位为1表示结点i已经被标记,初始时b为0,就是什么点都没有被标记。
那么开始搜索,不断枚举点,比如说枚举第i个结点,则调出来他与其他点的连接关系a[i],则b=b|a[i],一直枚举知道b=1<<n-1即可1<<n-1就是所有点都被标记的状态
但是DFS会超时,我们改变思路,只需枚举每个点是否被标记就可以了,枚举从1到1<<n-1,比如说枚举到i,其第j位为1表示我们对结点j操作,每枚举一个数,就统计一下它的二进制中有多少个1,统计满足条件时1的最小个数就可以了~
【吐槽】
竟然不是DP,竟然不是DP,还好不是,状压DP用着还不熟练。
一开始真的用DFS,但是TLE,接着加了各种剪枝,还是TLE,最后死心想想还是枚举一下标记状态吧,结果竟然AC了,比DFS加上剪枝还要快!还是位运算大法好啊。
【代码】
RunID |
User |
Problem |
Result |
Memory |
Time |
Language |
Length |
Submit Time |
2549596 |
Accepted |
0 KB |
1516 ms |
939 B |
2014-07-31 16:15:30 |
#include<cstdio> #include<cstring> #include<cmath> #include<iostream> #define eps 0.000001 #define N 30000 #define M 60000 using namespace std; int i,j,k,n,m,x,y,T,b,a[300],tar,ans,tt; int main() { scanf("%d",&T); while (T--) { memset(a,0,sizeof(a)); scanf("%d",&n); tar=(1<<n)-1; for (i=1;i<=n;i++) { scanf("%d",&k); a[i]=a[i]|1<<(i-1); for (j=1;j<=k;j++) { scanf("%d",&x); a[i]=a[i]|1<<(x-1); } } ans=300;b=0; for (i=1;i<=tar;i++) { b=i;tt=0; for (j=1;j<=n;j++) if ((b&(1<<(j-1)))) { tt++; } for (j=1;j<=n;j++) { if ((i&(1<<(j-1)))) { b=b|a[j]; if (b==tar) break; } } if (b==tar) ans=min(ans,tt); } printf("%d ",ans); } }