SMU-ACM Spring 2024 2nd


The Second Week

一、前言

日拱一卒无有尽,功不唐捐终入海。 ————《法华经·观世音菩萨普门品》
周日川大线上校赛,三人组队十二个小时。玛卡巴卡打得还不错吧感觉。
哎哟周二的cf个人赛,C题做崩了之后整个心态没了,做了俩三个小时,看着D题写不进去,交了16次也是给我整笑了。后来发现错的真的很无语。
周六晚上做了一下上届川大决赛题,哎哟签到题心态做崩了。


二、算法

1.二叉树

typedef struct treeNode
{
    int id;
    struct treeNode* LChild;
    struct treeNode* RChild;
}TREE,*LPTREE;
//结构体建树节点,LP一般代表指针别名

LPTREE createNode(int id,LPTREE l,LPTREE r) {
    LPTREE newNode = (LPTREE)malloc(sizeof(TREE));
    //malloc函数的作用是申请内存空间并返回给指针变量
    newNode->id = id;
    newNode->LChild = l;
    newNode->RChild = r;
    return newNode;
}
//建立一个节点

void insertNode(LPTREE parentNode,LPTREE LChild,LPTREE RChild) {
    parentNode -> LChild = LChild;
    parentNode -> RChild = RChild;
}
//在空节点下插入左右子节点

void printCurNodeData(LPTREE curDate) {
    std::cout << curData->data << endl;
}
//终于打印节点了

void preOrder(LPTREE root) {
    if(root != NULL) {
        printCurNodeData(root);
        preOrder(root->LChild);
        preOrder(root->RChild);
    }
}
//前序遍历:根左右(递归方式)



void midOrder(LPTREE root) {
    if(root != NULL) {
        midOrder(root->LChild);
        printCurNodeData(root);
        midOrder(root->RChild);
    }
}
//中序遍历:左根右(递归方式)

void lastOrder(LPTREE root) {
    if(root != NULL) {
        preOrder(root->LChild);
        preOrder(root->RChild);
        printCurNodeData(root);
    }
}
//后序遍历:左右根(递归方式)

2.贪心

贪心算法就是通过局部最优解达到整体最优解,经典的是选择排序。

<1>(CF set1746 B)

vjudge上的贪心题单,看了别人的代码写的。
题解:
给出t组案例,每组数组a包含n个数字0或1,要求求出最少经过几次运算,可以使得数组a是一个递增数组,每次运算即把ai加到aj上,去除ai。
从左右俩边往中间遍历,注意不要超出边界,每次有不符合递增条件的数字,都进行一次运算,然后继续遍历,直到l>=r。
数组的最小数字是0,但最大数字未必是1。
代码:

#include<iostream>
#include<utility>

using namespace std;

int main () {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    long long t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        int a[n];
        int ans = 0;
        for(int i = 0; i < n; i++) {
            cin >> a[i];
        }
        int l = 0, r = n-1;
        while(l < r) {
            while(a[l] == 0 && l < n)l++;
            while(a[r] != 0 && r > -1)r--;
            if(l >= r)break;
            ans++;
            l++;
            r--;
        }
        cout << ans << endl;
    }
    return 0;
}

3.快速幂

const long long mod = 1e9 + 10;
long long quick_pow ( long long base, long long b ) {
    long long ans = 1;
    while(b) {
        if( b & 1 != 0) {
            ans = ans * base % mod;
        }
        base = base * base % mod;
        b >>= 1;
    }
    return ans;
}

<1>(四川大学线上校赛 E)

做了三个小时,但现在网站不知道怎么个事,打不开了,找出了代码但是原题好像暂时看不了了。
题解:
打表可以看出1对应的是3的一次方,111对应的是3的三次方,以及给出的一组数据可以论证。而1000会比111多2,以此类推每一位的1可以代表增加的数字多少,数据过大所以得开快速幂。
代码:

#include<iostream>

const int MOD = 1e9 + 7;

using namespace std;

