在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并且改码风之前搞来的,而且原来的模板里是没有矩阵加法的,所以这次单独手写就造成两种风格混在一起看起来很怪。不过之后有空应该还会搞一次大的模板整理,会统一码风。
因为要完成学校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;
}