SDNU_ACM_ICPC_2021_Winter_Practice_2nd [个人赛]

SDNU_ACM_ICPC_2021_Winter_Practice_2nd [个人赛]

传送门

A - Different Divisors

题意:

输入一个数x,让你找到一个至少有四个因子且这四个因子的大小差至少为x的一个最小的数

思路:

首先我们假设要求的这个数是ans,要让她最小,则其中四个因子的两个就是1和ans本身,这样我们只需要找到剩下的两个因子a,b;使得1,a,b三个数直接的大小差至少为x即可。但是,如果你找的a和b是一个合数,那这个合数至少有一个小于本身且不同于1和它本身的一个因子,这个因子也一定是ans的因子,这样的话就不能让1和这个因子的大小差不小于x了,所以说我们必须要找素数。

第一步先打个素数表,然后再进行一次循环,找两个满足条件的数就break掉

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
ll tr[100005];
inline int IntRead()
{
    char ch = getchar();
    int s = 0, w = 1;
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
            w = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        s = s * 10 + ch - '0',
        ch = getchar();
    }
    return s * w;
}
bool is_prime(ll n)///素数判定,用来打表的
{
    if(n == 1)  return 1;
    for(int i = 2; 1ll * i * i <= n; ++i)
    {
        if(n % i == 0)  return 0;
    }
    return 1;
}
int main()
{
    ll len = 0;
    for(int i = 1; i <= 100005; i++)//打表
    {
        if(is_prime(i) == 1)
            tr[len++] = i;
    }
    ll n, t;
    //cout<<tr[0];
    t = IntRead();
    while(t--)
    {
        n = IntRead();
        ll a = 1, ans = 1, sum = 0;//a是指上一个的因子,ans是我们最终的结果,sum用来计数
        for(int i = 1; i < 100005; i++)
        {
            if(tr[i] - a >= n)
            {
                ans *= tr[i];//如果找到了就乘起来
                a = tr[i];//更新a的值
                sum++;//用来计数
            }
            if(sum == 2)//满两个数就退出循环
            {
                cout<<ans<<endl;
                break;
            }
        }
    }
    return 0;
}

D - 这不是签到题

题意:

原来你有两个二进制数a和b,而d是这两个二进制数按位相加,不会进位的加法,每个位最大是2,数字大的就是大,比如102 > 21 021 = 21,还有的是这个d数字不喜欢重复的东西,所以会把自身连续的重复的数字变成不重复的,比如:1211 —> 121 , 022000 —> 020。不幸的是,Mike把a弄丢了,为了让他开心,你必须找到一个a,使得d的值最大

思路:

因为是找最大的数,我们肯定是让每一位都尽可能的大,但是还得考虑不能出现重复的,因为一旦出现重复的数,你的位数就会少,肯定就会小,举个栗子:

SDNU_ACM_ICPC_2021_Winter_Practice_2nd [个人赛]

b是001011,要让d最大,那么a的第一位绝对是1,这个时候d的第一位是1;要防止d串去重,我们只能让a的第二位是0,所以d的第二位是0;下一个就可以不管b是几,直接让a取最大,也就是a的第三位是1,这个时候d的第三位是1+1也就是2;因为第三位是2,我们要避免第四位也是2而被去重了,所以就要看b串的第四位是0,我们就让a的第四位是1,所以d的第四位是1……就这样一直判断下去

脱离这个例子,我们会发现d的第i位是由d的第i-1位和b的第i位这两个数来决定的,所以可以分情况讨论一下:

当d的第i-1位是2的时候,如果bi是0,则ai取1,如果bi是1,则ai取0。

当d的i-1位是1的时候,如果bi是0,则ai取0,;如果bi是1,则ai取1

当d的第i-1位是0的时候,就可以不用管bi的值,直接让ai取1。

