训练---疑难杂题(待补)


前言

这一个专题就是把之前训练里面,没有涉及到的蓝桥杯题目咱们讲一讲!


一、修改数组(多种写法,超nice)

任意门
感觉这道题目有一点点哈希表那味。
方法一:平衡树set O ( n l o g n ) O(nlogn) Onlogn
方法二:并查集 O ( n + m ) O(n+m) O(n+m)
在这里插入图片描述

写法一:

#include <cstdio>
#include <algorithm>
#include<map>

using namespace std;

const int N=1100010;

int p[N];

int find(int x)
{
    if(p[x]!=x)p[x]=find(p[x]);
    return p[x];
}

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<N;i++)p[i]=i;
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        x=find(x);
        printf("%d ",x);
        p[x]=x+1;
    }
    return 0;
}

写法二:

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

using namespace std ;

const int N = 1000010 ;
int st[N] ;

//每次+1会超时,可以采用 “跳”
int main(){
    int n ;
    cin >> n ;
    for(int i=0;i<n;i++){
        int a ;
        cin >> a ;
        while(st[a]){
            a = st[a] ++ ;
        }
        st[a] = a;
        cout << a << ' ' ;
    }

    return 0 ;
}

写法三:平衡树写法

c++里面的话,平衡树就是set和map
将每次询问的值加入区间中并进行不断的合并,询问。因为每次询问的值可能在已知区间内,所有要注意upper_bound求的时候要加入{x,inf}。
二分写法:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
#include<climits>

using namespace std;

int n;

typedef pair<int, int> PII;
set<PII> sgs;


int main() {
	int x;
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%d", &x);
		if (i == 0) {
			sgs.insert({ x,x });
			printf("%d ", x);
			continue;
		}
		//在集合中去找一个区间包含x的,本来我们只需要找一个数x
		//但是由于set里面存的区间,一个数和一个区间肯定无法直接比较大小
		//所以我们要把这个数x变成一个区间,即<x,INT_MIN>,就可以用lower_bound查找了
		//找到的是第一个大于或等于这个pair的pair(pair先比较第一个关键字的大小。
		//如果相等,再比较第二个关键字的大小)。

		//二分查找包含x的区间
		auto it = sgs.lower_bound(PII(x, INT_MIN));
		//如果包含 即[*,?] (?>=x)
		if (it != sgs.end() && it->second <= x) {
			//printf("包含\n");
			//修改x为这个区间的右端点+1,即让x脱离这个区间。
			x = it->first + 1;
			//合并这两个区间,且必然能合并
			int l = it->second;//备份区间左值
			sgs.erase(it);//删除原来的区间
			sgs.insert(PII(x, l));//插入新的区间
			//我们去找一个区间的右端点大于等于x+1
			//即[3,x]->[?,?] (?>=x+1)
			it = sgs.lower_bound({ x,l });
			auto back = sgs.lower_bound(PII(x+1, INT_MIN));
			if (back != sgs.end() && back->second == x + 1) {
				//如果找到了这样的一个pair(右端点大于x,左端点等于x+1)
				//那么这两个区间又可以合并了
				int r = back->first;
				int l = it->second;
				sgs.erase(it);
				sgs.erase(back);
				sgs.insert(PII(r, l));
			}
		}
		else {
			//没有一个区间包含x,那么x就不需要修改
			//先插入到set里面
			sgs.insert(PII(x, x));
			//检查是否可以合并 [?,x-1],[x,x],[x+1,?]
			//找右端点大于等于x-1的
			auto it = sgs.lower_bound(PII(x, x));//先定位
			auto pre = it;
			if (pre != sgs.begin()) {
				pre--;
				if (pre->first + 1 == it->second) {
					//合并
					int l = pre->second;
					int r = it->first;
					sgs.erase(pre);
					sgs.erase(it);
					sgs.insert({ r,l });
				}
			}

			//定位[?,x]
			it = sgs.lower_bound(PII(x, INT_MIN));
			auto back = sgs.lower_bound({ x + 1,INT_MIN });
			if (back != sgs.end() && back->second == it->first + 1) {
				//合并
				int l = it->second;
				int r = back->first;
				sgs.erase(it);
				sgs.erase(back);
				sgs.insert(PII(r, l));
			}
		}
		printf("%d ", x);
	}
	printf("\n");
	return 0;
}

写法二:

#include<bits/stdc++.h>

using namespace std;
typedef pair<int,int>PII;
const int N=100010;

int main()
{
    int n;
    cin>>n;
    set<PII>segs;
    while(n--)
    {
        int x;
        cin>>x;
        auto it=segs.lower_bound({x,INT_MIN});//返回第一个大于等于x的数,x为右边的端点
        if(it!=segs.end()&&it->second<=x)x=it->first+1;//如果是落在已经存在的区间里面
        cout<<x<<" ";
        segs.insert({x,x});
        if(it!=segs.begin())it--;//找到it的前驱节点
        for(int i=0;i<=2&&it!=segs.end();i++)//合并他后面的两次,避免合并后的又要再合并一次
        {
            auto j=it;
            j++;
            if(j!=segs.end()&&it->first+1==j->second)//first是右端点,second是左端点
            {
                int l=it->second,r=j->first;
                segs.insert({r,l});
                segs.erase(it);
                segs.erase(j);
                it=segs.find({r,l});
            }
            else it=j;
        }
    }
    cout<<endl;
    return 0;
}