long long quick_pow(long long b) {
    long long res = 1;
    long long base = 3;
    while(b>0) {
        if(b & 1) res *= base;
        res %= MOD;
        base *= base;
        base %= MOD;
        b >>= 1;
    }
    return res;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    string l;
    cin >> l;
    long long ans = 1;
    long long t = 1;
    for(int i = 0; i < l.length(); i++) {
        if(l[i] == '1') {
            long long res = quick_pow(l.length()-1-i);
            res += 2;
            ans += (t*res);
            ans -= t;
            //减去一开始的自己
            t = t*2;
            //每找到一个1会使后面所有情况多出2
            t = t % MOD;
            ans %= MOD;
        }
    }
    cout << ans << endl;
    return 0;
}

4.string函数

之前写过一些,但是又发现了新的函数,导致我天梯赛浪费了一部分时间。

string s2 = s1.erase(0,2);
//s1,s2都会删除索引为0开始的2个字符
    
string s2 = s1.insert(0,"abc");
//s1,s2都会在索引为0的位置插入“abc”

s1.replace(0,2,"abc");
//把s1中的从0开始长度为2的字符替换成“abc”

string s2 = s1.substr(0,2);
//s2会截取s1从0开始的2个字符

string s2;
s2.assign(s1);
//s1的内容重新赋值给s2
s2.assign(s1,1,3);
//s2的内容为s1从1开始的3个字符
//那跟substr函数好像一样一样的诶...

<1>(团队程序设计天梯赛 L1-5)

别再来这么多猫娘了! 15分

比赛的时候一直t,没办法全过,后来看到群里学长们在讨论才知道,替换词里可能存在违禁词,无限替换循环。题目本身还是很简单的,就不写题解了。下次一定要注意不要直接用替换词!!!尤其是acm赛制!
代码:

#include<iostream>

using namespace std;

string wjc[102];
int main() {
    int n;
    cin >> n;
    for(int i = 0; i < n; i++) {
        cin >> wjc[i];
    }
    int k;
    cin >> k;
    string wb;
    getline(cin,wb);
    getline(cin,wb);
    int res = 0;
    for(int i = 0; i < n; i++) {
        int t = 0;
        t = wb.find(wjc[i],t);
        while(t != -1) {
            wb.erase(t,wjc[i].length());
            wb.insert(t,"?|?|");
            t = wb.find(wjc[i],t);
            res++;
        }
    }
    if(res >= k) {
        cout << res << endl;
        cout << "He Xie Ni Quan Jia!" << endl;
    }
    else {
        int lt = 0;
        lt = wb.find("?|?|",lt);
        while(lt != -1) {
            wb.erase(lt,4);
            wb.insert(lt,"<censored>");
            lt = wb.find("?|?|",lt);
        }
        cout << wb << endl;
    }
    return 0;
}

5.dfs和bfs

<1>(AcWing 843)

y总的课,学了好几次这俩个算法了,dfs搞得差不多了,能写,bfs还是只懂概念,希望下次可以写出来。
题解:

代码:

#include<iostream>

using namespace std;

char a[10][10];
//储存棋盘的状态
int ls[10];
//储存这一列是否有皇后,可以用bool函数
int zx[20];
//储存这一斜是否有皇后
int yx[20];
//储存另一斜是否有皇后
int hs;

