A.算法提高 找素数

用到的算法:素数筛选
注意数据范围,L和R的选取范围在之间。说明普通的素数筛选暴力求解肯定是不行的。但是题目中给了,我们就可以根据这个关键来打表。
一个左右的合数,它的某个因子必定,所以我们可以先素数筛选出前的质数。然后给定的L和R,如果R在之前,就直接在之前打好的素数表直接计数复杂度O(),然后如果,我们就用前的质数去把后面的合数给筛掉,打个表。注意表需要进行坐标映射,也就是本来是[L,R]区间,然后映射到[0,R-L],这样打表就不会超内存了。打表完了之后,计个数就可以了,复杂度

总时间复杂度

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 5e4+10;
typedef long long ll;
int L,R;
bool vis[maxn];//前sqrt的表
bool vis2[1000010];//[0,R-L]的表
int P[maxn],tail;//保存前sqrt的质数

void init(){
    vis[0] = vis[1] = true;
    for(ll i = 2;i<maxn;i++){
        if(!vis[i]){
            P[tail++] = i;
            for(ll j = i*i;j<maxn;j+=i){
                vis[j] = true;
            }
        }
    }
}
void judge(){ //用于对拍
    int cnt = 0;
    for(int i = L;i<=R;i++){
        int ok = 1;
        for(int j = 2;j*j<=i;j++){
            if(i%j == 0){
                ok = 0;
                break;
            }
        }
        if(ok) cnt++;
    }
    cout<<cnt<<endl;
}
void fun(){
    int cnt = 0;
    if(R<maxn){
        for(int i = L;i<=R;i++){
            if(!vis[i]) cnt++;
        }
    }else{
        for(ll i = 0;i<tail && P[i]*P[i]<=R;i++){
            for(ll j = (L+P[i]-1)/P[i];j<=R/P[i];j++){ //(L+P[i]-1)/P[i]为向上取整
                if(j == 0 || j == 1) continue;
                vis2[P[i]*j-L] = true; //-L是为了映射
            }
        }
        for(int i = 0;i<=R-L;i++)
            if(!vis2[i]) cnt++;
    }
    cout<<cnt<<endl;
}
int main(){
    init();
    cin>>L>>R;
    if(L == 1) L = 2;//1不是质数,直接跳过
    fun();
    return 0;
}

B.算法训练 区间k大数查询

用到的算法:排序

这题重在数据范围不大,询问次数也不大。我们完全可以在每次询问的时候,把[L,R]区间内的数拷贝出来,排个序,然后求第K大的数。总时间复杂度,是可以1s计算完的
总时间复杂度
AC 代码

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 1010;

int N,M;
int arr[maxn];

int fun(int l,int r,int k){
    int len = r-l+1,tail = 0;
    vector<int> ve(len);
    for(int i = l;i<=r;i++) ve[tail++] = arr[i];
    sort(ve.begin(),ve.end());
    return ve[len-k];
}
int main(){
    cin>>N;
    for(int i = 1;i<=N;i++) scanf("%d",&arr[i]);
    cin>>M;
    int l,r,k;
    while(M--){
        scanf("%d %d %d",&l,&r,&k);
        printf("%d\n",fun(l,r,k));
    }
    return 0;
}

C.算法训练 操作格子

用到的算法:线段树单点修改

这题是一个线段树的模板题,只不过是把区间求和和区间求最值绑定在了一起。具体怎么做就不讲了,因为线段树只是个工具,这题只是进行修改和查询而已,只是在用这个工具。
如果没有学习过线段树的同学,请看这个入门视频(代码风格不一样,但是思想是一样的):传送门

#include <iostream>
#include <algorithm>
#include <vector>
#define fs first
#define sc second
using namespace std;
const int maxn = 100010;
typedef pair<int,int> pii;
int N,M;
int arr[maxn];

struct node{
    int l,r;
    pii p;
}tr[4*maxn];

void pushup(node &fa,node &L,node &R){
    fa.p.fs = L.p.fs+R.p.fs;
    fa.p.sc = max(L.p.sc,R.p.sc);
}
void build(int u,int l,int r){
    if(l == r){
        tr[u] = {l,r,{arr[l],arr[l]}};
    } else{
        tr[u] = {l,r};
        int mid = (l+r)>>1;
        build(u*2,l,mid);
        build(u*2+1,mid+1,r);
        pushup(tr[u],tr[u*2],tr[u*2+1]);
    }
}
void modify(int u,int idx,int v){
    if(tr[u].l == idx && tr[u].r == idx){
        tr[u].p = {v,v};
    }else{
        int mid = (tr[u].l+tr[u].r)>>1;
        if(idx<=mid) modify(u*2,idx,v);
        else modify(u*2+1,idx,v);
        pushup(tr[u],tr[u*2],tr[u*2+1]);
    }
}
node query(int u,int l,int r){
    if(tr[u].l>=l && tr[u].r<=r){
        return tr[u];
    }else{
        int mid = (tr[u].l+tr[u].r)>>1;
        node p1,p2,p3;
        if(l<=mid) p2 = query(u*2,l,r);
        if(r>mid) p3 = query(u*2+1,l,r);
        pushup(p1,p2,p3);
        return p1;
    }
}
int main(){
    cin>>N>>M;
    for(int i = 1;i<=N;i++) scanf("%d",&arr[i]);
    build(1,1,N);
    int op,a,b;
    while(M--){
        scanf("%d%d%d",&op,&a,&b);
        if(op == 1){
            modify(1,a,b);
        }else if(op == 2){
            node res = query(1,a,b);
            printf("%d\n",res.p.fs);
        }else{
            node res = query(1,a,b);
            printf("%d\n",res.p.sc);
        }
    }
    return 0;
}

