2020年第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(上海)

B Mine Sweeper II

因为题目要求最多能修改 n * m / 2 向下取整次,我们可以判断 A 和 B 之间有几个不同的位置并统计个数,最终如果不同的个数不超过 n * m / 2,那就可以直接修改。超过了就输出 A 的反图,这样可以证明答案是正确的。可以多举几个例子验证一下,这里不进行严格证明。比赛的时候就要大胆猜结论,同时也要细心考虑到特殊情况。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;

int n, m;
int cnt_a;
int cnt_b;
int cnt;
char a[1010][1010];
char b[1010][1010];

int main(){
	cin >> n >> m;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			cin >> a[i][j];
		
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			cin >> b[i][j];
	
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++){
			if(a[i][j] != b[i][j]) cnt++;
		}
	}

	if(cnt <= n * m / 2){
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= m; j++){
				cout << a[i][j];
			}
			cout << '\n';
		}
	}
	else{
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= m; j++){
				if(a[i][j] == '.') cout << "X";
				else cout << ".";
			}
			cout << '\n';
		}
	}
}

D Walker

分四种情况,第一种让 A 走完全程,第二种让 B 走完全程,第三种让 A 向右走到头 B 向左走到头,前三种都直接算就可以了。第四种就有些麻烦了,在考虑完之前的情况后,只剩下在 A 和 B 之间有一个分界点,左边让 A 走,右边让 B 走,这种情况了。由于中间的位置很多,我们要利用二分来找到最优的位置(或者三分也可以,这应该是个单峰函数)。每次找到的位置我们都可以计算出左边所用的时间和右边所用的时间,如果左边比右边大,说明左边走的慢可以往左移动一些,反之就是往右。最后注意点精度问题就可以了。

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int t;
double n, p1, v1, v2, p2;
double ans_1, ans_2, ans_3, ans;

int check(double x){
    double t1 = (min(x - p1, p1) + x) / v1;
    double t2 = (min(n - p2, p2 - x) + n - x) / v2;
    ans = min(ans , max(t1, t2));
    if(abs(t1 - t2) <= 0.0000001) return 0;
    if(t1 - t2 < 0) return -1;
    return 1;
}

int main(){
    cin >> t;
    while(t--){
        cin >> n >> p1 >> v1 >> p2 >> v2;
        if(p1 > p2){
            swap(p1, p2);
            swap(v1, v2);
        }
        ans_1 = (min(p1, n - p1)  + n) / v1;
        ans_2 = (min(p2, n - p2)  + n) / v2;
        ans_3 = max((n - p1) / v1, p2 / v2);
        ans = min(ans_1, min(ans_2, ans_3));
        double l = p1;
        double r = p2;
        while(r - l >= 0.0000001){
            double mid = (l + r) / 2;
            if(check(mid) < 0) l = mid;
            else r = mid;
        }
        printf("%.10lf\n", ans);
    }
    system("pause");
}

E The Journey of Geor Autumn

这道题是个 dp,我们设 dp[n] 表示在 k 的情况下的答案,那么考虑 n 个数中最小的那个数的放置情况,显然只能放在前 k 个数中,于是枚举这个数的位置。那么更新方程为:

dp[n] = \sum_{i = 1}^{k}dp[n - i]*\textrm{C}_{n - 1}^{i - 1}*(i-1)!

可以进一步推导一下

dp[n] = (n-1)!\sum_{i=1}^{k}\frac{dp[n-i]}{(n-i)!}

这里还要用前缀和优化一下,因为有除数取模,别忘了取逆元。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;

const int mod = 998244353;
const int maxn = 10000100;
int n, k;
int dp[maxn];
int sum[maxn];
int fac[maxn];
int fav[maxn];

int q_pow(int a, int b, int p){
    int cnt = 1;
    while(b){
        if(b & 1) cnt = 1ll * cnt * a % mod;
        b >>= 1;
        a = 1ll * a * a % mod;
    }
    return cnt;
}

int main(){
    cin >> n >> k;
    dp[0] = dp[1] = 1;
    sum[1] = 1;
    sum[2] = 2;
    fac[0] = 1;
    for(int i = 1; i <= maxn; i++) fac[i] = 1ll * fac[i - 1] * i % mod;
    fav[maxn - 1] = q_pow(fac[maxn - 1], mod - 2, mod);
    for(int i = maxn - 2; i >= 0; i--) fav[i] = 1ll * fav[i + 1] * (i + 1) % mod;
    for(int i = 2; i <= n; i++){
        dp[i] = 1ll * fac[i - 1] * (sum[i] - sum[max(0, i - k)]) % mod;
        sum[i + 1] = (sum[i] + 1ll * dp[i] * fav[i] % mod) %mod;
    }
    dp[n] = (dp[n] + mod) % mod;
    cout << dp[n] << '\n';
    system("pause");
}

