2020 ACM-ICPC, Asia Shanghai Regional

2020 ACM-ICPC, Asia Shanghai Regional

题目ABCDEFGHIJKLM
solved-🚫🚫---🚫--🚫

✔:比赛时通过;🚫:赛后通过;⚪:比赛时尝试了未通过;-:比赛时未尝试


B. Mine Sweeper II

solved by lllllan. 2:05(+1)
题意: 在一个 n ∗ m n*m nm的扫雷地图中,‘ X X X’表示地雷‘ . . .’表示没有地雷,没有地雷的方格上的数值为四周八个方格中含地雷的个数。现给出两个 n ∗ m n*m nm扫雷的地图,要求将第二个地图通过不多于 n ∗ m / 2 n*m/2 nm/2的操作次数【每次操作可将‘ X X X’更改为‘ . . .’,或将’ . . .‘更改为’ X X X‘】,使得变换后的地图中的权值和与第一个地图相等,输出变换后的地图。

题解: 操作次数刚好限制为地图方格个数的一半,然后考虑极端情况,两个地图完全一致,权值和必然相同;两个地图完全相反,权值和也相同。所以只要数出两个地图不同方格的个数,考虑将第二地图变换成和第一地图完全一致还是完全相反。最后特判 1 ∗ 1 1*1 11的地图即可。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 1e3 + 10;

int n, m;
char a[N][N], b[N][N];
int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i) {
		scanf("%s", a[i] + 1);
	}
	int cnt = 0;
	for (int i = 1; i <= n; ++i) {
		scanf("%s", b[i] + 1);
		for (int j = 1; j <= m; ++j) {
			if (a[i][j] != b[i][j]) cnt++;
		}
	}
	
	int flag = 1;
	if (cnt > (n * m / 2)) flag = 0;
	
	if (n == 1 && m == 1) {
		printf("%c\n", b[1][1]);
		return 0;	
	}
	
	if (flag) {
		for (int i = 1; i <= n; ++i) {
			printf("%s\n", a[i] + 1);
		}
	} else {
		for (int i = 1; i <= n; ++i) {
			for (int j = 1; j <= m; ++j) {
				printf("%c", a[i][j] == '.' ? 'X' : '.');
			}
			printf("\n");
		}
	}
	return 0;
} 

C. Sum of Log

solved by Sstee1XD. (-)
题意:
1 0 5 10^5 105组数据中,每组给定两个非负整数 X , Y ( X , Y ≤ 1 0 9 ) X,Y(X,Y \leq 10^9) X,Y(X,Y109) ∑ i = 0 X ∑ j = [ i = 0 ] Y [ i & j = 0 ] [ l o g 2 ( i + j ) + 1 ] \sum_{i = 0}^X\sum_{j=[i=0]}^Y[i\&j=0][log_2(i+j)+1] i=0Xj=[i=0]Y[i&j=0][log2(i+j)+1]

题解:
很容易想到在二进制下进行数位 d p dp dp,传递枚举到的位置,第一个出现 1 1 1的位置,以及 X X X Y Y Y是否达到上边界。但是这样时间复杂度达到了 O ( T ∗ 30 ∗ 30 ∗ 2 ∗ 2 ) O(T * 30 * 30 * 2 * 2) O(T303022),虽然只要初始化得好的话在现场赛和CF的机子上能刚好跑过去,但是赛后的牛客上是跑不过去的。那么要如何优化我们的时间复杂度呢?我们发现当第一个出现 1 1 1的位置在前面确定之后,后面所有数字能产生的贡献都是一样的,所以如果我们把dp数组里传递的值从产生的贡献改成能产生贡献的数量,就可以把时间复杂度降到 O ( T ∗ 30 ∗ 2 ∗ 2 ∗ 2 ) O(T * 30 * 2 * 2 * 2) O(T30222)

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

template <class T> inline void read(T &x) {
    int f = 0; x = 0; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
    for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
    if (f) x = -x;
}

const int N = 32;
const ll mod = 1e9 + 7;

int dp[N][N][2][2];
int tot1, tot2, dig1[N], dig2[N];
int x, y, ans;

