蓝桥杯 第十讲 疑难杂题

1242. 修改数组

做法一:并查集变种

image

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

using namespace std;

const int N = 2e6;
int p[N];
int n,x;

int find(int x)
{
    if(p[x]!=x) p[x] = find(p[x]);
    return p[x];
}
int main()
{
    scanf("%d", &n);
   
    for (int i = 1; i < N; i ++ )
    {
        p[i] = i;
    }
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &x);
        x = find(x); //找到大于x的第一个没有被用过的数
        cout<<x<<' ';
        p[x] = x+1; //将x的祖先指向x的下一个数
    }
    return 0;
}

1234. 倍数问题

image

朴素01背包做法(超时超空间)

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

using namespace std;

const int N = 1e5 + 10,K = 1e3 + 5;

int f[N][4][K];//f[i][j][k]表示前i个物品中,选取j个,总和%m的三数之和的最大值
int a[N];
int n,m;
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &a[i]);
    }
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<4;j++)
        {
            for(int k=0;k<m;k++)
            {
                f[i][j][k] = -2e9;
            }
        }
    }
    for (int i = 0; i <= n; i ++ )
    {
        f[i][0][0] = 0;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j = 1;j<4;j++)
        {
            for(int k=0;k<m;k++)
            {
                f[i][j][k] = max(f[i-1][j][k],f[i-1][j-1][((k - a[i])%m + m)%m] + a[i]);
            }
        }
    }
    cout<<f[n][3][0]<<endl;
    return 0;
}

贪心优化

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

using namespace std;

const int N = 1e5 + 10,K = 1e3 + 5;

int f[4][N]; //f[i][j]:选取了i个数,总和%m为j的总和的最大值
int n,m,x;
vector<int>a[N]; //a[i][j]:总和%m为i的数为j

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ )
    {
        scanf("%d", &x);
        a[x%m].push_back(x);
    }
   memset(f,-0x3f3f3f3f,sizeof f); //初始化负无穷
   f[0][0] = 0;//选0个数,和为0的最大值为0
   for(int i=0;i<m;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++) //枚举选取的总和%m为k
               {
                   f[j][k] = max(f[j][k],f[j-1][(k-x%m+m)%m]+x);
               }
           }
       }
   }
   cout<<f[3][0]<<endl;
    return 0;
}

1213. 斐波那契

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

using namespace std;

typedef long long LL;

LL p;