这个方法只能过一部分数据,当数据过大的时候无法过,甚至在蓝桥杯官网的编译是过不了的!!因为不认auto。所以不能使用to_string、stoi、stol、auto、unordered_map、unordered_set这些好用的函数啦~
但是有网友说现在比赛认了嘿嘿。

写法四:略带暴力法

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,a[100001];
	bool book[1000001]={0};//标记数组
	cin>>n;
	for(int i=0;i<n;i++){//输入
		cin>>a[i];
	}
	book[a[0]]=1;
	for(int i=1;i<n;i++){
		if(book[a[i]]==0){//没有出现过
			book[a[i]]=1;//标记位置为1
			continue;
		}
		else{//出现过
			while(book[a[i]]!=0){//一直加一直到没出现过
				a[i]+=1;
			}
			book[a[i]]=1;//将符合条件的数加一
		}
	}
	for(int i=0;i<n;i++){//输出
		cout<<a[i]<<" ";
	}
	return 0;
}

写法五:线段树写法

#include<iostream>
using namespace std;
const int MAX_N=1e5;
const int SIZE=1e6+1e5+5;
int tree[SIZE<<2];//维护最大值,用过的点换成0 
int tsize=1;
inline int ls(int p){return p<<1|1;}
inline int rs(int p){return (p<<1)+2;}
void init(int n)//n为输入数据最大值加1e5+5(保险) 
{
	tsize=1;
	while(tsize<n)tsize<<=1;
	int last_left=tsize-1;
	for(int i=last_left;i<last_left+tsize;i++)tree[i]=i-last_left;
	while(last_left)
	{
		for(int i=(last_left-1)>>1;i<last_left;i++)tree[i]=max(tree[ls(i)],tree[rs(i)]);
		last_left=(last_left-1)>>1;
	}
}
void pull_up(int p)
{
	tree[p]=max(tree[ls(p)],tree[rs(p)]);
}
int query(int x,int p=0,int l=0,int r=tsize)
{
	if(l+1==r)
	{
		tree[p]=0;
		return l;
	}
	int ans;
	if(tree[ls(p)]>=x)ans=query(x,ls(p),l,(l+r)>>1);
	else ans=query(x,rs(p),(l+r)>>1,r);
	pull_up(p);
	return ans;
}
int main()
{
	int n;
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int m=0;
	cin>>n;
	int x;
	init(1e6+1e5+5);
	for(int i=0;i<n;i++)
	{
		cin>>x;
		cout<<query(x)<<" ";
	}
	return 0;
}


二、倍数问题(背包问题)

任意门
这一道题就是通过选取数字得到k的最大整数倍。
这样子的组合问题求最优解就是典型的背包问题—组合问题求最优解。
在这里插入图片描述

左右两边分别求最大值,然后取一个max就可以了。

#include<bits/stdc++.h>

using namespace std;
//我们这里用vector来存余数前三大的数
const int N=1010;
int n,m;
vector<int>a[N];
int f[4][N];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
    {
        int x;
        scanf("%d",&x);
        a[x%m].push_back(x);
    }
    memset(f,-0x3f,sizeof f);
    f[0][0]=0;
    for(int i=0;i<m;i++)
    {
        sort(a[i].begin(),a[i].end());
        reverse(a[i].begin(),a[i].end());
        for(int u=0;u<3&&u<a[i].size();u++)
        {
            int x=a[i][u]; //我们只用去看那个余数的最大的三个数
            for(int j=3;j>=1;j--)
                for(int k=0;k<m;k++)
                f[j][k]=max(f[j][k],f[j-1][(k-x%m+m)%m]+x);
        }
    }
    printf("%d\n",f[3][0]);  
    
    return 0;
}

三、斐波那契(数论、这一题自知能力不行,我跳过了)

任意门
将问题转化一下,就会简单很多。但是想到真的是难!!!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

using namespace std;

typedef long long LL;

LL p;

LL qmul(LL a, LL b)
{
    LL res = 0;
    while (b)
    {
        if (b & 1) res = (res + a) % p;
        a = (a + a) % p;
        b >>= 1;
    }
    return res;
}

void mul(LL c[][2], LL a[][2], LL b[][2])  // c = a * b
{
    static LL t[2][2];
    memset(t, 0, sizeof t);

    for (int i = 0; i < 2; i ++ )
        for (int j = 0; j < 2; j ++ )
            for (int k = 0; k < 2; k ++ )
                t[i][j] = (t[i][j] + qmul(a[i][k], b[k][j])) % p;

    memcpy(c, t, sizeof t);
}

LL F(LL n)
{
    if (!n) return 0;

    LL f[2][2] = {1, 1};
    LL a[2][2] = {
        {0, 1},
        {1, 1},
    };

    for (LL k = n - 1; k; k >>= 1)
    {
        if (k & 1) mul(f, f, a);  // f = f * a
        mul(a, a, a);  // a = a * a
    }

    return f[0][0];
}

