AtCoder Beginner Contest 191

C - Digital Graffiti

题目链接: link.

题意:

在一个矩阵中 ‘ . ’ ‘.’ .代表空,#代表图案,现在矩阵中用#绘制了几个多边形,多边形之间没有相交,矩阵为 n ⋅ m n·m nm的,现在矩阵的中多边形有多少个直边,直边说明没有断开,也没有突起和凹下。

思路:

我的思路是对于图形的上面下面左边右边都判断一下,即几个上面,几个下面,几个左面,几个右面。对于正面来说,就是没有断开,比较暴力的做法,但是很好理解

#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int n, m;
char s[N][N];

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%s", s[i] + 1);
    }
    int res = 0;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s[i][j] == '#' && s[i - 1][j] != '#') {
                int k;
                res++;
                for (k = j + 1; k <= m; k++) {
                    //当前点是‘#’,且不是内部点,必须是最外部的点
                    if (s[i][k] == '#' && s[i - 1][k] != '#') {
                        continue;
                    } else
                        break;
                }
                j = k - 1;
            }
        }
    }
   
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s[i][j] == '#' && s[i + 1][j] != '#') {
                res++;
                int k;
                for (k = j + 1; k <= m; k++) {
                    //最下面的‘#’点,且不是内部点
                    if (s[i][k] == '#' && s[i + 1][k] != '#') {
                        continue;
                    } else
                        break;
                }
                j = k - 1;
            }
        }
    }
    
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (s[j][i] == '#' && s[j][i - 1] != '#') {
                res++;
                int k;
                for (k = j + 1; k <= n; k++) {
                    //最左边的‘#’号点
                    if (s[k][i] == '#' && s[k][i - 1] != '#') {
                        continue;
                    } else
                        break;
                }
                j = k - 1;
            }
        }
    }
   
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (s[j][i] == '#' && s[j][i + 1] != '#') {
                res++;
                int k;
                for (k = j + 1; k <= n; k++) {
                    //最右边的‘#’点
                    if (s[k][i] == '#' && s[k][i + 1] != '#') {
                        continue;
                    } else
                        break;
                }
                j = k - 1;
            }
        }
    }
    cout << res << endl;
}

另一种思路:

借鉴网上其他大佬的思路,对于多边形是都是直边时,多边形的顶点数=边数,即多边形直角边的数量=边数,是个定理(规律),在此记个笔记。

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
char s[N][N];

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%s", s[i] + 1);
    }
    int res = 0;
    for (int i = 1; i < n; i++) {
        for (int j = 1; j < m; j++) {
            int cnt = 0;
            if (s[i][j] == '.') cnt++;
            if (s[i][j + 1] == '.') cnt++;
            if (s[i + 1][j] == '.') cnt++;
            if (s[i + 1][j + 1] == '.') cnt++;
            if (cnt == 1 || cnt == 3) res++;
        }
    }
    cout << res << endl;
    return 0;
}

D - Circle Lattice Points

题目链接: link.

题意:

给一个圆的圆心坐标 ( x , y ) (x,y) (x,y),半径 R R R,现在问你有多少个整数点(坐标的值都是整数值)坐落在圆内或圆上

思路:

坐标的范围给的是 1 0 5 10^5 105肯定不能两重循环枚举每个整数点都判断一遍,跑不了 O ( n 2 ) O(n^2) O(n2),需要只跑一遍循化,那么就可以只枚 x x x的值,然后求出 y y y的范围在范围之内的个数好算,直接 ∣ u p − d o w n + 1 ∣ |up-down+1| updown+1,操作起来就是枚举 x x x的值,从 x − r x-r xr枚举到 x + r x+r x+r,此时知道了圆心 ( x , y ) (x,y) (x,y),也知道了枚举的一个点的位置 ( x i , y i ) (x_i,y_i) (xi,yi),那么此时这个点的上边界和下边界根据勾股定理来求,拿上边界距离,就是 y i + R 2 − ( x − x i ) 2 y_i+\sqrt{R^2 - (x-x_i)^2} yi+R2(xxi)2 就是上边界的值,下边界同理,所以这个 x i x_i xi值下,也就是确定一个竖轴,确定出上下边界,那么上面的点数就是 ∣ u p − d o w n + 1 ∣ |up-down+1| updown+1
注意: 这个题比较卡精度,所以我开了 l o n g d o u b l e long double longdouble,且半径 R R R还加了一个偏移量eps,这个题对于取整问题,可以直接用函数 f l o o r floor floor向上取整和 c e i l ceil ceil向下取整

#include <bits/stdc++.h>
using namespace std;
#define eps 1e-14
#define ll long long
int main() {
    long double x, y, r;
    cin >> x >> y >> r;
    r += eps;
    ll res = 0;
    for (int i = ceil(x - r); i <= floor(x + r); i++) {
        long double up, down;
        up = y + sqrt(r * r - (x - i) * (x - i));
        down = y - sqrt(r * r - (x - i) * (x - i));
        res += floor(up) - ceil(down) + 1;
    }
    cout << res << endl;
}

E - Come Back Quickly

题目链接: link.

题意:

n n n个点 m m m条边的单向图, a i a_i ai b i b_i bi c i c_i ci代表 a a a b b b的边权为 c c c,现在让你输出从每个点出发,又回到自己的最短路的距离(必须要走),如果走不了,那就输出-1

思路:

