BCPC2021预赛

补题链接
我无颜……

[F]

分析:

关于“排名”没转化过来。
实际上排名 = 小于等于自己的数的个数。那么可以针对每一个比自己小的数计算,考虑它们各自出现在多少个区间中,位置在自己左边和右边的分开计算。再加上自己的答案。
于是用树状数组就能做了。时间复杂度 $ O(nlogn) $ 。

代码如下
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
int const N=5e5+5;
int n,a[N];
ll ans[N],tl[N<<1],tr[N<<1];
ll ql(int x){ll ret=0; while(x){ret+=tl[x]; x-=(x&(-x));} return ret;}
void addl(int x,int v){while(x<=n){tl[x]+=v; x+=(x&-x);}}
ll qr(int x){ll ret=0; while(x){ret+=tr[x]; x-=(x&(-x));} return ret;}
void addr(int x,int v){while(x<=n){tr[x]+=v; x+=(x&-x);}}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        ans[i]=(ll)i*(n-i+1);
        ans[i]+=ql(a[i])*(n-i+1);
        addl(a[i],i);
    }
    for(int i=n;i;i--)
    {
        ans[i]+=qr(a[i])*i;
        addr(a[i],n-i+1);
    }
    for(int i=1;i<=n;i++)
        printf("%lld\n",ans[i]);
    return 0;
}

[G]

分析:

$ 2*10^5 $ 是可以考虑 $ O( n \sqrt{n} ) $ 的做法的。此题就可以按 $ |T_i| $ 是否大于 $ \sqrt{ |S| } $ 分开做。
$ |T_i| \leq \sqrt{ |S| } $ 的,可以预处理出 $ f[len][i][c] $ 表示 $ mod(len) $ 下 $ i $ 位置上字符 $ c $ 在 $ S $ 中出现的次数。预处理的复杂度是 $ O( n \sqrt{n} ) $ ,每个这样的 $ T_i $ 计算答案的复杂度是 $ O( |T_i| ) $ ,即 $ O( \sqrt{n} ) $ 。
$ |T_i| \geq \sqrt{ |S| } $ 的,直接做即可。每个这样的 $ T_i $ 计算答案的复杂度是 $ O(n) $ ,但是这样的串不超过 $ \sqrt{n} $ 个。
然后总的复杂度是 $ O( n \sqrt{n} ) $ 。这种套路掌握得还是不熟……

代码如下
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int const N=2e5+5,M=505;
int n,m,ans[N],f[M][M][30];
char s[N],t[N];
void Init()
{
    m=500;
    for(int i=1;i<=n;i++)
    {
        int c=s[i]-'a';
        for(int j=1;j<=m;j++) f[j][(i-1)%j][c]++;
    }
}
int main()
{
    scanf("%s",s+1); n=strlen(s+1);
    Init();
    int q; scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {   
        scanf("%s",t+1); int len=strlen(t+1);
        int ans=0;
        if(len<=m)
        {
            for(int j=1;j<=len;j++)
                ans+=f[len][j-1][t[j]-'a'];
        }
        else
        {
            int k=1;
            for(int i=1;i<=n;i++)
            {
                ans+=(s[i]==t[k]); k++;
                if(k>len)k=1;
            }
        }
        printf("%d\n",n-ans);
    }
    return 0;
}

[H]

分析:

把能否杀死对方转化成两方各自参数(攻击*血量)的比较,就可以确定要选定的随从是哪个(攻击*血量最大或次大的才有能力杀死其他所有人);
选定的随从杀死其他所有人的过程中受到的伤害是固定的,其中还会有个最大的伤害。根据这个就能判断能否达成题目要求了。
注意判断攻击等于0的情况。
然而一直WA在第45个点,不知为啥。先放在这吧……