void add(int &a, int b) {
    a += b;
    if (a >= mod) a -= mod;
}

int dfs(int pos, bool lim1, bool lim2, bool ok) {
    if (pos == -1) return ok;
    if (dp[pos][lim1][lim2][ok]) return dp[pos][lim1][lim2][ok];
    int to1 = lim1? dig1[pos] : 1;
    int to2 = lim2? dig2[pos] : 1;
    int tmp = 0;
    for (int i = 0; i <= to1; ++i) {
        for (int j = 0; j <= to2; ++j) {
            if (i && j) continue;
            int num = dfs(pos - 1, lim1 && i == to1, lim2 && j == to2, ok || i || j);
            add(tmp, num);
            if (!ok && (i || j)) add(ans, (ll)num * (ll)(pos + 1) % mod);
        }
    }
    return dp[pos][lim1][lim2][ok] = tmp;
}

void calc(int x, int y) {
    int len = 0;
    memset(dig1, 0, sizeof(dig1));
    memset(dig2, 0, sizeof(dig2));
    while (x) {
        dig1[len++] = x & 1;
        x >>= 1;
    }
    len = 0;
    while (y) {
        dig2[len++] = y & 1;
        y >>= 1;
    }
    dfs(30, true, true, false);
}

void solve() {
    ans = 0;
    memset(dp, 0, sizeof(dp));
    scanf("%d %d", &x, &y);
    calc(x, y);
    printf("%d\n", ans);
}

int main() {
    int t = 1;
    scanf("%d", &t);
    while (t--) solve();
    return 0;
} 

D. Walker

Solved By Sstee1XD. (-)

题意:
给你一条坐标从 0 0 0开始的水平线,再给你两个点各自的横坐标和速度,每个点可以任意移动,问最短的时间让两个点遍历完整条线段。

题解:
分类讨论,合并情况后发现可以分成三种情况。

  • 让一个点走完全程
  • 两个点往对方的的方向走(对穿),走到尽头
  • 两个点各自走完自己的线段,在中间相遇。其中每个点又有两种情况,可以先走中间,或者先走到自己这边的尽头。

对于前两种情况可以直接求,第三种情况不好确定相遇点,所以考虑二分时间,对于两个点都选择能让自己多走中间线段的方案,相加判断长度。

#include <bits/stdc++.h>

using namespace std;

#define double long double
#define endl "\n"
#define dbg(x...) do { cout << #x << " -> "; err(x); } while (0)
void err() { cout << endl; }
template <class T, class... Ts>
void err(const T& arg, const Ts&... args) {
    cout << arg << ' '; err(args...);
}
template <class T> inline void read(T &x) {
    int f = 0; x = 0; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
    for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
    if (f) x = -x;
}

typedef long long ll;
const int inf = 0x3f3f3f3f;
const double eps = 1e-11;

double tot, p1, p2, v1, v2;

bool sgn(double x, double y) {
    return fabs(y - x) <= eps;
}

bool check(double t) {
    // 前后两种分别是先走中间和先走到尽头的情况
    double d1 = max((t - p1 / v1) * v1 / 2, (t - 2 * p1 / v1) * v1);
    double d2 = max((t - (tot - p2) / v2) * v2 / 2, (t - 2 * (tot - p2) / v2) * v2);
    if (d1 < 0 || d2 < 0) return false;
    return d1 + d2 >= p2 - p1;
}

void solve() {
    cin >> tot >> p1 >> v1 >> p2 >> v2;
    if (p2 < p1) swap(p1, p2), swap(v1, v2);
    double ans = inf;

    ans = min(ans, (min(p1, tot - p1) + tot) / v1); // only one point
    ans = min(ans, (min(p2, tot - p2) + tot) / v2);

    ans = min(ans, max((tot - p1) / v1, p2 / v2)); // cross each other

    double l = 0, r = inf, mid;
    while (!sgn(l, r)) {
        mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    ans = min(ans, mid);
    
    cout << ans << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);cout.tie(nullptr);
    cout << fixed << setprecision(20);
    int _T = 1;
    cin >> _T;
    while (_T--) solve();
}

