【蓝桥杯】备战刷题,冲刺国赛

本文介绍了四个算法问题的解题思路:拓扑排序用于发现环,通过修改入度并使用队列进行遍历;倍数问题采用01背包策略,考虑对k取模后的最大值;质数拆分利用线性筛法筛选质数,再用动态规划求解;日志统计应用双指针算法,查找在时间差d内获赞至少k次的热帖。
摘要由CSDN通过智能技术生成

目录

1.发现环

 2.倍数问题

3.质数拆分

4.日志统计


1.发现环

 解题思路:本题是判断环的问题,但该题又是无向图,那么我们可以用修改过的拓扑排序来做,把入度改为1,让入度为1的点入队,并用一个vis数组来判断该点是否访问过。

#include<bits/stdc++.h>
using namespace std;
const int N=200010;
int h[N],e[N],ne[N],idx;
int n;
int q[N],d[N];//q表示队列,d表示点的度数
bool vis[N];

void add(int a,int b)//给图建立边
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void topo(int n)
{
 int hh=0,tt=-1;
 for(int i=1;i<=n;i++)
 { //遍历每一个节点, 入度为1则入队
  if(d[i]==1)
  {
   q[++tt]=i;
   vis[i]=true;
  } 
 }
 while(hh<=tt)
 {
  int t=q[hh++];
   //遍历头节点的每一个出边,把一些入度为1的判断一下
  for(int i=h[t];i!=-1;i=ne[i])
  {
   int j=e[i];
   d[j]--;
    //如果结点j入度为1则入队
   if(d[j]==1)
   {
    vis[j]=true;
    q[++tt]=j;
   }
  }
 }
}

int main()
{
 cin>>n;
 memset(h,-1,sizeof h);
 for(int i=0;i<n;i++)
 {
  int a,b;
  scanf("%d%d",&a,&b);
  add(a,b);
  add(b,a);
  d[a]++;
  d[b]++;
 }
 topo(n);
 bool flag=true;//判断是不是第一个输出的数
 for(int i=1;i<=n;i++)
 {
  if(!vis[i])
  {
   if(flag)
   {
    cout<<i;
    flag=false;
   }
   else cout<<" "<<i;
  }
 }
 return 0;
}

 2.倍数问题

 

 解题思路:

首先看这个题,从n个数中选三个数,且三个数相加的和是k的倍数,且满足倍数中最大,那么这一看就是背包空间3,选数满足特殊要求,的01背包吧。因为k只有1e3的数据量,因此我们可以从k入手。我们先按照每个数对k取余的数字将每个数字分类。而对k取余就相当于是对k取模

但是在C++ 的取模正数是正数,负数是负数,而真正意义上的取模符号结果都是正的比如 -1 % 2 用C++的结果是-1而实际真正的结果是1,(数学上模的结果都是正的),所以会看到  (____ % k + k ) % k 解决上面的问题 比如-5 % 3 = 1; 而C++中-5 % 3 的结果是 -2 ,我们通过 ((-5 % 3) + 3) % 3 = 1 ;这样就得到真正的结果。

关于为什么是 (k-x%m+m)%m

同余模定理:(a+b)%c=(a%c+b%c)%c;

首先(x+m)%m是对x+m取模,因为取模结果只能为非负数;

回到刚才的问题,现在我们要找出t,使得(i+j+t)%m=0,那么t%m=(0-(i+j)%m+m)%m,而通过推论我们发现满足条件时(i+j+t)=0、m或2*m,也就是说这个0实际上换成m或2m都没问题(已验证AC)。

这个明白了,后面dp那里的(k-x%m+m)%m也就迎刃而解,就是求出所有余数情况下最大的值。

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

using namespace std;

const int N=1e5+10,M=1e3+10;

vector<int> a[M];//vector<int> a[M]这是个二维数组,vector,<int>可以看作第一维,M是第二维度,就是有M个vector。M代表个数
int f[4][N];//4代表当前选的数,N代表余数 ,该集合表示前i 个数中任意选,最多选 j 个且总和是 k 的倍数的最大总和


int get_id(int x,int n)//求余数的函数
{
    return (x%n+n)%n;
}
int main()
{
    int n,k;
    cin>>n>>k;//读入n个数,k的倍数
    for(int i=1;i<=n;i++)
    {
        int t;
        cin>>t;
        a[t%k].push_back(t);//将数按余数分组
    }
    memset(f,-0x3f,sizeof f);//一开始是从前0个开始选所以应该初始化为负的无穷大
    f[0][0]=0;// 只有 选 0 个 且 体积 为 0 才合法
    for(int i=0;i<k;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++)//每个分组最多选三个最大的
// 因为只选3个,所以u小于3


         {
             int x=a[i][u];//单独取出来余数
             for(int j=3;j>=1;j--)//j代表已经选了多少个数
              for(int m=0;m<k;m++)//枚举每个余数,每个余数的范围都是从0~k
               f[j][m]=max(f[j-1][get_id(m-x,k)]+x,f[j][m]); / /x是当前枚举的余数,已经选了j-1个数,
         }
    }
    cout<<f[3][0];//选了三个 mod k 为0 即 是 k 的 倍数
}

3.质数拆分

 解题思路:这题就是01背包,先用线性筛把2019前的质数筛出来,然后再用dp求出2019可以拆成的方法数。

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

using namespace std;
typedef long long LL;

const int N = 2030;
bool st[N];
int primes[N],cnt;
LL f[N]; //f[i][j]表示在前i个质数中能够构成和为j的方案个数

//线性筛把质数搞出来
void get_primes(int n)
{
    for(int i = 2;i <= n;i++)
    {
        //如果是质数,就把加到数组中去
        if(!st[i]) primes[cnt++]  = i;
        //从小到大枚举所有的质数
        for(int j = 0; primes[j] <= n/i;j++)
        {
            st[primes[j] * i] = true;
            if(i%primes[j]==0)break;
        }
    }
}

int main()
{
	//用筛法先把2-2019的所有质数都筛出来
	get_primes(2019);
	//初始化
	f[0] = 1;
	for(int i =0; i <cnt;i++)
		for(int j = 2019;j>=primes[i];j--)
		{
		
		 f[j] +=  f[j-primes[i]];
		}
		
	cout << f[2019] << endl;
	
	return 0;
}

4.日志统计

 

 题解思路:这道题运用双指针算法,先把每个获赞的帖子存起来,然后再判断在时间差d里面是否获得至少k个赞,满足则是热帖

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

using namespace std;
 const int N=100010;
typedef pair<int,int>PII;
PII a[N];用来存日志的时间和id
bool st[N];//用来标记帖子的id号
int cnt[N];//用来记录一个id号获得的赞数
int main()
{
  int n,d,k;
  cin>>n>>d>>k;
  for(int i=0;i<n;i++)scanf("%d%d",&a[i].first,&a[i].second);
  sort(a,a+n);从小到大排序
  for(int i=0,j=0;i<n;i++)
  {
    int id =a[i].second;
    cnt[id]++;
    while(a[i].first-a[j].first>=d)//超出时间不符合则删去不符合的日志
    {
      cnt[a[j].second]--;
      j++;
    }
    if(cnt[id]>=k)st[id]=true;
  }
  for(int i=0;i<=100000;i++)
  {
    if(st[i])cout<<i<<endl;//输出热帖的id
  }
  
  return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值