1027div3全套
Codeforces Round 1027 (Div. 3)
A. Square Year
给出一个四位数的数字n(可能有前导零),问是否能将n表示为\(n=(a+b)^2\)的形式
将n用字符串读入转成整数后枚举a, b即可,a、b的范围可以控制为100以内
需要注意的是读题不要读错了,一开始把题读成了a和b各自的数字必须分别来自n了浪费了几分钟
代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
int main()
{
    scanf("%d",&T);
    while(T--) {
        int flag=0;
        scanf("%d",&n);
        for(int i=0;i<=100;i++) {
            for(int j=0;j<=100;j++) {
                if(pow(i+j,2)==n) {
                    flag=1;
                    printf("%d %d\n",i,j);
                    break;
                }
            }
            if(flag) {
                break;
            }
        }
        if(!flag) {
            printf("-1\n");
        }
    }
    return 0;
}
B. Not Quite a Palindromic String
对于一个字符串S(下标为1-n且n为偶数 ),每有一对 \(S_i==S_{n-i+1}\),累计一分,现给出一个由01构成的字符串,问能否通过重新排列,使其分数等于m
找出以下性质:
1. 所有排列中,分数有最大值和最小值
2. 分数最小值:把0和1堆在两边,这样minn= max(cnt0,cnt1) - n/2 (如 1100|0000 , max(cnt0,cnt1)比n/2多的那部分就是最小的分数)
3. 分数最大值:把0和1尽可能对称的堆,这样根据奇偶性, maxn = n/2 - cnt0%2(如1100|0001 和1000|0001,如果0和1是奇数个那就比等分少一个,是偶数就刚好等分n)
3. 要改变当前排列的分数,只能通过交换两边的一对不同的元素,这样会让分数+2或-2(所有分数的奇偶性相同)
所以要求m在maxn和minn之间并且奇偶性和他们相同,满足就YES否则NO
#include<bits/stdc++.h>
using namespace std;
int T,n,m;
int main()
{
    scanf("%d",&T);
    while(T--) {
        int cnt0=0;
        int cnt1=0;
        scanf("%d%d\n",&n,&m);
        for(int i=1;i<=n;i++) {
            char ch=getchar();
            if(ch=='0') cnt0++;
            else cnt1++;
        }
        int minn=n/2-min(cnt0,cnt1);
        int maxn;
        if(cnt0%2) {
            maxn=n/2-1;
        } else {
            maxn=n/2;
        }
        if(m>maxn||m<minn||m%2!=minn%2) {
            printf("NO\n");
        } else {
            printf("YES\n");
        }
    }
    return 0;
}
C. Need More Arrays
给出一个\(a_i\le a_{i+1}\)的数组,我们可以删去任意元素,然后从 \(a_1\) 到 \(a_n\) 依次进行判定:
初始时i=1,\(a_1\) 被写入一个新数组;
之后如果 \(a_i+1<a_{i+1}\) ,那么 \(a_{i+1}\) 将被写入一个新数组;否则 \(a_{i+1}\) 将被写入与 \(a_i\) 相同的数组;
问我们删去任意元素后,新数组最多能因这个判定分成多少个子数组
题目说的很复杂,其实就是如果下一位等于上一位,或者等于上一位+1,就分裂;下一位至少比上一位大2时才不分裂
所以我们能想到:
1. 相同的数字是无用的,因为我们只用统计子数组的个数
2. 删数的话就得让原本不会分裂的数分裂,就删\(a_{i}==a_{i-1}\)的\(a_i\)即可(杀多了也没关系,正如1所说只用统计子数组个数)
删完后跑一遍就行了(边跑边删也行)
#include<bits/stdc++.h>
using namespace std;
int T,n,m;
map<int,int>Map;
int main()
{
    scanf("%d",&T);
    while(T--) {
        int cnt=0;
        int x;
        scanf("%d\n",&n);
        for(int i=1;i<=n;i++) {
            scanf("%d",&x);
            Map[x]=1;
        }
        int last=-1;
        for(auto var : Map) {
            if(var.first>last+1) cnt++,last=var.first;
        }
        printf("%d\n",cnt);
        Map.clear();
    }
    return 0;
}
D. Come a Little Closer
\(10^9 \times10^9\)的一个矩阵里有n个怪物分布在不同的位置,我们可以操纵一个怪物到一个指定位置(每个位置最多一个怪物),然后用一个矩形围住所有怪物(边可以重合)问矩形面积最小为多少

