2020ICPC·小米 网络选拔赛第一场 全部题解

整理的算法模板合集: ACM模板


题目传送门

题目总体情况

  • A 题:数论 + 动态规划

  • B 题:计算几何 + 最短路

  • C 题:模拟

  • D 题:图论(求连通块个数)

  • E 题:数据结构

  • F 题:二分答案

  • G 题:图论

  • H 题:略

  • I 题:搜索(BFS)/ 并查集

  • J 题:二维前缀和 + 二维差分

  • K 题:数学(难)

A、Intelligent Warehouse

在这里插入图片描述
题目大意:
给你一个序列,让你选出最多的数,使得选中的数之间互为倍数( a i a_i ai a j a_j aj的倍数或者 a j a_j aj a i a_i ai的倍数)

思路:

数据2e5,求的是最多的个数,考虑DP

官方题解:
在这里插入图片描述

方法一:直接暴力转移方程(注意,如果把范围设置为10000000才能AC,大一点都会TLE)

#include <cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>

using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 10000007;

int n;
int f[N];
int cnt[N];
int maxv;

int main()
{
    scanf("%d", &n);
    for(int i = 1;i <= n; ++ i){
        int x;
        scanf("%d", &x);
        cnt[x] ++;
    }
    int maxv = 10000000;
    int ans = 0;
    for(int i = 1 ;i <= maxv; ++ i){
        if(cnt[i]){
            f[i] += cnt[i];
            for(int j = 2 * i; j <= maxv; j += i){
                f[j] = max(f[j], f[i]);
            }
            ans = max(ans, f[i]);
        }
    }
    printf("%d\n", ans);
    return 0;
}

方法二(优化):

按照题解里的方法,筛出来所有的素数,然后再枚举所有的素数,因为任意合数都可以用素数凑出来(唯一分解定理)

B、Intelligent Robot

在这里插入图片描述
题目大意:给你一个 n * m 的迷宫,迷宫里有k堵乱放的墙,你不能穿过墙,求给定两点间的最短距离。

在这里插入图片描述
就是一个板子题嘛,一个月没碰计算几何神TM我一开始叉乘写错了,找了半天…

有用的点只有开始和结束的点以及所有墙的端点,因为不能穿过墙但是可以蹭着墙皮走,也就是说端点是可以走的,所以我们枚举所有墙的端点,如果能直接到达就连边,求一次最短路即可。(两点之间线段最短嘛)

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
//typedef __int128 int;oj上可用,编译器上不可用
const int N = 500007, M = 500007, INF = 0x3f3f3f3f;
const double eps = 1e-8;
const ll mod = 19260817;
typedef pair<int, int >PII;
typedef pair<double, int>PDI;

struct Point {
    double x, y;
    Point(double x = 0, double y = 0):x(x), y(y){ }
    double operator ^ (const Point&p)const{return x*p.y-y*p.x;}
}point[N];

typedef Point Vector;

struct Line {
    Point a, b;

}line[N];

int n, m, k;
bool vis[N];
double dist[N];
int head[N], ver[M], nex[M], tot;
double edge[M];

Vector operator + (Point a, Point b){return Point(a.x + b.x, a.y + b.y);}
Vector operator - (Point a, Point b){return Vector(a.x - b.x, a.y - b.y);}
Vector operator * (Vector a, double p){return Vector(a.x * p, a.y * p);}
double get_dist(Point a, Point b)
{
    double xx = a.x - b.x;
    double yy = a.y - b.y;
    return sqrt(xx * xx + yy * yy);
}
int dcmp(double x){
    if(fabs(x) < eps) return 0;
    else return x < 0 ? -1 : 1;
}
//重载等于运算符
bool operator == (const Point& a, const Point& b){return !dcmp(a.x - b.x) && !dcmp(a.y - b.y);}

double Dot(Vector A, Vector B){return A.x * B.x + A.y * B.y;}
double Cross(Vector A, Vector B){return A.x * B.y - A.y * B.x;}