这个题给的点数和边数并不多,可以对每个点都跑一遍 D i j k s t r a Dijkstra Dijkstra来求从每个点开始到其他点的最短路,然后再求最短环即可,也就是 r e s = m i n ( r e s , f ( i , j ) + f ( j , i ) ) res=min(res,f(i,j)+f(j,i)) res=min(res,f(i,j)+f(j,i)),但这个题有一个细节很恶心,通常我们默认点从自己到自己的最短路一般是0,但是这个题的边可以是自己连接自己,也就是说这里的自环边是要考虑进来的,与平常的最短路不一样。
所以要开个数组, C i r c l e [ i ] Circle[i] Circle[i]存的是从 i i i的自环边的最短边

#include <bits/stdc++.h>
using namespace std;
const int N = 2020, M = 4040;
#define inf 0x3f3f3f3f
typedef pair<int, int> PII;
bool vis[N];
int h[N], e[M], ne[M], w[M], idx;
int dis[N], Circle[N];
int f[N][N];
int n, m;

void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void dijkstra(int s) {
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    dis[s] = 0;
    priority_queue<PII, vector<PII>, greater<PII> > q;
    q.push({0, s});
    while (q.size()) {
        auto t = q.top();
        q.pop();
        int now = t.second, distance = t.first;
        if (vis[now]) continue;
        vis[now] = 1;
        for (int i = h[now]; ~i; i = ne[i]) {
            int j = e[i];
            if (dis[j] > dis[now] + w[i]) {
                dis[j] = dis[now] + w[i];
                q.push({dis[j], j});
            }
        }
    }
}

int main() {
    memset(h, -1, sizeof(h));
    scanf("%d%d", &n, &m);
    memset(Circle, 0x3f, sizeof(Circle));

    while (m--) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        if (a == b) {
            Circle[a] = min(Circle[a], c);
            continue;
        }
        add(a, b, c);
    }

    for (int i = 1; i <= n; i++) {
        dijkstra(i);
        memcpy(f[i], dis, sizeof(f[i]));
    }

    for (int i = 1; i <= n; i++) {
        int res = inf;
        for (int j = 1; j <= n; j++) {
            if (i == j) continue;
            res = min(res, f[i][j] + f[j][i]);
        }
        res = min(res, Circle[i]);
        if (res == inf) res = -1;
        printf("%d\n", res);
    }
    return 0;
}

F - GCD or MIN

题意:

n n n个数,从 a 1 a_1 a1 a 2 a_2 a2 a n a_n an,现在有两种操作、
1. 1. 1.选择两个数 a i a_i ai a j a_j aj m i n min min(最小值),并从数组中删除这两个值,把得到的最小值放在数组中。
2. 2. 2.选择两个数 a i a_i ai a j a_j aj g c d gcd gcd(最大公约数),并从数组中删除这两个数,把得到的最大公约数放到数组中
一共操作 n − 1 n-1 n1次,问最后剩下的数有几种情况

思路:

首先分析直观的 g c d gcd gcd m i n min min操作, g c d ( a , b ) < = m i n ( a , b ) gcd(a,b)<=min(a,b) gcd(a,b)<=min(a,b)这个是能简单分析出来的,所以能够确定数组中的最小值一定是答案之一,且其他答案的大小一定 < < <数组最小值
也就是说答案一定是如此{a,b,c…z},按照大小排序后,z为所有数都取 m i n min min的情况,也就是原数组的最小值。而其他数 a , b , c . . . a,b,c... a,b,c...则是小于 z z z的最小公约数,那要找到这些数,设为 x x x,就会出现原数组的子集 g c d ( a l a l + 1 . . . a r . . . ) gcd(a_la_{l+1}...a_r...) gcd(alal+1...ar...) = = = x x x,但是不能通过枚举原数组的自己来得到答案,那么反向思考,来枚举因子,看这个因子最后通过多个数取 g c d gcd gcd后能否得到当前枚举的这个因子,枚举因子的范围一定要小于原数组的最小值。
还有一层分析,所有拥有因子 x x x a i a_i ai数的 g c d gcd gcd一定是 x x x的倍数
开一个 m a p map map, f [ x ] f[x] f[x]存的是因子是x的情况下,能整除 x x x的所有的数的 g c d gcd gcd,最终统计答案的时候,最小值算一个, f [ x ] = x f[x]=x f[x]=x的也算一种, f [ x ] = x f[x]=x f[x]=x说明有多个数取了 g c d gcd gcd然后又与其他值取了 m i n min min

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
map<int, int> f;
int n;
int a[N];
set<int> s;
set<int>::iterator it;

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    sort(a + 1, a + n + 1);

    int res = 1;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= a[i] / j && j < a[1]; j++) {
            if (a[i] % j == 0) {
                if (!f[j] && j < a[1]) {
                    f[j] = a[i];
                }
                if (j < a[1]) {
                    f[j] = __gcd(f[j], a[i]);
                    s.insert(j);
                }
                if (j * j == a[i]) continue;
                if (!f[a[i] / j] && a[i] / j < a[1]) {
                    f[a[i] / j] = a[i];
                }
                if (a[i] / j < a[1]) {
                    f[a[i] / j] = __gcd(a[i], f[a[i] / j]);
                    s.insert(a[i] / j);
                }
            }
        }
    }
    for (it = s.begin(); it != s.end(); it++) {
        cout << *it << " " << f[*it] << endl;
        if (f[*it] == *it) {
            res++;
        }
    }
    cout << res << endl;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值