2021“MINIEYE杯”中国大学生算法设计超级联赛(7)
1003. Fall with Trees
- 题意
一棵完全二叉树,且子节点和父节点高度一定,同一深度的子节点y坐标相同,且每两个子节点的距离相同, 求整棵树连起来的总面积。
- 思路
建议推公式求,暴力调精度会出大问题(
呜呜呜)。
首先(S_i =h * frac{w_k + w_{k + 1} }{2})
然后每个(w_1 = fabs(x_2 - x_1), h = y_1 - y_0),(w_i = 2 * w_1 - 2^{1-i})
然后推出公式展开求一个等比前(n)项就可以了。
(ANS = sum_{i=1}^{k-1} S_i = w_1 * h / 2.0 * (4 * (k - 1) - 3 * (2 - 2^{k-2}))
code :
void solve(){
db x0,x1,x2,y0,y1,y2;
int k;
sc("%d", &k);
scanf("%lf%lf%lf%lf%lf%lf", &x0, &y0, &x1, &y1, &x2, &y2);
db h = fabs(y1 - y0);
db w = fabs(x2 - x1);
db ans = w * h / 2.0 * (4.0 * (k - 1) - 3.0 * (2.0 - pow(2,2 - k)));
pr("%.3lf
", ans);
}
1004. Link with Balls
- 题意
给你一个(n),(m),有(2n)个筐,你可以从(2x - 1)的筐中拿(k*x)个球(k为非负整数),也可以从(2x)的框中最多拿(x)个球, 问如果要(m)个球,有多少种拿法。
- 思路
1 2 3 4 5 6 (+infty) 0 ~ 1 2k 0 ~ 2 3k 0 ~ 3 我们可以发现,第一个为正无穷,然后其实将(2 and 3)组合,也可以得到正无穷。 最后我们就得到(n)个无穷筐和一个从(0 o n)的筐。 然后就枚举最后那个筐选中的个数,然后在剩下的在前面的无穷筐中选即可 然后就是隔板法取即可,在((m - i))个数中,用(n - 1)个隔板划分成(n)个区域 计算(sum_{i = 0}^nC_{m - i + n - 1}^{n-1}) 由公式(sum_{i=0}^nC_{k+i}^k = C_{n + k + 1}^{k + 1}) 即(ans = C_{n + m}^n - C_{m - 1}^n)
code :
ll fact[N], infact[N];
void init() {
fact[0] = infact[0] = 1;
for (int i = 1 ; i < N; ++i) {
fact[i] = fact[i - 1] * i % MOD;
infact[i] = infact[i - 1] * pow_mod(i, MOD - 2) % MOD;
}
}
ll C(ll a,ll b) {
if(a<0||b<0||a<b) return 0;
return fact[a] * infact[a - b] % MOD * infact[b] % MOD;
}
ll qmi(int a, int k, int p) {
LL res = 1;
while (k) {
if (k & 1) res = (LL)res * a % p;
k >>= 1;
a = (LL)a * a % p;
}
return res;
}
void solve(){
int n,m;
cin >> n >> m;
ll ans = C(n + m,n) - C(m - 1,n) + 3 * mod;
cout << ans % mod << endl;
}
1006. Link with Grenade
- 题意
一个人,往任意方向扔一颗速度为(v),爆炸范围为(r)的手榴弹,并且将在(t)秒后爆炸,问不炸死自己的概率。
- 思路
画图形,推一遍物理公式即可
code : (略)(过于难看)
1007. Link with Limit
- 题意
定义一个函数(f(x)), 且(x)和(f(x))都属于([1,n]) , 且(f_n(x) = f(f_{n-1}(x)) and f_1(x) = f(x))
定义(x)的权力为
(g(x) = lim_{n o +infty} frac{1}{n} sum_{i=1}^{n}f_i(x))
问是否所有(x)的权利都相同 ((1 <= x <= n))
- 思路
题意其实看懂就知道当(n o +infty)时,非环点的权值可以忽略因为一定要(* frac{1}{+infty}), 然后其实就是权值就是一堆环内一直跳的点,那么拓扑排序清边,然后一直找环即可,判断是否每个环的点权平均值都相同即可。
code:
int a[N], ru[N];
int q[N];
void solve(){
int n;
cin >> n;
memset(ru, 0, sizeof ru);
fep(i,1,n) {
cin >> a[i];
ru[a[i]] ++;
}
int hh = 0,tt = -1;
fep(i,1,n) {
if(ru[i] == 0) q[++ tt] = i;
}
while(hh <= tt) {
int t = q[hh ++];
ru[a[t]] --;
if(ru[a[t]] == 0) {
q[++ tt] = a[t];
}
}
ll va = -1, vb = -1;
for(int i = 1;i <= n;i ++) {
ll sa = 0,sb = 0;
if(ru[i]) {
int j = i;
while(ru[j]) {
ru[j] = 0; // 每个点只会出现在一个环上
sa ++;
sb += j;
j = a[j];
}
if(va == -1) {
va = sa, vb = sb;
}else {
if(va * sb != vb * sa) {
cout << "NO" << endl;
return;
}
}
}
}
cout << "YES" << endl;
}
1008. Smzzl with Greedy Snake
- 题意
贪吃蛇,可以直走 = (f),顺时针旋转(90^{circ}) = (c), 逆时针(90^{circ}) = (u), 每次吃完一个才会出现下一个,问你如何用最少的行动吃完。
- 思路
模拟0.0
code:
void solve(){
int x,y,d;
cin >> x >> y >> d; // 0 - y+, 1- x+, 2- y-,3 x-
int m;
cin >> m;
string str = "";
fep(i,1,m) {
int a,b;
cin >> a >> b;
int flag = -1; // 0 - 左上, 1- 左下,2- 右上, 3- 右下
// 先判断直走
if(a > x) flag += 2;
if(b < y) flag += 2;
else flag ++;
if(flag == 0) {
if(d == 1) d = 0, str += "u";
if(d == 2) d = 3, str += "c";
if(d == 0) {
while(y < b) str += "f", y ++;
str += "u";
while(x > a) str += "f", x --;
d = 3;
}
else if(d == 3) {
while(x > a) str += "f", x --;
str += "c";
while(y < b) str += "f", y ++;
d = 0;
}
}else
if(flag == 1) {
if(d == 0) d = 3, str += "u";
if(d == 1) d = 2, str += "c";
if(d == 2) {
while(y > b) str += "f", y --;
str += "c";
while(x > a) str += "f", x --;
d = 3;
}
else if(d == 3) {
while(x > a) str += "f", x --;
str += "u";
while(y > b) str += "f", y --;
d = 2;
}
}else
if(flag == 2) {
if(d == 2) d = 1, str += "u";
if(d == 3) d = 0, str += "c";
if(d == 0) {
while(y < b) str += "f", y ++;
str += "c";
while(x < a) str += "f", x ++;
d = 1;
}
else if(d == 1) {
while(x < a) str += "f", x ++;
str += "u";
while(y < b) str += "f", y ++;
d = 0;
}
}else {
if(d == 3) d = 2, str += "u";
if(d == 0) d = 1, str += "c";
if(d == 1) {
while(x < a) str += "f", x ++;
str += "c";
while(y > b) str += "f", y --;
d = 2;
}
else if(d == 2) {
while(y > b) str += "f", y --;
str += "u";
while(x < a) str += "f", x ++;
d = 1;
}
}
}
cout << str << endl;
}
1010. Smzzl with Tropical Taste
- 题意
一个泳池,一个人每秒忘泳池里倒(q)升的冰红茶,一个人每秒从泳池里喝(p)升的冰红茶(
手动滑稽),问对任意(G)升冰红茶,都存在一个时间(T),在男孩和了(t)秒后的,喝了大于(G)升的冰红茶。
- 思路
只有当倒的冰红茶小于喝的时候才会有限制,其他时候都是无限制的。
code :
const double eps = 1e-6;
void solve(){
db n,m;
cin >> n >> m;
if(n - m > eps) {
cout << "ENJ0Y YOURS3LF!" << endl;
}else {
cout << "N0 M0R3 BL4CK 1CE TEA!" << endl;
}
}
1012. Yiwen with Sqc
- 题意
给一个字符串,(sqc(s,l,r,c))表示字符串(s)中从(l o r)中(c)字符出现的次数,然后求一个:
(sum_{c=97}^{122}sum_{i=1}^{n}sum_{j=i}^{n} sqc(s,i,j,c)^2 mod 998244353)
- 思路
我的解法是推公式写的。。
就比如: ababa
1: a a贡献 a : 1
2: b ab贡献a: 1,b : 1 + 1
3: a aba贡献a: 4 + 1 + 1, b : 1 + 1
4: b abab贡献a:4 + 1 + 1, b : 4 + 4 + 1 + 1
5: a ababa贡献a: 9 + 4 + 4 + 1 + 1, b : 4 + 4 + 1 + 1.
然后发现其他字符变动对自己的贡献是没有影响的。
之后就靠脑子想了,
(a)中的贡献为(a_1 + a_3 + a_5),
当(a_1)进来时为1,
(a_3)进来后即为((a_1=1 + 1)^2 + 1 + k_i),(k_i)表示有多少次没有出现当前字母了,表示(k_i)个(+1),
然后(a_5)进来,就是((a_1=1 + 1 + 1)^2) + (a_2=1 + 1)^2 + (a_2=1 + 1)^2 + 1 + k_i = 9 + 4 + 4 + 1 + 1), 然后计算公式(O(26n))即可求解,没有卡常。。
code:
char str[N];
int f[N][27];
int s[N][27];
int cnt[27], k[27]; // cnt表示当前某个字符贡献了多少次如+4+1+1,贡献为6,cnt为3, k表示已经多久没出现当前字符
void solve(){
sc("%s", str + 1);
int n = strlen(str + 1);
for(int i = 1;i <= n;i ++) {
fep(j,1,26) f[i][j] = f[i - 1][j], s[i][j] = s[i - 1][j], k[j] ++;
if(!cnt[str[i] - 'a' + 1]){
int now = str[i] - 'a' + 1;
f[i][now] = i;
s[i][now] += cnt[now] + k[now];
cnt[now] += k[now];
k[now] = 0;
}
else {
int now = str[i] - 'a' + 1;
f[i][now] += cnt[now] + k[now] + 2 * s[i][now] % mod;
f[i][now] %= mod;
cnt[now] += k[now];
s[i][now] += cnt[now];
k[now] = 0;
}
}
int ans = 0;
for(int i = 1;i <= n;i ++) {
fep(j,1,26){
ans += f[i][j];
ans %= mod;
}
}
fep(i,1,26) k[i] = cnt[i] = 0;
pr("%lld
", ans);
}