void add(int x, int y, double z)
{
    ver[tot] = y;
    edge[tot] = z;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

bool segment_proper_intersection(Point a1, Point a2, Point b1, Point b2)
{
    double c1 = Cross(a2 - a1, b1 - a1), c2 = Cross(a2 - a1, b2 - a1);
    double c3 = Cross(b2 - b1, a1 - b1), c4 = Cross(b2 - b1, a2 - b1);
    return dcmp(c1) * dcmp(c2) < 0 && dcmp(c3) * dcmp(c4) < 0;
}

void init()
{
    memset(head, -1, sizeof head);
    memset(vis, 0, sizeof vis);
    tot = 0;
}

void dijkstra(int s, int n)
{
    for(int i = 1; i <= n; ++ i){
        dist[i] = 1e12;
    }

    dist[s] = 0.0;
    priority_queue<PDI, vector<PDI>, greater<PDI> >q;
    q.push({0.0, s});
    while(q.size()){
        int x = q.top().second;
        q.pop();
        if(vis[x])continue;
        vis[x] = 1;
        for(int i = head[x]; ~i; i = nex[i]){
            int y = ver[i];
            double z = edge[i];
            if(dist[y] - eps >= dist[x] + z){
                dist[y] = dist[x] + z;
                q.push({dist[y], y});
            }
        }
    }
}

int main()
{
    init();
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= k; ++ i){
        scanf("%lf%lf%lf%lf", &line[i].a.x, &line[i].a.y, &line[i].b.x, &line[i].b.y);
    }
    for(int i = 1; i <= k; ++ i){
        point[2 * i - 1] = line[i].a;
        point[2 * i] = line[i].b;
    }
    scanf("%lf%lf%lf%lf", &point[0].x, &point[0].y, &point[2 * k + 1].x, &point[2 * k + 1].y);

    for(int i = 0; i <= 2 * k + 1; ++ i){//只需要走端点才是最近的
        for(int j = i + 1; j <= 2 * k + 1; ++ j){
            bool flag = true;
            for(int s = 1; s <= k; ++ s){
                if(segment_proper_intersection(point[i], point[j], line[s].a, line[s].b)){
                    flag = false;
                    break;
                }
            }
            if(flag){
                add(i, j, get_dist(point[i], point[j]));
                add(j, i, get_dist(point[i], point[j]));
            }
        }
    }
    dijkstra(0, 2 * k + 1);
    printf("%.4f\n", dist[2 * k + 1]);
    return 0;
}

C、Smart Browser

在这里插入图片描述

在这里插入图片描述

水题模拟,队友写的。

#include <iostream>
#include <cstring>

using namespace std;

const int N=1e5+10;
char a[N];

int main()
{
	cin>>a;
	int num,ans=0;
	for(int i=0;i<strlen(a);i++)
	{
		int num=0; 
		while(a[i]=='w')
		{
			ans++;
			num++;
			i++;
		}
		if(num) ans+=num-1;
	}
	cout<<ans<<endl;
	return 0;
}

D、Router Mesh

在这里插入图片描述
题目大意:求分别把每个点删除以后连通块的个数

几乎就是一个tarjan模板题了,(但是我因为最近在玩莫比乌斯反演,快一个月没怎么碰图论了,导致这道题竟然还卡了我一会…)

官方题解:
在这里插入图片描述

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;
const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;

int n, m;
int dfn[N], low[N], tim;
int ver[M], nex[M], edge[M], head[N], tot;
int ans;
int cut[N];
int num[N];//把这个点删除以后能分成多少块

