HDU 4818 RP problem (2013年长春现场赛F题) 高斯消元性质利用

题目大意:

就是现在一个图中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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值