//龟速乘(解决相乘爆longlong的问题)
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;
}
//计算2x2矩阵相乘,c=a*b
void mul(LL c[][2], LL a[][2], LL b[][2]) {
    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);
}
//快速幂(解决求斐波那契数列第n项的问题)
LL F(LL n) {
    if (!n) return 0;
    LL f[2][2] = {1, 1}; //f(1)=f(2)=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];
}
//(F(m-1) * F(n mod m) - 1) mod F(m)
LL H(LL m, LL k) {
    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;
    }
}
//求(F(n) - 1)mod F(m)
LL G(LL n, LL m) {
    //m是偶数
    if (m % 2 == 0) {
        //n/2m也是偶数
        if (n / m % 2 == 0) {
            if (n % m == 0) return F(m) - 1;
            else return F(n % m) - 1;
        }
        //n/2m是奇数
        else {
            return H(m, n % m);
        }
    }
    //m是奇数
    else {
        //n/m是偶数,且n/2m是偶数
        if (n / m % 2 == 0 && n / 2 / m % 2 == 0) {
            if (n % m == 0) return F(m) - 1;
            else return F(n % m) - 1;
        }
        //n/m是偶数,且n/2m是奇数
        else if (n / m % 2 == 0 && n / 2 / m % 2) {
            if (m == 2 && n % m == 1) return F(m) - 1;
            else return F(m) - F(n % m) - 1;
        }
        //n/m是奇数,且n/2m是偶数
        else if (n / m % 2 && n / 2 / m % 2 == 0) {
            return H(m, n % m);
        }
        //n/m是奇数,且n/2m是奇数
        else {
            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;
    cin >> n >> m >> p ;
    cout << (G(n + 2, m) % p + p) % p << endl;
    return 0;
}

1206. 剪格子

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

using namespace std;

const int N = 11,INF = 0x3f3f3f3f;

int g[N][N];
int S;
int n,m;
int ans = INF;
int dx[4] = {-1,1,0,0},dy[4] = {0,0,-1,1};
bool st[N][N];

void dfs(int x,int y,int sum,int cnt)
{
    if(sum == S/2)
    {
        ans = min(ans,cnt);
        return;
    }
    if(sum > S/2) return;
    
   
    for (int i = 0; i < 4; i ++ )
    {
        int nx = x + dx[i],ny = y + dy[i];
        if(nx<1||nx>m||ny<1||ny>n||st[nx][ny]) continue;
         st[nx][ny] = true;
        dfs(nx,ny,sum+g[nx][ny],cnt+1);
        st[nx][ny] = false;
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++ )
    {
        for (int j = 1; j <= n; j ++ )
        {
            scanf("%d", &g[i][j]);
            S += g[i][j];
        }
    }
    if(S%2 == 1)
    {
        cout<<0<<endl;
        return 0;
    }
    st[1][1] = true;
    dfs(1,1,g[1][1],1);
    if(ans == INF) ans = 0;
    cout<<ans<<endl;
    return 0;
}

523. 组合数问题

image

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

using namespace std;
typedef long long LL;

const int N = 2005;

int n,m,k,t;
int c[N][N];//组合数
int s[N][N];//二维前缀和


int main()
{
    scanf("%d%d", &t,&k);
    
    for (int i = 0; i < N; i ++ ) //求组合数
    {
        for (int j = 0; j <= i; j ++ ) //C[i][j]:i在下,j在上,j一定不大于i才有意义
        {
            if(j == 0) c[i][j] = 1 % k;
            else c[i][j] = (c[i-1][j-1]+c[i-1][j])%k; //c[i][j] = c[i-1][j] + c[i-1][j-1]
        }
    }
    for (int i = 0; i < N; i ++ ) //求二维前缀和
    {
        for (int j = 0; j < N; j ++ )
        {
            if(j<=i && c[i][j] == 0) s[i][j] = 1;//整除余数为0,则c[i][j]为0,表示c[i][j]为k的倍数
            if(i-1>=0) s[i][j] += s[i-1][j];
            if(j-1>=0) s[i][j] += s[i][j-1];
            if(i-1>=0 && j-1>=0) s[i][j] -= s[i-1][j-1];
             
        }
    }
    while(t--)
    {
        scanf("%d%d", &n, &m);
        cout<<s[n][m]<<endl;
    }
    return 0;
}

1171. 距离

tarjan算法:求二叉树结点的公共祖先

image
Tarjan – 离线求LCA
在线做法:读一个询问,处理一个,输出一个
离线做法:读完全部询问,再全部处理完,再全部输出
在深度优先遍历时,将所有点分成三大类
2号点:代表已经访问并结束回溯
1号点:代表正在访问
0号点:代表还没有访问过
其中所有2号点和正在搜索的1号点路径中已经通过并查集合并成一个集合
1、先求出所有点到根结点的距离depth[],设x号点和y号点的最近公共祖先是p,则x和y的最近距离等于depth[x] + depth[y] - 2 * depth[p]
2、在深度优先遍历1号点中的u点的时候,需要把u的查询的另外一个点的最短距离进行计算并存储,最后把u点合并到上一结点的集合
时间复杂度 O(n+m)

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

using namespace std;
typedef pair<int, int> PII;

const int N = 1e4 + 10,M = 2*N;

int h[N],e[M],ne[M],w[M],idx;

int dist[N];
int n,m,x,y,k;
vector<PII> query[N]; //first查询的另外一个点,second存查询编号
int res[M*2]; //res[i]:求第i个查询的结果
int p[N]; //并查集
int st[N];//结点状态:0为未访问,1为访问1次,2为已访问且已回溯
int find(int x)
{
    if(p[x]!=x) p[x] = find(p[x]);
    return p[x];
}
void add(int a,int b,int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}
void dfs(int u,int dad) //求出根结点到其他结点的路径长度
{
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j = e[i];
        if(j == dad) continue;
        dist[j] = dist[u] + w[i];
        dfs(j,u);
    }
}

void tarjan(int u) //求公共祖先的同时算出两点的最短路径
{
    st[u] = 1;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j = e[i];
        if(!st[j])
        {
            tarjan(j);
            p[j] = u; //合并j与u的所在集合
        }
    }
    for(auto t:query[u])
    {
        int j = t.first,id = t.second;
        if(st[j] == 2)
        {
            int anc = find(j); //求出的公共祖先
            res[id] = dist[u] + dist[j] - 2*dist[anc];
        }
    }
    st[u] = 2; //标记已回溯
}
int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
    }
    for (int i = 0; i < n-1; i ++ )
    {
        scanf("%d%d%d", &x, &y,&k);
        add(x,y,k);
        add(y,x,k);
    }
    for (int i = 0; i < m; i ++ )
    {
        scanf("%d%d", &x, &y);
        query[x].push_back({y,i});//注意:两个都要存
        query[y].push_back({x,i});
    }
    dfs(1,-1);
    
    tarjan(1);
    for(int i=0;i<m;i++)
    {
        cout<<res[i]<<endl;
    }
    return 0;
}
posted @   安河桥北i  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示