AtCoder Beginner Contest 314

E - Roulettes

题意:有N个转盘,每个转盘上有Pi个点数,转动一次转盘花费Ci,每次转动得到该转盘上的点数是等可能的,求获得M点所需要的最小花费。(概率dp)

关键:1.定义状态dp[i]为获得点数大于等于i所需要的最少花费
           2.对于每个状态dp[i],对于当前某一个转盘,可以从dp[i-p1]...dp[i-pi]转移过来

        3.由于转盘上可能有为0的点,故需要移项得到公式。
代码实现
#include <string>
#include <algorithm>
#include <math.h>
#include <cmath>
#include <set>
#include <queue>
#include <cstring>
#include <map>
#include<iomanip>
#include<iostream>
#include<stack>
#include<unordered_set>
#define io ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
using namespace std;
const int N = 1e2 + 10;
int n, m;
int c[N], p[N];
int s[N][N];
double dp[N];
int main()
{
	io;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> c[i] >> p[i];
		for (int j = 1; j <= p[i]; j++)
		{
			cin >> s[i][j];
		}
	}
	for (int i = 1; i <= m; i++)
	{
		dp[i] = 1e9;
		for (int j = 1; j <= n; j++)
		{
			int p1 = 0;
			double p2 = 0;
			for (int k = 1; k <= p[j]; k++)
			{
				if (!s[j][k])
					p1++;
				else
					p2 += dp[max(0, i - s[j][k])];
			}
			dp[i] = min(dp[i], (p2 + c[j] * p[j]) / (p[j] - p1));
		}
	}
	cout << fixed << setprecision(6) << dp[m];
	return 0;
}

F - A Certain Game

题意:有N个玩家,初始时每个人是一支队伍,每次选择两人p、q所在的队伍(保证p、q两人不在同一支队伍中)对战,设a为p的队伍成员数量,b为q的队伍成员数量,那么p所在的队伍获胜的概率为(a/(a+b)),q所在的队伍获胜的概率为(b/(a+b))。两支队伍对战后,合并为1支队伍,问经过N-1次对战后,每个人获胜场次的期望是多少。
关键:1.对决结果可以用一棵生成树来表示,这颗树的叶子节点表示每个玩家,边的权值表示每场对决产生的期望胜场。那么我们每产生一场对决就生成一个新的节点,代表新合并的队伍,并建立两条边连向对决的两只队伍,边的权值为期望胜场。每个玩家最终的期望获胜场次就是从根节点走到叶节点的路径权值之和。我们可以通过并查集维护集合关系,最后跑一遍dfs就可以得到答案;

代码实现
#include <string>
#include <algorithm>
#include <math.h>
#include <cmath>
#include <set>
#include <queue>
#include <cstring>
#include <map>
#include<iomanip>
#include<iostream>
#include<stack>
#include<unordered_set>
#define io ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
using namespace std;
typedef long long ll;
const int N = 3*(2e5 + 8);
const ll mod = 998244353;
int f[N],siz[N];
int h[N], e[N],ne[N], idx;ll w[N];
ll ans[N];
bool vis[N];
ll inv(ll a, ll b)
{
	ll ans = 1;
	while (b)
	{
		if (b & 1)
			ans = (ans * a) % mod;
		b >>= 1;
		a = (a * a) % mod;
	}
	return ans;
}
int find(int k)
{
	if (f[k] == k)
		return k;
	return f[k] = find(f[k]);
}
void add(int a, int b, ll c)
{
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u)
{
	vis[u] = true;
	for (int i = h[u]; ~i; i = ne[i])
	{
		int j = e[i];
		ll v = w[i];
		if (!vis[j])
		{
			ans[j] += (ans[u] + v) % mod;
			dfs(j);
		}
	}
}
int main()
{
	io;
	int n;
	cin >> n;
	int node = n;
	memset(h, -1, sizeof h);
	for (int i = 1; i <= n; i++)
	{
		f[i] = i;
		siz[i] = 1;
	}
	for (int i = 1; i < n; i++)
	{
		int p, q;
		cin >> p >> q;
		int fp = find(p), fq = find(q);
		ll a = siz[fp], b = siz[fq];
		ll v = inv(a + b, mod - 2);
		++node;
		f[node] = node;
		add(node, fp, a * v), add(node, fq, b * v);
		f[fq] = f[fp] = node;
		siz[node] = siz[fp] + siz[fq];
	}
	dfs(node);
	for (int i = 1; i <= n; i++)
		cout << ans[i] << " ";
	return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值