代码如下
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
int const N=1e5+5;
struct Nd{
    ll a,h;
}a[N];
int n;
bool cmp(Nd x,Nd y){return (x.a*x.h==y.a*y.h) ? x.a>y.a : x.a*x.h>y.a*y.h;}
ll cal(ll h,ll a)
{
    if(a==0)return -1;
    return (h/a)+(h%a!=0);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld%lld",&a[i].a,&a[i].h);
    sort(a+1,a+n+1,cmp);
    if(n==1){puts("NO"); return 0;}
    // if(n>=3 && (a[1].a*a[1].h==a[2].a*a[2].h) && (a[2].a*a[2].h==a[3].a*a[3].h)){puts("NO"); return 0;}

    if(a[1].a==0 || a[2].a==0){puts("NO"); return 0;}

    ll mx=0,all=0; int p; //最大伤害,总伤害,最大伤害者

    for(int i=2;i<=n;i++)
    {
        ll t=cal(a[i].h,a[1].a);
        all += t*a[i].a;
        if(t*a[i].a>=mx)mx=t*a[i].a,p=i;
    }
    // if(a[1].h-all<=0 && a[1].h-all+mx>0 && cal(a[1].h-all+mx,a[p].a)==cal(a[p].h,a[1].a))
    //     {puts("YES"); return 0;}
    if(a[1].h-all<=0 && a[1].h-all+mx>0)
    {
        for(int i=2;i<=n;i++)
        {
            ll t=cal(a[i].h,a[1].a);
            if(t*a[i].a==mx && cal(a[1].h-all+mx,a[i].a)==t)
                {puts("YES"); return 0;}
        }
    }

    mx=0; all=0; //最大伤害,总伤害
    for(int i=1;i<=n;i++)
    {
        if(i==2)continue;
        ll t=cal(a[i].h,a[2].a);
        all += t*a[i].a;
        if(t*a[i].a>=mx)mx=t*a[i].a,p=i;
    }
    // if(a[2].h-all<=0 && a[2].h-all+mx>0 && cal(a[2].h-all+mx,a[p].a)==cal(a[p].h,a[2].a))
    //     {puts("YES"); return 0;}
    if(a[2].h-all<=0 && a[2].h-all+mx>0)
    {
        for(int i=1;i<=n;i++)
        {
            if(i==2)continue;
            ll t=cal(a[i].h,a[2].a);
            if(t*a[i].a==mx && cal(a[2].h-all+mx,a[i].a)==t)
                {puts("YES"); return 0;}
        }
    }
    puts("NO");
    return 0;
}

[F]

分析:

终态就是左右横跳。想想可以发现只要纵坐标能相差2,左右横跳就没问题。
一开始向上跳可以确定对方在自己上面还是下面;然后借助初始距离大于4,再上跳一次或者下跳两次就能保证纵坐标相差2。

代码如下
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
    int d,d2;
    scanf("%d",&d);
    printf("U\n"); fflush(stdout);
    scanf("%d",&d2);
    if(d2==d){printf("U\n"); fflush(stdout);}
    else
    {
        printf("D\n"); fflush(stdout);
        scanf("%d",&d);
        printf("D\n"); fflush(stdout);
    }
    int f=0;
    while(1)
    {
        scanf("%d",&d);
        if(d==0||d==-1)break;
        printf("%c\n",(f)?'L':'R');
        fflush(stdout);
        f^=1;
    }
    return 0;
}

[J]

分析:

第一步分析答案是怎样组成的。对于移动第 $ i $ 个文件而言,对答案的贡献是:( 之前移动的文件中 $ q_j > q_i $ 的个数 $ ) $ + $ ( $ 之前没动(之后移动)的文件中 $ p_j < p_i $ 的个数 ) $ + $ ( $ 1 $ (从移动后文件跨越到未移动文件) )。这点我也是容易想到的。
但是,最后一次不需要移动回未移动文件区域;然后我的思路就乱了。实际上这里可以先给问题加个限制条件,要求最后一次也要移动回未移动文件区域,得到做法后再考虑去掉条件。
加上限制条件后,可以把答案写出式子来了;然后经过一些化简,可以发现按二位偏序的拓扑序移动是最优的。这样去掉限制条件也变得容易了,此题就做完了。题解讲得很清楚,赞。
心得就是:在基础思路上,我还需要:加限制条件,写式子化简。

posted @ 2021-12-05 21:43  Zinn  阅读(46)  评论(0编辑  收藏  举报