牛客题单——贪心、数论

题单链接

反素数

反素数就是一个数的因子大于所有小于这个数因子的数

题目中要求的区间内的最大反素数 等价于 求这个区间内因子最多的数且这个数最小
可以用反证法进行证明:
在这里插入图片描述
假设在当前区间中的答案是x,如果y的约数个数大于x,那么x就不是反素数,这样就有矛盾了!如果z约数个数大于x,那么z就应该是一个反素数,这样x就不是最大的了,这样就又矛盾了!

由素因子分解定理,我们可以将n进行分解
n的素因子一定不会超过10个,因为前10个素数的乘积就会越界
所有指数和不会超过30,因为230就已经达到上界了
这样我们就可以dfs了

如果将素因子分解的结果按素因子升序排列,那么所有的指数就一定会按降序排列
因为底数对因子的数量是没有影响的,但是越大的素数的指数越大这个数就会越大,这样的话就不容易得到最小的结果了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int ps[] = {
    
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
int n;
int minnum = 0, res; //res最终答案,minnum表示最终答案有多少个因子

//u表示当前枚举到哪一个质数
//last表示上一个质数的次数
//p表示当前这个数是多少
//num表示当前有多少个因子
void dfs(int u, int last, int p, int num)
{
    
    
    if (num > minnum || (num == minnum && res > p))
    {
    
    
        res = p;
        minnum = num;
    }
    for (int i = 1; i <= last; i++)
    {
    
    
        if ((ll)p * ps[u] > n) break;
        p *= ps[u];
        dfs(u + 1, i, p, num * (i + 1));
    }
}
int main()
{
    
    
    cin >> n;
    dfs(0, 30, 1, 1);
    cout << res << endl;
    return 0;
}

Prime Distance(质数距离)

英文题先解释一下,给定一个区间[l,r]求这个区间中连续素数之差最大和最小的两组素数,如果没有的话输出没有即可

假设已经求出了给定区间中的所有素数,只需要便利一边所有的素数就可以求出最大值和最小值,题目里给定的区间长度的范围是1e6,因此这样是可以的,再看这个题的数的范围:2e9,肯定是不能用筛直接求解的,根本开不了这么大的数组!因此需要换一种思路来求解给定区间中的所有的素数

一个和数m一定有一个因子n,并且满足n<m1/2,基于这个性质,我们可以先求解所有2e91/2范围中的所有的素数,再由这些素数筛掉给定区间内所有的和数,这样就可以得到给定区间内所有的素数,这样问题就解决了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e4+10,M=1e6+10;
int prime[N],cnt;
bool st[N];
int primelr[M]; //存区间l~r中所有的素数
bool stlr[M]; //将l~r映射到0~1e6的范围里,存区间中的数是不是素数
void get_prime(int n)
{
    
    
    for(int i=2;i<=n;i++)
    {
    
    
        if(!st[i]) prime[cnt++]=i;
        for(int j=0;prime[j]*i<=n;j++)
        {
    
    
            st[prime[j]*i]=1;
            if(prime[j]%i==0) break;
        }
    }
}
int main()
{
    
    
    get_prime(N-1);
    ll l,r;
    while(cin>>l>>r)
    {
    
    
        memset(stlr,0,sizeof(stlr));
        //筛掉区间中所有的素数
        for(int i=0;i<cnt;i++)
        {
    
    
            int p=prime[i];
            //注意这里要把质数p本身略过去
            for(ll j=max((l+p-1)/p*p,(ll)2*p);j<=r;j+=p) stlr[j-l]=1;
        }
        int num=0;
        for(int i=0;i<=r-l;i++)
        {
    
    
            //注意这里1要进行单独处理
            if(stlr[i]==0&&i+l>1)
                primelr[num++]=i+l;
        }
        //for(int i=0;i<num;i++) cout<<primelr[i]<<endl;
        //cout<<num<<endl;
        if(num<2) cout<<"There are no adjacent primes."<<endl;
        else
        {
    
    
            int mip=0,map=0;
            for(int i=0;i<num-1;i++)
            {
    
    
                int d=primelr[i+1]-primelr[i];
                if(d<(primelr[mip+1]-primelr[mip])) mip=i;
                if(d>(primelr[map+1]-primelr[map])) map=i;
            }
            printf("%d,%d are closest, %d,%d are most distant.\n",primelr[mip],primelr[mip+1],primelr[map],primelr[map+1]);
        }
    }
    return 0;
}
//2147483629,2147483647 are closest, 2147483587,2147483629 are most distant.

国王的游戏

看上去像是二分,其实用贪心就可以解决
这道题只需要将所有的大臣按左右手的数的乘积从小到大排序即可,这样得到的大臣拿到金币的最大值就是最小的

关键难在高精度!
这个题需要写高精度除法和高精度乘法

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int n;
PII p[N];
vector<int> mul(vector<int>a, int b) 高精度乘法
{
    
    
    vector<int> res;
    int t = 0;
    for (int i = 0; i < a.size(); i ++ )
    {
    
    
        t += a[i] * b;
        res.push_back(t % 10);
        t /= 10;
    }
    while (t)
    {
    
    
        res.push_back(t % 10);
        t /= 10;
    }
    return res;
}
vector<int> div(vector<int>a, int b) //高精度除法
{
    
    
    vector<int> res;
    bool f = 0;
    int t=0;
    for (int i = a.size() - 1; i >= 0; i -- )
    {
    
    
        t = t * 10 + a[i];
        int x = t / b;
        if (f || x)
        {
    
    
            f = 1;
            res.push_back(x);
        }
        t %= b;
    }
    return vector<int>(res.rbegin(),res.rend());
}
vector<int> max_vec(vector<int> a, vector<int> b) //高精度比较
{
    
    
    if (a.size() > b.size()) return a;
    if (a.size() < b.size()) return b;
    if (vector<int>(a.rbegin(), a.rend()) > vector<int>(b.rbegin(), b.rend())) return a;
    return b;
}
int main()
{
    
    
    cin >> n;
    for (int i = 0; i <= n; i ++ )
    {
    
    
        int a, b;
        cin >> a >> b;
        p[i] = {
    
    a * b, a};
    }
    sort(p + 1, p + n + 1);
    vector<int> temp(1, 1);
    vector<int> res(1, 0);
    for (int i = 0; i <= n; i ++ )
    {
    
    
        if (i) res = max_vec(res, div(temp, p[i].first / p[i].second)); //国王不给自己发金币
        temp = mul(temp, p[i].second);
    }
    for (int i = res.size() - 1; i >= 0; i -- ) cout << res[i];
    cout << endl;
    return 0;
}

余数之和

这是一道数论题
对于取余运算有这样的等价关系:k%n=k-(k/n)*n
利用上述表示我们可以快速求解余数之和,但是我们需要用一个可以快速得到k/n结果的方法
先用暴力做法打一个表
在这里插入图片描述
可以发现很多的数都是重复出现且连续的
因此如果能够快读得到每个数出现的上界和下界就可以减少很多循环次数
已知下界 l 可以得到上界是 r=k/(k/l),利用上下界和等差数列求和公式,就可以解决这个问题了
代码中还要注意一些边界问题,详细的看代码吧

扫描二维码关注公众号,回复: 12506602 查看本文章
#include <bits/stdc++.h>
using namespace std;
typedef long long ll; 
int main()
{
    
    
    int n,k;
    cin>>n>>k;
    ll res=(ll)n*k;
    int l,r;
    for(l=1;l<=n;l=r+1)
    {
    
    
        if(k/l==0) break;
        r=min(k/(k/l),n);
        res-=(ll)(k/l)*(l+r)*(r-l+1)/2;
    }
    cout<<res<<endl;
    return 0;
}

Color a Tree(给树上色)

在这里插入图片描述
英文的题先解释一下
给树上的每一个节点染色,给一个节点染色的前提条件是它的父节点已经被染色,然后每个点染色的都有一个时间戳(1、2、3…),题目中给定每个节点的权值,求每个节点 时间戳*权值 之和的最小值是多少

这道题可以参考一下打水问题,小朋友打水,要等待时间最少就要让打水越磨叽的小朋友越晚打水。这个问题也是一样的,我们需要让权值越大的点越先染色,即其父节点染色之后直接将它进行染色,这样就可以将这两个点看成是一个点

经过上述过程,就可以得到一堆点点组合,怎么判断点的集合谁先谁后呢?经过数学推导,我们可以通过比较点均值的方法得到点的集合谁先谁后

答案可以用迭代的形式得到,每次得到每个集合对答案的贡献,累加即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1010;
int n,root;
struct Node
{
    
    
    int fath,size,value;
    double ave;
}node[N];
int find()
{
    
    
    double ave=-1;
    int res=1;
    for(int i=1;i<=n;i++)
    {
    
    
        if(i!=root&&node[i].ave>ave)
        {
    
    
            ave=node[i].ave;
            res=i;
        }
    }
    return res;
}
int main()
{
    
    
    cin>>n>>root;
    for(int i=1;i<=n;i++)
    {
    
    
        cin>>node[i].value;
        node[i].size=1;
        node[i].ave=node[i].value;
    }
    ll res=0;
    for(int i=1;i<=n;i++) res+=node[i].value;
    for(int i=1;i<n;i++)
    {
    
    
        int a,b;
        cin>>a>>b;
        node[b].fath=a;
    }
    for(int i=1;i<n;i++)
    {
    
    
        int temp=find();
        int pare=node[temp].fath;
        res+=node[temp].value*node[pare].size;
        node[temp].ave=-2;
        for(int j=1;j<=n;j++)
        {
    
    
            if(node[j].fath==temp)
                node[j].fath=pare;
        }
        node[pare].value+=node[temp].value;
        node[pare].size+=node[temp].size;
        node[pare].ave=(double)node[pare].value/node[pare].size;
    }
    cout<<res<<endl;
    return 0;
}

Stall Reservations(畜栏预定)

这道贪心题还是比较容易想到的

先把区间按左端点从小到大排序
然后依次枚举每个区间
如果枚举到的当前区间之前所有的区间都没有完成吃草,就重新分配一个畜栏
如果之前有已经结束吃草的牛,那么当前这头牛就去之前的畜栏吃草即可

这里比较难模拟的地方是
输出每头牛使用的畜栏编号,这里需要维护每一个区间是第几个输入的
重新分配畜栏的时候可能有多头牛都结束了吃草,要找最先结束的那一头牛,用的是小根堆维护每头牛结束吃草的时间

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 5e4 + 10;
int n;
pair<PII, int> cow[N];
int res[N];
priority_queue<PII, vector<PII>, greater<PII>> vis; //第一个元素存区间的右端点,第二个元素存当前用到的畜栏编号
int main()
{
    
    
    cin >> n;
    for (int i = 0; i < n; i++)
    {
    
    
        scanf("%d%d", &cow[i].first.first, &cow[i].first.second);
        cow[i].second = i;
    }
    sort(cow, cow + n);
    //for (int i = 0; i < n; i++) cout<<cow[i].first.first<<" "<<cow[i].first.second<<" "<<cow[i].second<<endl;
    for (int i = 0; i < n; i++)
    {
    
    
        if (vis.empty() || vis.top().first >= cow[i].first.first)
        {
    
    
            PII temp = {
    
    cow[i].first.second, vis.size() + 1};
            res[cow[i].second] = temp.second;
            vis.push(temp);
        }
        else
        {
    
    
            auto temp = vis.top();
            vis.pop();
            res[cow[i].second] = temp.second;
            temp.first = cow[i].first.second;
            vis.push(temp);
        }
    }
    cout << vis.size() << endl;
    for (int i = 0; i < n; i++)
        cout << res[i] << endl;
    return 0;
}

Sunscreen(防晒)

这道贪心题做的有些许的迷惑

我们可以将牛晒太阳的spf值的范围看作一个区间,防晒霜看作一个点

一开始的贪心思路是所有的区间按左端点从小带大排列
然后从前往后遍历所有的区间
让每一头牛选择在其区间范围内最小的防晒霜,有选择就累加得到答案

然而!!这就wa了!!
奇妙的是如果只改变上述思路中的遍历顺序,将从小到大遍历改成从大到小遍历就ac了!!
那么为什么这样是对的呢??
有的博客上面说要先选靠后的点,因为靠后的点不容易用到
我感觉这样的证明并没有能说服我(手动狗头)

接下来我给出一个证明(可能说的不明白,勿喷勿喷)
首先将每一个区间看成一个结点,每一瓶防晒霜看作一个结点,如果这头牛可以选择这瓶防晒霜就在两个结点之间连一条边
那么这个问题就转化成了求一个二分图的最大匹配问题
只需要证明利用上述做法得到的二分图中不存在一条增广路径就能说明求得的的一个最大值
假设存在一条增广路径,说明可以从一瓶防晒找到一条路径使得匹配数+1
举例一条最短的增广路径
如果存在一个点能引出一条增广路径,那么根据这个算法得到的这个区间原来占的那个点则有两种情况(向区间序列之前的区间上占,向区间序列之后的区间上占)
如果要向区间序列之前的区间上占,那么能引出增广路径的那个点也能占,与最短增广路径矛盾
如果要向区间序列之后的区间上占,那么则可以循环推导,占区间序列的最后一个区间没有被匹配过,但是根据算法会优先匹配区间序列的最后一个区间,这就矛盾了!
因此!!这个算法是正确的!!

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int>PII;
const int N=2510;
PII cow[N];
int spf[1010];
int main()
{
    
    
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++) cin>>cow[i].first>>cow[i].second;
    for(int i=0;i<m;i++)
    {
    
    
        int c,s;
        cin>>s>>c;
        spf[s]+=c;
    }
    sort(cow,cow+n);
    int num=0;
    for(int i=n-1;i>=0;i--)
    {
    
    
        for(int j=cow[i].second;j>=cow[i].first;j--)
        {
    
    
            if(spf[j]>0)
            {
    
    
                num++;
                spf[j]--;
                break;
            }
        }
    }
    cout<<num<<endl;
    return 0;
}

