11.1 NOIP模拟赛 (afternoon)

本文介绍了三道算法题目:寻找指定范围内幸运数字的数量、利用自定义位运算求序列价值总和、以及优化蚂蚁运输效率的问题。每道题目都详细阐述了解题思路并提供了源代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

幸运数字(number)

Time Limit:1000ms Memory Limit:64MB

题目描述

LYK 最近运气很差,例如在 NOIP 初赛中仅仅考了 90 分,刚刚卡进复赛,于是它决定使
用一些方法来增加自己的运气值。
它觉得,通过收集幸运数字可以快速的增加它的 RP 值。
它给幸运数字下了一个定义: 如果一个数x能被3整除或被5整除或被7整除,则这个
数为幸运数字。
于是它想让你帮帮它在 L~R 中存在多少幸运数字。

输入格式(number.in)

第一行两个数 L,R。

输出格式(number.out)

一个数表示答案。

输入样例

10 15

输出样例

4

数据范围

对于 50%的数据 1<=L<=R<=10^5。
对于 60%的数据 1<=L<=R<=10^9。
对于 80%的数据 1<=L<=R<=10^18。
对于 90%的数据 1<=L<=R<=10^100。
对于另外 10%的数据 L=1,1<=R<=10^100。
对于 100%的数据 L,R 没有前导 0。

思路;

容易发现1-n中,假设第i个数 i%3==0||i%5==0||i%7==0
那么统计出能被3整除的数为ans1
能被5整除的数为ans2
能被7整除的数为ans3
那么我们发现有些数可以同时被3和5整除,例如15
同理也有同时被5和7整除,例如 35;
也会有同时被3和7整除的数 例如21;
还有同时被3、5和7整除的数 例如105
可以看出来 只用ans=(ans1+ans2+ans3)是不对的
这样多重复了很多数,再减去能同时被两个数整除的数的个数也是不对的
因为多减了能同时被3、5、7同时整除的数 所以要再加上能被3个数整除的个数
写个简单程序验证一下就明白了
最恶心的来了,这题要写高精!!!!!!!
高精除、高精加、高精减!!!!!!!

代码:

// 由于我的U盘忘家里了,所以找了另一个大神的%%%%
// 等那会U盘再改吧 
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<iostream>
#define N 1000010
using namespace std;
int n,m;
struct node
{
    int x,y;
};node a[N];
int read()
{
    int num=0,flag=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
    while(c>='0'&&c<='9'){num=num*10+c-'0';c=getchar();}
    return num*flag;
}
bool check(int k)
{
    int mn=1e9,mx=-1e9;
    for(int i=1;i<=m;i++)
    {
        if(a[i].y-a[i].x<=k)continue;
        mx=max(a[i].x+a[i].y-k,mx);
        mn=min(a[i].x+a[i].y+k,mn);
    }
    if(mx>mn)return false;
    mx=-1e9,mn=1e9;
    for(int i=1;i<=m;i++)
    {
        if(a[i].y-a[i].x<=k)continue;
        mx=max(a[i].x-a[i].y-k,mx);
        mn=min(a[i].x-a[i].y+k,mn);
    }
    if(mx>mn)return false;
    return true;
}
int main()
{
    freopen("jh.in","r",stdin);
    //freopen("ant.in","r",stdin);
    //freopen("ant.out","w",stdout);
    int t;scanf("%d%d",&n,&t);
    while(t--)
    {
        int x,y;scanf("%d%d",&x,&y);
        if(x>y)swap(x,y);
        if(x!=y){a[++m].x=x;a[m].y=y;}
    }
    int l=0,r=N,ans;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(check(mid))
        {
            ans=mid;r=mid-1;
        }
        else l=mid+1;
    }
    printf("%d",ans);
    return 0;
}

位运算(bit)

Time Limit:2000ms Memory Limit:64MB

题目描述

lyk 最近在研究位运算。它发现除了 xor,or,and 外还有很多运算。
它新定义了一种运算符“#”。
具体地,可以由 4 个参数来表示。令 a[i][j]表示 i#j。其中 i,j 与 a 的值均∈[0,1]。
当然问题可以扩展为>1 的情况,具体地,可以将两个数分解为 p 位,然后对于每一位
执行上述的位运算,再将这个二进制串转化为十进制就可以了。
例如当 a[0][0]=0,a[1][1]=0,a[0][1]=1,a[1][0]=1 时,3#4 在 p=3 时等于 7,2#3 在
p=4 时等于 1(实际上就是异或运算)。
现在 lyk 想知道的是,已知一个长为n的数列 b。它任意选取一个序列 c,满
足 c1<c2<...<ck,其中 1≤c1 且 ck≤n,定义这个序列的价值为b{c1}#b{c2}#...#b{ck}
的平方。
这里我们假设 k 是正整数,因此满足条件的 c 的序列个数一定是 2^n−1 。lyk 想知道
所有满足条件的序列的价值总和是多少。
由于答案可能很大,你只需输出答案对 1,000,000,007 取模后的结果即可。