其实思路很简单,我们只要找到在四个方向上分布的最远的怪物,然后找到在四个方向上分布的第二远的怪物,由此判断把哪个方向上最远的怪物移到后方最优即可
但这个思路会有两个bug:
1. 移动到后方后,可能在后方的每一个位置都有怪物,此时要特判一下,要在一个方向上延伸一格
2. 可能会出现某个点既是上方的最远点,又是右方的最远点(即处在边角上),这时也要特判,而且要在比较四个方向的时候就要特判,而且这也会影响到bug1,所以那里也要再特判一下
要想到到这两个bug不难,但调起来很是花时间(毕竟此时已经按照之前的思路写了一大坨了)
修正两个bug不难,但byd就是写的依托,码力有待提高
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,m;
struct node {
    int i,j;
}a[10],b[10];
signed main()
{
    scanf("%lld",&T);
    while(T--) {
        int x,y;
        scanf("%lld",&n);
        for(int i=1;i<=n;i++) {
            scanf("%lld%lld",&x,&y);
            if(i==1) {
                for(int j=1;j<=9;j++) {
                    a[j].i=x;
                    a[j].j=y;
                }
                b[4].j=2e9;
                b[2].i=0;
                b[6].j=0;
                b[8].i=2e9;
            } else {
                if(a[4].j>y) {
                    b[4].j=a[4].j;
                    b[4].i=a[4].i;
                    a[4].j=y;
                    a[4].i=x;
                } else if(b[4].j>y) {
                    b[4].j=y;
                    b[4].i=x;
                }
                if(a[2].i<x) {
                    b[2].i=a[2].i;
                    b[2].j=a[2].j;
                    a[2].i=x;
                    a[2].j=y;
                } else if(b[2].i<x) {
                    b[2].i=x;
                    b[2].j=y;
                }
                if(a[8].i>x) {
                    b[8].i=a[8].i;
                    b[8].j=a[8].j;
                    a[8].i=x;
                    a[8].j=y;
                } else if(b[8].i>x) {
                    b[8].i=x;
                    b[8].j=y;
                }
                if(a[6].j<y) {
                    b[6].j=a[6].j;
                    b[6].i=a[6].i;
                    a[6].j=y;
                    a[6].i=x;
                } else if(b[6].j<y) {
                    b[6].j=y;
                    b[6].i=x;
                }
            }
        }
        int J=a[6].j-a[4].j+1;
        int I=a[2].i-a[8].i+1;
        int w=b[8].i-a[8].i;
        int s=a[2].i-b[2].i;
        int A=b[4].j-a[4].j;
        int d=a[6].j-b[6].j;
        //printf("w=%lld s=%lld a=%lld d=%lld\n",a[8].i,a[2].i,a[4].j,a[6].j);
        //printf("w=%lld s=%lld a=%lld d=%lld\n",b[8].i,b[2].i,b[4].j,b[6].j);
        //printf("X=%lld Y=%lld\n",I,J);
        //printf("%lld %lld %lld %lld\n",w,s,A,d);
        int maxI=max(w,s);
        int maxJ=max(A,d);
        int flag=0;
        if(maxI*J>=maxJ*I) {
            flag=1;//表示缩小I方向,J不变,I=I-maxI; 
        }
        int maxn=max({w*J,s*J,A*I,d*I});
        //printf("maxn=%lld\n",maxn);
        if(a[6].i==a[2].i && maxn < d*I+s*J-s*d) maxn=d*I+s*J-s*d,flag=2,maxI=s,maxJ=d;
        if(a[6].i==a[8].i && maxn < d*I+w*J-d*w) maxn=d*I+w*J-d*w,flag=2,maxI=w,maxJ=d;
        if(a[8].i==a[4].i && maxn < A*I+w*J-A*w) maxn=A*I+w*J-A*w,flag=2,maxI=w,maxJ=A;
        if(a[4].i==a[2].i && maxn < A*I+s*J-A*s) maxn=A*I+s*J-A*s,flag=2,maxI=s,maxJ=A;
        //printf("maxn=%lld\n",maxn);
        int ans=I*J-maxn;
        if(n==1)ans=1;
        if(ans<n) {
            if(flag==1) ans=min(ans+I-maxI,ans+J);
            else if(flag==2) ans=min(ans+I-maxI,ans+J-maxJ);
            else ans=min(ans+I,ans+J-maxJ);
        }
        printf("%lld\n",ans);
    }
    return 0;
}
/*
5
1 2
4 2
4 3
3 1
3 2
w=1 s=4 a=1 d=3
X=4 Y=3
0 0 1 1
ans=8
01000
00000
11000
01100
*/
E. Kirei Attacks the Estate
一棵树的根节点为1,定义一个节点的危险度为\(a_i−a_{pi}+a_{ppi}−…\)(\(a_{pi}\)是\(a_i\)的父亲,以此类推),注意:向上累计任意个父节点,不必是全部,可以为0
求每个节点的最大的危险度