Hankson的趣味题

lcm(x,b0)==b1说明b1是x的倍数,x是b1的约数
因此枚举b1所有的约数判断是否符合题目的要求即可

暴力枚举所有约数的复杂度妥妥n1/2也就是差不多4w+,测试数据2k,整体复杂度差不多1e7左右,求gcd和lcm都是logn的非常快,因此1s也是可以过的

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a0, a1, b0, b1;
int gcd(int a,int b)
{
    
    
    return b?gcd(b,a%b):a;
}
int lcm(int a,int b)
{
    
    
    return (a/gcd(a,b))*b;
}
int main()
{
    
    
    int n;
    cin>>n;
    while (n--)
    {
    
    
        cin >> a0 >> a1 >> b0 >> b1;
        ll num=0;
        for(int i=1;i*i<=b1;i++)
        {
    
    
            if(b1%i) continue;
            if(gcd(i,a0)==a1&&lcm(i,b0)==b1) num++;
            if(i!=b1/i&&gcd(b1/i,a0)==a1&&lcm(b1/i,b0)==b1) num++;
        }
        cout<<num<<endl;
    }
    return 0;
}

Visible Lattice Points(可见的点)

在这里插入图片描述
这道题运用的是互质的内容
设坐标系中某个点的坐标是(x0,y0),则这个点与原点连线的方程是y=(y0/x0)*x,其中斜率是k=y0/x0,如果这个点的两个坐标值不互质的话,那么一定可以约分,那么这个点就一定会被约分后的那个点挡住,那么这个点一定不可见,因此这个点如果可见的话,那么这个点的两个坐标一定是互质的