D.算法提高 士兵排队问题

用到的算法:拓扑排序
这是一个拓扑排序的模板题,主要是让大家回顾一下拓扑排序,这里是否可以排队其实就是问是否可以构成一个拓扑图,无果无法构成一个拓扑图就必定图中存在环。所以先要记录一下拓扑图中有多少人,然后在遍历拓扑图的时候,记录出去了多少人。如果存在环,那么必定存在有人还在图里面,因为环的存在出不来。

这题也是有一个坑点:他说的是能不能求出一种高低排序,我做的是否同时存在有两个入度为0的点,结果他的意思是是否存在环。

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int maxn = 300;
typedef pair<int,int> pii;

int in[maxn];//记录士兵的入度
bool st[maxn];//是否出现过
vector<char> h[maxn];//存储图
vector<char> ans; //结果
int main(){
    char str[4];
    while(~scanf("%s",str+1)){
        h[str[1]].push_back(str[3]);
        in[str[3]]++;
        st[str[1]] = st[str[3]] = true;
    }
    queue<char> q;
    for(int c = 'A';c<='Z';c++){
        if(in[c] == 0 && st[c]){
            q.push(c);
        }
    }
    int total = 0;
    for(int c = 'A';c<='Z';c++) if(st[c]) total++;
    while (q.size()){
        char u = q.front();q.pop();
        total--;
        ans.push_back(u);
        for(int i = 0;i<h[u].size();i++)
        {
            char v = h[u][i];
            --in[v];
            if(in[v] == 0) q.push(v);
        }
    }
    if(total == 0){
        for(int i = 0;i<ans.size();i++) cout<<ans[i];
    }else{ //total不为0,就说明有环
        puts("No Answer!");
    }
    return 0;
}

E.基础练习 十六进制转八进制

用到的算法:高精度
这题可以现将十六进制的每一位转换成4个二进制数,然后在对每3个二进制数进行转换成8进制就可以了。只是一些细节问题需要注意,比如二进制转八进制的时候,可能不是3的倍数,这时候就需要在前面补0,在输出的时候,如果有前导0就不需要输出。还有一些需要反着来处理的,也需要注意。

#include <iostream>
#include <algorithm>
#include <map>
#include <string>
#include <queue>
#include <vector>
using namespace std;
const int maxn = 300;
typedef pair<int,int> pii;

int T;
map<char,int> mp;
void init(){ //初始化一下,把十六进制位数映射一下。
    for(char c = '0';c<='9';c++) mp[c] = c-'0';
    for(char c = 'A',v = 10;c<='F';c++,v++) mp[c] = v;
}
void fun(string &s){
    vector<int> bin(s.length() * 4);int idx = 3;//需要四倍长度
    for(int i = 0;i<s.length();i++,idx+=4){ // 十六进制转2进制
        int t = mp[s[i]];
        int ind = idx;
        while(t){
            bin[ind--] = t % 2;
            t/=2;
        }
    }
    while(bin.size()%3) bin.insert(bin.begin(),0); //补前导0,长度成3的倍数
    vector<int> eight;int cur = 0;
    for(int i = 0;i<bin.size();i++){ //二进制转8进制
        cur = cur*2+bin[i];
        if((i+1)%3 == 0)
            eight.push_back(cur),cur = 0;
    }
    for(int i = 0;i<eight.size();i++){
        if(i == 0 && eight[i] == 0) continue;
        cout<<eight[i];
    }
    cout<<endl;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    init();
    cin>>T;
    while(T--){
        string s;cin>>s;
        fun(s);
    }

    return 0;
}

F.算法训练 最大最小公倍数

用到的算法:思维
这题的关键在与只选取3个数,我们知道如果只选择两个数的话,那么肯定选两个相邻的两数,因为他两互质所以最小公倍数是最大的。所以面对要选择三个数,我们也可以设法选出3个互质的数。对于相邻的a,b,c三个数有奇偶奇,偶奇偶,可以发现奇偶奇三个数一定是互质的,a与b互质,b与c互质,a与b之间不可有共因子2,|c-a|<3,所以也不可能存在共因子3,之后的更别说了。
然后是偶奇偶,其最大最小公倍数必定至少除2,所以可以尝试把其中一个偶换成奇数,且不构成3的倍数,或者整体-1,变成奇偶奇。

