2020-08-10 23:47阅读: 852评论: 9推荐: 3

2020百度之星程序设计大赛复赛

A. Battle for Wosneth (Hdu 6838)

题目大意

初始Alice有无限血,Bobm滴血。Alicep%命中Bob,并使Bob减少一滴血,自身回复一滴血。Bobq%概率命中Alice,并使Alice减少一滴血,但自身血不变。问当Bob血量减少为0时,Alice的期望血量变化值是多少。结果对998244353取模。

解题思路

Bob血量大于1时,设Alice命中一次Bob,自身血量变化的期望值为x,则(此处p,q为小数)

x=p×(1q)+(1p)×(q+x)

解得

x=1qp

所以Bob从m滴血扣到1滴血时,Alice的血量变化期望值为

(m1)×x=(m1)(1qp)

Bob剩下一滴血时,由于如果Alice命中他,则Bob不会反击,这是与上方的区别所在,设Alice命中Bob,自身血量变化值为y,则

y=p×1+(1p)×(q+y)

解得

y=1qp+q

所以最终答案

ans=(m1)×x+y=(1qp)×m+q

这可以理解为先假设m轮,Bob都会反击,造成变化期望值为(1qp)×m,再减去最后一次Bob实际反击的变化(扣血)期望p1(1p)×q=q

即为

(1qp)×m(q)=(1qp)×m+q

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mo = 998244353;
LL qpower(LL a, LL b)
{
LL qwq = 1;
while (b)
{
if (b & 1)
qwq = qwq * a % mo;
b >>= 1;
a = a * a % mo;
}
return qwq;
}
LL inv(LL x)
{
return qpower(x, mo - 2);
}
int main(void)
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int kase;
cin >> kase;
for (int ii = 1; ii <= kase; ii++)
{
LL m, q, p;
cin >> m >> p >> q;
p = p * inv(100) % mo;
q = q * inv(100) % mo;
LL ans = ((1 - q * inv(p) % mo + mo) % mo * m % mo + q) % mo;
cout << ans << endl;
}
return 0;
}


B. Binary Addition (Hdu 6839)

题目大意

给你一串无限长的01ST,其中第n+1位及以后都是0。现你有两种操作作用于S串:

  • 将某一位与1异或
  • 将其视为一个数,对它加一。其中最低位在最左边

求最小的操作次数,使得S串变成T串。

解题思路

这种看似麻烦的题要去想想特别之处

可以证明猜测操作二要执行则仅可能执行一次

操作二有什么用?

如果第一位是0,操作二与操作一没区别。

如果第一位是1,操作二就能够将前面一连串的1变成0,在这之后的0变成1

如果我们会执行两次操作二,由于执行了第一次操作二,前面的数变成了0,我们要重新变成1,才能再执行操作二。而这最终的结果也只是把某一位变成1,而这一结局采用操作一可以一步到位。

所以我们就得到了个重要性质:操作二只能执行一次或者不执行

所以,我们就枚举操作二的执行效果,即枚举i,把前i个数变成1,并把第i+1个数变成0,然后执行一次操作二,剩下的全部执行操作一即可。

num0[i]表示S串的前i个数中0的个数,num1[i]表示T串的前i个数中1的个数,cnt[i]表示ST串的[i..n]中不同的数的个数。

则此时的次数就为num0[i]+(s[i+1]==1)+1+num1[i]+(t[i+1]==0)+cnt[i+2]

对所有i以及cnt[1]取最小值即是答案。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x)
{
int s = 0, c = getchar();
x = 0;
while (isspace(c))
c = getchar();
if (c == 45)
s = 1, c = getchar();
while (isdigit(c))
x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s)
x = -x;
}
template <typename T>
void write(T x, char c = ' ')
{
int b[40], l = 0;
if (x < 0)
putchar(45), x = -x;
while (x > 0)
b[l++] = x % 10, x /= 10;
if (!l)
putchar(48);
while (l)
putchar(b[--l] | 48);
putchar(c);
}
const int N = 1e5 + 8;
char s[N], t[N];
int n;
int cnt[N];
int num0[N];
int num1[N];
int qwq(int pos)
{
return num0[pos] + (s[pos + 1] == 1) + 1 + num1[pos] + (t[pos + 1] == 0) + cnt[pos + 2];
}
int main(void)
{
int kase;
read(kase);
for (int ii = 1; ii <= kase; ii++)
{
read(n);
scanf("%s", s + 1);
scanf("%s", t + 1);
num0[0] = num1[0] = 0;
for (int i = 1; i <= n; ++i)
{
s[i] -= '0';
t[i] -= '0';
num0[i] = num0[i - 1] + (s[i] == 0);
num1[i] = num1[i - 1] + (t[i] == 1);
}
cnt[n + 1] = 0;
cnt[n + 2] = 0;
bool sign = false;
int cur = n;
for (int i = n; i >= 1; --i)
{
cnt[i] = cnt[i + 1] + (s[i] ^ t[i]);
}
int ans = cnt[1];
for (int i = 1; i <= n; ++i)
{
ans = min(ans, qwq(i));
}
write(ans, '\n');
}
return 0;
}