记得每次都要更新ai,bi,di的值

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
inline int IntRead()
{
    char ch = getchar();
    int s = 0, w = 1;
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
            w = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        s = s * 10 + ch - '0',
        ch = getchar();
    }
    return s * w;
}
int main()
{
    int t, n, a, b,c;
    string s;
    t = IntRead();
    while(t--)
    {
        n = IntRead();
        cin>>s;
        c = 1;
        b = s[0] - '0';
        a = (s[0] - '0') + c;
        printf("%d",c);
        //cout<<a<<' '<<b<<' '<<c<<endl;
        for(int i = 1; i < s.size(); i++)
        {
            b = s[i] - '0';
            if(a == 1)
            {
                if(b == 0)
                {
                    c = 0;
                    printf("0");

                }
                else
                {
                    c = 1;
                    printf("1");
                    //cout<<1;
                }

            }
            else if(a == 2)
            {
                if(b == 1)
                {
                    c = 0;
                    printf("0");
                    ///cout<<c;
                }
                else
                {
                    c = 1;
                    printf("1");
                    //cout<<c;
                }
            }
            else if(a == 0)
            {
                c = 1;
                printf("1");
                //cout<<c;
            }
            a = b + c;
            //cout<<a<<' '<<b<<' '<<c<<endl;
        }
        cout<<endl;
    }
    return 0;
}

G - A Simple Problem with Integers

线段树区间修改加区间查询的板子题

本来抱着wa的心态把之前wa了的板子打上去了,只不过这次把所有的int全换成了ll,结果AC了,可给我高兴坏了.jpg

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using namespace std;
//#define endl '/n'
//#define fast ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
inline int IntRead()
{
    char ch = getchar();
    int s = 0, w = 1;
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') w = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        s = s * 10 + ch - '0',
        ch = getchar();
    }
    return s * w;
}
ll tr[100005], d[400005], b[400050];
ll n, a, f, c, k, q;
void built(ll s, ll t, ll p)
{
    if(s == t)
    {
        d[p] = tr[s];
        return;
    }
    ll m = (s + t) / 2;
    built(s, m, p * 2);
    built(m + 1, t, p * 2 + 1);
    d[p] = d[p * 2] + d[p * 2 + 1];
}
void update2(ll l, ll r, ll c, ll s, ll t, ll p)
{
    if(l <= s && t <= r)
    {
        d[p] += c * (t - s + 1);
        b[p] += c;
        return;
    }
    ll m = (s + t) / 2;
    if(b[p])
    {
        d[p * 2] += b[p] * (m - s + 1);
        d[p * 2 + 1] += b[p] * (t - m);
        b[p * 2] += b[p];
        b[p * 2 + 1] += b[p];
        b[p] = 0;
    }
    if(l <= m)
        update2(l, r, c, s, m, p * 2);
    if(r > m)
        update2(l, r, c, m + 1, t, p * 2 + 1);
    d[p] = d[p * 2] + d[p * 2 + 1];
}


ll getsum2(ll l, ll r, ll s, ll t, ll p)
{
    if(l <= s && t <= r)
    {
        return d[p];
    }
    ll m = (s + t) / 2;
    if(b[p])
    {
        d[p * 2] += b[p] * (m - s + 1);
        d[p * 2 + 1] += b[p] * (t - m);
        b[p * 2] += b[p];
        b[p * 2 + 1] += b[p];
        b[p] = 0;
    }
    ll sum = 0;
    if(l <= m)
        sum += getsum2(l, r, s, m, p * 2);
    if(r > m)
        sum += getsum2(l, r, m + 1, t, p * 2 + 1);
    return sum;
}

int main()
{
    char a;
    n = IntRead();
    q = IntRead();
    for(int i = 1; i <= n; i++)
        tr[i] = IntRead();
    built(1, n, 1);
    while(q--)
    {
        cin>>a;
        if(a == 'Q')
        {
            f = IntRead();
            c = IntRead();
            cout<<getsum2(f, c, 1, n, 1)<<'\n';
        }
        else
        {
            f = IntRead(), c = IntRead(), k = IntRead();
            update2(f, c, k, 1, n, 1);
//            for(int i = 1; i <=  10; i++)
//                cout<<d[i]<<' ';
        }
    }
    return 0;
}

H - 最少拦截系统

题意:

导弹拦截系统发射的炮弹第一次可以到达任意高度,该系统之后所能达到的高度不会高于上次拦截的高度,问你需要几套系统能完全拦截导弹