#include <iostream>
#include <algorithm>
#include <map>
#include <string>
#include <queue>
#include <vector>
using namespace std;
const int maxn = 300;
typedef pair<int,int> pii;
typedef long long ll;

ll N;
int main(){
    cin>>N;
    ll mx = 0;
    if(N<3) mx = 2;
    else if(N&1){ //奇偶奇
        mx = N*(N-1)*(N-2);
    }else{
        if(N%3 !=0){ //N和N-3不是3的倍数
            mx = N*(N-1)*(N-3);
        }else{ //整体-1
            mx = (N-1)*(N-2)*(N-3);
        }
    }
    cout<<mx<<endl;
    return 0;
}

G.算法提高 欧拉函数

用到的算法:欧拉筛
线性复杂度,把前maxn个数的欧拉值筛选出来

#include <iostream>
#include <algorithm>
#include <map>
#include <string>
#include <queue>
#include <vector>
using namespace std;
const int maxn = 2e6+10;
typedef pair<int,int> pii;
typedef long long ll;

ll N;
int E[maxn];
void init()
{
    E[1] = 1;
    for(int i=2;i<maxn;i++){
        if(!E[i])
            for(int j=i;j<maxn;j+=i){
                if(!E[j]) E[j]=j;
                E[j] = E[j]/i*(i-1);
            }
    }
}
int main(){
    init();
    cin>>N;
    cout<<E[N]<<endl;
    return 0;
}

H. 算法训练 乘法次数

用到的算法:快速幂
可以假定底数是2,然后就把 转换成二进制的形式,假如转换后的二进制,共有cnt个1,最高位1是poww,那么cnt个1就是cnt个底数相乘需要cnt-1次乘法,最高次方是poww需要经过poww-1次乘法,所以总共需要cnt+poww-2次乘法

#include <iostream>
#include <cmath>
using namespace std;
typedef long long ll;
const ll mod = 1000000007;
int T;
ll ksm(ll a,ll b){
    int poww = 0,cnt = 0;
    while(b){
        if(b&1) {
            cnt += 1;
        }
        a = a*a% mod;// 也可以不写
        poww++;
        b>>=1;
    }
    return poww+cnt-2;
}
int main(){
    cin>>T;
    while(T--){
        ll b;scanf("%lld",&b);
        printf("%lld\n",ksm(2,b));
    }

    return 0;
}

I.算法训练 方格取数

用到的算法:动态规划
此题不可以采用贪心策略的dp(分别走两次dp),因为有后效性。
考虑两个人同时走,就有四种可能,下下,右右,下右,右下,一共要走2*n-1步,所以dp需要三次循环,枚举步数,枚举第一个人的位置,枚举第二个人的位置。位置可以用一维来表示,也就是(x,y) 只用保存x就可以了,y = 步数-x来表示,然后要保证y不越界就好。然后每一步都是上一步下下,右右,下右,右下中取最大值+目前两人的位置上的数,如果两个人位置一样则只加一次。

#include <iostream>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
int N;
ll mp[11][11];
ll dp[22][11][11];

ll fun(){
    for(int k = 2;k<=N+N;k++){
        for(int i1 = 1;i1<=N;i1++){
            for(int i2 = 1;i2<=N;i2++){
                int j1 = k-i1,j2 = k-i2; //求出y坐标
                if(j1<1 || j2<1 || j1>N || j2>N) continue; //保证不越界
                ll mx = 0;
                mx = max(mx,dp[k-1][i1-1][i2]);//i1-1表示上一步是y+1,i2不减表示上一步是x+1
                mx = max(mx,dp[k-1][i1][i2-1]);
                mx = max(mx,dp[k-1][i1][i2]);
                mx = max(mx,dp[k-1][i1-1][i2-1]);
                if(i1 == i2) dp[k][i1][i2] = mp[i1][j1] + mx;
                else dp[k][i1][i2] = mp[i1][j1] + mp[i2][j2] + mx;
            }
        }
    }
    return dp[2*N][N][N];
}
int main(){
    cin>>N;
    int x,y,v;
    while(cin>>x>>y>>v){
        if(x == 0 && y == 0 && v == 0 ) break;
        mp[x][y] = v;
    }
    printf("%lld\n",fun());

    return 0;
}

J.基础练习 01字串

用到的算法:二进制枚举
这题也可以用DFS枚举,更可以直接写答案。
可以发现题目要求输出的字符串,其实就是0~31的二进制代码,所以二进制枚举一下就可以了。

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

int main(){
    for(int i = 0;i<(1<<5);i++){
        for(int j = 4;j>=0;j--){
            if(i>>j &1) putchar('1');
            else putchar('0');
        }
        puts("");
    }

    return 0;
}