C. Range k-th Maximum Query (Hdu 6840)

题目大意

给定一个n个数的序列,以及正整数k,l,要求对它重新排序,使得所有长度为l的子区间的第k大的数和和最大和最小。求最大值和最小值。

解题思路

我们将数列从大到小排列。前k1大的数不会对答案有贡献。

要让和最大,我们期望大的数对答案的贡献尽可能多。

于是我们可以构造这样的序列,它是由若干个长度为l的区间构成。

每个这样的区间,前lk个位置标记为红,后k个位置标记为蓝。

我们将这个排好序的序列,从左到右,按顺序填充蓝的位置,放完蓝的,然后再从右到左,按顺序填充红的位置。

最后一个长度不足l的区间(如果有的话),前lk个位置标记为红,后k个位置标记为蓝(如果有的话)。

这样就是最大值的构造。

最小值,将数列从小到大排列,前k大的数也就是说前lk+1小的数,再按照上面构造就可以了。

既然构造出来了,答案自然也就能求出来了。

最大值的情况,设d=nlr=n%l

排好序的数列里,kdk1的数都对答案有1次的贡献,其中位置是k的倍数的还有额外的(lk)次的贡献。

最后第dk位的贡献次数跟r有关

如果r>(lk),则第dk+1位到第dk+r(lk)的数对答案也有1次的贡献。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x)
{
int s = 0, c = getchar();
x = 0;
while (isspace(c))
c = getchar();
if (c == 45)
s = 1, c = getchar();
while (isdigit(c))
x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s)
x = -x;
}
template <typename T>
void write(T x, char c = ' ')
{
int b[40], l = 0;
if (x < 0)
putchar(45), x = -x;
while (x > 0)
b[l++] = x % 10, x /= 10;
if (!l)
putchar(48);
while (l)
putchar(b[--l] | 48);
putchar(c);
}
const int N = 1e5 + 8;
int n, k, l, d, r;
LL a[N];
LL solve(LL a[], int k)
{
LL ans = 0;
for (int i = k; i < d * k; ++i)
{
ans += a[i] * (1 + (l - k) * (i % k == 0));
}
ans += a[d * k] * min(r + 1, l - k + 1);
if (r > l - k)
{
for (int i = d * k + 1, yu = r - l + k; yu; ++i, --yu)
{
ans += a[i];
}
}
return ans;
}
int main(void)
{
int kase;
read(kase);
for (int ii = 1; ii <= kase; ii++)
{
read(n);
read(l);
read(k);
for (int i = 1; i <= n; ++i)
{
read(a[i]);
}
sort(a + 1, a + 1 + n, greater<int>());
d = n / l;
r = n % l;
LL ans1 = solve(a, k);
sort(a + 1, a + 1 + n);
k = l - k + 1;
LL ans2 = solve(a, k);
printf("%lld %lld\n", ans1, ans2);
}
return 0;
}


题目大意

qwq

解题思路

qwq

神奇的代码
qwq


E. Battle for Wosneth2 (Hdu 6842)

题目大意

初始Alicen滴血,Bobm滴血。Alicep%命中Bob,并使Bob减少一滴血,但自身血不变;Bobq%概率命中Alice,并使Alice减少一滴血,但自身血不变。当一方血量减为0时,对方获胜。问Alice获胜的概率。答案对998244353取模。

解题思路

我们抽象成一个二维平面图,左下角(0,0),初始位于(n,m),然后有三个移动方向,问移动到(r,0)的概率是多少(r是任意一个不大于n的数)。

当前位置为(i,j)

  • 移动到(i1,j1)的概率a=pq1(1p)(1q)

  • 移动到(i,j1)的概率b=p(1q)1(1p)(1q)

  • 移动到(i1,j)的概率c=q(1p)1(1p)(1q)

