题目大意:
就是现在一个图中100个点, 代表100个人, 每个人如果视另外一个人为朋友, 就有一条从这个人联想其他人的边, 每天每个人自己拥有的RP值要平分给所有其视为朋友的人, 同样的一个人分发RP值之后也会受到RP值, 如果这个过程不会改变RP值的分布, 则这样一个RP值的分布是稳定的, 现在所有人的RP总和是1, 对于给定的图, 计算是否存在唯一的稳定分布, 如果存在唯一的稳定分布, 那么对于这个稳定分布, 你有一个朋友是编号n - 1的那个, 现在要想从这个点出发连一条有向边(不造成重边), 如果连上之后得到的新的图形中稳定分布RP值之后这个朋友能得到更多稳定RP值, 找出使得新的图中n - 1拥有的RP值最大的方案, 需要连到哪个人, 如果有多个输出编号最小的, 如果没有输出-1
大致思路:
自己做的时候只想到找是否有多个稳定解直接用Gauss消元但是没有想到后面添加边的方法要怎么做, 暴力枚举+高斯消元的话复杂度太高肯定会超时
后来看了一些题解发现方法很巧妙, 利用了Gauss消元进行的时候一个特性, 就是讲前n个变量的系数都变成了1, 其他的都变成了0, 就造成了很多巧合会导致可以将其他变量附加到n个方程中1次Gauss消元即可
具体过程看代码, 另外这个方法真的很巧妙, 真是很深地利用了Gauss消元...
为什么这样做是对的需要一定的意会吧...具体解释的话很长不太好写...算了不写了...看代码中方程是怎么构造的吧, 应该能意会出来
代码如下:
Result : Accepted Memory : 1800 KB Time : 156 ms
/* * Author: Gatevin * Created Time: 2015/4/9 21:19:29 * File Name: Rin_Tohsaka.cpp */ #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> #include<iomanip> using namespace std; const double eps(1e-8); typedef long long lint; #define foreach(e, x) for(__typeof(x.begin()) e = x.begin(); e != x.end(); ++e) #define SHOW_MEMORY(x) cout<<sizeof(x)/(1024*1024.)<<"MB"<<endl #define maxn 110 double a[maxn][maxn*2], x[maxn]; int var, equ; int Gauss() { for(int k = 0, col = 0; k < equ && col < var; k++, col++) { int max_r = k; for(int i = k + 1; i < equ; i++) if(fabs(a[i][col]) > fabs(a[max_r][col])) max_r = i; if(fabs(a[max_r][col]) < eps) return 0; if(k != max_r) { for(int j = col ; j < var; j++) swap(a[k][j], a[max_r][j]); swap(x[k], x[max_r]); } x[k] /= a[k][col]; for(int j = col + 1; j < var; j++) a[k][j] /= a[k][col]; a[k][col] = 1; for(int i = 0; i < equ; i++) if(i != k) { x[i] -= x[k]*a[i][k]; for(int j = col + 1; j < var; j++) a[i][j] -= a[k][j]*a[i][col]; a[i][col] = 0; } } return 1; } bool G[maxn][maxn]; int deg[maxn]; int from[maxn]; int n, m; /* * 对于n个点的RP值平衡关系可以建立n个方程, 当时这n个方程只要其中n - 1个满足那么第n个一定满足 * 应为其他点的RP值守恒了, 最后一个点的自然也守恒了 * 所以第n个方程应该是所有点的RP和是1, 这样一共得到了n个当成的n元方程组 * 为了一起解决接下来添加边的问题, 由于添加边的话, 未知数的数量没有改变 * 考虑添加var的数量, 改造原来的方程组, 对于每一个方程, 后面都添加一些变量 * 这些变量依次代表了新图中每个点的解, 这狗构造很巧妙, 利用了上面这个Gauss消元过程中 * 消元之后n个方程的前n个未知数都只有对应的a[i][i]是1 * 那么附加的一些变量就没有被消掉, 却可以利用这个状态直接得到解 * 需要意会, 看代码自己画一下就明白了 */ int main() { int t, u, v; scanf("%d", &t); while(t--) { scanf("%d %d", &n, &m); memset(G, 0, sizeof(G)); memset(deg, 0, sizeof(deg)); for(int i = 0; i < m; i++) { scanf("%d %d", &u, &v); if(!G[u][v]) deg[u]++; G[u][v] = 1; } var = equ = n; memset(a, 0, sizeof(a)); memset(x, 0, sizeof(x)); for(int i = 0; i < n; i++) { a[i][i] = -1; for(int j = 0; j < n; j++) if(G[j][i]) a[i][j] = 1./deg[j]; } for(int i = 0; i < n; i++) a[n - 1][i] = 1; x[n - 1] = 1; /* * 上面构造出来的前n个方程组中没有n - 1的平衡态方程 * 那么当新增加可能的边的时候只会对前n - 1个方程造成影响 * 对于这样的情况附加一个变量即可 */ for(int i = 0; i < n - 1; i++) if(!G[n - 1][i])//可以连接的边 { for(int j = 0; j < n - 1; j++) if(G[n - 1][j] || j == i) a[j][var] = 1./(deg[n - 1] + 1); a[equ - 1][var] = 1; from[var] = i; var++; } if(!Gauss()) printf("INF\n"); else { int ans = -1; double rp = x[n - 1]; for(int i = n; i < var; i++)//这里刚开始逆向循环的...于是悲剧了没有编号最小 if(x[n - 1] / a[n - 1][i] > rp) rp = x[n - 1] / a[n - 1][i], ans = from[i]; printf("1 %d\n", ans); } } return 0; }