G. Fibonacci

solved by Tryna. 0:16(+2)

题意: 定义 g ( x , y ) g(x, y) g(x,y),当 x ⋅ y x ·y xy为偶数时 g ( x , y ) = 1 g(x,y) = 1 g(x,y)=1,求 ∑ i = 1 n ∑ j = i + 1 n g ( f i , f j ) \sum_{i = 1}^{n}\sum_{j = i + 1}^{n}g(f_i, f_j) i=1nj=i+1ng(fi,fj) f f f为斐波那契数列

题解: 答案为 ( n − 1 ) ∗ n − ( 2 ∗ ( n / 3 ) + n M o d 3 ) ∗ ( ( 2 ∗ ( n / 3 ) + n M o d 3 − 1 ) ) 2 \frac{(n - 1) * n - (2 * (n / 3) + n Mod 3) * ((2 * (n / 3) + nMod 3 - 1))}{2} 2(n1)n(2(n/3)+nMod3)((2(n/3)+nMod31))

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n;
int main() {
    scanf("%lld", &n);
    ll ans = (n - 1) * n / 2;
    ll cnt = (2 * (n / 3) + n % 3);
    ll sum = cnt * (cnt - 1) / 2;
    printf("%lld\n", ans - sum);
    return 0;
}

I. Sky Garden

solved by Tryna.(-)

题意: 给出n个同心圆,每个圆被等分成 2 m 2m 2m份,求每个点到其他点最短距离之和

题解: 比赛的时候写到这题没时间了,然后赛后自己花了两个小时写出来了。这题的关键在于求出一个圆上每个点到其他点的最短距离和。想了一下本来想通过找规律来找到这个答案的,后来发现有点难实现,看了一下数据范围,发现可以 O ( n 2 ) O(n^2) O(n2)来算出答案。对于一个点,它有两种方案,一种是走两条半径,另外一种是走圆弧。求出一个圆上所有点到其他点的最短距离和后,这个圆上的点要想到外面的大圆上,他至少需要经过 R ( 大 圆 的 半 径 ) − r ( 小 圆 的 半 径 ) R(大圆的半径) - r(小圆的半径) Rr的距离,想要距离最短,必须要走小圆的圆弧。可以发现小圆上的一个点到大圆上所有点的距离就是之前算出来的小圆上每个点到其他点的最短距离和加上 2 m ( R − r ) 2m(R - r) 2m(Rr),知道了这些接下来随便乱搞一下就出来了,注意要特判 m = 1 m = 1 m=1的情况,因为此时圆心是没有交点的。代码写的比较长。

#include<bits/stdc++.h>
using namespace std;
#define eps 1e-8
const double pi = acos(-1.0);
double n, m;

int main() {
    scanf("%lf %lf", &n, &m);
    if(m == 1){
        double result = n * (n + 1);
        for(int i = 1; i <= n; i++){
            for(int j = i + 1; j <= n; j++)
                result += 4 * j;
        }
        printf("%.10f\n", result);
        return 0;
    }
    double ans = 2 * m * (n * (n + 1) / 2);
    double c1 = 0, angle = 1 / (2 * m), sum = 0;
    for(int i = 1; i <= 2 * m; i++){
        angle = 1.0 / (2 * m);
        for(int j = i + 1; j <= 2 * m; j++){
            if(i == 1){
                if(angle - 0.5 > eps)
                    c1 += min((double)2, 2 * (1 - angle) * pi);
                else
                    c1 += min((double)2, 2 * angle * pi);
                angle += 1.0 / (2 * m);
            }
            else{
                if(angle - 0.5 > eps){
                    sum += min((double)2, (1 - angle) * 2 * pi);
                }
                else sum += min((double)2, 2 * angle * pi);
                angle += 1.0 / (2 * m);
            }
        }
    }
    sum += c1;
    for(int i = 1; i <= n; i++){
        double cnt = i * sum;
        for(int j = i + 1; j <= n; j++) cnt += 2 * m * (2 * m * (j - i) + c1 * i);
        ans += cnt;
    }
    printf("%.10f\n", ans);
	return 0;
}

