个人自由训练报告-1

Codeforces Round #822 (Div. 2)

贴上赛果
[图片]

赛题分析

A)Select Three Sticks

  • 给定一些棍子的长度,每次操作可以对一根棍子加减1,求最少的操作数,使得存在3根等长的棍子。
    对棍子长度排序后,答案即“隔一根棍子的长度差(这种策略是将两边的棍子长度都变为跟中间的棍子一样)”和“两根一样的棍子与相邻第三根棍子的长度差(将第三根棍子变成与这两根等长)”这两个数的最小值。
    下面只给main函数,代码头部重复的部分放在文档末尾。后面也这么做。
int main(){
        int a[301]={0},d[302]={0};
for(int t=read();t--;){
        int n=read();
        for(int i=1;i<=n;++i)a[i]=read();
        sort(a+1,a+n+1);
        for(int i=1;i<=n;++i)d[i]=a[i]-a[i-1];
        d[0]=d[n+1]=1.05e9;
        int ans=2e9;
        for(int i=1;i<n;++i)
                if(d[i]==0&&d[i+1]==0)
                        ans=min(ans,min(d[i-1],d[i+2]));
        for(int i=1;i<n-1;++i)
                ans=min(ans,a[i+2]-a[i]);
        printf("%d\n",ans);
}
        return 0;
}

B)Bright, Nice, BrilliantBright, Nice, Brilliant

  • n层的金字塔(杨辉三角式排列),每个格子可以插火把,其亮度值即它“头顶上”所有格子加上自己的火把总数。
  • 要求每层亮度一致,且每层第一个格子的亮度之和最大。
    样例已经给出123层时候的金字塔,不免会想向下构造第四层金字塔。
    不难发现,这一层都不插火把的话,边上的格子得到的亮度恰好比中间的格子少一个。于是边上的格子必须插火把而中间的格子必须空着。归纳构造发现往下每一层都是如此。
    至于亮度最大,显然边上的格子的最大亮度值即它的层数;而上述构造方法满足了这一点,于是成为合法答案之一。
int main(){
for(int t=read();t--;){
        int n=read();
        puts("1");
        for(int i=2;i<=n;++i){
                printf("1 ");
                for(int j=2;j<i;++j)printf("0 ");
                puts("1");
        }
}
        return 0;
}

C)Removing Smallest Multiples

  • S是从1到n的正整数集。一次操作会选出k∈[1,n],并移除S中目前存在的k的最小倍数,并花费k的代价。
  • 给定n和T,求使得S变成T的最小代价。
    既然移除k的(目前最小的)倍数花费的代价都是k,那么必然贪心地从最小的k开始枚举其倍数$$
    k,2k,3k,...,j\cdot k$$,直至$$(j+1)\cdot k$$存在于T中或者大于n。然后将它们都以k的代价删除
    注意不要重复删除,用类似埃氏筛的做法,即使用vis[]数组标记S中被删除过的元素即可。
constexpr int N=1e6+2;
char T[N];
bool vis[N];
int main(){
for(int t=read();t--;){
        int n=read();
        scanf("%s",T+1);
        memset(vis,0,n+1);
        long long ans=0;
        for(int i=1;i<=n;++i){
                for(int j=i;j<=n&&(T[j]=='0'/*||vis[i*j]*/);j+=i){
                        if(!vis[j])ans+=i;
                        vis[j]=1;
                }
        }
        printf("%lld\n",ans);
} 
        return 0;
}

D)Slime Escape

  • 有长为n的数组,一开始在下标k并具有$$a_k$$的生命值,可以向左或向右将途径的数吃掉(即加到自己的生命值中并将该数置0)。
  • 任何时候一旦生命值为负即失败。问能否走到0或者n+1的位置。
    有性质:如果能吃遍i到j的格子,那么吃他们的顺序无关紧要(但需要保证可行)。
    既然如此,用最简单的顺序来贪心求解即可。
    向左走出去的做法:一直向左走,直到直接走出去了或者生命值不够了而被迫转向。如果转向后不能增加生命值至需要的点,那么失败。
    向右走同理,两个过程用类似双指针的做法一起进行即可。
