寒假训练 第一场

一:前缀和

1.题目:数圈圈

题目链接:数圈圈

解题思路:

题目给出两个数字a, b, 让你求出 a - b 范围内每个数字”圈“的总和。 对于这样的区间和问题,直接预处理出前缀和。查询即可

代码块:

`#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;

int cir[10] = {1, 0, 0, 0, 1, 0, 1, 0, 2, 1};
int f[N];

int main()
{
    int n;
    cin >> n;
    
    //根据数据范围,预处理出 1 - 1e6 的前缀和数组
    for(int i = 1; i <= 1000000; i++)
    {
        int x = i;
        int sum = 0;
        while(x)
        {
            sum += cir[x % 10];
            x /= 10;
        }   
        f[i] += f[i - 1] + sum; 
    }
    for(int i = 0; i < n; i++)
    {
        int a, b;
        cin >> a >> b;
        cout << f[b] - f[a - 1] << endl;
    }
    return 0;
    }

2.题目:珂朵莉与宇宙

题目链接:珂朵莉与宇宙

解题思路:

假设暴力处理,那么需要枚举所有的子区间内的和,枚举区间和用前缀和,并枚举理论上符合的 j^2。
即为, s[r] - s[l] = j^2,枚举三个变量肯定超时。
通过变化式子
s[r] - j^2 = s[l], 那么只需要枚举 r, 和 j, 和符合的 s[l], 的个数,因为s[l], 可以循环 r 时,一并维护出,所以优化掉一个枚举。

代码块:

#include
#include
using namespace std;
#define ll long long
const int N=1e6+10;
ll a[N];
ll cn[N];
int main(){
     ll n;cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)a[i]+=a[i-1];
    //mapma;
    cn[0]=1;
    ll sum=0;
    for(int i=1;i<=n;i++){
        for(int j=0;j*j<=a[i];j++){
            sum+=cn[a[i]-j*j];
        }
        cn[a[i]]++;
    }
    cout<<sum<<endl;
    return 0;
}

3.题目:激光炸弹

题目链接:激光炸弹

解题思路:

二维前缀和,因为我们要枚举很多子区间,观察数据范围,如果枚举子区间会超时。对于二维区间和的问题,我们使用二维前缀和。
不会二维前缀和可以参考以下文章
二维前缀和

代码块:

#include
using namespace std;
const int N = 5010;

int g[N][N];

int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

    int n, m;
    cin >> n >> m;

    for(int i = 0; i < n; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        a++, b++;
        g[a][b] = c;

    }
    
    for(int i = 1; i < N; i++)
        for(int j = 1; j < N; j++)
        {
            g[i][j] = g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1] + g[i][j];
        }
    

    int ans = 0;

    for(int i = m; i < N; i++)
        for(int j = m; j < N; j++)
        {
            int x2 = i, y2 = j;
            int x1 = i - m + 1, y1 = j - m + 1;

            ans = max(ans, g[x2][y2] - g[x1 - 1][y2] - g[x2][y1 - 1] + g[x1 - 1][y1 - 1]);
        }

    cout << ans;


    return 0;
}

二:差分

1.题目:校门外的树

题目链接:校门外的树

解题思路:

首先观察数据范围,考虑暴力是否可做,可做,这里不提供暴力解法。
这个题推荐使用差分来做,来练习差分的使用。
差分可以实现对一个区间的所有数,加减一个数字。对此题,我们假设 0 代表有树木,对一段区间砍树就规定为对一段区间的数 -1。
这样我们就减少了去遍历区间内的每个数字进行减法,而在最后所有操作进行完毕之后,统一计算每个点的值。如果还是0,那么还有树。

代码块:

#include
using namespace std;

const int N=1e4 + 10;

int f[N];
int n, m;

int main()
{
    cin >> n >> m;
    
    for(int i = 0; i < m; i++)
    {
        int a, b;
        cin >> a >> b;
        f[a]--;
        f[b + 1]++;
    }

    int ans = 0;

    for(int i = 0; i <= n; i++)
    {
        f[i] += f[i - 1];
        if(!f[i]) ans++;
    }

    cout << ans << endl;


    return 0;
}

2.题目:货物种类

题目链接:货物种类

解题思路:

不难发现这个不同于以前的统计个数吗,而是统计不同种类的数量。所以我们不能对个数进行维护差分数组。
所以我们要对不同的种类维护差分数组,所以我们需要一个一个种类的进行加。并且重复区间不能重复计数,需要区间合并。
所以我们先结构体存下来,进行排序。
因为我们要进行区间合并,所以尽可能 l 的小的排在前面。
区间合并时:分三种情况。

对于同一个种类的区间合并:
1.如果说下一个区间的 l 大于当前维护的区间的 r, 说明这俩区间没有交集,那么 维护区间数组,更新维护的区间
2.否则当前l 小于等于当前维护区间的 r,说明他俩有交集,如果说当前 r > 当前维护的区间,那么维护区间 r 需要扩大了。

遇到不同种类了
把之前维护的区间更新差分数组,然后更新区间

代码块:

#include
using namespace std;
const int N = 1e5 + 10;


int f[N];

struct Node
{
    int l, r, id;
}e[N];

bool bmp(Node x, Node y)
{
    if(x.id != y.id)
    return x.id < y.id;
    else if(x.l != y.l)return x.l < y.l;
    else return x.r < y.r;
}

int main()
{
    int n, m;
    cin >> n >> m;

    for(int i = 1; i <= m; i++) cin >> e[i].l >> e[i].r >> e[i].id;

    sort(e + 1, e + m + 1, bmp);

    int st = e[1].l, ed = e[1].r;
    int last = e[1].id;

    for(int i = 2; i <= m; i++)
    {
        if(last != e[i].id)
        {
            last = e[i].id;
            f[st]++, f[ed + 1]--;
            st = e[i].l, ed = e[i].r;
        }else
        {
            if(e[i].l > ed)
            {
                f[st]++, f[ed + 1]--;
                st = e[i].l, ed = e[i].r;
            }else if(ed < e[i].r) ed = e[i].r;
        }
    }


    f[st]++, f[ed + 1]--;

    int mxx = 0;
    int ans = 0;

    for(int i = 1; i <= n; i++)
    {
        f[i] += f[i - 1];
        if(f[i] > mxx)
        {
            mxx = f[i];
            ans = i;
        }
    }


    cout << ans << endl;




    return 0;
}

3.题目:放学后茶会的甜点

题目链接:[放学后茶会的甜点]

解题思路:

优先学习二维差分:

二维差分
二维差分预处理题目对子区间增加 1。题目问取任意大小的区间的最大平均甜度,既然是平均甜度,所以我们取最大值那一个即可。
因为平均值不会超过最大值。

代码块:

#include
using namespace std;

const int N = 1010;

int g[N][N];

int main()
{
    int n, m, t;
    cin >> n >> m >> t;

    for(int i = 1; i <= t; i++)
    {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;

        g[x1][y1] += 1;
        g[x2 + 1][y1] -= 1;
        g[x1][y2 + 1] -= 1;
        g[x2 + 1][y2 + 1] += 1;

    }

    int ans = 0 ;
    
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
    {
        g[i][j] += g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1];
        
        ans = max(ans, g[i][j]);
    }


    cout << ans <<endl;
    
    return 0;
}

三:贪心

1.题目:小杰的签到题

题目链接:小杰的签到题

解题思路:

首先不希望桌子有空闲时间,所以谁先到谁就先排队。
所以我们先排序所有人的到达时间,从到达时间小的开始放置。
假设对于我们当前要放置的队伍来说,肯定放在最先结束的桌子上。找到三个桌子谁先结束。所以我们需要维护每个桌子当前结束时间。

代码块:

void solved()
{
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i];
    cin >> m;
    
    //初始化桌子,时间都归0
    for(int i = 0 ;i < 3; i++)
        que[i] = 0;
    
    //排序让先到的进行
    sort(a, a + n);

    for(int i = 0; i < n; i++)
    {
        int x = a[i];
        int mnn = 1e9; // 找到三个桌子谁最先结束
        int xb = 0;    //记录是第几个桌子
        for(int j = 0; j < 3; j++)
        {
            if(que[j] < mnn) mnn = que[j], xb = j;
        }
        
        //如果说当前队伍大于等于最先结束的了,那么肯定按照当前队伍到达的时间来开始
        if(x >= mnn)
        {
            que[xb] = x + m;
        }else que[xb] = mnn + m; 
        //如果我们到达的时候还没有空桌子,那么肯定等空桌子结束我们开始

    }
    
    //三个桌子找到最晚结束的就是我们的答案
    int ans = 0;
    for(int i = 0; i < 3; i++)
    {
        ans = max(ans, que[i]);
    }

    cout << ans << endl;

}

2.题目:排座椅

题目链接:排座椅

解题思路:

首先题目中说所有说话的人是相邻的(上下左右), 对于竖着相邻的人来说,横着的过道才能影响,对于横着相邻的来说,竖着的过道影响。
对于一条竖着的过道,影响的只能是相邻两列的人,所以贪心的来说,对于每一条过道我们都希望是影响最多的人。
那么统计一下每对人如果要拆开,需要在哪一列 / 一行,防止过道。然后排序从大到小,输出题目要求的过道数量即可。
需要注意的点,题目要求输出过道的位置时,要求从小到大输出。
因为我们需要输出哪个过道被统计的数量最多,所以我们在统计次数的时候,也要标记上是哪个过道,这样排序的时候我们知道是哪个过道最多。
所以使用结构体存。

代码块:

#include
using namespace std;
const int N = 2010;
int hs[N], ls[N];

struct Node
{
    int id, w;
}he[N], le[N]; //分别统计行和列的次数


bool bmp(Node x, Node y)
{
    return x.w > y.w;
}


int main()
{
    int n, m, k, l, d;
    cin >> n >> m >> k >> l >> d;

    for(int i = 0; i < d; i++)
    {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        
        //上下相邻
        if(x1 == x2)
        {
            he[min(y1, y2)].w++;
            he[min(y1, y2)].id = min(y1, y2); //避免排序时候不知道是哪个过道
        }else if(y1 == y2) //左右相邻
        {
            le[min(x1, x2)].w++;
            le[min(x1, x2)].id = min(x1, x2);
        }
    }
    
    //按照统计次数,从大到小
    sort(he, he + N, bmp), sort(le, le + N, bmp);

    
    //因为题目要求输出的时候按照 过道的下标从小到大
    vector<int> ans1, ans2;

    for(int i = 0; i < k; i++)
        ans1.push_back(le[i].id);

    for(int i = 0; i < l; i++)
        ans2.push_back(he[i].id);
        
        
    sort(ans1.begin(), ans1.end()), sort(ans2.begin(), ans2.end());

    for(int i = 0; i < ans1.size(); i++)
        cout << ans1[i] << " ";
    cout << endl;
    for(int j = 0; j < ans2.size(); j++)
        cout << ans2[j] << " ";


    return 0;
}

3.题目:纪念品分组

题目链接:纪念品分组

解题思路:

题目规定,最多 2 个物品一组,并且组内最大物品不能超过上限值,问你最少多少组。
我们肯定希望尽可能满足 2 个物品一组,所以我们让最小的价值和最大的价值组一块是最优解。如果说不能组一块,那最大价值只能自己一组。
那么最小值就依次找次小值。

代码块:

#include
using namespace std;
const int N = 30010;

int n, m;
int a[N];

int main()
{
    cin >> m >> n;

    for(int i = 0; i < n; i++) cin >> a[i];

    sort(a, a + n);

    int l = 0;
    int ans = 0;

    for(int i = n - 1; i >= l; i--)
    {
        if(i == l)
        {
            ans++;
            break;
        }

        if(a[i] >= m) ans++;
        else
        {
            if(a[i] + a[l] > m)
            {
                ans++;
            }else
            {
                ans++;
                l++;
            }
        }
    }

    cout << ans;

    return 0;
}

4.题目:巨石滚滚

题目链接:巨石滚滚

解题思路:

需要注意的点,土球会先撞,再回复,如果说撞的时候就 < 0, 那么就失败了。

首先我们可以把所有的障碍分为两类,一类是撞完会增加的,另一类是撞完会减少的。
因为我们希望尽可能的把球的稳定性变成最大,然后再减小,这样尽可能满足更多的障碍。
所以我们先撞增加的,再撞减小的。

对于先撞增加的这一类:
因为先撞再增加,所以尽可能的使得撞的值小的放在前面,让球的稳定性尽可能大,避免撞完就小于0。

可能会说,存在一个增加的障碍,但是他的损耗是很大的,肯定会导致撞完就小于0,不能回复。
这样因为他是撞完增加的,我们把他放在前面,是不是最优解?。
因为我们的要求是能够到达终点,而不是通过最多的障碍。
因为这个是损耗大的回复,所以我们肯定把他放在这一类的最后来撞,因为这时已经是极大值的稳定性,如果这时都不能通过,所以肯定不能到达终点。

对于撞完损耗这一类:
因为我们目标是到重点,那么如果到终点,我们最大值 + 回复 一定大于所有的损耗 (损耗是一个定值),否则肯定会在路上失败。
那么我们尽可能的让回复多的在前面,这样就尽可能的满足稳定性最大。

代码块:

#include
using namespace std;

typedef pair<int, int> PII; //typedef 同define, pair用法 csdn搜索

#define x first
#define y second


const int N = 500010;

int n, m;

bool bmp1(PII a, PII b)
{
    return a.x < b.x;
}

bool bmp2(PII a, PII b)
{
    return a.y > b.y;
}

PII all[N];

void solved()
{
    cin >> n >> m;

    vector<PII> zh, fu;

    for(int i = 0; i < n; i++)
    {
        cin >> all[i].x >> all[i].y;

        if(all[i].y - all[i].x >= 0) zh.push_back(all[i]);
        else fu.push_back(all[i]);
    }

    sort(zh.begin(), zh.end(), bmp1);
    sort(fu.begin(), fu.end(), bmp2);

    long long sum = m;

    for(int i = 0; i < zh.size(); i++)
    {
        if(sum - zh[i].x < 0)
        {
            cout << "No" << endl;
            return;
        }else sum -= zh[i].x, sum += zh[i].y;
    }

    for(int i = 0; i < fu.size(); i++)
    {
        if(sum - fu[i].x < 0)
        {
            cout << "No" << endl;
            return;
        }else sum -= fu[i].x, sum += fu[i].y;
    }

    cout << "Yes" << endl;
}
int main()
{

    int T;
    cin >> T;
    while(T--) solved();

    return 0;
}

四:二分&二分答案

1.题目:.完全平方数

题目链接:完全平方数

解题思路:

暴力的解法就是 从 左区间l到右区间r 找到满足条件数的个数。但考虑到 l和r的大小 可以先预处理出来所有的平方数,然后用二分查找到 处于区间(l,r)满足条件数的范围。即 查找 l和r所在预处理数组中的位置。

代码块:

#include
#include
using namespace std;
#define ll long long
vector<ll>v;
void solve(){
    ll l1,r1;cin>>l1>>r1;
    ll l=0,r=v.size()-1;
    ll k1=-1;
    ll k2=-1;
    //找到第一个 大于等于 l1的
    while(l<r){
        ll mid=(l+r)>>1;
        if(v[mid]>=l1)r=mid;
        else l=mid+1;
    }
    k1=l;
    //找到最后一个 小于等于r1的
    l=0,r=v.size()-1;
    while(l<r){
        ll mid=(l+r+1)>>1;
        if(v[mid]<=r1)l=mid;
        else r=mid-1;
    }
    k2=l;
    cout<<k2-k1+1<<endl;
}
int main(){
    for(ll i=0;i*i<=1e9;i++){
        v.push_back(i*i);
    }
    ll t;cin>>t;
    while(t--)solve();
}

2.题目:解方程

题目链接:解方程

解题思路:

可以使用二分答案 枚举区间(0,100)内的所有数字,注意写好check函数就可以了。这里可以学习经典 浮点数二分的处理方法。

代码块:

#include
#include
#define ll long long
using namespace std;
double n;
double check(double m){
     double s=m*m*m*m*2018+m*21+5*m*m*m+5*m*m+14;
    return s;
}
void solve(){
    cin>>n;
    if(check(100)<n||n<14){
        cout<<"-1"<<endl;
        return;
        }
    double l=0.0,r=100.0;
    while(abs(r-l)>0.00001){
        double mid=(l+r)/2.0;
        if(check(mid)>=n)r=mid;
        else l=mid+0.00000001;
    }
    printf("%.4f\n",l);
}
int main(){
    ll t;cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

3.题目:跳石头

题目链接:跳石头

解题思路:

使用二分答案,通过枚举 答案 即最短跳跃距离的最大值,check()函数是判断该答案是否可行,是通过 计算 在满足该条件下需要移走岩石的个数,是否满足题目限制的最大移动岩石个数。

代码块:

#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
const double eps = 1e-4;
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }//最小公约数__gcd()
using namespace std;
const int NX=1e5+10;
ll x[NX];
ll y[NX];
ll L, N, M;
bool check(ll k)
{
    ll sum = 0;
    for (int i = 1; i <= N; i++)
    {
        ll j = i - 1;
        while (y[j] != 0)j--;
        if ((x[i] - x[j])<k)
        {
            sum++;
            y[i] = 1;
            if (sum > M)return false;
        }
    }
    ll s = N;
    while (y[s] != 0)s--;
    if ((x[N + 1] - x[s]) < k)sum++;
    if (sum >M) return false;
    else return true;
}
int main()
{
    cin >> L >> N >> M;
    for (int i = 1; i <= N; i++)
    {
        cin >> x[i];
    }
    x[0] = 0;
        x[N + 1] = L;
    ll l = 0;
    ll r = 1e9+10;
    while (l < r)
    {
        memset(y, 0, sizeof(y));
        ll mid = (l + r) >> 1;
        if (check(mid))l =mid+1;
        else r = mid;
    }
    cout << l-1<< endl;
    return 0;
}

4.题目:借教室

题目链接:[借教室]

解题思路:

该题目 相当于是 跳石头 的一个拓展,同样是枚举答案 需要通知哪个申请人修改订单。注意check()函数的书写,在判断答案 是否可行的时候 需要用到 一点差分的知识。 使用差分处理完该申请人 之前的所有订单,然后看是否有不符合要求的情况,即供不应求(需要的教室个数 大于 有空闲的教室个数。

代码块:

#include
#include
#include
using namespace std;
#define ll long long
const int N=1e6+10;
ll n,m;
ll a[N],b[N],c[N],d[N],e[N]={0};
bool check(int k)
{
    memset(e,0,sizeof(e));
   for(int i=1;i<=k;i++)
   {
       e[c[i]]+=b[i];
       e[d[i]+1]-=b[i];
   }
    for(int i=1;i<=n;i++)
    {
        e[i]+=e[i-1];
        if(e[i]>a[i])return false;
    }
    return true;
}
int main()
{
cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=m;i++)cin>>b[i]>>c[i]>>d[i];
    ll l=1,r=m;
    while(l<r)
    {
      ll mid=(l+r)>>1;
       if(check(mid))l=mid+1;
        else r=mid;
    }
    if (l != m){
   cout<<"-1"<<endl;
    cout<<l<<endl;
    }
    else cout<<"0"<<endl;
    return 0;
}

五:双指针算法

1.题目:滑动窗口

题目链接:滑动窗口

解题思路:

双指针经典用法,需要 用两个指针维护一个窗口的长度,然后不停的在一个数组上滑动。首先 需要注意,两个指针必须一起移动,并且 该窗口的长度不会改变,所以 可以使用 滑动窗口去 维护 给定区间长度 的最值。

代码块:

#include
#include 
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
const int N = 1e6+10;
ll p[N], d[N]={0};
ll a[N];
ll q[N];
int main()
{
	ll n, k; cin >> n >> k;
	ll be = 0, ed =-1;
	for (int i = 1; i <= n; i++)cin >> a[i];
    // be 和 ed 就是 维护 窗口 长度 的两个指针 
	for (int i = 1; i <= n; i++)
	{   
        // 当窗口 长度大于给定值的时候 就将左端点++ 减小窗口长度 维护窗口长度
		if (be <= ed && i - k + 1 > q[be])be++;
        // 维护 窗口的最值 保证最值一定在 数组的最左边 即 be位置
		while (be <= ed && a[q[ed]] >= a[i])ed--;
		q[++ed] = i;
		if (i >= k)printf("%lld ", a[q[be]]);
	}
    cout<<endl;
	be = 0; ed = -1;
	for (int i = 1; i <= n; i++)
	{
        // 当窗口 长度大于给定值的时候 就将左端点++ 减小窗口长度 维护窗口长度
		if (be <= ed && i - k + 1 > q[be])be++;
        // 维护 窗口的最值 保证最值一定在 数组的最左边 即 be位置
		while (be <= ed && a[q[ed]] <= a[i])ed--;
		q[++ed] = i;
		if (i >= k)printf("%lld ", a[q[be]]);
	}
    cout<<endl;
	return 0;
}

2.题目:逆序数(归并排序)

题目链接:[逆序数]

解题思路:

首先我们知道逆序数的定义,然后发现通过 归并排序 的过程,可以计算逆序数,然后 归并排序 其实就是通过递归 在回溯的过程中(通过定义 发现 回溯过程返回的都是 有序数组),,使用 双指针 有序合并两个有序数组。

代码块:

#include
using namespace std;
#define ll long long
const int N=1e5+10;
ll q[N],tmp[N];
ll ans=0;
void merge_sort(ll q[], ll l, ll r)
{
    if (l >= r) return;

    ll mid =( l + r) >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);

    ll k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else {tmp[k ++ ] = q[j ++ ];ans+=mid-i+1;}

    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];

    for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
int main(){
    ll n;cin>>n;
    for(int i=1;i<=n;i++)cin>>q[i];
    merge_sort(q, 1, n);
    cout<<ans<<endl;
    return 0;
}

六:递归&分治

1.题目:数的计算

题目链接:数的计算

解题思路:

首先使用暴力 可以发现f(i)=f(1)+f(2)+…+f(i/2)然后写一个递归函数就可以
但是 发现题目数据 发现这样 暴力过不了,因为 我们做了很多重复的计算,比如 当i为奇数的时候 它能添加数字的情况跟(i-1)添加数字的情况一样,当i为偶数的时候,就相当于在(i-1)的基础上加上了 i可添加的自然数的情况,即(i/2)的情况一样。得到普遍规律: 当i为奇数的时候f(i)=f(i-1) 当i为偶数的时候 f(i)=f(i-1)+f(i/2).

代码块:

#include
using namespace std;
#define ll long long
ll dfs(ll k){
    if(k==1)return 1;
    if(k%2)return dfs(k-1);
    else return dfs(k-1)+dfs(k/2);
}
int main(){
  ll n;cin>>n;
   cout<< dfs(n)<<endl;
  return 0;
}

2.题目:小q的数列

题目链接:小q的数列

解题思路:

题目有两个要求,第一个要求 就可以直接通过 题目给出的条件进行递归就可以,第二个条件 通过枚举不难发现跟二进制有关系,每次一个新数x的出现一定是在 2的x次方-1的位置出现。

代码块:

#include
#include
#include
#include
#define ll long long
using namespace std;
ll solve(ll n)
{
 if(n==0)return 0;
    if(n==1)return 1;
    return solve(n/2)+solve(n%2);
}
int main()
{
   
 ll t;cin>>t;
    while(t--)
    {
        ll n;cin>>n;
        ll a=solve(n);
        ll p=pow(2,a)-1;
        printf("%lld %lld\n",a,p);
    }
    return 0;
}

3.题目:求先序排列

题目链接:求先序排列

解题思路:

我们首先 需要学习 树的三种遍历方式(本质计算 对根节点的访问顺序不同),先序排列(根左右) 中序排列(左根右)后序排列(左右根)先序排列的第一个是根节点,后序排列的最后一个节点是根节点。我们可以先根据后序序列找到根节点,然后根据这个根节点来对中序以及后序序列进行分割,于是可以得到左右字数,然后对左子树以及右子树进行递归地处理。

代码块:

#include
#include
using namespace std;
#define ll long long
void solve(string s1,string s2){
    ll k1=s1.size();
    ll k2=s2.size();
    if(k2<=0)return;
      cout<<s2[k2-1];
    ll p=s1.find(s2[k2-1]);
    solve(s1.substr(0,p),s2.substr(0,p));
    solve(s1.substr(p+1,k1-p-1),s2.substr(p,k2-p-1));
}
int main()
{
	string s1,s2;cin>>s1>>s2;
                solve(s1,s2);
	return 0;
 }

七:STL

1.题目:扫雷游戏

题目链接:扫雷游戏

解题思路:

根据题意 如果该位置是非地雷格的时候 你需要 计算它周围的地雷个数,如果是地雷格的话 就直接输出‘*’
这里有个小技巧 在地图上 位置的遍历可以用 x和y相对于原来位置数值的变化 直接判断八个方向,在代码中用 d_x和d_y实现。

代码块:

#include
#include
#include
#include
#include
#include
#include
#define  ll  long long
using namespace std;
char a[110][110];
ll  b[110][110];
// 八个位置 x 和 y的变化 分别是 下 上 右 左 左上 左下 右下 右上
ll  d_x[10] = {0,0,1,-1,-1,-1,1,1};
ll  d_y[10] = {1,-1,0,0,-1,1,1,-1};
ll  check(int x, int y)
{
	ll  sum = 0;
	for (int i = 0; i < 8; i++)
	{
		ll t_x = x + d_x[i];
		ll t_y = y + d_y[i];
		if (a[t_x][t_y] == '*')sum++;
	}
	return sum;
}
int main()
{
	
	ll  x, y;cin >> x >> y;
	for (int i = 1; i <= x; i++){
        for (int j = 1; j <= y; j++)cin >> a[i][j];
    }
    for (int i = 1; i <= x; i++){
		for (int j = 1; j <= y; j++)
		{
        // 如果该位置是非地雷格 则通过 check 函数计算旁边位置地雷的个数
			if (a[i][j] == '?')b[i][j]=check(i, j);
		}
	}
	for (int i = 1; i <= x; i++)
	{
		for (int j = 1; j <= y; j++)
		{
			if (a[i][j] == '*')cout << a[i][j];
			else cout << b[i][j];
		}
		cout << endl;
	}
	return 0;
}

2.题目:图书管理员

题目链接:图书管理员

解题思路:

首先理解题意我们发现我们需要 找到是否能够找到读者需要的书,找到的话输出 图书编码最小的书,反之输出 -1,我们可以 先将图书排序,然后每次进行查询的时候,就遍历一遍 如果存在就输出(因为已经进行了排序,所以能够保证 每次最先找到的是图书编码最小的书),不存在就输出-1.

代码块:

#include
#include
#include
#include
#include
#include
#include
#define  ll  long long
using namespace std;
vector<string>v;
bool cmp(string &s1,string &s2)
{
	if (s1.size() == s2.size()) return s1 < s2;
	else	return s1.size() < s2.size();
}
bool judge2(string &s, string &s1)
{
    // 使用 双指针 从尾往前判断 是否 图书编码 是否是以读者的需求码结尾
	ll a = s.size()-1;
	ll b = s1.size()-1;
	while (s[a]==s1[b])
	{
		a--;b--;
		if (a == -1 || b == -1)break;
	}
	if (b == -1)return true;
	else return false;
}
void judge(string &s,int n)
{
	for (int i = 0; i < v.size(); i++)
	{
        // 图书编码长度小于读者的需求码 必定不满足,所以直接跳过
		if (v[i].size() < n)continue;
        // 图书编码长度等于读者的需求码
		else if (v[i].size() == n)
		{
            // 如果 图书编码等于读者的需求码
			if (v[i] == s){
				cout << v[i] << endl;
				return;
			}
		}
		else
		{
            // 判读图书编码 是否是 以读者的需求码结尾
			if (judge2(v[i], s)){
				cout << v[i] << endl;
				return;
			}
		}
	}
	cout << "-1" << endl;
}
int main()
{
	ll n,m;cin >> n>>m;
	for (int i = 1; i <= n; i++){
		string s;cin >> s;
		v.push_back(s);
	}
    //对书 进行排序
	sort(v.begin(), v.end(),cmp);
	while (m--)
	{
		ll x;cin >> x;
		string s;cin >> s;
        // 判断 该书是否存在
		judge(s,x);
	}
	return 0;
}

你可能感兴趣的:(算法)