void add(int x,int y){
    ver[tot] = y;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

void tarjan(int x, int rt){//计算该割点连接了多少个连通块
    dfn[x] = low[x] = ++ tim;
    int flag = 0;
    int cnt = 0;//(割开这个点会将图分成cnt个连通块)
    for(int i = head[x]; ~i;i = nex[i]){
        int y = ver[i];
        if(!dfn[y]){
            tarjan(y, rt);
            low[x] = min(low[x], low[y]);
            if(dfn[x] <= low[y]){//该割点返回了一条独立的路
                cnt ++ ;
                flag ++ ;
                if(x != rt || flag > 1)cut[x] = 1;
            }
        }
        else low[x] = min(low[x], dfn[y]);
    }
    if(x != rt)cnt ++ ;//如果不是根的话说明该点的上面应该可以割出来一个连通块
    num[x] = cnt;
}



int main(){
    scanf("%d%d", &n, &m);

        memset(head, -1, sizeof head);
        memset(dfn, 0, sizeof dfn);
        tot = tim = 0;
        for(int i = 1;i <= m;++i){
            int x,y;
            scanf("%d%d",&x,&y);
            add(x,y);add(y,x);
        }
        ans = 0;
        int cnt = 0;
        for(int i = 1;i <= n;++ i){//数据从0到n - 1
            if(!dfn[i]){
                cnt ++ ;//可能有孤立点(孤立连通块)
                tarjan(i, i);
            }
        }
        //cnt是当前连通块的个数
        for(int i = 1; i <= n; ++ i){
            if(head[i] == -1){
                printf("%d ", cnt - 1);
            }
            else
                printf("%d ", num[i] + cnt - 1);
        }
        puts("");
}

E、Phone Network

F、Design Problemset

在这里插入图片描述
题目大意:制作一个题集,题目数量要在[L,R]之间,并且每个题集的每种题需要 L i [ i ] , R i [ i ] L_i[i],R_i[i] Li[i],Ri[i] 道题。其中每种题有a[i]道,题集要求把所有的题都用完,求最多能做多少个题集。

大致算了一下,中间的结果最大可以达到 1 0 23 10^{23} 1023,会爆long long…真狠。所以我们可以使用__int128来存(甚至可以水高精)

官方题解:
在这里插入图片描述

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
typedef __int128 ll;
//__int128, __int128_t
//#define int _int128
const int N = 500007, M = 500007, INF = 0x3f3f3f3f;
const double eps = 1e-6;
const int mod = 80112002;
typedef pair<int, int >PII;

__int128 read(){
    __int128 x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)&&ch!='-')ch=getchar();
    if(ch=='-')f=-1;
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return f*x;
}

void print(__int128 x){
    if(x<0)putchar('-'),x=-x;
    if(x>9)print(x/10);//注意这里是x>9不是x>10 (2019.10 wa哭了回来标记一下)
    putchar(x%10+'0');
}

ll k, LL, RR, L[N], R[N];
ll a[N], minv;
ll sumL, sumR;



bool check(ll A)
{
    ll need = A * (LL - sumL);
    ll sumA = 0;
    for(int i = 1; i <= k; ++ i)
        if(a[i] < A * L[i])return false;

    for(int i = 1; i <= k ; ++ i){
        sumA += min(a[i] - L[i] * A, (R[i] - L[i]) * A);
    }
    return sumA >= need;
}

int main()
{
    k = read(), LL = read(), RR = read();
    ll l = 0, r = 0, ans = 0;
    for(int i = 1; i <= k; ++ i){
        a[i] = read();
        r += a[i];
    }

    for(int i = 1; i <= k; ++ i){
        L[i] = read(), R[i] = read();
        sumL += L[i], sumR += R[i];
    }
    
    if(sumL > RR || sumR < LL){
        puts("0");
        return 0;
    }

    while(l <= r){
        ll mid = l + r >> 1;
        if(check(mid))ans = mid, l = mid + 1;
        else r = mid - 1;
    }
    print(ans);
    return 0;
}

G、Tree Projection

在这里插入图片描述
在这里插入图片描述

H、Grouping

在这里插入图片描述

在这里插入图片描述

I、Walking Machine

在这里插入图片描述
**题目大意:**一个 n ∗ m n*m nm的迷宫,给你每个点能够走的方向,求能够出去的点的个数。

并查集过的,之前有一道题洛谷上的奶酪,跟这道题差不多,那道题就是用bfs或者并查集可过,我的dfs剪枝超时,所以试了一下并查集就过了。我用并查集只是多了阿尔法 α ( n ∗ m ) < 5 \alpha(n*m)<5 α(nm)<5的常数,所以同样可以过。

官方题解:

