简单路径-bitset初体验
题目描述
给定一个nn个点的无向图,求这个图中有多少条长度为4的简单路径。
n≤1500
输入
第一行一个数n
接下来n行每行n个0或1
第i行第j列是1表示i与j联通
输出
输出简单路径的个数
样例输入
5
00011
00000
00010
10100
10000
样例输出
2
提示
n<=1500
复制一下cgt大佬的ppt(未经授权hhh)
O(n^4)
不会做??
你可以离开这个教室了
暴力枚举四个城市即可
O(n^3)
70分做法:设经过的点为a-b-c-d,枚举中间边b-c,再枚举与b,c相连的点,设deg x 表示点x的度数,那么边b-c对答案的贡献为(du[b]-1)(du[c]- 1) - 经过b-c这条边的三元环个数。 计算三元环的个数只需要枚举除b,c之外的另一个点即可,时间复杂度O(n^3)
70分算法的瓶颈在于三元环计数
那如何解决呢
so easy
我们能想到压位
记s[i][1]为i和1~32的状态
第x位上是1的话就说明它和x号城市有连边
s[i][2]为i和33~64号……以此类推
i点和j点形成三元环的个数s可以这样写出
for k=1 ~ n/32 s=s+(s[i][k]&s[j][k] 中1的个数)
前面提到统计1的个数可以预处理出
所以时间复杂度变为了O(n^3 / 32)
n=1500时大约为1s
因此我们小小地使用了一个压位技巧便能AC此题
题目源JZOJ4857
所以我开始了手动bitset(程序灰常的丑嘤嘤嘤)
#include<bits/stdc++.h> using namespace std; typedef long long ll; int a[1605][1605],s[1600][50],f[1605],b[66000]; int main() { int n; scanf("%d",&n); for (int i=1;i<(1<<16);i++) b[i]=b[i>>1]+(i&1); for (int i=1;i<=n;i++) { for (int j=1;j<=n;j++) { scanf("%1d",&a[i][j]); if (a[i][j]==1) { f[i]++; } } } for (int i=1;i<=n;i++) for (int j=1;j<=(n-1)/30+1;j++) for (int k=(j-1)*30+1;k<=j*30;k++) s[i][j]=(s[i][j]<<1)|a[i][k]; ll num=0; for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) { if (a[i][j]==1) { for (int k=1;k<=(n-1)/30+1;k++) { int xx=s[i][k]&s[j][k]; int xxx=xx>>16; xx=xx&((1<<16)-1); num=num-b[xxx]-b[xx]; } num=num+(f[i]-1)*(f[j]-1); } } printf("%lld",num); }
然而我发现c++这种神奇的东西还有美丽的bitset,于是不用白不用哈
#include<bits/stdc++.h> using namespace std; bitset<1600>a[1600]; int num[1600]; int main() { int n; long long sum; cout<<sum; scanf("%d",&n); for (int i=0;i<n;i++) cin>>a[i]; for (int i=0;i<n;i++) num[i]=a[i].count(); for (int i=0;i<n;i++) for (int j=0;j<n;j++) if ((i!=j)&&(a[i][n-j-1]==1)) { sum=sum+(num[i]-1)*(num[j]-1)-(a[i]&a[j]).count(); } printf("%lld",sum); return 0; }
是不是很简单呢啦啦啦。
就是酱紫。
真是充实的一天哇。
还充实巩固了RMQ
for (int j=1;j<=20;j++) for (int i=1;i+(1<<j)-1<=n;i++) f1[i][j]=max(f1[i][j-1],f1[i+(1<<(j-1))][j-1]);