问题描述
克拉克是一名人格分裂患者。某一天克拉克变成了一名图论研究者。 他学习了最小生成树的几个算法,于是突发奇想,想做一个位运算and的最大生成树。 一棵生成树是由n−1n-1n−1条边组成的,且nnn个点两两可达。一棵生成树的大小等于所有在生成树上的边的权值经过位运算and后得到的数。 现在他想找出最大的生成树。
输入描述
第一行是一个整数T(1≤T≤5)T(1 \le T \le 5)T(1≤T≤5),表示数据组数。 每组数据第一行是两个整数n,m(1≤n,m≤300000)n, m(1 \le n, m \le 300000)n,m(1≤n,m≤300000),分别表示点个数和边个数。其中n,m>100000n, m > 100000n,m>100000的数据最多一组。 接下来mmm行,每行333个整数x,y,w(1≤x,y≤n,0≤w≤109)x, y, w(1 \le x, y \le n, 0 \le w \le 10^9)x,y,w(1≤x,y≤n,0≤w≤109),表示x,yx, yx,y之间有一条大小为www的边。
输出描述
每组数据输出一行一个数,表示答案。若不存在生成树,输出000。
输入样例
1 4 5 1 2 5 1 3 3 1 4 2 2 3 1 3 4 7
输出样例
1</pre>
题目要求生成树相互&后的最大值。 我们确定最后的ans的值不会大于(1<<30)[定会小于最大边的权值]。 利用二进制思路,我们从最高位来枚举结果,若结果第i为1,那么我们就只需要考虑权值第i位为1 的边,然后利用并查集判断这些边是否是生成树,若是,继续枚举(i-1)位,但是在符合第i位为1的前提下进行。因为结果是最大值。
/* data: 3 3 1 2 1 1 3 11 2 3 10 */ #include<stdio.h> #include<math.h> #include<stdlib.h> #include<algorithm> #include<string.h> #include<vector> using namespace std; typedef long long LL; const int M = 1e9; struct node{ int ver; int vertex; int val; }edge[300010],tmp[300010]; int fa[300010]; int find(int x){ if(fa[x] != x) fa[x] = find (fa[x]); return fa[x]; } void merge(int x,int y){ int dx = find(x); int dy = find(y); if(dx != dy){ fa[dx] = dy; } } int main() { int i,j,k,n,m,t,ans,mm; scanf("%d",&t); while(t--){ ans = 0 ; scanf("%d%d",&n,&m); for(i=0;i<m;i++){ scanf("%d%d%d",&edge[i].ver,&edge[i].vertex,&edge[i].val); } for(i=30;i>=0;i--){ for(int i=1;i<=n;i++){ fa[i] = i; } int count; count = mm = 0 ; for(j=0;j<m;j++){ //if(i==1) printf("i==1\n"); //if((ans!=0 && ((1<<i) & ans)) || ans == 0) // ans尽量大,所以第一次按最高位得到的ans定有用,但后面满足生成树还需判断 if((1<<i) & edge[j].val){ merge(edge[j].ver,edge[j].vertex); tmp[mm++] = edge[j]; } } for(j=1;j<=n;j++){ if(fa[j] == j){ count ++; } } if(count == 1){ m = mm; for(int i = 0;i<m;i++){ edge[i] = tmp[i]; } ans += (1<<i); } } printf("%d\n",ans); } return 0; }
借鉴http://blog.csdn.net/aozil_yang/article/details/50642966#reply
法二:找规律:
其实随便画几个就能看出规律来,
我直接说出规律了,,比如说N = 8的时候,
那么结果就是:
S1 = 1 + 2 + 3 + 4 + 5
S2 = 2 + 4 + 6 + 8
S3 = 3 + 6 + 9
S4 = 4 + 8
S5 = 5
S1 + S2 +... + S5 在加上N,就是结果!
规律很明显,很容易敲出代码了!
你可以在圆上给n个点进行标号,然后你从1号开始往后连线,直到最后,你会发现,能产生交点的只有2,3,4.。。。n-3,n-2位置的点才会产生交点。所以总共循环n-3次,所以n = 8 只要5次就够,s1,s2,s3..s5,就是对应的2号位置的连线,3号位置的连线,直到6号位置的连线!
#include<iostream> #include<cstdio> #include<set> #include<map> #include<vector> #include<cstring> #include<string> #include<sstream> #include<algorithm> #include<cmath> #include<cctype> #include<cstdlib> #define mem(x) memset(x,0,sizeof(x)); #define mem1(x) memset(x,-1,sizeof(x)); using namespace std; const int maxn = 1000 + 10; const int maxt = 75 + 10; const double eps = 1e-8; const int INF = 1e8; const double pi = acos(-1.0); typedef long long ll; typedef unsigned long long llu; int main() { int n,T; cin >> T; while(T--){ cin >> n; llu num = n-3; llu sum = n; for (int i = 1; i <= n-2; ++i){ sum += num*i+num*(num-1)/2*i; num--; } cout << sum << endl; } return 0; }