L. Traveling in the Grid World

Solved By Sstee1XD. (-)

题意:
给你一个n行m列的方格,左上角为 ( 0 , 0 ) (0, 0) (0,0)点。现在要从 ( 0 , 0 ) (0, 0) (0,0)点走到 ( n , m ) (n, m) (n,m)点,走的规则如下:

  • 若两点之间不存在其他的整点,则能直接走,否则不能走
  • 任意两次路线的斜率应该不同

问你要走的最短距离是多少。

题解:
显然,当 g c d ( n , m ) = 1 gcd(n, m) = 1 gcd(n,m)=1时,直接输出。其余情况,不会证明,瞎猜一下要转一次得到最优答案。此时我们发现越往下面找答案会越大(三角形两边之和大于第三边),所以找到第一个点后就 b r e a k break break,最后输出最小的答案。

#include <bits/stdc++.h>

using namespace std;

#define endl "\n"
#define dbg(x...) do { cout << #x << " -> "; err(x); } while (0)
void err() { cout << endl; }
template <class T, class... Ts>
void err(const T& arg, const Ts&... args) {
    cout << arg << ' '; err(args...);
}
template <class T> inline void read(T &x) {
    int f = 0; x = 0; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
    for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
    if (f) x = -x;
}

typedef long long ll;
const int inf = 0x3f3f3f3f;

ll gcd(ll a, ll b) {
    return a == 0? b : gcd(b % a, a);
}

double getDis(double x, double y) {
    return sqrt(x * x + y * y);
}

ll n, m;
double k;

void solve() {
    scanf("%lld %lld", &n, &m);
    if (gcd(n, m) == 1) {
        printf("%.10f\n", getDis(n, m));
        return;
    }
    k = (double)m / (double)n;
    double minn = n + m;
    for (ll _x = 0; _x < n; ++_x) {
        ll _y = k * _x + 1;
        for (; _y <= m; ++_y) {
            if (gcd(_x, _y) != 1) continue;
            if (n * _y == m * _x) continue;
            minn = min(minn, getDis(_x, _y) + getDis(n - _x, m - _y));
            break;
        }
        
    }
    printf("%.10f\n", minn);
}

int main() {
    int _T = 1;
    scanf("%d", &_T);
    while (_T--) solve();
    return 0;
}

M. Gitignore

solved by lllllan. 4:36(+1)
题意: N N N个可折叠的文件和 M M M个不可折叠的文件,考虑 N N N个可折叠的文件最后必须显示多少个。【比如 a / b a/b a/b a / c a/c a/c可折叠为 a / a/ a/,最后只需显示一个文件即可】

题解: 先对 M M M个不可折叠的文件进行前缀标记【比如 a / b / c / d a/b/c/d a/b/c/d文件是不可折叠的,则用 m a p map map记录下 a a a a / b a/b a/b a / b / c a/b/c a/b/c均为不可折叠】。然后再遍历前 N N N个可折叠的文件,对必须显示的文件进行计数,可以折叠成已有显示的文件进行忽略。

#include<bits/stdc++.h>
using namespace std;

int _T, n, m;
map<string, int> mp, vis;

void run () {
	string s[100];
	mp.clear(), vis.clear();
	
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		cin >> s[i];
	}
	for (int i = 1; i <= m; ++i){
		string str;
		cin >> str;
		int len = str.size();
		for (int j = 0; j < len; ++j) {
			if (str[j] == '/') {
//				cout << str.substr(0, j) << endl;
				mp[str.substr(0, j)] = 1;
			}
		}
	}
	
	int ans = 0;
	for (int i = 1; i <= n; ++i) {
		int flag = 1;
		int len = s[i].size();
		for (int j = 0; j <= len; ++j) {
			if (s[i][j] == '/') {
				string tem = s[i].substr(0, j);
				if (mp[tem]) flag = 1;
				else {
					if (vis[tem]) flag = 0;
					vis[tem] = 1;
					break;
				}
			}
		}
		ans += flag;
	}
	cout << ans << endl;
}

int main () {
	cin >> _T;
	while (_T--) run();	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值