随性补题记录

文章目录

    • 1、Pass the Buck(ICPC Greater New York Region 2019 C)(2020.4.25)
    • 2、关押罪犯(2020.4.27)

这里集中的是一些思路和来源比较散,不太好详细分类到具体补题记录里面的题。随性更新。

1、Pass the Buck(ICPC Greater New York Region 2019 C)(2020.4.25)

在Leetcode比赛前帮同学看的一道题。然后发现比较有意思就顺手做了。(虽然题面和输入输出都给得很丑)

看了半天,才发现意思就是给定一幅有向图,其中某个人 s s s在最开始会给他一个buck,之后会等可能地把这个buck传递给所有和他相邻的人中的某一个(包括自己)。若有个人 x x x在传递的过程中把这个buck传给了自己那他就赢了。给定 P P P个询问,输出buck从 s s s出发最后 t t t赢的概率。挂一下输入输出,让你们体会一下有多么丑陋。

输入

5 4 1 2 2 1 3 2 2 4 2 3 5 1 4 1 1 1 2 2 2 3 3 3 4 4 4 \begin {matrix}5&4\\1&2\\2&1&3\\2&2&4\\2&3&5\\1&4\\1&1&1\\2&2&2\\3&3&3\\4&4&4 \end {matrix} 512221123442123412343451234

第一行的意思是5个人,4组询问。之后五行,第 i i i行第一个数 d d d表示有 d d d个人与第 i i i个人相邻,之后 d d d个数表示与第 i i i个人相邻的人的编号。(这里保证不会有自环,虽然题目里没说)(而且为什么不按边读入啊…)之后四行,每行三个数,第一个数表示是第几组询问(多此一举…),第二个数表示从谁出发,第三个数表示谁赢。

输出

1 0.61818 2 0.47273 3 0.45455 4 0.47273 \begin {matrix}1&0.61818\\2&0.47273\\3&0.45455\\4&0.47273 \end {matrix} 12340.618180.472730.454550.47273

每行第一个数表示第几组询问,第二个五位小数表示概率。输出相对来说倒挺正常。

这题的思路很有意思。一个人胜利的条件一定是buck从起点 s s s出发,然后通过若干个人到终点 t t t,并且这之中不能有人自己给自己,最后 t t t再把buck给自己。

然后会发现这个玩意就是个矩阵乘法嘛。因为之前刚好学过相关内容:设 G G G为邻接矩阵(仅包含01),那么 G k [ a ] [ b ] G^k[a][b] Gk[a][b]表示从 a a a点恰好经过 k k k条边到 b b b点的路径数。 k = 0 k=0 k=0的时候就是单位阵,也成立。

那么我们类比到这里,如果把邻接矩阵这样表示: G [ a ] [ b ] = 0 G[a][b]=0 G[a][b]=0表示 a a a b b b没有路径, G [ a ] [ b ] = 1 / d G[a][b]=1/d G[a][b]=1/d表示从 a a a走到 b b b的概率为 1 / d 1/d 1/d,那么 G k [ a ] [ b ] G^k[a][b] Gk[a][b]就表示从 a a a点走 k k k条边到达 b b b点的概率。

于是我们就读入矩阵。不过因为在走的时候不可以一个人到自己再到另一个点,这样到自己的那个人就赢了。所以我们一开始把到自己的概率全部设成0防止自环。另外由于自己可以到自己,所以某个点到其它点的概率不是 1 / d 1/d 1/d而是 1 / ( 1 + d ) 1/(1+d) 1/(1+d)

最后算出来的答案乘个到自己的概率就可以了。答案 a n s = Σ i = 0 + ∞ G i [ s ] [ t ] d [ t ] + 1 ans={{\Sigma_{i=0}^{+\infty} G^i[s][t]}\over d[t] + 1} ans=d[t]+1Σi=0+Gi[s][t]

上AC代码。

