"蔚来杯"2022牛客暑期多校训练营1
C Grab the Seat!
D Mocha and Railgun
题意:
给定一个圆环,中心为(0, 0)。给定T个查询,每次给定环的半径及点Q(xq, yq)及以Q为终点的线段AB的半长为d。求环上最多删除的圆弧长度。
环上某点p可被删除的条件:
- p的投影在AB上
- A B ⃗ ∗ A P ⃗ > 0 \vec{AB} * \vec{AP} > 0 AB∗AP>0
思路:
弧长l = 弧度 * r。
要想l最大,那么弧度最大。
可画图模拟。
除此之外需要知道弧度。
acos(), asin()函数可直接得到相应边的夹角的弧度值。
code:
typedef pair <int, int> PII;
const double PI= acos(-1.0);
int main()
{
int T;
scanf("%d", &T);
while(T --)
{
double r;
scanf("%lf", &r);
double xq, yq, d;
scanf("%lf %lf %lf", &xq, &yq, &d);
double ans = 0;
double cq = sqrt(xq * xq + yq * yq);
if(xq * xq + yq * yq >= d * d)
{
double cb = cq + d;
double ca = cq - d;
double t1 = acos(cb / r);
double t2 = acos(ca / r);
ans = max(ans, (t2 - t1) * r);
}
else
{
double ca = d - cq;
double cb = cq + d;
double t1 = asin(ca / r);
double t2 = asin(cb / r);
ans = max(ans, (t1 + t2) * r);
}
printf("%.9lf\n", ans);
}
return 0;
}
I Chiitoitsu
题意:
给定34种形状,每种形状含有4张牌。整个过程只有一名玩家。在初始时会得到13张牌。(保证存在 <= 2张形状一样的牌)在每一轮, 执行以下步骤。
- 先从牌堆中摸一张牌
- 如果此时满足了7对不同的牌。那么直接获胜。
- 此时拥有14张牌,需要弃牌且弃牌不再放入牌堆中。
求期望多少轮可以获胜。
思路:
典型的期望DP。
期望DP往往采取倒推。(写法简单,如果正着推,还额外需要维护到当前点的概率,利用加权求和。此外题目往往起点唯一,到达起点的概率和一定为1,终点不唯一。那么起点的ans即为整体的ans。)
由于所求为期望轮数。可以把其抽象成一个有向无环图。边权为1。
由于考虑倒着推。
$f[i][j]: 牌堆中还剩下i张牌,且有j张单牌的集合。
每一轮摸牌存在两种情况。
- 摸牌为无用牌,直接弃掉。 概率p1 = i − 3 ∗ j i \frac{i - 3 * j}{i} ii−3∗j, f [ i ] [ j ] = p 1 ∗ ( f [ i − 1 ] [ j ] + 1 ) f[i][j] = p1 * (f[i - 1][j] + 1) f[i][j]=p1∗(f[i−1][j]+1)
- 摸牌为有用牌可以和之前的牌配对。且此时未满足获胜条件,然后再从牌堆中选一张单牌弃掉。概率p2 = 3 ∗ j i \frac{3 * j}{i} i3∗j, f [ i ] [ j ] = p 2 ∗ ( f [ i − 1 ] [ j − 2 ] + 1 ) f[i][j] = p2 * (f[i - 1][j - 2] + 1) f[i][j]=p2∗(f[i−1][j−2]+1)
- 摸牌为有用牌可以和之前的牌配对。且此时满足获胜条件 概率p2 = 3 ∗ j i \frac{3 * j}{i} i3∗j, f [ i ] [ j ] = p 2 ∗ ( f [ i − 1 ] [ j − 1 ] + 1 ) f[i][j] = p2 * (f[i - 1][j - 1] + 1) f[i][j]=p2∗(f[i−1][j−1]+1)
i
f
(
i
−
1
>
=
0
A
N
D
j
>
=
2
)
f
[
i
]
[
j
]
=
p
1
∗
(
f
[
i
−
1
]
[
j
]
+
1
)
+
p
2
∗
(
f
[
i
−
1
]
[
j
−
2
]
+
1
)
if(i - 1 >= 0 \;AND \;j >= 2) \;f[i][j] = p1 * (f[i - 1][j] + 1) + p2 * (f[i - 1][j - 2] + 1)
if(i−1>=0ANDj>=2)f[i][j]=p1∗(f[i−1][j]+1)+p2∗(f[i−1][j−2]+1)
i
f
(
i
−
1
>
=
0
A
N
D
j
=
=
1
)
f
[
i
]
[
j
]
=
p
1
∗
(
f
[
i
−
1
]
[
j
]
+
1
)
+
p
2
∗
(
f
[
i
−
1
]
[
j
−
1
]
+
1
)
if(i - 1 >= 0 \;AND \;j == 1) \;f[i][j] = p1 * (f[i - 1][j] + 1) + p2 * (f[i - 1][j - 1] + 1)
if(i−1>=0ANDj==1)f[i][j]=p1∗(f[i−1][j]+1)+p2∗(f[i−1][j−1]+1)
倒着推,由于DFS特性。期望DP往往采用记忆化搜索。
初始化终点f[0–123][0] = 0
code:
ll f[150][20];
ll get_mod0(ll a, ll b)
{
return (a % mod - b % mod + mod) % mod;
}
ll get_mod1(ll a, ll b)
{
return (a % mod + b % mod) % mod;
}
ll get_mod2(ll a, ll b)
{
return (a % mod * (b % mod)) % mod;
}
ll ksm(ll base, ll power)
{
ll res = 1;
while(power)
{
if(power & 1)
res = get_mod2(res, base);
base = get_mod2(base, base);
power >>= 1;
}
return res;
}
ll dfs(int i, int j) // 123*13
{
if(j == 0) return 0;
if(f[i][j] >= 0) return f[i][j];
f[i][j] = 0;
if(i - 1 >= 0 && j >= 2) f[i][j] = (get_mod1(get_mod2(get_mod2(3ll * j, ksm(i, mod - 2)), dfs(i - 1, j - 2)), get_mod2(get_mod2(get_mod0(i * 1ll, 3ll * j), ksm(i, mod - 2)), dfs(i - 1, j))) + 1) % mod;
if(i - 1 >= 0 && j == 1) f[i][j] = (get_mod2(get_mod2(get_mod0(i * 1ll, 3ll * j), ksm(i * 1ll, mod - 2)), dfs(i - 1, j)) + 1) % mod;
return f[i][j];
}
int main()
{
int T;
scanf("%d", &T);
int cnt = 0;
for(int j = 1 ; j <= 4 ; j ++)
{
for(int i = 1 ; i <= 9 ; i ++)
{
if(j == 4 && i > 7) continue;
string s;
s += to_string(i);
if(j == 1)
s += "m";
else if(j == 2)
s += "p";
else if(j == 3)
s += "s";
else s += "z";
mp[s] = ++ cnt;
}
}
for(int i = 0 ; i <= 123 ; i ++)
{
for(int j = 0 ; j <= 13 ; j ++)
f[i][j] = -1;
}
int t = 0;
while(T --)
{
string str;
cin >> str;
int len = str.length();
vector <int> alls;
int c = 0;
for(int i = 0 ; i < len ; i += 2)
{
int t = mp[str.substr(i, 2)];
book[t] ++;
if(book[t] == 2)
c ++;
alls.push_back(t);
}
for(auto t : alls)
book[t] = 0;
printf("Case #%d: %lld\n", ++t, dfs(123, 13 - c * 2));
}
return 0;
}
J Serval and Essay
题意:
给定n个论点,每个论点i存在k个前提。k个前提都满足才能证明论点i时正确的。可选择一个论点作为基本论点,求正确的论点有多少个。
∑
n
<
=
2
e
5
,
∑
i
=
1
i
=
n
k
[
i
]
<
=
5
e
5
\sum n <= 2e5, \; \sum_{i = 1} ^ {i = n} k[i] <= 5e5
∑n<=2e5,∑i=1i=nk[i]<=5e5
a
[
i
]
[
j
]
!
=
i
,
当
j
!
=
k
时,
a
[
i
]
[
j
]
!
=
a
[
i
]
[
k
]
a[i][j]\; != i, 当j != k时,a[i][j] != a[i][k]
a[i][j]!=i,当j!=k时,a[i][j]!=a[i][k]
思路:
题意说的非常繁琐。
把题目进行转化为:
给定
n
个点和若干条边的有向图且不存在重边和自环,如果一个点的所有入边都染上了
给定n个点和若干条边的有向图且不存在重边和自环,如果一个点的所有入边都染上了
给定n个点和若干条边的有向图且不存在重边和自环,如果一个点的所有入边都染上了
颜色,那么当前点也会被染色。可以随机选定一个点进行染色
,
求最多多少个点被染色
颜色,那么当前点也会被染色。可以随机选定一个点进行染色,求最多多少个点被染色
颜色,那么当前点也会被染色。可以随机选定一个点进行染色,求最多多少个点被染色
可以发现转化题意后非常明了。
假设被1号点染色的集合为A, 被3号点染色的集合为B,3号点又能被1号点染色。
那么集合B必被集合A包含。
说明全图一定存在若干个集合。 对每个集合取max即为答案。
如图,1号点可被2号点染色,那么1号点和2号点可看作为一个集合。
那么对于图中4号点,就可看作由1号点2号点组成的集合的一条出边。也可被染色。
相当于把2号点所有出边合并到1号点上。 但是不能把2号点的入边合并到1号点上(违背了题意)
考虑如何进行合并。 可暴力set跑启发式合并即可。对于每个点如果只有一条边指向它,即可被染色。
code:
set <int> from[maxn], to[maxn];
int fa[maxn], siz[maxn];
int find(int x)
{
if(fa[x] == x) return x;
else return fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
int a = find(x), b = find(y);
if(a == b) return ;
if(to[a].size() < to[b].size()) swap(a, b); //b合并到a上
fa[b] = a, siz[a] += siz[b];
vector <PII> alls;
for(auto t : to[b])
{
to[a].insert(t);
from[t].erase(b);
from[t].insert(a);
if(from[t].size() == 1)
{
alls.push_back({t, a});
}
}
for(auto t : alls)
merge(t.first, t.second);
}
int main()
{
int T;
scanf("%d", &T);
int cnt = 0;
while(T --)
{
int n;
scanf("%d", &n);
for(int i = 1 ; i <= n ; i ++)
fa[i] = i, from[i].clear(), to[i].clear(), siz[i] = 1;
for(int i = 1 ; i <= n ; i ++)
{
int k;
scanf("%d", &k);
while(k --)
{
int v;
scanf("%d", &v); // v --> i
from[i].insert(v);
to[v].insert(i);
}
}
for(int i = 1 ; i <= n ; i ++)
{
if(from[i].size() == 1)
{
merge(*from[i].begin(), i);
}
}
int res = 0;
for(int i = 1 ; i <= n ; i ++)
res = max(res, siz[find(i)]);
printf("Case #%d: %d\n", ++ cnt, res);
}
return 0;
}