Codeforces Round #741 Div. 2 A B C D1 D2


四题下班,每次D2都做不出或者没啥时间了…
大胆假设,猜到结论就可以冲了(bushi

A. The Miracle and the Sleeper

题目链接
在这里插入图片描述
题意: 已知两个整数 l l l r r r, l ≤ r l≤r lr。求所有 r ≥ a ≥ b ≥ l r≥a≥b≥l rabl的整数对 ( a , b ) (a,b) (a,b) a a a m o d mod mod b b b的最大可能值。

分析: 在给定的 l , r l,r l,r中,选择 r ≥ a ≥ b ≥ l r≥a≥b≥l rabl,的 a , b a,b a,b使得 a m o d b a mod b amodb最大。可以想到如果有数 x x x,使得 r r r ÷ \div ÷ x = 1...... x − 1 x=1......x-1 x=1......x1,这样的 x x x最大是多少? r = 2 x − 1 r=2x-1 r=2x1 x = ( r + 1 ) / 2 x=(r+1)/2 x=(r+1)/2,如果 l ≤ ( r + 1 ) / 2 l \leq (r+1)/2 l(r+1)/2,那么答案就是 ( r + 1 ) / 2 − 1 (r+1)/2-1 (r+1)/21
否则,即 l ≥ ( r + 1 ) / 2 l \geq (r+1)/2 l(r+1)/2时,只能这样构造, r ÷ l = 1..... r − l r \div l=1.....r-l r÷l=1.....rl,答案也就是 r − l r-l rl

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

int main()
{
    int t;scanf("%d",&t);
    while(t--){
        int l,r;scanf("%d %d",&l,&r);
        if((r+1)/2>=l)printf("%d\n",(r+1)/2-1);
        else printf("%d\n",r%l);
    }
    return 0;
}

B. Scenes From a Memory

题目链接
在这里插入图片描述
题意: 给定一个正整数n,它在十进制表示法中不包含零。问:这个数最多可以去掉多少位数字,这样这个数就不是质数了。

分析: 仔细分析,从一大串数字删掉最多的数字后,剩下的数是一个合数。看了样例之后大胆猜测剩下的位数不是1就是2,然后莽了一发对了(不要学我)。
其实仔细分析,因为答案保证一定有解,假设剩下的数是三位。能找到一个三位数,使得这个三位数是一个合数,但是其任意两个数组成的两位数不是合数吗。感觉证明不是很科学。起床之后再想想…起床了
我们来证明一下,如果一个数是三位数,你总是可以从其中至少删除一个数来得到一个数不是质数
法一:可以通过对所有三位数的数字爆搜来得到解,我没有写了,大家可以试试。
法二:如果一个数中包含 1 , 4 , 6 , 8 , 9 1,4,6,8,9 14689,那么其中任意一个数字就是答案,这几个数都是非素数。
如果一个三位数中没有这些数字呢?也就是只包含 2 , 3 , 5 , 7 2,3,5,7 2357

  • 如果这个数中,存在 2 2 2或者 5 5 5不在第一位,那么可以删掉一个数变成合数。比如 752 752 752 723 723 723中选择 75 75 75 72 72 72
  • 如果是存在任意两个相同的数,比如 22 , 33 , 55 , 77 22,33,55,77 22335577,这些数都是合数。
  • 同时包含 5 , 7 5,7 57的数,是肯定能够构成 57 57 57 75 75 75,能被 3 3 3整除,是合数。
  • 剩下的情况就是, 273 , 237 273,237 273237,删除 3 3 3即可构成 27 27 27,能被 3 3 3整除,也是合数
    所以上述就证明了,留下来的数字肯定不超过两位,然后直接暴力模拟就行了。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N=55;
char s[N];
bool check(int x){
    if(x<=3)return x>1;
    if(x%2==0||x%3==0)return 0;
    for(int i=5;i<=sqrt(x);i+=6){
        if(x%i==0||x%(i+2)==0)return 0;
    }
    return 1;
}
int main()
{
    int t;scanf("%d",&t);
    while(t--){
        int n,flag=0;scanf("%d",&n);
        scanf("%s",s+1);
        for(int i=1;s[i];i++){
            int p=s[i]-'0';
            if(!check(p)){
                printf("%d\n%d\n",1,p);
                flag=1;
                break;
            }
        }
        if(flag)continue;
        for(int i=1;s[i];i++){
            for(int j=i+1;s[j];j++){
                int p=(s[i]-'0')*10+s[j]-'0';
                if(!check(p)){
                    printf("%d\n%d\n",2,p);
                    flag=1;
                    break;
                }
            }
            if(flag)break;
        }
    }
    return 0;
}

C. Rings

题目链接
在这里插入图片描述
题意: 给定一个01串,从中选出两个长度大于等于 ⌊ n 2 ⌋ \lfloor \frac{n}{2} \rfloor 2n的区间 t t t w w w,使得两个区间所构成的十进制数满足 f ( t ) = f ( w ) ⋅ k f(t)=f(w)⋅k f(t)=f(w)k。( f ( x ) f(x) f(x)就是 x x x的十进制数, k k k为非负整数)

分析: 很巧妙的题目,这个题目就分为两种情况讨论。

  • 全为 1 1 1,这种情况只需要输出长度大于等于 n / 2 n/2 n/2的两个区间,且这两个区间长度为倍数关系即可。我直接输出了 1 , n / 2 ∗ 2 ; 1 , n / 2 1,n/2*2;1,n/2 1,n/22;1,n/2
  • 不全为 1 1 1,在 0001000 0001000 0001000中,可以选择 0001000 , 001000 0001000,001000 0001000,001000,在 100100 100100 100100中,选择 00100 00100 00100 0100 0100 0100。可以看出些什么?
    从左往右在前面一半的数中,找到第一个 0 0 0,可以看出 0 x 0x 0x( x x x是0右边的任意 01 01 01串)和 x x x是相等的。所以直接从当前0的位置,直接输出答案到结尾,另一个部分就是当前0后面的位置到结尾。
    在后面一半中,从右往左找一遍。同理, 1111000 1111000 1111000中选择 1111000 1111000 1111000 111100 111100 111100是可行解。 x 0 x0 x0(x是0左边的任意01串)等于 x × 2 x \times 2 x×2所以选择的两个部分分别是,从首位到当前0的位置,从首位到当前0前面的一个位置。
    非常巧妙的题目。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N=2e4+5;
char s[N];
int main()
{
    int t;scanf("%d",&t);
    while(t--){
        int n;scanf("%d",&n);
        scanf("%s",s+1);
        int flag=0;
        for(int i=1;i<=n/2;i++){
            if(s[i]=='0'){
                printf("%d %d %d %d\n",i,n,i+1,n);
                flag=1;
                break;
            }
        }
        if(!flag){
            for(int i=n;i>n/2;i--){
                if(s[i]=='0'){
                    printf("%d %d %d %d\n",1,i,1,i-1);
                    flag=1;
                    break;
                }
            }
        }
        if(!flag)printf("%d %d %d %d\n",1,n/2*2,1,n/2);
    }
    return 0;
}

D1. Two Hundred Twenty One (easy version)

题目链接
在这里插入图片描述
题意: 一台机器的主要部件是沿着一条直线排列的 n n n根杆,编号从 1 1 1 n n n。每个这些杆必须携带一个 1 1 1 − 1 -1 1的电荷(否则机器将不工作)。
这台机器工作的另一个条件是,所有杆上的电荷的符号变量之和必须为零。也就是要满足 a 1 − a 2 + a 3 − a 4 + … = 0 a_{1}−a_{2}+a_{3}−a_{4}+…=0 a1a2+a3a4+=0 , or ∑ i = 1 n ( − 1 ) i − 1 ⋅ a i = 0 \sum_{i=1}^{n}{(-1)^{i-1}}\cdot a_{i}=0 i=1n(1)i1ai=0。在第 i i i个问题中,如果机器只由数为 l i l_{i} li r i r_{i} ri的杆组成,那么最小多少杆可以从机器中移除,以使其余杆上的符号可变的电荷之和为零?也就是通过上面的式子算完之后为0。

分析:
做的时候又是看样例盲猜一手结论,答案就是 0 , 1 , 2 0,1,2 012,然后简单证明了一下。
结论: 奇数肯定只能删 1 1 1个,偶数判断一下是不是 0 0 0,不是 0 0 0就是 2 2 2。判断是不是 0 0 0需要先与处理一下前缀和。证明早上再补叭,困了( ̄o ̄) . z Z 醒了
证明: 感觉官方的证明很可,我这里翻译加自己的话解释一下。引入一个新的数组 b b b,如果移除第 i i i个元素,则 b i b_{i} bi等于整个数组的按照题意的带符号和。
对于一个串 a a a

  • 如果 a i = a i + 1 a_{i}=a_{i+1} ai=ai+1,那么 a i a_{i} ai被移除时,和 a i + 1 a_{i+1} ai+1被移除时的效果是一样的 ∣ b i − b i + 1 ∣ = 0 |b_{i}-b_{i+1}|=0 bibi+1=0
  • 如果 a i ≠ a i + 1 a_{i} \not= a_{i+1} ai=ai+1,用 f ( l , r ) f(l,r) f(l,r)表示区间 l l l r r r的符号变量和。容易得到, b i = f ( 1 , i − 1 ) ± a i + 1 ∓ f ( i + 2 , n ) b_{i}=f(1,i−1)±a_{i+1}∓f(i+2,n) bi=f(1,i1)±ai+1f(i+2,n) b i + 1 = f ( 1 , i ) ∓ f ( i + 2 , n ) b_{i+1}=f(1,i)∓f(i+2,n) bi+1=f(1,i)f(i+2,n)。所以 b i − b i + 1 = ∓ a i ± a i + 1 b_{i}-b_{i+1}=∓a_{i}±a_{i+1} bibi+1=ai±ai+1,因为 a i ≠ a i + 1 a_{i} \not= a_{i+1} ai=ai+1,所以 ∣ b i − b i + 1 ∣ = 2 |b_{i}-b_{i+1}|=2 bibi+1=2.

如果 n n n是奇数,那么存在 k k k使 b k = 0 b_{k}=0 bk=0
证明:

  • 如果 b 1 = 0 b_{1}=0 b1=0 b n = 0 b_{n}=0 bn=0,则得证。
  • 如果 b 1 < 0 , b n > 0 b_{1}<0,b_{n}>0 b1<0,bn>0,因为数组中相邻的值 b b b相差要么为 2 2 2,要么为 0 0 0所有的元素都是偶数相邻b的差值不可能存在连续的2或者连续的-2。要使得 b 1 < 0 , b n > 0 b_{1}<0,b_{n}>0 b1<0,bn>0,那么从 1 − n 1-n 1n的之间的 b b b值肯定存在一个是 0 0 0
  • b 1 > 0 , b n < 0 b_{1}>0,b_{n}<0 b1>0,bn<0同理
  • 如果 n > 1 n>1 n>1,则不可能有 b 1 > 0 b_{1}>0 b1>0 b n > 0 b_{n}>0 bn>0的情况。
    设整段的符号变量和为 s u m sum sum,则 b 1 = − s u m ± 1 b_{1}= - sum±1 b1=sum±1, b n = s u m ± 1 b_{n}=sum±1 bn=sum±1。因为 b 1 b_{1} b1 b n b_{n} bn是偶数,所以它们中至少有一个是零或者它们的符号不同。同理,也不可能有 b 1 < 0 b_{1}<0 b1<0 b n < 0 b_{n}<0 bn<0的情况。

n n n为偶数的时候,同理,可以删除一个数之后转换为奇数的情况。如果变量和已经为0了,答案就是0

所以,如果变量和已经为零,输出为零;否则,如果段是奇数长度,输出1;偶数长度,输出2。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N=300005;
char s[N];
int sum[N];
int main()
{
    int t;scanf("%d",&t);
    while(t--){
        int n,q;scanf("%d %d",&n,&q);
        scanf("%s",s+1);
        if(s[1]=='+')sum[1]=1;
        else sum[1]=-1;
        for(int i=2;i<=n;i++){
            if(s[i]=='+')sum[i]=sum[i-2]+1;
            else sum[i]=sum[i-2]-1;
        }
        while(q--){
            int l,r;scanf("%d %d",&l,&r);
            if((r-l+1)%2)printf("1\n");
            else{
                int ju=sum[r-1]-sum[l-2]-(sum[r]-sum[l-1]);
                //cout<<ju<<endl;
                if(!ju)printf("0\n");
                else printf("2\n");
            }
        }
    }
    return 0;
}

D2. Two Hundred Twenty One (hard version)

题目链接
在这里插入图片描述
题意: 和上题意思一致,加了一个要求,需要输出你删除的杆是哪一个。

分析: 上题已经得出来了一些结论。

  • 如何在奇数段中找到要删除哪一个元素。如果暴力枚举每一个元素,肯定会TLE。考虑一下b数组中的函数形式,b数组画出来的函数图要么是经过x轴的一次函数,要么为三次函数,(只是类似这种形式,因为b数组两两差值可能为0)这就给了我们二分的机会。假设一开始 l = 1 , r = n l=1,r=n l=1,r=n,因为 b 1 b_{1} b1 b n b_{n} bn符号相同或者至少其中一个为 0 0 0
  • 每一次找到 m i d = ⌊ l + r 2 ⌋ mid= \lfloor \frac{l+r}{2} \rfloor mid=2l+r,如果 b m i d = 0 b_{mid}=0 bmid=0或者 b l = 0 b_{l}=0 bl=0或者 b r = 0 b_{r}=0 br=0,我们就都找到了一个可行解。否则,如果 b l b_{l} bl b m i d b_{mid} bmid符号一致,那么需要在 m i d mid mid的右半边找,更新 l l l m i d mid mid。否则,要在 m i d mid mid的左半边找, r = m i d r=mid r=mid

这样算最终一定会得到一个答案,因为我们知道一定存在一个 i i i使得 b i = 0 b_{i}=0 bi=0。这可以用 ∣ b i − b i + 1 ∣ ≤ 2 |bi−bi+1|≤2 bibi+12,而且所有 b i b_{i} bi都是偶数来证明,前面一题其实也说过了。

以上是官方的解法,以下是法二
我们已经确定了,当长度为奇数时,需要删除一个,一定会存在第 i i i个数,使得删完 i i i之后总贡献为 0 0 0,这个点就是我们要求的点。所以有 s u m [ r ] − s u m [ i ] = s u m [ i − 1 ] − s u m [ l − 1 ] sum[r]-sum[i]=sum[i-1]-sum[l-1] sum[r]sum[i]=sum[i1]sum[l1]。这个式子的意思就是 i i i右边的贡献等于 i i i左边的贡献,因为删除 i i i之后, i i i右边的贡献符号会反过来,所以删除完i点之后,左右两边贡献为0。进一步处理这个式子得到 s u m [ r ] + s u m [ l − 1 ] = s u m [ i ] + s u m [ i − 1 ] sum[r]+sum[l-1]=sum[i]+sum[i-1] sum[r]+sum[l1]=sum[i]+sum[i1] s u m [ l − 1 ] + s u m [ r ] sum[l-1]+sum[r] sum[l1]+sum[r]是一个定值,所以我们可以二分 i i i来求出答案。因为不知道二分缩小区间往哪个方向,做两次二分即可。

代码:
法一:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N=300005;
char s[N];
int sum[N];
int to_sum(int l,int r){
    if(l>r)return 0;
    return (l%2==1?sum[r]-sum[l-1]:sum[l-1]-sum[r]);
}
int get_b(int l,int r,int m){//这一部分就是在求我们定义的b
    int ans=to_sum(l,m-1);
    if((m-l+1)%2)ans+=to_sum(m+1,r);//后面半部分的计算,需要根据删除的m位置的正负来判断
    else ans-=to_sum(m+1,r);
    return ans;
}
int get_sign(int x){
    return x>0?1:-1;
}
int find_ans(int l,int r){
    if(l==r)return l;
    int la=l,ra=r;
    while(la<ra){
        int mid=la+ra>>1;
        int lb=get_b(l,r,la);//l到r删去la
        int mb=get_b(l,r,mid);//l到r删去mid
        int rb=get_b(l,r,ra);//l到r删去ra
        if(lb==0)return la;
        if(mb==0)return mid;
        if(rb==0)return ra;
        if(get_sign(lb)==get_sign(mb))la=mid;
        else ra=mid;
    }
    return -1;
}
int main()
{
    int t;scanf("%d",&t);
    while(t--){
        int n,q;scanf("%d %d",&n,&q);
        scanf("%s",s+1);
        for(int i=1;i<=n;i++){
            if(i%2)sum[i]=sum[i-1]+(s[i]=='+'?1:-1);
            else sum[i]=sum[i-1]-(s[i]=='+'?1:-1);
        }
        while(q--){
            int l,r;scanf("%d %d",&l,&r);
            if(!to_sum(l,r))printf("0\n");
            else{
                bool is_even=false;
                if((r-l+1)%2==0)is_even=true,l++;
                int pos=find_ans(l,r);
                if(is_even)printf("2\n%d %d\n",l-1,pos);
                else printf("1\n%d\n",pos);
            }
        }
    }
    return 0;
}

法二:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N=300005;
char s[N];
int sum[N];
int to_sum(int l,int r){
    if(l>r)return 0;
    return (l%2==1?sum[r]-sum[l-1]:sum[l-1]-sum[r]);
}
int find_ans(int l,int r){
    if(l==r)return l;
    int k=sum[r]+sum[l-1];
    //cout<<k<<endl;
    int la=l,ra=r;
    while(la<=ra){
        int mid=la+ra>>1;
        int m=sum[mid]+sum[mid-1];
        //cout<<mid<<" "<<sum[mid]<<" "<<sum[mid-1]<<endl;
        if(m==k)return mid;
        else if(m>k)la=mid+1;
        else ra=mid-1;
    }
    la=l,ra=r;
    while(la<=ra){
        int mid=la+ra>>1;
        int m=sum[mid]+sum[mid-1];
        //cout<<mid<<" "<<sum[mid]<<" "<<sum[mid-1]<<endl;
        if(m==k)return mid;
        else if(m<k)la=mid+1;
        else ra=mid-1;
    }
    return -1;
}
int main()
{
    int t;scanf("%d",&t);
    while(t--){
        int n,q;scanf("%d %d",&n,&q);
        scanf("%s",s+1);
        for(int i=1;i<=n;i++){
            if(i%2)sum[i]=sum[i-1]+(s[i]=='+'?1:-1);
            else sum[i]=sum[i-1]-(s[i]=='+'?1:-1);
        }
        while(q--){
            int l,r;scanf("%d %d",&l,&r);
            if(!to_sum(l,r))printf("0\n");
            else{
                bool is_even=false;
                if((r-l+1)%2==0)is_even=true,l++;
                int pos=find_ans(l,r);
                if(is_even)printf("2\n%d %d\n",l-1,pos);
                else printf("1\n%d\n",pos);
            }
        }
    }
    return 0;
}
  • 9
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

a碟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值