值得注意的是,从(i,1)移动到(i,0)的概率是d=p1(1p)(1q)

所以我们先计算移动到(r,1)的概率,最后再计算移动到(r,0)的概率。

这里有两个自由变量。

如果我们假设移动到(r,1),或者说,水平方向进行了i=nr次移动,还要假设,我们进行了x次情况一的移动,则情况二进行了m1x次,情况三进行了ix次移动。

则移动到m=1的概率为

i=0n1x=0min(i,m1)Cm1+ixixCm1xaxbm1xcix

很显然这式子整不动。通常处理方法就是交换求和顺序。我们从实际意义来说明。

我们先假设进行了x次情况一的移动,则情况二进行了m1x次,情况三进行了i次移动,其中0in1x

则移动到m=1的概率为

x=0min(m1,n1)i=0n1xCm1+iiCm1xaxbm1xci=x=0min(m1,n1)Cm1xaxbm1xi=0n1xCm1+iici

而后面这一项可以事先预处理一个前缀和S(r)=i=0rCm1+iici

这样,最终的答案就是

ans=d×x=0min(m1,n1)Cm1xaxbm1xS(n1x)

值得注意的是在计算bm1x时,万万不可算出bm1然后除以b除以b(取模意义上)。

因为当b=0的时候,这样算的话00=0

而实际上我们应该认为00=1,也就是说应当采用快速幂计算 (虽然会多个log)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x)
{
int s = 0, c = getchar();
x = 0;
while (isspace(c))
c = getchar();
if (c == 45)
s = 1, c = getchar();
while (isdigit(c))
x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s)
x = -x;
}
template <typename T>
void write(T x, char c = ' ')
{
int b[40], l = 0;
if (x < 0)
putchar(45), x = -x;
while (x > 0)
b[l++] = x % 10, x /= 10;
if (!l)
putchar(48);
while (l)
putchar(b[--l] | 48);
putchar(c);
}
const LL mo = 998244353;
const int N = 2e5 + 8;
int n, m;
LL jie[N], invjie[N];
LL sum[N];
LL p, q;
LL a, b, c, d;
LL inv100 = 828542813;
LL ans;
LL qpower(LL a, LL b)
{
LL qwq = 1;
while (b)
{
if (b & 1)
qwq = qwq * a % mo;
b >>= 1;
a = a * a % mo;
}
return qwq;
}
LL inv(LL x)
{
return qpower(x, mo - 2);
}
LL C(int n, int m)
{
if (n < m)
return 0;
return jie[n] * invjie[m] % mo * invjie[n - m] % mo;
}
int main(void)
{
int kase;
read(kase);
jie[0] = invjie[0] = 1;
for (int i = 1; i < N; ++i)
{
jie[i] = jie[i - 1] * i % mo;
invjie[i] = inv(jie[i]);
}
for (int ii = 1; ii <= kase; ii++)
{
read(n);
read(m);
read(p);
read(q);
p = p * inv100 % mo;
q = q * inv100 % mo;
d = inv((1 - (1 - p) * (1 - q) % mo + mo) % mo);
a = p * q % mo * d % mo;
b = p * ((1 - q + mo) % mo) % mo * d % mo;
c = q * ((1 - p + mo) % mo) % mo * d % mo;
sum[0] = 1;
LL tmp = c;
for (int i = 1; i < n; ++i)
{
sum[i] = (sum[i - 1] + tmp * C(m - 1 + i, i) % mo) % mo;
tmp = tmp * c % mo;
}
ans = 0;
LL qaq = 1;
// LL qbq = qpower(b, m - 1);
// LL invb = inv(b);
int up = min(m - 1, n - 1);
for (int i = 0; i <= up; ++i)
{
ans = (ans + qaq * qpower(b, m - i - 1) % mo * C(m - 1, i) % mo * sum[n - i - 1] % mo) % mo;
// ans = (ans + qaq * qbq % mo * C(m - 1, i) % mo * sum[n - i - 1] % mo) % mo; // 0^0 = 1
qaq = qaq * a % mo;
// qbq = qbq * invb % mo;
}
ans = ans * p % mo * d % mo;
write(ans, '\n');
}
return 0;
}


F. Query on the Tree (Hdu 6843)

题目大意

qwq

解题思路

qwq

神奇的代码
qwq


本文作者:~Lanly~

本文链接:https://www.cnblogs.com/Lanly/p/13473041.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ~Lanly~  阅读(852)  评论(9编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.