思路:

当时我一看这个题,就寻思这不是那动态规划嘛,之前比较懒没学,就直接跳过了,做到后面发现这是动态规划吗,怎么做出来十几个人,就感觉不对劲,就不抱希望地回头仔细读题,却发现可以直接贪心做

我是先取了一个数组d,用来存需要的拦截系统,最坏的情况下是一个炮弹比一个高,所以数组得开三万,初始只有一个系统,其值为第一个炮弹的高度,两个for循环,外循环是对炮弹的高度 tr[i] ,内循环是对出现的系统的高度d[j],如果tr[i] > 所有的d[j],说明前面的所有系统都不好使了,需要一个新系统,这个新系统的d[len++] = tr[i];如果tr[i] < 某一个的d[j],说明第j个系统能拦住他,就可以更新一下这个d[j] 的值为tr[i]。最后只需要输出数组的长度即可

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using namespace std;
inline int IntRead()
{
    char ch = getchar();
    int s = 0, w = 1;
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
            w = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        s = s * 10 + ch - '0',
        ch = getchar();
    }
    return s * w;
}
int tr[30005], d[30005];
int main()
{
    int n;
    while(cin>>n)
    {
        for(int i = 1; i <= n; i++)
            tr[i] = IntRead();
        d[1] = tr[1];
        int len = 1, k = 0;//数组长度为len,初始为1,k用来判断是否需要新系统
        for(int i = 2; i <= n; i++)
        {
            k = 0;//每次循环都要初始化k的值
            for(int j = 1; j <= len; j++)
            {
                if(tr[i] <= d[j])
                {
                    d[j] = tr[i];
                    k = 1;
                    break;//找到了就跳出,进行下一个炮弹的拦截
                }
            }
            if(k == 0)//k == 0说明没找到能拦截的系统,就得添加新系统
            {
                d[++len] = tr[i];
            }
        }
        cout<<len<<endl;
    }
    return 0;
}

I - How Many Tables

并查集板子题,但是记得要初始化数组,一不小心就wa了.jpg

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using namespace std;
inline int IntRead()
{
    char ch = getchar();
    int s = 0, w = 1;
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
            w = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        s = s * 10 + ch - '0',
        ch = getchar();
    }
    return s * w;
}
int tr[10005];
int find1(int x)
{
    if(tr[x] == x)
        return x;
    if(tr[x] != x)
    {
        tr[x] = find1(tr[x]);
        return tr[x];
    }
}
void ran(int a, int b)
{
    int x = find1(a);
    int y = find1(b);
    if(x != y)
        tr[y] = x;
}
int main()
{
    int n, m, a, b, t;
    t = IntRead();
    while(t--)
    {
        int sum = 0;
        memset(tr, 0, sizeof(tr));
        n = IntRead();
        m = IntRead();
        for(int i = 1; i <= n ; i++)
        {
            tr[i] = i;
        }
        for(int i = 1; i <= m; i++)
        {
            a = IntRead();
            b = IntRead();
            ran(a, b);
        }
        for(int i = 1; i <= n; i++)
        {
            if(tr[i] == i)
                sum++;
        }
        printf("%d\n",sum);
        //cout<<sum<<endl;
    }
    return 0;


}

M - Intersecting Lines

题意:

给你四个点,(x1,y1)(x2,y2)(x3,y3)(x4,y4),前两个点构成一条直线,后两个点构成一条直线,问你这两条直线 位置关系

思路:

看起来挺简单的,其实你得对解析几何了解的比较透彻才行

