某个ZZ查资料得到的经验教训——
- 某个算法有中文名字就不要去查原名(╯‵□′)╯︵┻━┻
- 除非你要看证明过程或者是写博客,否则有些东西,不一定要去看维基百科OR论文。毕竟看到一些数学类的知识,头疼<(_ _)>
- 要看清题号再做题◑﹏◐,否则就会找到一道(自认为)很难的题,然后自闭挡四防御力up
- 不要熬夜U•ェ•*U
具体内容代码见
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstring> 2 #include<iostream> 3 #include<cstdio> 4 #include<algorithm> 5 using namespace std; 6 /* 7 大致思路————首先看能否生成最小生成树 8 其次找最小生成树中可以被替换的权值,每次枚举一种, 9 把其他权值进行缩点。由于我们不在意其他边究竟是什么,所以只要知道是否联通即可 10 后再辅以矩阵树定理和乘法原理求值 11 前置知识————1.最小生成树 12 2.矩阵树定理 13 3.分块的基本思想(其实不是必需的QAQ,现理解也可以) 14 4.乘法原理 15 (划掉)5.数学计算并且不会算错数(划掉) 16 9.磅15便士 17 */ 18 namespace the_Death{ 19 const int maxn=1e3+5,MAXN=1e3+5,mod=31011; 20 int n,m,tot,fa[maxn],a[maxn][maxn],del[maxn],val[maxn]; 21 //val[maxn]->指的是有多少不重复的值 22 //del[maxn]->是指缩点重构后的图 23 //a[maxn][maxn]->基尔霍夫矩阵 24 struct ziji{ 25 int x,y,z; 26 bool operator <(const ziji&b)const{return z<b.z;} 27 #define x(i) mn[i].x 28 #define y(i) mn[i].y 29 #define z(i) mn[i].z 30 #define u(i) tree[i].x 31 #define v(i) tree[i].y 32 #define w(i) tree[i].z 33 }mn[maxn],tree[MAXN]; 34 //mn[maxn]->是原先保存图的数组 35 //tree[maxn]->是最小生成树中树的构成点的图 36 inline int find(int x){return fa[x]==x?x:find(fa[x]);} 37 inline void it(){for(register int i=1;i<=n;i++)fa[i]=i;} 38 inline void add(int x,int y){ 39 --a[x][y],--a[y][x],++a[x][x],++a[y][y]; 40 //构造基尔霍夫矩阵 41 } 42 inline void merge(int x,int y){fa[find(x)]=find(y);} 43 inline int gauss(int n){ 44 //利用辗转相除术进行高斯消元 45 //之所以用辗转相除,只为了保持精度 46 int ans=1; 47 for(register int i=1;i<=n;i++){ 48 for(register int k=i+1;k<=n;k++){ 49 while(a[k][i]){ 50 int d=a[i][i]/a[k][i]; 51 for(register int j=i;j<=n;j++) 52 a[i][j]=(a[i][j]-1LL*d*a[k][j]%mod+mod)%mod; 53 swap(a[i],a[k]),ans=-ans; 54 //交换行,行列式改变 55 } 56 } 57 ans=1ll*ans*a[i][i]%mod,ans=(ans+mod)%mod; 58 //乘法原理累计 59 } 60 return ans; 61 } 62 inline bool kruskal(){ 63 sort(tree+1,tree+1+m);it(); 64 int cnt=0; 65 for(register int i=1;i<=m;i++){ 66 int root=find(u(i)),root1=find(v(i)); 67 if(root==root1) continue; 68 fa[root]=root1,mn[++cnt]=tree[i]; 69 //把用到的点放到mn[maxn]中去 70 if(w(i)!=val[tot]) val[++tot]=w(i); 71 //记录不重复的点权 72 } 73 return cnt==n-1; 74 } 75 inline void addedge(int v){ 76 for(register int i=1;i<n&&z(i)!=v;i++) merge(x(i),y(i)); 77 for(register int i=n-1;i&&z(i)!=v;i--) merge(x(i),y(i)); 78 //双向更新新树的联通 NOTICE:YOU MUST DO IT LIKE THIS 79 80 } 81 inline int getblock(){ 82 //得到它缩点后有多少个点 83 int blo=0; 84 for(register int i=1;i<=n;i++) 85 if(find(i)==i) del[i]=++blo; 86 for(register int i=1;i<=n;i++) del[i]=del[find(i)]; 87 //类似于分块的操作 88 return blo; 89 } 90 inline void rebuild(int v){ 91 //每次都要去找一个边权,看他有几种转化的边,所以每一次都要rebuild 92 memset(a,0,sizeof(a)); 93 for(register int i=1;i<=m;i++) 94 if(w(i)==v) add(del[u(i)],del[v(i)]); 95 //如果找到了,那你就记录构造基尔霍夫矩阵 96 } 97 int main(){ 98 scanf("%d%d",&n,&m);int ans=1; 99 for(register int i=1;i<=m;i++) 100 scanf("%d%d%d",&u(i),&v(i),&w(i)); 101 if(!kruskal()){puts("0");system("pause");return 0;} 102 for(register int i=1;i<=tot;i++){ 103 it();addedge(val[i]);int blo=getblock(); 104 rebuild(val[i]);ans=1ll*ans*gauss(blo-1)%mod; 105 } 106 printf("%d\n",ans);system("pause");return 0; 107 } 108 } 109 int main(){ 110 the_Death::main();return 0; 111 }