在这里插入图片描述

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>

using namespace std;
typedef long long ll;
const int N = 5000007, M = 50007, INF = 5000007;
const double eps = 1e-6;

int n, m;
int fa[N];
string s[N];
int ans ;

int Find(int x)
{
    if(fa[x] == x) return x;
    return fa[x] = Find(fa[x]);
}

void unions(int x, int y)
{
    int fx = Find(x), fy = Find(y);
    fa[fx] = fy;
}

int main()
{

    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n * m; ++ i)
        fa[i] = i;
    fa[INF] = INF;
    for(int i = 0; i < n; ++ i){
        cin >> s[i];
    }
        //cout << "ok" << endl;
    for(int i = 0; i < n; ++ i){
        for(int j = 0; j < m; ++ j){
            if(s[i][j] == 'W'){
                if(i - 1 < 0)unions(i * m + j, INF);
                else unions(i * m + j, (i - 1) * m + j);
            }
            else if(s[i][j] == 'S'){
                if(i + 1 >= n)unions(i * m + j, INF);
                else unions(i * m + j, (i + 1) * m + j);
            }
            else if(s[i][j] == 'A'){
                if(j - 1 < 0)unions(i * m + j, INF);
                else unions(i * m + j, i * m + j - 1);
            }
            else if(s[i][j] == 'D'){
                if(j + 1 >= m)unions(i * m + j, INF);
                else unions(i * m + j, i * m + j + 1);
            }
        }
    }
    //cout << "ok" << endl;
    for(int i =  0; i < n; ++ i){
        for(int j = 0; j < m; ++ j){
            if(Find(i * m + j) == INF)
                ans ++ ;
        }
    }
    printf("%d\n", ans);
}

J、Matrix Subtraction

在这里插入图片描述

题意:有一个 n ∗ m n * m nm 的矩阵 M,通过反复选择 a ∗ b a*b ab大小的子矩阵 并且使子矩阵中的所有元素都减去 1 ,问最终矩阵 M 能否变为全 0。如果可能,在一行中打印 ^_^,否则在一行中打印 QAQ

思路:

我们直接 n 2 n^2 n2暴力枚举 a ∗ b a*b ab的子矩阵,每一块都减去 当前值 使之变为 0,利用二维差分可以快速实现这个操作。如果中途出现了负数或最后不全为 0,则输出 QAQ。(本来想的是用二维树状数组,结果写了半个小时调不出来…)注意

官方题解:

在这里插入图片描述

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 2007, M = 500007, INF = 0x3f3f3f3f;
const double eps = 1e-6;

int a[N][N];
int n, m, A, B;

void add(int x_1, int y_1, int x_2, int y_2, int z){
    a[x_1][y_1] += z;
    a[x_2 + 1][y_1] -= z;
    a[x_1][y_2 + 1] -= z;
    a[x_2 + 1][y_2 + 1] += z;
}

bool check()
{
    for(int i = 1; i <= n; ++ i){
        for(int j = 1 ; j <= m; ++ j){
            if(a[i][j] != 0)
                return false;
        }
    }
    return true;
}
int t;
int main()
{
    scanf("%d", &t);
    while(t -- ){
        memset(a, 0, sizeof a);
        scanf("%d%d%d%d", &n, &m, &A, &B);

        for(int i = 1; i <= n; ++ i){
            for(int j = 1; j <= m; ++ j){
                int x;
                scanf("%d", &x);
                add(i, j, i, j, x);
            }
        }

        bool success = 1;

        for(int i = 1; i <= n; ++ i){
            for(int j = 1; j <= m; ++ j){
                a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
                if(a[i][j] < 0){success = 0;break;}

                int ii = i + A - 1, jj = j + B - 1;
                if(ii <= n && jj <= m)
                    add(i, j, ii, jj, -a[i][j]);
            }
            if(success == 0)break;
        }

        if(success == 0){
            puts("QAQ");
            continue;
        }
        if(check())puts("^_^");
        else puts("QAQ");
    }
    return 0;
}

K、Sqrt Approaching

在这里插入图片描述

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁凡さん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值