我用的是最老土的办法:设直线,推公式,而非那些花里胡哨的向量计算法(好吧,其实是我忘了向量那些公式是什么了……手动狗头

相信学过高中数学的同志们都知道考试最后一个解析几何的题,基本上都要考虑斜率不存在的特殊情况!

对,这个题你得先考虑斜率不存在的时候,两个直线的斜率都不存在的时候,你还得判断这两条直线是不是同一条直线(我就是wa在了这里!!!当时想了半天都觉得自己思路没问题,后来就下定决心如果再找不出错就去吃饭,结果一不小心找到了.jpg),判断完斜率不存在之后就可以直接算k1,b1,k2,b2,然后推个公式带进去即可。还要注意题目对输出的一些要求就能AC。

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using namespace std;
int main()
{
	int n;
    double  x1, x2, x3, x4, y1, y2, y3, y4;
    scanf("%d",&n);
    printf("INTERSECTING LINES OUTPUT\n");
    while(n--)
    {
      scanf("%lf%lf%lf%lf%lf%lf%lf%lf",&x1,&y1,&x2,&y2,&x3,&y3,&x4,&y4);

        if(x1 == x2 && x3 == x4)//同时没有斜率
        {
        	if(x1 == x3)//判断是不是重合了
        		cout<<"LINE"<<endl;
        	else
        		cout<<"NONE"<<endl;
        }
        else if(x1 == x2 && x3 != x4)//一个有斜率一个没有斜率
        {
            double k2,b2,x,y;
            k2 = (y4 - y3) / (x4 - x3);//公式,手推
            b2 = (x4 * y3 - x3 * y4) / (x4 - x3);
            x = x1;
            y = k2 * x + b2;
            printf("POINT ");
            printf("%.2lf %.2lf\n", x, y);
//            cout<<x<<' '<<y<<endl;
        }
        else if(x1 != x2 && x3 == x4)//同样是一个有斜率一个没有
        {
            double k1,b1,x,y;
            k1 = (y2 - y1) / (x2 - x1);
            b1 = (x2 * y1 - x1 * y2) / (x2 - x1);
            x = x3;
            y = k1 * x + b1;
            printf("POINT ");
            printf("%.2lf %.2lf\n", x, y);
            //cout<<x<<' '<<y<<endl;
        }
        else
        {
            double k1, k2, b1, b2, x, y;
            k1 = (y2 - y1) / (x2 - x1);
            k2 = (y4 - y3) / (x4 - x3);
            b1 = (x2 * y1 - x1 * y2) / (x2 - x1);
            b2 = (x4 * y3 - x3 * y4) / (x4 - x3);
            if(k1 == k2)//斜率相同则进行进一步的判断
            {
                if(b1 == b2)//重合
                    printf("LINE\n");
                else if(b1 != b2)
                    printf("NONE\n");
            }
            else
            {
                x = (b2 - b1) / (k1 - k2);
                y = k1 * x + b1;
                printf("POINT ");
                printf("%.2lf %.2lf\n",x, y);
            }
        }
    }
	printf("END OF OUTPUT\n");
    return 0;
}


J - The Suspects

题意:

0号同学有带病毒的嫌疑,给你n个团体,如果团体内有一个人有嫌疑,则整个团体都有嫌疑,问有多少带病毒嫌疑的人

思路:

并查集的变形,因为0号有嫌疑,最后要求所有带病毒嫌疑的人,就可以利用并查集合并并查找0号个体所在的团体,最终输出该团体的人数即可。

又因为是0号有嫌疑,是最小的一号,我们就可以采用取小原则,而非之前的靠左原则,即合并的时候谁的号小就归顺谁,这样能保证头领是0号,然后查找的时候只需要找他的头领是不是0号即可!

#include <bits/stdc++.h>
using namespace std;
const int N = 30000 + 5;
int n, m, k, tr[N];//
void init()//初始化数组
{
    for(int i = 0; i < n; ++i)
    {
        tr[i] = i;
    }
}
int find1(int v)//俗称找爹函数
{
    if(f[v] == v)//找到了就返回爹
        return v;
    else//没找到就继续找爹
    {
        f[v] = find1(f[v]);
        return f[v];
    }
}
void ranran(int u, int v)//合并
{
    int x = find1(u);//先把x,y的爹找出来
    int y = find1(v);
    if(x < y)//取小原则
        f[y] = x;
    else if(x > y)
        f[x] = y;
}
int a, b, c, sum;//sum用来计数,记得要清0
int main()
{
    while(cin>>n>>m)
    {
        if(n == 0 && m == 0)//输入都是0的时候结束程序
            break;
        init();//初始化
        sum = 0;//清0
        while(m--)//m组例子
        {
            cin>>a>>b;
            a--;//a个数,而b就是a个数中的一个,所以写while之前得让a--
            while(a--)
            {
                cin>>c;
                ranran(b, c);//每一次都A1与Ai进行并查集操作
            }
        }
        for(int i = 0; i < n; i++)
        {
            if(f[i] == 0 || find1(i) == 0)//两种情况,一种是本身就是0,另一种是认的爹是0
                sum++;
        }
        cout<<sum<<endl;
    }
    return 0;
}

B - String LCM

题意:

给你n对字符串,让你判断两个串的“最小公倍数”,如:abab 与 ab的最小公倍数是abab,如果没有的话就输出-1

代码如下,具体思路我会单独开一个博客写

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using namespace std;
int next1[100005],next2[100005], len1, len2;
string s1, s2;
void getnext1()
{
    int j, k;
    j = 0;
    k = -1;
    next1[0] = -1;
    while(j < len1)
    {
        if(k == -1 || s1[j] == s1[k])
            next1[++j] = ++k;
        else
            k = next1[k];
    }
}
void getnext2()
{
    int j, k;
    j = 0;
    k = -1;
    next2[0] = -1;
    while(j < len2)
    {
        if(k == -1 || s2[j] == s2[k])
            next2[++j] = ++k;
        else
            k = next2[k];
    }
}
int gcd(int a, int b)
{
    if(b) return gcd(b, a % b);
    else return a;
}
int main()
{
    int n, a, b, k;
   // string s1, s2;
    cin>>n;
    while(n--)
    {
        memset(next1, 0, sizeof(next1));
        memset(next2, 0, sizeof(next2));
        k = 1;
        cin>>s1>>s2;
        len1 = s1.size();
        len2 = s2.size();
        getnext1();
        getnext2();
        a = len1 - next1[len1];
        b = len2 - next2[len2];
        if(len1 % a != 0)
            a = len1;
        if(len2 % b != 0)
            b = len2;
//        for(int i = 1; i <= len1; i++)
//            cout<<next1[i]<<' ';
 //       cout<<len1 - next1[len1 - 1]<<endl<<len2 - next2[len2 - 1];
        if(a != b || len1 % a != 0 || len2 % b != 0)
        {
//            cout<<a<<' '<<b<<endl<<len1<<' '<<len2;
//            cout<<"第一处"<<endl;
            cout<<-1;
        }
        else
        {
            for(int i = 0; i < a; i++)
            {
                if(s1[i] != s2[i])
                {
                    k = 0;
                    break;
                }
            }
            if(k == 0)
            {
                cout<<-1;
            }
            else
            {
                int c = (len1 / a * len2 / b) / gcd(len1 / a, len2 / b);
                while(c--)
                {
                    for(int i = 0; i < a; i++)
                        cout<<s1[i];
                }
            }
        }
        cout<<endl;
    }
    return 0;
}

}

总结:

A题:思维题,最少四个因子,代表你只需要找两个因子即可,再仔细分析一下就知道是找素数因子

B题: KMP的next数组的思维题(我是真没想到是KMP的题.jpg

D题:01字符串的问题,题干很长,看似很难,其实不难,就是第一位一定是1,然后下面每一位都判断选1的时候加起来和上一位一不一样,不一样就选1,一样就选0

G题:线段树区间查询区间修改,套板子即可

H题:贪心,每次从第一个系统开始判断,如果能拦住就更新一下,拦不住就再找一个系统,最后输出总系统的个数

I题:并查集板子

J题:并查集的升级版,根据题意改一下find函数,将以左为尊改为以小为尊

M题:给你四个点,前两个点确定一条直线,后两个点确定一条直线,问你这两条直线的位置关系,题目不难,就是细节比较多,判斜率不存在和斜率存在,在分别判是平行还是重合还是相交,特别注意不要忘了判两个直线都没斜率的时候是否重合,剩下的的就考的是数学知识,推个公式即可

上一篇:Illustrator(AI)设计制作商业文字Logo实例教程


下一篇:Photoshop打造出舞台的梦幻迷离彩虹背景