输入格式(bit.in)

第一行两个数 n,p。
第二行 4 个数表示 a[0][0],a[0][1],a[1][0],a[1][1]。
第三行 n 个数表示 bi(0<=bi<2^p)。

输出格式(bit.out)

一个数表示答案。

输入样例

3 30
0 1 1 0
1 2 3

输出样例

28

样例解释

{1}的价值为 1,{2}的价值为4,{3}的价值为 9,{1,2}的价值为 9, {1,3}的价值为 4,{2,3}
的价值为1,{1,2,3}的价值为0,因此7个子集的价值总和为 28。

数据范围

总共 10 组数据。
对于第 1,2 组数据 n<=5。
对于第 3,4 组数据 n<=10000,p=1。
对于第 5 组数据 a 值均为 0。
对于第 6 组数据 a 值均为 1。
对于第 7 组数据 a[0][0]=0,a[1][0]=0,a[1][1]=1,a[0][1]=1。
对于第 8,9 组数据 n<=1000,p<=10。
对于所有数据 n<=10000,1<=p<=30。

思路:

将一个数的平方拆成若干幂次的平方,例如7^2=(1+2+4)^2。
观察答案的形式为1*1+1*2+1*4+2*1+2*2+2*4+4*1+4*2+4*4。
枚举每两位,令dp[i][j][k]表示到第i位,此时第一位为j,第二位为k的方案总数,累加即可。

代码:

//听的我一脸懵逼 只好放std了
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <assert.h>
using namespace std;
long long sum,ans;
int dp[3][3],DP[3][3];
int a[3][3],n,l,k,b[50005],X1,Y1,X2,Y2,i,j;
const int MOD=1000000007;
int main()
{
    freopen("bit.in","r",stdin);
    freopen("bit.out","w",stdout);
    scanf("%d%d",&n,&k); assert(1<=n&&n<=50000&&1<=k&&k<=30);
    scanf("%d%d%d%d",&a[0][0],&a[0][1],&a[1][0],&a[1][1]);
    assert(0<=a[0][0] && a[0][0]<=1 && 0<=a[0][1] && a[0][1]<=1 && 0<=a[1][0] && a[1][0]<=1 && 0<=a[1][1] && a[1][1]<=1);
    for (i=1; i<=n; i++)
    {
        scanf("%d",&b[i]);
        assert(0<=b[i] && b[i]<=(1<<k));
    }
    for (i=0; i<k; i++)
      for (j=i; j<k; j++)
      {
        for (X1=0; X1<2; X1++)
          for (Y1=0; Y1<2; Y1++) dp[X1][Y1]=DP[X1][Y1]=0;
        for (l=1; l<=n; l++)
        {
            for (X1=0; X1<2; X1++)
                for (Y1=0; Y1<2; Y1++)
                if (dp[X1][Y1])
            {
                if (b[l]&(1<<i))
                   X2=a[X1][1]; else X2=a[X1][0];
                if (b[l]&(1<<j))
                    Y2=a[Y1][1]; else Y2=a[Y1][0];
                DP[X2][Y2]+=dp[X1][Y1];
                if (DP[X2][Y2]>=MOD) DP[X2][Y2]-=MOD;
            }
            if (b[l]&(1<<i)) X2=1; else X2=0;
            if (b[l]&(1<<j)) Y2=1; else Y2=0;
            DP[X2][Y2]++; if (DP[X2][Y2]>=MOD) DP[X2][Y2]-=MOD;
            for (X1=0; X1<2; X1++)
              for (Y1=0; Y1<2; Y1++)
              {
                dp[X1][Y1]+=DP[X1][Y1];
                if (dp[X1][Y1]>=MOD) dp[X1][Y1]-=MOD;
                DP[X1][Y1]=0;
              }
        }
        sum=(1<<i);
        sum*=(1<<j);
        sum%=MOD;
        if (i!=j) sum+=sum;
        ans+=sum*dp[1][1];
        ans%=MOD;
      }
    cout<<ans;
    return 0;
}

蚂蚁运输(ant)

Time Limit:5000ms Memory Limit:64MB

题目描述

