近年icpc签到题专题训练
2021ICPC昆明
K - King of Gamers
题意:
n n n 盘游戏, 给你一个临界线 x = a b x = \frac{a}{b} x=ba, 小 G G G 会在胜率小于等于 x x x 时胜出,大于 x x x 时失败,求 n n n 盘的期望胜场数。
思路:
考虑前 n − 1 n - 1 n−1 场,设在前 n − 1 n - 1 n−1 场胜了 t t t 场, 那么前 n − 1 n - 1 n−1 场的胜率为 t n − 1 \frac{t}{n - 1} n−1t。
当 t n − 1 > x \frac{t}{n - 1} > x n−1t>x时, 第 n n n 场必定会失败, 则共胜出 t t t 场,此时的 t > x ( n − 1 ) t > x(n - 1) t>x(n−1), 所以 t t t 为 ⌈ x ( n − 1 ) ⌉ \left \lceil x(n - 1) \right \rceil ⌈x(n−1)⌉, 即 t = x ( n − 1 ) + 1 t = x(n - 1) + 1 t=x(n−1)+1, 所以共会胜出 x ( n − 1 ) + 1 x(n - 1) + 1 x(n−1)+1场。
当 t n − 1 ≤ x \frac{t}{n - 1} ≤ x n−1t≤x时, 第n场必定会胜利, 则共胜出 t t t场,此时的 t ≤ x ( n − 1 ) t ≤ x(n - 1) t≤x(n−1), 所以 t t t 为 ⌊ x ( n − 1 ) ⌋ \left \lfloor x(n - 1) \right \rfloor ⌊x(n−1)⌋, 即 t = x ( n − 1 ) t = x(n - 1) t=x(n−1), 所以共会胜出 x ( n − 1 ) + 1 x(n - 1) + 1 x(n−1)+1场。
综上所述, 两种情况的胜场都为 x ( n − 1 ) + 1 x(n - 1) + 1 x(n−1)+1。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long LL;
const int N = 2e6 + 10, M = 2e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-6;
typedef pair<int, int> PII;
void solve(){
int n, a, b;
cin >> n >> a >> b;
cout << (n - 1) * a / b + 1 << endl;
}
signed main(){
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
G - Glass Bead Game
题意:
有 n n n 个物品, 每个物品被选中的概率为 p i p_i pi, 然后将他移动到最前面, 花费为它前面的物品的数量,求第 m m m 次的期望花费。
思路:
要求的其实就是选到某个小球的概率乘上这个小球前面有几个小球的期望。对于每两个物品
a
a
a 和
b
b
b ,在做第m次操作前
a
a
a 在
b
b
b 的前面的概率为
p
a
p
b
+
p
a
\frac{p_a}{p_b + p_a}
pb+papa, 此时就有
p
b
p_b
pb的概率选中b, 所以这是对于移动b的期望为
p
a
p
b
p
b
+
p
a
\frac{p_ap_b}{p_b + p_a}
pb+papapb。
枚举每两个物品,期望加起来就是答案。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long LL;
const int N = 2e6 + 10, M = 2e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-6;
typedef pair<int, int> PII;
double p[N];
int n;
void solve(){
cin >> n;
double res = 0;
for(int i = 1; i <= n; i ++) cin >> p[i];
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
if(i != j){
res += p[i] * p[j] / (p[i] + p[j]);
}
}
}
printf("%.12lf", res);
}
signed main(){
int T = 1;
while(T--) solve();
return 0;
}
2021ICPC澳门
C - Laser Trap
题意:
二维平面上有 n n n 个点,这 n n n 个点形成了一个完全图, 你现在在 ( 0 , 0 ) (0,0) (0,0), 想要不穿过任何边逃出这里,求至少需要删掉几个点。
思路:
对于本题,可以看出,所有点都需要在从原点引出的直线的同一侧。对所有点按照到原点的角度排序一下,然后双指针维护直线一边的点的个数,取 m i n min min 就好了,需要注意精度问题。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const long double pi = acosl(-1);
const int N = 2e6 + 10;
int n;
typedef pair<double, double> pdd;
long double angel[N];
void solve()
{
cin >> n;
long double x, y;
for(int i = 1; i <= n; i ++ ) cin >> x >> y, angel[i] = atan2(1.0 * y, 1.0 * x);
sort(angel + 1, angel + 1 + n);
for(int i = 1; i <= n; i ++ )
angel[i + n] = angel[i] + 2 * (long double)pi;
int ans = n;
for(int i = 1, j = 1; i <= n; i ++ )
{
while(j <= 2 * n && angel[j] - angel[i] < (long double)pi)
j ++;
ans = min(ans, j - i - 1);
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T -- ) solve();
}
2021ICPC上海
D - Strange_Fractions
题意:
给你 p p p 和 q q q ,求一个 a , b a,b a,b, 使得 p q = a b + b a \frac{p}{q} = \frac{a}{b} + \frac{b}{a} qp=ba+ab。
思路:
对式子进行化简, 可得 a = b ∗ p ± p 2 − 4 q 2 2 q a = b * \frac{p \pm \sqrt{p^2 - 4 q^2}}{2 q} a=b∗2qp±p2−4q2, 因此,需要满足 p 2 − 4 q 2 > 0 p^2 - 4 q^2 > 0 p2−4q2>0且为完全平方数,然后令 b = 2 q b = 2q b=2q, a = p + p 2 − 4 q 2 a = p + \sqrt{p^2 - 4 q^2} a=p+p2−4q2即可。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long LL;
const int N = 2e6 + 10, M = 2e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-6;
typedef pair<int, int> PII;
void solve(){
int p, q;
cin >> p >> q;
int x = p * p - 4 * q * q;
if(x < 0){
cout << "0 0\n";
return;
}
int qx = sqrt(x);
if(qx * qx != x){
cout << "0 0\n";
return;
}
cout << p + qx << ' ' << 2 * q << endl;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
K - Circle of Life
题意:
你有一条具有 n n n 个节点的链,你可以在任意个节点位置上放置 “ T w i n k l e ” “Twinkle” “Twinkle”, 他每一秒会向两边分裂,如果一条边的左右两个节点同时分裂, 则它们会因为碰撞而消失,如果一个点的左右两个节点同时分裂,它们也会因为碰撞而消失,求一个初始放法,使得链上永远都存在至少一个 “ T w i n k l e ” “Twinkle” “Twinkle”
思路:
不难发现,对于
1001
1001
1001 这样的循环节,是永远不会消失的,并且不会受其他地方的影响,因此,如果长度取余
4
4
4 等于
0
0
0 , 直接构造
n
/
4
n/4
n/4 个
1001
1001
1001, 那么余数为
1
、
2
、
3
1、2、3
1、2、3 的怎么办呢, 可以发现,
10001
10001
10001 也是一个合法的循环节,因此余数为1的只需要在最前面是
10001
10001
10001 就好了,然后再进行手模,
1001
1001
1001 后面加上
10
10
10 也不会受影响,所以余数为
2
2
2 的也可以解决了。最后对于余数为
3
3
3 的,可以是最前面是
10001
10001
10001,中间一堆
1001
1001
1001, 最后加上
10
10
10,这样所有的数据都可以解决了,其中
n
=
3
n = 3
n=3 的时候需要特判,是无解的。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long LL;
const int N = 2e6 + 10, M = 2e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-6;
typedef pair<int, int> PII;
void solve(){
int n;
cin >> n;
if(n == 2) cout << "10";
else if(n == 3) cout << "Unlucky";
else{
if(n % 4 == 0){
for(int i = 1; i <= n / 4; i ++){
cout << "1001";
}
}
else if(n % 4 == 2){
for(int i = 1; i <= n / 4; i ++){
cout << "1001";
}
cout << "10";
}
else if((n - 5) % 4 == 0){
cout << "10001";
for(int i = 1; i <= (n - 5) / 4; i ++){
cout << "1001";
}
}
else if((n - 5) % 4 == 2){
cout << "10001";
for(int i = 1; i <= (n - 5) / 4; i ++){
cout << "1001";
}
cout << "10";
}
}
}
signed main(){
int T = 1;
// cin >> T;
while(T--) solve();
return 0;
}
2021ICPC南京
J. Xingqiu’s Joke
题意
给你
a
a
a 和
b
b
b ,一次操作你可以选择
1.对两个数除以他们的公共质因数
2.这对两个数同时
+
1
+1
+1 或
−
1
-1
−1
求把他们中的一个数变成
1
1
1 至少需要几次操作
思路
这两个操作中,肯定是操作 1 1 1 的效果比较好,所以,能尽量做操作 1 1 1 就做操作 1 1 1 ,那么就是把他们都像可以被除的数靠近,可以发现,两个数可以被整除,那么他们的差也一定可以被这个数整除,那么,只要考虑他们的差就可以了,操作 2 2 2 对于他们的差是不变的,所以直接对他们的差分解质因数,然后枚举每个质因数,如果可以被整除,就搜索他们除以这个质因数后的状态,最后所有情况取个 m i n min min 就好了。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long LL;
const int N = 4e6 + 10, M = 2e6, inf = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-6;
typedef pair<int, int> PII;
vector<int> pri;
map<PII, int> mp;
int dfs(int a, int dif){
if(a == 0) return inf;
if(a == 1) return 0;
if(dif == 1) return a - 1;
if(mp[{a, dif}]) return mp[{a, dif}];
int res = a - 1;
for(int i : pri){
if(dif % i == 0){
int tmp = a % i;
res = min(res, dfs(a / i, dif / i) + tmp + 1);
res = min(res, dfs(a / i + 1, dif / i) + i - tmp + 1);
}
}
return mp[{a, dif}] = res;
}
void solve(){
pri.clear();
mp.clear();
int a, b;
cin >> a >> b;
if(a > b) swap(a, b);
int c = b - a;
for(int i = 2; i * i <= c; i ++){
if(c % i == 0){
while(c % i == 0) c /= i;
pri.push_back(i);
}
}
if(c > 1) pri.push_back(c);
cout << dfs(a, b - a) << endl;
}
signed main(){
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
2020ICPC济南
G - Xor Transformation
题意:
给你 x x x 和 y y y,你要将 x x x 变成 y y y,一次操作可以将 x x x 异或上小于 x x x 的任何数,求最小操作次数。
思路:
很明显,
x
⊕
(
x
⊕
y
)
=
y
x \oplus (x \oplus y) = y
x⊕(x⊕y)=y
如果
x
⊕
y
<
=
x
x\oplus y <= x
x⊕y<=x 的话, 就直接异或上它, 不然的话,
x
x
x 可以先异或上
y
y
y(
y
y
y 保证了一定小于
x
x
x, 异或之后的数字一定是大于原来的
x
x
x 了的),然后再异或
x
x
x。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;
int n;
int p[N];
int v[N];
signed main()
{
ios::sync_with_stdio(false);
int x, y;
cin >> x >> y;
if((x ^ y) < x){
cout << 1 << endl;
cout << (x ^ y) << endl;
}
else{
cout << 2 << endl;
cout << y << ' ' << x << endl;
}
}