constexpr int N=2e5+2;
int a[N];
ll s[N];
int main(){
for(int t=read();t--;){
        int n=read(),k=read();
        /*
        for(int i=1;i<=n;++i)sum[i]=sum[i-1]+(a[i]=read());
        if(sum[k]>=0||sum[n]-sum[k-1]>=0){
                puts("YES"); continue;
        }
        ll maxl=-2e15,maxr=-2e15;
        for(int i=1;i<k;++i)maxl=max(maxl,sum[k-1]-sum[i-1]);
        for(int i=k+1;i<=n;++i)maxr=max(maxr,sum[i]-sum[k]);
        puts(sum[k]+maxr>=0||sum[n]-sum[k-1]+maxl>=0?"YES":"NO");
        */
        for(int i=1;i<=n;++i)a[i]=read();
        ll suml=0,sumr=0,s1=a[k],s2=a[k];
        
        s[k-1]=a[k-1];s[k+1]=a[k+1];s[k]=0;
        for(int i=k-2;i>0;--i)        s[i]=s[i+1]+a[i];
        for(int i=k+2;i<=n;++i)        s[i]=s[i-1]+a[i];
        
        for(int l=k-1,r=k+1;l&&r<=n;){
                bool mov=0;
                for(;s[l]+s2>=0&&l;--l)s1=max(s1,a[k]+s[l]),mov=1;
                for(;s[r]+s1>=0&&r<=n;++r)s2=max(s2,a[k]+s[r]),mov=1;
                //printf("# %d %d %lld %lld\n",l,r,s[l]+s2,s[r]+s1);
                if(!mov){
                        
                        puts("NO"); goto loop;
                }
        }
        puts("YES");
        loop: ;
}
        return 0;
}
  • 这个题卡了一段时间,因为写代码的时候整了点花活,试图将sum[l]直接在下面的循环中统计出来。使用了过多+=运算符致使代码实际产生的效果与设想的不一致,debug了一会。最终老老实实先统计sum[]数组再使用。

补题 E)Rectangular Congruence

  • 给出质数n和一个长为n的数列$$\lbrace b_n\rbrace$$,要求构造一个$$n\times n$$矩阵满足下面性质。题中所有整数∈缩剩余系[0,n)。
  • 对于 $$∀1≤r_1≤r_2≤n,1≤c_1≤c_2≤n$$ 有 $$a_{r1,c1}+a_{r2,c2}≢a_{r1,c2}+a_{r2,c1}(\mod n)$$。
  • \[\]

把最关键的条件扭转一下,变成$$a_{r1,c1}-a_{r1,c2}≢a_{r2,c1}-a_{r2,c2} (\mod n)$$,即要求任意两行(列)的同一列(行)之差不同。
由n是质数,可以先构造$$a_{i,j}=i\cdot j$$符合关键条件,再每一行(列)同时加某数使得满足$$b_i$$的条件。

int main(){
        int a[351][351],b[351];
        int n=read();
        for(int i=1;i<=n;++i)b[i]=read();
        for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
                a[i][j]=i*j%n;
        for(int i=1;i<=n;++i){
                int d=b[i]-a[i][i];
                for(int j=1;j<=n;++j)
                        (a[i][j]+=d+n)%=n;
        }
        for(int i=1;i<=n;++i){
                for(int j=1;j<=n;++j)
                        printf("%d ",a[i][j]);
                puts("");
        }
        return 0;
}

总结

时间历程如上面截图所示。
时间有一部分用在了使码风简洁和减小常数上了,不利于快速过题。
有时候会下意识“优化”一些写法。除非题目卡常 否则不必优化写法。
附:缺省源

//#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
using ll = long long;
inline int read(){
        int x,ch,f=1;while((ch=getchar())<48||57<ch)if(ch=='-')f=-1;
        for(x=ch^48;47<(ch=getchar())&&ch<58;x=x*10+(ch^48));
        return x*f;
}
constexpr int N=2e5+2;
posted @ 2023-02-16 09:58  全球通u1  阅读(26)  评论(0编辑  收藏  举报