#include 
using namespace std;
typedef long long ll;
const int MAXN=100;
const int UPLIMIT = 1e3 + 10;//因为只要保留五位小数,没必要加到正无穷。设个上限就行了
struct Matrix//祖传模板
{
    int n,m;
    double v[MAXN][MAXN];
    Matrix(int n,int m):n(n),m(m) {}
    void init()
    {
        memset(v,0,sizeof(v));
    }
    Matrix operator *(const Matrix B) const
    {
        Matrix C(n,B.m);
        C.init();
        for (int i=0;i<n;++i)
            for (int j=0;j<m;++j)
            {
                if (v[i][j]==0) continue;
                for (int k=0;k<B.m;++k)
                    C.v[i][k]=C.v[i][k]+v[i][j]*B.v[j][k];
            }
        return C;
    }
    Matrix operator +(const Matrix B) const
    {
        Matrix C(n, m);
        C.init();
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                C.v[i][j] = v[i][j] + B.v[i][j];
        return C;
    }
};
Matrix qpow(Matrix a,ll x)//矩阵快速幂
{
    Matrix ret(a.n,a.m);
    ret.init();
    for (int i=0;i<a.n;++i) ret.v[i][i]=1;
    while (x)
    {
        if (x&1) ret=ret*a;
        a=a*a;
        x>>=1;
    }
    return ret;
}
int main()
{
    int n, p; cin >> n >> p;
    Matrix G(n, n), ans(n, n); G.init(); ans.init();
    int d[n];
    for (int i = 1; i <= n; ++i)
    {
        scanf("%d", &d[i - 1]);
        for (int j = 1; j <= d[i - 1]; ++j)
        {
            int x; scanf("%d", &x);
            G.v[i - 1][x - 1] = 1.0 / (d[i - 1] + 1);
        }//这里不要赋自己到自己的概率
    }
    for (int i = 0; i <= UPLIMIT; ++i)//从零开始
        ans = ans + qpow(G, i);
    for (int  i = 1; i <= p; ++i)
    {
        int j, s, t; scanf("%d%d%d", &j, &s, &t);
        double sav = ans.v[s - 1][t - 1] * (1.0 / (d[t - 1] + 1));
        printf("%d %.5lf\n", j, sav);
    }
    return 0;
}

这里的矩阵模板是在下定决心狠练dp并且改码风之前搞来的,而且原来的模板里是没有矩阵加法的,所以这次单独手写就造成两种风格混在一起看起来很怪。不过之后有空应该还会搞一次大的模板整理,会统一码风。

2、关押罪犯(2020.4.27)

因为要完成学校OJ上的任务然后做的这题…

一看题面要让最大最小,显然二分就行了。之后判断可行性,就是要让所有怨气值大于 c c c的囚犯分到不同的牢房里,如果出现矛盾就返回不可行。于是发现这是个节点间带关系的玩意儿,然后上带权并查集就完事了。最后1A。不过种类并查集似乎写起来比较方便,不过还不太懂。抽空补一补。

(怎么感觉带权并查集要好理解一点…)

#include 
using namespace std;
typedef long long ll;
const int MAXN = 2e4 + 10;
const int MAXM = 1e5 + 10;
int par[MAXN], r[MAXN];//带权并查集
void init(int n)
{
    for (int i = 1; i <= n; ++i) par[i] = i, r[i] = 0;
}
int find(int x)
{
    if (par[x] == x) return x;
    else
    {
        int temp = par[x];
        par[x] = find(par[x]);
        r[x] = (r[x] + r[temp]) % 2;
        return par[x];
    }
}
void unite(int x, int y, int relat)
{
    int xx = find(x), yy = find(y);
    if (xx != yy)
    {
        par[xx] = par[yy], r[xx] = (r[x] + r[y] - relat + 2) % 2;
    }
}
struct edge
{
    int from, to, cost;
}e[MAXM];
int n, m;
bool check(int x)
{
    init(n);
    for (int i = 1; i <= m; ++i)
        if (e[i].cost > x)
        {
            if (find(e[i].from) != find(e[i].to)) unite(e[i].from, e[i].to, 1);
            else if (r[e[i].from] == r[e[i].to]) return 0;
        }
    return 1;
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++i)
        scanf("%d%d%d", &e[i].from, &e[i].to, &e[i].cost);
    int l = 0, r = 1e9, ans = 0;
    while (l <= r)
    {
        int mid = (l + r) >> 1;
        if (check(mid)) ans = mid, r = mid - 1;
        else l = mid + 1;
    }
    printf("%d", ans);
    return 0;
}

你可能感兴趣的:(随性补题记录)