LYK 在观察一些蚂蚁。
蚂蚁想要积攒一些货物来过冬。积攒货物的方法是这样的。
对于第i只蚂蚁, 它要从li出发, 拿起货物, 走到ri处放下货物, 需要消耗的时间为|ri-li|。
而且所有蚂蚁都是可以同时进行的,也就是说,假如有 m 只蚂蚁,那么运输完货物的时间
为 max{|ri-li|}。
LYK 决定帮蚂蚁一把,它发明了空间传输装置。具体地,当蚂蚁走到 X 处时,它可以不
耗费任意时间的情况下瞬间到达 Y,或者从 Y 到达 X。也就是说,一个蚂蚁如果使用了空间
传输装置,它耗费的时间将会是 min{|li-X|+|ri-Y|,|li-Y|+|ri-X|},当然蚂蚁也可以选择徒步走
到目标点。
由于空间传输装置非常昂贵,LYK 打算只建造这么一台机器。并且 LYK 想让蚂蚁运输完
货物的时间尽可能短,你能帮帮它吗?

输入格式(ant.in)

第一行两个数 n,m,n 表示 li,ri 的最大值。
接下来 m 行,每行两个数 li,ri。

输出格式(ant.out)

一个数表示答案

输入样例

5 2
1 3
2 4

输出样例

1

数据范围

对于 20%的数据 n,m<=100。
对于 40%的数据 n,m<=1000。
对于 60%的数据 n<=100000,m<=1000。
对于 80%的数据 n,m<=100000。
对于 100%的数据 n,m<=1000000,1<=li,ri<=n(li=ri 时你甚至可以无视这只蚂蚁) 。

样例解释

令空间传输装置的参数中 X=2,Y=3 或者 X=3,Y=2 都行。

思路:

观察到答案具有可二分性。我们可以二分答案。
由于路径都是无向的,因此对于任意一条方案li,ri若li>ri则可以交换li和ri。
我们设二分的答案为x。
对于那些li+x>=ri的方案我们直接默认为可行。
我们规定X<=Y。
对于li+x<ri的方案。只有一种可能能够完成,即,从li出发,到达X,到达Y,到达ri。
也就是说,如果X确定,Y存在于一段区间内。
我们来看li>=X的情况。
先求出X=n时符合条件的Y的区间。当X慢慢减少时,这个区间肯定也在慢慢合拢,并且满足li>=X的条件也会越来越多。我们可以线性求出所有这个区间。
对于li<X的情况也同理。
这样就能做到线性判断,总复杂度nlgn。(这里默认n与m同阶)

代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<assert.h>
using namespace std;
int n,m,L[1000005],R[1000005],l,r,mid,i,ll[1000005],rr[1000005];
struct node {int x,y;} t[1000005];
int cmp(node i,node j) {return i.x<j.x;}
bool OK(int x) {
    int cnt=m,MIN=2000005,MAX=-5;
    for (int i=n; i>=1; i--) {
        while (cnt && t[cnt].x==i) {
            if (t[cnt].y-t[cnt].x>x) MIN=min(MIN,t[cnt].y-t[cnt].x);
            if (t[cnt].y-t[cnt].x>x) MAX=max(MAX,t[cnt].x+t[cnt].y);
            cnt--;
        }
        ll[i]=MAX-x-i; rr[i]=MIN+x+i;
    }
    cnt=1; MIN=2000005; MAX=-5;
    for (int i=1; i<=n; i++) {
        while (cnt<=m && t[cnt].x==i) {
            if (t[cnt].y-t[cnt].x>x) MIN=min(MIN,t[cnt].x+t[cnt].y);
            if (t[cnt].y-t[cnt].x>x) MAX=max(MAX,t[cnt].y-t[cnt].x);
            cnt++;
        }
        ll[i]=max(ll[i],MAX-x+i);
        rr[i]=min(rr[i],MIN+x-i);
        if (ll[i]<=rr[i]) return true;
    }
    return false;
}
int main() {
    freopen("ant.in","r",stdin);
    freopen("ant.out","w",stdout);
    scanf("%d%d",&n,&m);
    assert(1<=n && n<=1000000 && 1<=m && m<=1000000);
    for (i=1; i<=m; i++) {
        scanf("%d%d",&L[i],&R[i]);
        assert(1<=L[i] && L[i]<=n && 1<=R[i] && R[i]<=n);
        if (L[i]>R[i]) swap(L[i],R[i]);
        t[i].x=L[i]; t[i].y=R[i];
    }
    sort(t+1,t+m+1,cmp);
    l=0; r=n; mid=(l+r)/2;
    while (l<=r) {
        if (OK(mid)) {r=mid-1; mid=(l+r)/2;} 
        else{
            l=mid+1;
            mid=(l+r)/2;
        }
    }
    cout<<l;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值