G Fibonacci

应该是本场签到题,找规律。因为斐波那契数列的构成形式,数字从第一个开始就是:奇奇偶奇奇偶奇奇偶...所以循环节就是 3。我们假设 cnt 表示有多少个完整的循环节,siz 就是多余的奇数的个数。(2 * cnt + siz) * cnt 就是奇数 * 偶数的个数,cnt * (cnt - 1) / 2 就是偶数 * 偶数的个数。

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define int long long

int n;
int ans;

signed main(){
    cin >> n;
    int cnt = n / 3;
    int siz = n % 3;
    ans = cnt * (cnt - 1) / 2 + (2 * cnt + siz) * cnt;
    cout << ans << endl;
}

I Sky Garden

题目要我们找到 n 个以原点为圆心的同心圆,被 m 条过原点的直线切割(平分同心圆),使上面的所以交点距离之和最小。

因为是总的和,所以我们没必要具体统计各个点之间的具体距离,直接一起考虑就可以了。

在圆上的每个点与原点的距离和为 2 * m * (r1 + r2)

在不同圆上的两个点的距离和为 2 * m * (r2 - r1)

在圆上的不同点之间的距离可以分为两类

1. 经过圆弧

2. 经过两次半径

处理完上述步骤后,再计算两圆上的 dis(pi, qj),其实就是求 dis(pi, pj) + dis(pj, qj)

最后还要考虑一种特殊情况 m = 1,要把答案减去 n * (n + 1) / 2 * 2

具体可以看这篇题解

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;

const int maxn = 1010;
const double pi = 3.141592654;
int n, m, k;
double ans;

double dis[maxn];

double cal(int r1, int r2){
    return ((2 * m - 2 * k - 1) * 2 * r1 + pi / m * k * (k + 1) * r1 + (2 * m - 1) * (r2 - r1)) * 2 * m + 2 * m * (r2 - r1); 
}

int main(){
    cin >> n >> m;
    k = 2 * m / pi;
    for(int i = 1; i <= n; i++){
        dis[i] = 1.0 * (2 * m - 2 * k - 1)  * i * 2 * m + pi * k * (k + 1) * i + 2 * m * i;
        ans += dis[i];
    } 
    for(int i = 1; i < n; i++)
        for(int j = i + 1; j <= n; j++)
            ans += cal(i, j);
    if(m == 1) ans -= n * (n + 1);
    printf("%.10lf\n", ans);
}

M Gitignore

暴力模拟就可以。我们分别从前往后统计 n 个文件路径和 m 个不能删除的文件路径。然后在 n 个里面找能删的地方。如果不能删或者删过就直接略过可以了。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<vector>
#include<map>
using namespace std;

#define int long long

int n, m, t;
string a, b;
string str;
map<string,int> mp;
map<string,int> vis;
vector<string> s[1005];


signed main(){
   cin >> t;
   while(t--){
       vis.clear();
       mp.clear();
       cin >> n >> m;
       for(int i = 1; i <= n; i++) s[i].clear();
       for(int i = 1; i <= n; i++){
           cin >> a;
           int len = a.size();
           str = "";
           for(int j = 0; j < len; j++){
               if(a[j] != '/') str += a[j];
               else{
                   str += '/';
                   s[i].push_back(str);
               }
               if(j == len - 1) s[i].push_back(str);
           }
       }
       for(int i = 1; i <= m; i++){
            cin >> a;
            int len = a.size();
            str = "";
            for(int j = 0; j < len; j++){
                if(a[j] != '/') str += a[j];
                else{
                    str += '/';
                    mp[str] = 1;
                }
                if(j == len - 1) mp[str] = 1;
            }
       }
       int ans = 0;
       for(int i = 1; i <= n; i++){
           for(int j = 0; j < s[i].size(); j++){
               if(vis[s[i][j]]) break;
               else{
                   if(mp[s[i][j]]) continue;
                   else{
                       vis[s[i][j]] = 1;
                       ans++;
                       break;
                   }
               }
           }
       }
       cout << ans << '\n';
   }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值