void dfs(int n) {
    //n表示遍历到第几行
    if(n == hs) {
        for(int i = 0; i < n; i++) {
            puts(a[i]);
            //直接输出a的这一整行
        }
        puts("");
        //换行输出
        return ;
        //遍历完成
    }
    for(int i = 0; i < hs; i++) {
        if(ls[i] == 0 && zx[i + n] == 0 && yx[hs - n - 1 + i] == 0) {
            //全都没有皇后,这一个位置可以放
            a[n][i] = 'Q';
            ls[i] = zx[i+n] = yx[hs-n-1+i] = 1;
            dfs(n + 1);
            //下一行
            a[n][i] = '.';
            ls[i] = zx[i+n] = yx[hs-n-1+i] = 0;
            //遍历完成恢复状态
        }
        else {
            a[n][i] = '.';
        }
        //可以不写的其实,但不写不知道为什么可以实现要求
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> hs;
    for(int i = 0; i < hs; i++) {
        for(int j = 0; j < hs; j++) {
            a[i][j] = '.';
        }
    }
    dfs(0);
    return 0;
}


6.博弈论

第一次听说这个名词,这应该就是针对这类型题的吧,俩个人互相算计的这种。题倒是看到过好几道了,做出来的是一个都没有。

<1>(四川大学线上校赛 J)

Alice and Bob
三个人做了一晚上也没做出来,最后还拼命掐点交WA,后来是问了学长之后又看了半天代码,似懂非懂。给他改得顺眼了点,又加了点注释。
题解:
黑板上写下2N个整数,以及一个至少为2的整数M,Alice和Bob轮流以最优策略删除数字,如果删除的数字之和mod M相等就是Bob赢,否则就是Alice赢。
大概思路应该是,如果全是偶数个,或者全都是大于2/m的组合并且组合数是偶数的话就是Bob赢,否则全是Alice赢。
代码:

#include<iostream>
#include<vector>
#include<unordered_map>
#include<utility>

#define int long long
#define double long double
using namespace std;

unordered_map<int, int>hm;
//储存数组中每个数字出现的次数
vector<int>ans;
//储存输入的数字

void fun ( vector<int> pp,int m ) {
    int sum = 0;
    for (int i = 0; i < pp.size(); i++) {
        int x=pp[i];
        //vector也可以直接遍历
        hm[x]++;
        if (hm[x] % 2 != 0) {
            sum++;
        } else {
            sum--;
        }
    }
    //sum判断是否所有数字都是偶数个
    if (m % 2 != 0 || sum==0) {
        if (sum)cout << "Alice" << endl;
        else cout << "Bob" << endl;
        return ;
    }
    //如果sum是0的话一定是Bob赢,如果m是奇数的话一定是Alice赢
    pp.clear();
    for ( auto[u,v] : hm ) {
        if ( v % 2 != 0 ) {
            pp.emplace_back ( u % ( m / 2 ));
        }
    }
    //a,b,c,a+m/2,b+m/2,c+m/2的情况
    if ( pp.size() / 2 % 2 != 0 ) {
        //3m 或者 m 都无法/2m
        cout << "Alice" << endl;
        return ;
    }
    else{
        sum = 0;
        for (int i = 0; i < pp.size(); i++) {
            int x = pp[i];
            hm[x]++;
            if ( hm[x] % 2 != 0) {
                sum++;
            } else {
                sum--;
            }
        }
        //同上思维
        if ( sum != 0 ) cout << "Alice" << endl;
        else cout << "Bob" << endl;
    }
    return ;
}

int32_t main() {
    int T = 1;
    while (T--) {
        int n, m;
        cin >> n >> m;
        for (int i = 0; i < 2 * n; i++) {
            int x;
            cin >> x;
            ans.emplace_back(x);
            //与push_back造成的结果相同,但是emplace_back更简便
        }
        fun(ans,m);
        //判断一下谁赢了
    }
    return 0;
}

7.素数

//开根号法
bool IsPrime (int x) {
    if (x <= 1) {
        return false;
    }
    for (int i = 2; i*i <= x; i++) {
        //必须有等于号,例如9;
        if (x % i == 0) return false;
    }
    return true;
}

    //埃拉托斯特尼筛法
    int pri[1000005];
    memset(pri,0,sizeof(pri));
    int n;
    cin >> n;
    for (int i = 2; i*i <= n; i++) {
        if(pri[i] == 0) {
            for (int j = i*i; j <= n; j+=i) {
                //i*i以前的数没有必要算,比i小的数字已经遍历完
                pri[j] = 1;
            }
        }
    }

<1>(CF The 21st Sichuan University Programming Contest I)

这真的是签到题
服了呀三个人做了大几个小时这题,愣是没跑过去,给我无语住了。至今不知道之前几段代码错在了哪里,服气了。
题解:
在数组a中寻找最大的数字ai满足其所有的质因子都在该数列中,如果不存在就输出-1,直接暴力求解见代码。
代码:

#include<iostream>
#include<algorithm>
using namespace std;

int main() {
    int st[1000005] = {0};
    int n;
    cin >> n;
    int a[n];
    for(int i = 0; i < n; i++) {
        cin >> a[i];
        st[a[i]] = 1;
    }
    sort(a,a+n);
    for(int i = n - 1; i >= 0; i--) {
        bool stt = true;
        if(a[i] == a[i+1])continue;
        for (int j = 2; j < a[i]; j++) {
            if(a[i] % j == 0) {
                bool pq = true;
                for (int q = 2; q*q <= j; q++) {
                    if(j % q == 0){
                        pq = false;
                    }
                }
                if(pq){
                    if(!st[j]){
                        stt = false;
                    }
                }
            }
        }
        if(stt){
            cout << a[i] << endl;
            return 0;
        }
    }
    cout << -1 << endl;
    return 0;
}

8.前缀和

一直以为前缀和超简单的,结果这题真就给我绕晕了。

<1>(山东理工ACM D)

会编程的老师

四个小时的比赛三个小时在做这题,然后还没做出来我也是服了。但是它的算法又很简单,不知道这题算是简单还是不简单了。过得人还巨多。
题解:
题目要求给定q次询问字符串xi,在长度为n的字符串s中寻找一个最长的满足xi中每个字符只出现一次的子串,输出最长长度,s只包含九个字符。
利用前缀和思维,储存s中每个位置包含的九个字母的数量,遍历起位置,结尾的位置用二分判断,满足条件的跟前几轮最大res比较,最终输出。具体见注释。
代码:

#include<iostream>
#include<algorithm>
#include<vector>
#include<unordered_map>
#include<map>
using namespace std;

int pre[10][10004];
//储存前缀和
map<char,int>vis;
//用了个map方便查找

int main() {
    vis['P']=0;
    vis['Q']=1;
    vis['Y']=2;
    vis['S']=3;
    vis['Z']=4;
    vis['T']=5;
    vis['M']=6;
    vis['N']=7;
    vis['B']=8;
    
    int n,q;
    cin >> n >> q;
    string s;
    cin >> s;
    s = ' ' + s;
    
    for (int i = 1; i < s.length(); i++) {
        for (int j = 0; j < 9; j++) {
            pre[j][i] = pre[j][i - 1];
            //储存到第i个位置,每个元素j出现几次
        }
        pre[vis[s[i]]][i]++;
        //当前位置出现的得再加上一
    }
    
    while(q--) {
//        cout << "si" << endl;
        string x;
        cin >> x;
        int res = 0;
        //末位置
        for (int i = 1; i < s.length(); i++) {
   //         cout << "aaa" << endl;
            auto check = [&] (int p) {
                bool st = true;
                for (int j = 0; j < x.length(); j++) {
                    if(pre[vis[x[j]]][p] - pre[vis[x[j]]][i - 1] >= 2) {
                        //如果到这个位置特定元素出现不止一次,那就直接放弃这个末尾
                        st = false;
                        return st;
                    }
                }
                return st;
            };//研究了好久,应该是在循环内定义了一个bool函数

    //        cout << "sto[" << endl;
            int l = i, r = s.length() - 1;
            int ans = 0;
            while (l <= r) {
                int  mid = l+r >> 1;
                if (check(mid)) {
                    l = mid + 1;
                    ans = mid;
                    //满足条件的最后一位就是ans
                }
                else r = mid - 1;
            }//一个二分开始判断
            
            if (ans) {
                bool st = true;
                for (int j = 0; j < x.length(); j++) {
                    if(pre[vis[x[j]]][ans] - pre[vis[x[j]]][i - 1] != 1) {
                        //哦!这里在判断这个结尾够不够,有些特定元素还没出现过,就不可以用这个结尾
                        st = false;
                        break;
                    }
                }
                if (st) {
                    res = max (res, ans - i + 1);
                    //正确就继续啦
 //                   cout << ans << ' ' << i << endl;
                }
            }
        }
        
        cout << res << endl;
    }
    return 0;
}

三、总结

前俩周比赛比懵了,好吧不找借口了,一旦不强制,我自己也好久不写题解了,实在不喜欢补题,希望之后稍微自律一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值