简单路径(Tourist Attractions) 题解 (模拟+二进制压位储存)
来源:互联网 发布:淘宝价格趋势图 编辑:程序博客网 时间:2024/04/27 23:25
给定一张n个点的无向图,统计有多少条简单路径恰好经过了4个点。
背景
在美丽的比特镇一共有n个景区,编号依次为1到n,它们之间通过若干条双向道路连接。
Byteasar慕名来到了比特镇旅游,不过由于昂贵的门票费,他只能负担起4个景区的门票费。他可以在任意景区开始游览,然后结束在任意景区。
Byteasar的旅游习惯比较特殊,一旦他路过了一个景区,他就一定会进去参观,并且他永远不会参观同一个景区两次。所以他想知道,有多少种可行的旅游路线,使得他可以恰好参观4个景区呢?即,有多少条简单路径恰好经过了4个点。
Input
第一行包含两个整数n,表示景区的总数。
第2至第n+ 1行,每行一个长度为n的01字符串,第i+ 1行第j 个字符为0表示i 和j 之间没有道路,为1表示有一条道路。
输入数据保证(i;j)的连接情况等于(j; i)的连接情况,且(i;i)恒为0。
Output
输出一行一个整数,即可行的路线总数。
Example
8条路线分别为:
1->2->3->4, 4->3->2->1,
2->3->4->1, 1->4->3->2,
3->4->1->2, 2->1->4->3,
4->1->2->3, 3->2->1->4。
Notes
部分分做法
- 40分做法:暴力枚举,时间复杂度O(n^4)
- 70分做法:设经过的点为a-b-c-d,枚举中间边b-c,再枚举与b,c相连的点,设deg x 表示点x的度数,那么边b-c对答案的贡献为(deg b-1)(deg c- 1) - 经过b-c这条边的三元环个数。 计算三元环的个数只需要枚举除b,c之外的另一个点即可,时间复杂度O(n^3)
AC做法
70分算法的瓶颈在于三元环计数。
设S x 表示所有和x有边的点的集合,那么其实就是统计Sb与Sc的并集的元素数。将S用二进制压位存储即可并行计算。时间复杂度O(n^3/32)。
70分代码
统计Sb与Sc的并集的元素数未加优化,超时
#include<cstdio>#include<iostream>#include<algorithm>#include<cstring>#include<cmath>#define LL long longusing namespace std;const int maxn=1505;int n,m,c,sum[maxn];int map[maxn][50];LL ans;char s[maxn];struct note{ int u,v;}a[maxn*maxn/2];void addedge(int x,int y){ a[++c].u=x;a[c].v=y;}int check(int x,int y)//统计并集元素数{ int tmp=0; for (int i=1;i<=m;i++) { int t=map[x][i]&map[y][i]; while (t>0) { t&=t-1; tmp++; } } return tmp;}int main(){ scanf("%d",&n); for (int i=1;i<=n;i++) { scanf("%s",s); for (int j=1;j<i;j++) if (s[j-1]-'0') addedge(i,j);//统计一条边即可,最后再*2 m=0; int cnt=0; LL num=0; for (int j=1;j<=n;j++) { if (s[j-1]-'0') sum[i]++;//sum记录与i点相连的点数 cnt++; num=num*2+s[j-1]-'0'; if (cnt==31)//二进制压缩31位记一次(因为int有32位,最高位为符号位) { map[i][++m]=num; num=cnt=0; } } if (cnt!=0) map[i][++m]=num; } for (int i=1;i<=c;i++) { ans+=1LL*(sum[a[i].u]-1)*(sum[a[i].v]-1)-check(a[i].u,a[i].v); } ans=1LL*ans*2; printf("%lld\n",ans); return 0;}
计算二进制数中1的位数
这里介绍一个计算二进制数中1的位数的方法(原帖称作完美法?!!!)
以下从原帖中复制
int BitCount5(unsigned int n){ unsigned int tmp = n - ((n >>1) &033333333333) - ((n >>2) &011111111111); return ((tmp + (tmp >>3)) &030707070707) %63;}
最喜欢这个,代码太简洁啦,只是有个取模运算,可能速度上慢一些。区区两行代码,就能计算出1的个数,到底有何奥妙呢?为了解释的清楚一点,我尽量多说几句。
第一行代码的作用
先说明一点,以0开头的是8进制数,以0x开头的是十六进制数,上面代码中使用了三个8进制数。
将n的二进制表示写出来,然后每3bit分成一组,求出每一组中1的个数,再表示成二进制的形式。比如n = 50,其二进制表示为110010,分组后是110和010,这两组中1的个数本别是2和3。2对应010,3对应011,所以第一行代码结束后,tmp = 010011,具体是怎么实现的呢?由于每组3bit,所以这3bit对应的十进制数都能表示为2^2 * a + 2^1 * b + c的形式,也就是4a + 2b + c的形式,这里a,b,c的值为0或1,如果为0表示对应的二进制位上是0,如果为1表示对应的二进制位上是1,所以a + b + c的值也就是4a + 2b + c的二进制数中1的个数了。举个例子,十进制数6(0110)= 4 * 1 + 2 * 1 + 0,这里a = 1, b = 1, c = 0, a + b + c = 2,所以6的二进制表示中有两个1。现在的问题是,如何得到a + b + c呢?注意位运算中,右移一位相当于除2,就利用这个性质!
4a + 2b + c 右移一位等于2a + b
4a + 2b + c 右移量位等于a
然后做减法
4a + 2b + c –(2a + b) – a = a + b + c,这就是第一行代码所作的事,明白了吧。
第二行代码的作用
在第一行的基础上,将tmp中相邻的两组中1的个数累加,由于累加到过程中有些组被重复加了一次,所以要舍弃这些多加的部分,这就是&030707070707的作用,又由于最终结果可能大于63,所以要取模。
需要注意的是,经过第一行代码后,从右侧起,每相邻的3bit只有四种可能,即000, 001, 010, 011,为啥呢?因为每3bit中1的个数最多为3。所以下面的加法中不存在进位的问题,因为3 + 3 = 6,不足8,不会产生进位。
tmp + (tmp >> 3)-这句就是是相邻组相加,注意会产生重复相加的部分,比如tmp = 659 = 001 010 010 011时,tmp >> 3 = 000 001 010 010,相加得
001 010 010 011
000 001 010 010
————————
001 011 100 101
011 + 101 = 3 + 5 = 8。(感谢网友Di哈指正。)注意,659只是个中间变量,这个结果不代表659这个数的二进制形式中有8个1。
注意我们想要的只是第二组和最后一组(绿色部分),而第一组和第三组(红色部分)属于重复相加的部分,要消除掉,这就是&030707070707所完成的任务(每隔三位删除三位),最后为什么还要%63呢?因为上面相当于每次计算相连的6bit中1的个数,最多是111111 = 77(八进制)= 63(十进制),所以最后要对63取模。
AC代码
#include<cstdio>#include<iostream>#include<algorithm>#include<cstring>#include<cmath>#define LL long longusing namespace std;const int maxn=1505;int n,m,c,sum[maxn];int map[maxn][50];LL ans;char s[maxn];struct note{ int u,v;}a[maxn*maxn/2];void addedge(int x,int y){ a[++c].u=x;a[c].v=y;}int check(int x,int y){ int tmp=0; for (int i=1;i<=m;i++) { int t=map[x][i]&map[y][i]; unsigned int tt=t; unsigned int st=tt - ((tt >>1) &033333333333) - ((tt >>2) &011111111111); tmp+=((st + (st >>3)) &030707070707) %63; } return tmp;}int main(){ scanf("%d",&n); for (int i=1;i<=n;i++) { scanf("%s",s); for (int j=1;j<i;j++) if (s[j-1]-'0') addedge(i,j); m=0; int cnt=0; LL num=0; for (int j=1;j<=n;j++) { if (s[j-1]-'0') sum[i]++; cnt++; num=num*2+s[j-1]-'0'; if (cnt==31) { map[i][++m]=num; num=cnt=0; } } if (cnt!=0) map[i][++m]=num; } for (int i=1;i<=c;i++) { ans+=1LL*(sum[a[i].u]-1)*(sum[a[i].v]-1)-check(a[i].u,a[i].v); } ans=1LL*ans*2; cout<<ans<<endl;//ans太大了lld输不了,建议用Int64d或cout return 0;}
【小技巧】
从范围小的变量入手;点与边之间的转换
- 简单路径(Tourist Attractions) 题解 (模拟+二进制压位储存)
- Tourist Attractions 简单路径 (bitset)
- JZOJ4857. 【GDOI2017模拟11.4】Tourist Attractions
- Tourist Attractions
- Tourist Attractions
- JZOJ4857. Tourist Attractions
- gdfzoj #1002 Tourist Attractions(bitset)
- 【loli的胡策】Tourist Attractions(bitset)
- 数据储存---模拟简单备份短信
- 【bzoj 十连测】[noip2016 第一场]Problem B. Tourist Attractions(枚举)
- 位运算模拟二进制加法--位图方法
- programming-challenges The Tourist Guide (110903) 题解
- 2016暑期集训---搜索(简单BFS+路径储存)
- [FFT 压位] Hillan模拟赛 A.简单字符串匹配
- MASM-两个16位二进制数减法模拟
- 图片二进制储存到数据库
- 用int数组简单模拟位数组
- poj_1068 Parencodings(简单模拟+位运算)
- go 语言http请求案列。
- 数据类型,简单的C语言程序。
- #洛谷 P1101单词方阵
- BF518的interrupt
- 华为组练习题(一)
- 简单路径(Tourist Attractions) 题解 (模拟+二进制压位储存)
- 【1】JS基本结构(输出浏览器的版本号)
- 一锅大杂烩——各种数论の板子
- 第一个不同字符的位置
- java学习笔记(五)之面向对象
- 流程图绘制经验积累
- 第18天 Java面向对象小程序(二)
- 【NOIP2001】数的划分
- 笔记(gdb)