模拟的话就是每个节点都向根节点进行一次遍历,枚举得到最大值\(O(n^2)\)
更进一步可以想到,可以从根节点进行遍历,中途记录遍历到该节点时的危险度,并由此推断子节点的危险度
而每个节点会根据深度的奇偶性分为两部分,一部分先加后减,一部分先减后加;
可以想到dp是非常适合用来求最大危险度的:上一个节点可以选/不选,然后取最大值赋给当前节点
为了解决当前深度的奇偶性的问题,每个节点有两个dp值,一个是给奇数深度用的dp,一个是给偶数深度用的dp
(以当前为odd节点为例)
dp_odd[X]=max(dp_odd[pa]+a[i], a[i]);
而当前节点还要记录另一奇偶性的
dp_even[X]=max(dp_even[pa]-a[i], -a[i])
dfs跑一遍即可
(题面说是绮礼入侵爱因兹贝伦被切嗣赶跑,月丑屋檐了)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n;
struct node {
    int dept;
    int val;
    int dp_odd;
    int dp_even;
    vector<int>v;
}edge[200100];
void dfs(int X,int dept,int pa) {
    edge[X].dept=dept;
    if(dept%2==0) {
        edge[X].dp_odd=max(edge[pa].dp_odd-edge[X].val,-1*edge[X].val);
        edge[X].dp_even=max(edge[pa].dp_even+edge[X].val,edge[X].val);
    } else {
        edge[X].dp_odd=max(edge[pa].dp_odd+edge[X].val,edge[X].val);
        edge[X].dp_even=max(edge[pa].dp_even-edge[X].val,-1*edge[X].val);
    }
    //printf("%d odd=%d even=%d\n",dept,edge[X].dp_odd,edge[X].dp_even);
    if(edge[X].v.size()==1&&dept!=1) {
        return;
    }
    for(auto var : edge[X].v) {
        if(var==pa) continue;
        dfs(var, dept+1, X);
    }
}
signed main()
{
    scanf("%lld",&T);
    while(T--) {
        int u,v;
        scanf("%lld",&n);
        for(int i=1;i<=n;i++) {
            scanf("%lld",&edge[i].val);
        }
        for(int i=1;i<=n-1;i++) {
            scanf("%lld%lld",&u,&v);
            edge[u].v.emplace_back(v);
            edge[v].v.emplace_back(u);
        }
        dfs(1,1,0);
        for(int i=1;i<=n;i++) {
            printf("%lld ", edge[i].dept%2 ? edge[i].dp_odd : edge[i].dp_even);
            edge[i].dp_even=edge[i].dp_odd=edge[i].val=edge[i].dept=0;
            edge[i].v.clear();
        }
        printf("\n");
    }
    return 0;
}
F. Small Operations
给定一个整数 x 和一个整数 k,可以进行任意次操作,每次操作可以令x=x*a,或者让x=x/a(但x必须被a整除),其中a为不大于k的任意整数
给出x, y, k,问:将x变成y最少用多少次操作,若不能变成y输出-1
可以想到在不考虑k的情况下,令x变成y一般是两步:(如果x和y是倍数关系则一步即可)
1. x=x/x
2. x=x*y
而显然这很容易超出k的范围,不过注意到还可以
1. x=x/gcd(x, y)
2. x=x*(y/gcd(x,y))
两个gcd可以相互抵消掉,而这样可以最大程度的降低对k的需求
不过显然x/gcd或者y/gcd还是可能比k大,这时ans就比2大了,以x/gcd为例,我们可以把x/gcd拆分成两部分,比如x1*x2=x/gcd,如果最优的x1或x2还是比k大就继续拆分,重复此流程。(最优的指枚举所有x1, x2并递归拆分后操作数最小的x1, x2)
但要注意直接硬拆复杂度肯定是很高的,而拆分一个数,本质上是对这个数生效,所以拆完一个数后记忆化这个数的操作数,可以很好的降低复杂度
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,a,b,k;
int dp[1000100];//i最少能表示成几份<=k的因数之积 
int flag[1000100];
int Fflag;
int DP(int X) {
    //if(k==13)printf("%lld %lld\n",X,dp[X]);
    if(dp[X]) return dp[X];
    else if(flag[X]||Fflag) {
        Fflag=1;
        return -1e15;
    } else if(X<=k) {
        flag[X]=1;
        dp[X]=1;
        return dp[X];
    } else {
        flag[X]=1;
        int minn=1e15;
        for(int i=2;i*i<=X;i++) {
            if(X%i==0) {
                if(X/i<=k&&i<=k) {
                    dp[X]=2;
                    return dp[X];
                }
                int cnt=0;
                cnt+=DP(X/i);
                cnt+=DP(i);
                minn=min(minn,cnt);
            }
        }
        if(minn>1e14||minn==0) {
            Fflag=1;
            return -1e15;
        }
        dp[X]=minn;
        return dp[X];
    }
}
signed main()
{
    scanf("%lld",&T);
    while(T--) {
        int cnt=0;
        scanf("%lld%lld%lld",&a,&b,&k);
        //printf("ANS=");
        int GCD=__gcd(a,b);
        if(a!=GCD) cnt+=DP(a/GCD);
        if(b!=GCD) cnt+=DP(b/GCD);
        if(Fflag) printf("-1\n");
        else printf("%lld\n",cnt);
        for(int i=0;i<=max(a/GCD,b/GCD);i++) {
            Fflag=0;
            dp[i]=0;
            flag[i]=0;
        }
    }
    return 0;
}
G. Build an Array
给一个空数组,每次操作可以向数组的左/右边添加一个元素,然后如果有相同的相邻的元素就相加后合并,还有就继续相加合并
(如124514在左边插入一个1,就变成了8514)
先给定一个数组,问是否可能通过k次操作使得空数组变成该数组(由题可知数组a各元素互不相同)
可以想到
1. 如果k<n,一定不可以,k次插入无法获得n个元素(但题目限定了不会出现这种情况,我一开始也没有考虑到)
2. 如果k==n,一定可以,直接一个个插入即可
3. 如果k>n,可能可以,因为可以通过连续插入两个相同元素使操作数+1;进一步想到,如果一个数是\(x\times 2^y\),那么这一个数最多可以插入y次
接下来开始讨论第三种情况
需要注意的是,并不是每个数都可以插入y次,比如4 2 5,如果先插入2,那么在插入4时就不能1+1+1+1,也不能2+2,只能插入4
我们可以将这种情况总结为:如果 \(a_i\%a_{上一个}==0\),则\(cnt+=a_i/(a_{上一个}\times2)\)
这也是我一开始预设的判断条件,但实际上这个判断条件不正确,比如5 1 5,并不影响,而4 1 4又会影响,所以又错误的想到对1进行特判
上一种会错杀10 2,正确的判断条件应该是如果 \(a_i\%a_{上一个}==0\&\&a_i/a_{上一个}==2^y\),则...(\(2^y\)指商为2的幂次)
What's more, 我们的cnt的更新也是错的,一开始想的是4 16只能把16拆成8+8和16,但实际上还可以拆成8+1*8、8+2*4、8+4+4,正确的更新应该是\(cnt+=a_i-a_{上一个}+1\)
然后如何判断插入的先后顺序,可以注意到会影响cnt计算的插入顺序,只取决于第一个插入的元素(插入第一个后,两侧的元素都可以找到自己的\(a_{上一个}\)了)但枚举第一个元素会\(O(n^2)\),所以可以想到比较常用的方法,用pre和suf记录cnt的前缀和后缀,最后枚举一遍第一个插入的元素就能\(O(n)\)了
最后,我们做这些是在求什么东西(没错我写完了才发现我没想明白自己在求的东西是否和答案有充要关系),我们在求数组a的最大的可插入数maxn,那maxn大于等于k就YES了吗?事实上是的,但一开始推的时候没有推明白,在maxn>=k时: 1. maxn==n >= k,YES
2. maxn>=k>n,maxn比n大,存在\(a_i\)被拆成了若干份,而我们一定可以令插入数从maxn到n任意变化, (如4可以拆成1+1+1+1变成4份,也可以拆成 2+1+1变成3份,最少变成一份),所以我们可以令插入数=k,YES
3. maxn<n,不可能存在
所以maxn>=k为YES的充要条件
以及注意前缀和、后缀和可能爆int
总结,做出该题需要想清楚的几个点: 0.(关键)为什么maxn大于k就行了 1. 为什么要构造pre和suf 2.(关键)pre和suf更新的式子 3.(关键)pre和suf两种更新方式的判断标准 4. 为什么会爆int
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,m;
int a[200100];
int b[200100];
int pre[200100];
int suf[200100];
int check(int X) {
    while(X%2==0) {
        X/=2;
    }
    if(X==1) return 1;
    return 0;
}
signed main()
{
    scanf("%lld",&T);
    while(T--) {
        int x;
        scanf("%lld%lld",&n,&m);
        for(int i=1;i<=n;i++) {
            scanf("%lld",&a[i]);
            b[i]=1;
            while(a[i]%(b[i]*2)==0) {
                b[i]*=2;
            }
        }
        a[0]=2e9;
        pre[0]=0;
        a[n+1]=2e9;
        suf[n+1]=0;
        /*printf("b[i]= ");
        for(int i=1;i<=n;i++) {
            printf("%d ",b[i]);
        }
        printf("\n");/**/
        for(int i=1;i<=n;i++) {
            pre[i]=pre[i-1];
            if(a[i]%a[i+1]==0&&check(a[i]/a[i+1])) {
            /*
            旧的判断标准:
             如果mod为0:
                如果a[i+1]==1:
                    如果a[i]==b[i] true
                    else false
                else true
            ------------
            但是是错的!!
            例如:10 和 2 
            新判断标准:
            如果mod为0:
                如果a[i]/a[i+1]为2的幂 
            */
                //pre[i]+=b[i]/(b[i+1]*2)+b[i+1]*2;
                pre[i]+=b[i]-b[i+1]*2+1;
            } else {
                pre[i]+=b[i];
            }
        }
        for(int i=n;i>=1;i--) {
            suf[i]=suf[i+1];
            if(a[i]%a[i-1]==0&&check(a[i]/a[i-1])) {
                //suf[i]+=b[i]/(b[i-1]*2)+b[i-1]*2;
                suf[i]+=b[i]-b[i-1]*2+1;
            } else {
                suf[i]+=b[i];
            }
        }
        int maxn=0;
        for(int i=1;i<=n;i++) {
            //printf("---%d %d %d\n",pre[i-1],b[i],suf[i+1]);
            maxn=max(maxn,pre[i-1]+suf[i+1]+b[i]);
        }
        //printf("maxn=%d\n",maxn);
        if(maxn>=m) {
            printf("YES\n");
        } else {
            printf("NO\n");
        }
    }
    return 0;
}
/*
做出该题需要想清楚的几个点:
    0.(关键)为什么大于就行了
    1. 为什么要构造pre和suf
    2.(关键)pre和suf更新的式子
    3.(关键)pre和suf两种更新方式的判断标准
    4. 为什么会爆int 
*/
                    
                
                
            
        
浙公网安备 33010602011771号