LL H(LL m, LL k)  // (F(m - 1) * F(k) - 1) mod F(m)
{
    if (k % 2) return F(m - k) - 1;
    else
    {
        if (k == 0 || m == 2 && m - k == 1) return F(m) - 1;
        else return F(m) - F(m - k) - 1;
    }
}

LL G(LL n, LL m)  // (F(n) - 1) mod F(m)
{
    if (m % 2 == 0)  // m是偶数
    {
        if (n / m % 2 == 0)  // n / m 是偶数
        {
            //cout << n << ' ' << m << ' ' << F(n % m) << endl;
            if (n % m == 0) return F(m) - 1;
            else return F(n % m) - 1;
        }
        else  // n / m 是奇数
        {
            return H(m, n % m);
        }
    }
    else  // m 是奇数
    {
        if (n / m % 2 == 0 && n / 2 / m % 2 == 0)  // n / m 是偶数,n / (2m) 是偶数
        {
            if (n % m == 0) return F(m) - 1;
            else return F(n % m) - 1;
        }
        else if (n / m % 2 == 0 && n / 2 / m % 2)  // n / m 是偶数,n / (2m) 是奇数
        {
            if (m == 2 && n % m == 1) return F(m) - 1;
            else return F(m) - F(n % m) - 1;
        }
        else if (n / m % 2 && n / 2 / m % 2 == 0)  // n / m 是奇数,n / (2m) 是偶数
        {
            return H(m, n % m);
        }
        else  // n / m 是奇数,n / (2m) 是奇数
        {
            if (n % m % 2)
            {
                if (m == 2 && m - n % m == 1) return F(m) - 1;
                else return F(m) - F(m - n % m) - 1;
            }
            else
            {
                return F(m - n % m) - 1;
            }
        }
    }
}

int main()
{
    LL n, m;

    while (cin >> n >> m >> p) cout << (G(n + 2, m) % p + p) % p << endl;

    return 0;
}

四、距离(离线Tarjan求LCA算法)

任意门
只要是在一个树上面的话,那么他的两个点之间的距离固定的。
x、y两点的最近共公祖先。
距离为:d(x)+d(y)-2d§
d§就是x、y的最近公共祖先

tarjan算法

大佬!!!
在线做法:边读边做
离线做法:先读完,再全部处理,最后全部输出。

Tarjon本质就是对向上标记法的一个优化,任取一个节点当成根节点进行dfs优先遍历,把所有节点分成三部分
1)已经遍历并且回溯的标记成2
2)正在遍历的没有回溯的标记成1
3)未遍历的标记成0

注意:顺序一定不能乱。
1)当这个点遍历子节点后的时候把子节点的祖宗更新成当前点
2)只有当前点回溯的时候才可以用这个点来计算所有之前为2的点,因为如果当前点为a,而b是a这条路径的上的点,并且b在a的下面,那么因为b先回溯,所以要等b回溯了之后才能正确的判断a.
在这里插入图片描述
3)加入询问的时候要加入两次

num[a].push_back = {b, i}, num[b].push_back(a, i}; 

在这里插入图片描述

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

using namespace std;

typedef pair<int, int> PII;

const int N = 10010, M = N * 2;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
int p[N];
int res[M * 2];//存储每个询问的结果
int st[N];
vector<PII> query[N];   // first存查询的另外一个点,second存查询编号

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}//e代表连接的点,w代表这个点的权值,ne代表下个点的下表,h[a]代表这个点的序号,我们用这个序号来标记

void dfs(int u, int fa)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        dist[j] = dist[u] + w[i];
        dfs(j, u);
    }
}

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void tarjan(int u)
{
    st[u] = 1;
    for (int i = h[u]; ~i; i = ne[i])//开始遍历这这个点所连接的点
    {
        int j = e[i];
        if (!st[j])
        {
            tarjan(j);
            p[j] = u;
        }
    }

    for (auto item : query[u])
    {
        int y = item.first, id = item.second;
        if (st[y] == 2)//y遍历过
        {
            int anc = find(y);//这是他们的最近公共祖先
            res[id] = dist[u] + dist[y] - dist[anc] * 2;
        }
    }

    st[u] = 2;//代表着这个点已经被遍历过了
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);//依次读入n-1条边
        add(a, b, c), add(b, a, c);
    }

    for (int i = 0; i < m; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);//读入询问的节点
        if (a != b)
        {
            query[a].push_back({b, i});
            query[b].push_back({a, i});
        }
    }

    for (int i = 1; i <= n; i ++ ) p[i] = i;

    dfs(1, -1);//处理dist数组
    tarjan(1);

    for (int i = 0; i < m; i ++ ) printf("%d\n", res[i]);

    return 0;
}

五、剪格子

任意门
不能有三种联通的部分。
在这里插入图片描述
DFS只能一笔画。而不能我们现在所需要的多笔画。

由于接下来的题目过难,待补

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值