那么如何解决这个问题呢?如果要进行遍历的话时间复杂度还是太高了
在这里可以用欧拉函数进行解决,求出数n的欧拉函数值,代表1~n中有几个数和n互质,就证明n这个点能组成几个互质的坐标
由于坐标系是对称的,因此将欧拉函数的值乘2就可以得到整张坐标系中互质的坐标的个数
特殊情况(1,1)这个点需要进行特殊处理,最终结果+1即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1010;
int primes[N], cnt; //表示筛的变量
int phi[N];         //存储欧拉函数
bool st[N];         //标记这个一个值有没有被用到过
ll sum[N];
void oul()
{
    
    
    phi[1] = 1;
    for (int i = 2; i <= N; i++)
    {
    
    
        if (!st[i])
        {
    
    
            primes[cnt++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; primes[j] <= N / i; j++)
        {
    
    
            st[primes[j] * i] = true;
            if (i % primes[j] == 0)
            {
    
    
                phi[primes[j] * i] = phi[i] * primes[j];
                break;
            }
            phi[primes[j] * i] = phi[i] * (primes[j] - 1);
        }
    }
    sum[1]=phi[1]*2;
    for(int i=2;i<N;i++) sum[i]=sum[i-1]+phi[i]*2;
}
int main()
{
    
    
    int t;
    cin>>t;
    oul();
    for(int i=1;i<=t;i++)
    {
    
    
        int n;
        cin>>n;
        cout<<i<<" "<<n<<" "<<sum[n]+1<<endl;
    }
    return 0;
}

阶乘分解

先用线性筛处理出所有的素数
然后求阶乘中每个素数出现的次数,次数不是0就输出

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const long long N= 1e6 + 10;
int primes[N], cnt;
bool st[N];
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()
{
    
    
	int n;
	cin>>n;
    get_primes(n);
    for(int i=0;i<cnt;i++)
	{
    
    
		int t=n,sum=0;
		while(t)
		{
    
    
			sum+=t/primes[i];
			t/=primes[i];
		}
		if(sum!=0) cout<<primes[i]<<" "<<sum<<endl;
	}
    return 0;
}

Radar installation

雷达设备!
这道题是一道典型的区间贪心问题
如果一个点到x轴到距离大于雷达的侦测半径,那么则一定侦测不到
然后对于每一个点都可以在x轴上找到一个区间,在这个区间内的点上放置雷达都可以侦测到这个小岛
然后这道题就转化为经典的区间选点问题,在给定的所有区间内选点使每个区间内都有点
只需要按右端点排序,每次选取最右边的点(越靠右点点越有可能覆盖更多的区间),如果这个点覆盖不到,那么答案加一即可

#include <bits/stdc++.h>
using namespace std;
const int N=1010;
struct sa
{
    
    
    double l,r;
}range[N];
double cmp(const sa &a,const sa &b)
{
    
    
    return a.r<b.r;
}
int main()
{
    
    
    int n;
    int d;
    scanf("%d%d",&n,&d);
    for(int i=0;i<n;i++)
    {
    
    
        int x,y;
        scanf("%d%d",&x,&y);
        if(y>d)
        {
    
    
            cout<<"-1"<<endl;
            return 0;
        }
        double l=sqrt((double)(d*d-y*y));
        range[i]={
    
    x-l,x+l};
    }
    sort(range,range+n,cmp);
    int res=0;
    double ed=-2e9;
    for(int i=0;i<n;i++)
    {
    
    
        if(range[i].l>ed)
        {
    
    
            res++;
            ed=range[i].r;
        }
    }
    cout<<res<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_46126537/article/details/113140465
今日推荐