彬彬的 GDKOI2023 游记

本文记录了作者参与编程竞赛的过程,包括题目解析、策略制定以及赛后反思。涉及动态规划、二分查找、树形结构等算法应用。
摘要由CSDN通过智能技术生成

难得和同学去广州,一定要好好打比赛。

day-5 (3.5周日)

早上被闹钟叫醒后习惯按掉了,幸好我哥开高速开到120km/h,8点20多卡点到校。

又双叒打模拟赛。

T1
奶牛Bessie有N块巧克力,从左往右排成一行,编号从0到N-1。第i块巧克力的颜色是color[i]。我们定义一个参数MaxLen,它表示:具有相同颜色的连续一段巧克力的最大长度。例如:有10块巧克力,颜色分别是: ADDDABBAAB,那么MaxLen=3,因为有3块颜色是D的巧克力,而且这3块巧克力的位置是连续的。为了使得MaxLen最大,Bessie可以交换相邻两块巧克力的位置,但是Bessie 总共交换的次数不能超过给定的值swap。那么MaxLen的最大值是多少?
n<=50

模拟题,直接暴力挪动巧克力就OK。当然,赛时药磕多了,满脑子 o ( n 2 ) o(n^2) o(n2)做法。

枚举一个巧克力作为中心,双指针左右遍历,遇到一个相同的巧克力就吃掉加上这块巧克力到中心的距离(先移动离中心近的,双指针遍历过程中先遇到的点一定比后遇到的点近),保证最后的和不大于swap即可。

但有个小问题,就是巧克力是一个区间而不是一个点,所以我们没移动一个巧克力到这个区间两端时要保证左右指针到这个区间距离相等,但是一边移动了一块,那一边的距离就会减少1。so我们要把对面的指针回拨1。

预计:100pts

T2

放了道CF404D

想了一下,一个 ? ? ?可以给答案 ∗ 2 *2 2,但与 1 1 1串一串的 ? ? ? ? 1 ? 1 ? 1 ? 1 ? ?1?1?1?1? ?1?1?1?1?之类)合在一起只可以给答案 ∗ 2 *2 2,然后只要用已知的 2 2 2 0 0 0 ∗ * 把这个扫雷做到不可以在推出1个 ? ? ?,在统计这两个情况的个数就做完了求幂就OK。

心态爆炸,调了2h多,最后大样例AC时都没发现……

预计:100pts

T4
有N个数,随机选择一段区间,如果这段区间的所有数的平均值在[l, r]中,则你比较厉害。求你比较厉害的概率。

剩下50分钟,想着要给T3留点家底,不能太厉害了,拿了30pts暴力走人。

预计:30pts

ps:正解请移步至下方T4正解。

T3
Mushroom手中有n个数排成一排,现在Mushroom想取一个连续的子序列,使得这个子序列满足:最多只改变一个数,使得这个连续的子序列是严格上升子序列,Mushroom想知道这个序列的最长长度是多少。

错位的人口普查题,预处理出连续上升的子序列段数,再判断一下相邻的子序列修改头尾的一个数可不可以接在一起求最大值。

但时间就剩20min了,彬彬(我)太逊了,本着不会分数最大化的原则,写了暴力……

预计:60pts

下午……

中午好好休息~~体验博彩旅游业(斗地主)~~的我难免又双叒迟到。

抱着视死如归的心情看了下成绩:

最底下没有,看来不会太差。

中间也没?不会没交吧?

然后,我 330 p t s / r a n k 1 330pts/rank 1 330pts/rank1(T1 100pts;T2 100pts;T3 100pts;T4 30pts)???

平复完心脏病开始补题。

发现T3数据没卡住 O ( n 2 ) O(n^2) O(n2)做法,让我想起了CCF。

T4正解
有点长

s u m [ ] sum[] sum[]为前缀和,则 j + 1 j+1 j+1 i i i的平均值为

s [ i ] − s [ j ] i − j \frac{s[i]-s[j]}{i-j} ijs[i]s[j]

满足

l ≤ s [ i ] − s [ j ] i − j ≤ r l \leq \frac{s[i]-s[j]}{i-j} \leq r lijs[i]s[j]r

对于后半部分

s [ i ] − s [ j ] ≤ r ∗ i − r ∗ j {s[i]-s[j]} \leq r*i-r*j s[i]s[j]rirj

s [ i ] − r ∗ i ≤ s [ j ] − r ∗ j s[i]-r*i \leq s[j]-r*j s[i]ris[j]rj

给原数组中每一项减去 r r r,求前缀和 s [ ] s[] s[]

s [ i ] ≤ s [ j ] ( i > j ) s[i] \leq s[j](i>j) s[i]s[j](i>j)

这是啥,逆序对!

然后求逆序对个数就是小于等于 r r r的区间个数(注意,这里不包括s[i]-s[0],也就是说,当s[i]小于等于0时也是小于等于 r r r的,要记得加上)。

对于前半边,我们求 < l \lt l <l的区间个数,用 ≤ r \leq r r的区间个数减一下就成。

code

#include <bits/stdc++.h>
using namespace std;

#define ll long long

const ll maxn = 5e5 + 5;

ll n, l, r;
ll sum[maxn], a[maxn], tree[maxn], tmp[maxn];

map<ll, ll> mp;

ll lowbit(ll x) { return x & (-x); }

void updata(ll x) {
    while (x <= maxn - 5) {
        tree[x]++;
        x += lowbit(x);
    }
}
ll getsum(ll x) {
    ll sum = 0;
    while (x) {
        sum += tree[x];
        x -= lowbit(x);
    }
    return sum;
}

void lsh() {
    mp.clear();
    memset(tmp, 0, sizeof(tmp));
    for (ll i = 1; i <= n; i++) tmp[i] = sum[i];
    sort(tmp + 1, tmp + n + 1);
    ll cnt = 0;
    for (ll i = 1; i <= n; i++) {
        if (mp[tmp[i]])
            continue;
        mp[tmp[i]] = ++cnt;
    }
    for (ll i = 1; i <= n; i++) sum[i] = mp[sum[i]];
}
ll get(ll x) {
    ll ans = 0;
    memset(sum, 0, sizeof(sum));
    for (ll i = 1; i <= n; i++) {
        sum[i] = sum[i - 1] + a[i] - x;
        if (sum[i] <= 0 && x == r)
            ans++;
        else if (sum[i] < 0 && x == l)
            ans++;
    }

    lsh();

    memset(tree, 0, sizeof(tree));
    for (ll i = n; i >= 1; i--) {
        ans += getsum(sum[i]);
        updata(sum[i] + (x == l));
    }
    return ans;
}

int main() {
    scanf("%lld%lld%lld", &n, &l, &r);

    for (ll i = 1; i <= n; i++) scanf("%lld", &a[i]);

    ll fz = get(r) - get(l), fm = (n + 1) * n / 2, gc = __gcd(fz, fm);
    fz /= gc;
    fm /= gc;
    if (fm != 1)
        printf("%lld/%lld", fz, fm);
    else
        printf("%lld", fz);
}
生活片段 晚上打电话祝我哥(开快车那个)生日快乐,我哥调侃我没有npy,尽管他也没有。

day-4 (3.6 周一)

早上来机房,发现——怎么也飞不出,模拟赛的世界。

教练甚至放心的给我们做提高组(保持微笑呵呵呵)。

T1
你有两个长度分别为 n, m的01 串 S, T。
有Q次询问,每次询问给出 l1, r1, l2, r2 ,其中 r1 − l1 + 1 =r2 − l2 + 1,令 a =S[l1 ... r1], b = T[l2 ... r2],你需要求出 ai ≠ bi的位置个数对 2 取模的结果。

n <=100000

想了30分钟,发现两个位置相同那么他们相加模2肯定为0,不同模2肯定为1,我们统计不同的个数模2就是答案。又因为不同两个位置的相加和肯定为1,而相同的相加不是2就是0,也就是对答案没有影响,所以求出S中 l 1... r 1 l1...r1 l1...r1的区间和加上T中 l 2... r 2 l2...r2 l2...r2的区间和相加模2就是答案。

隔壁的wangziye推翻正确结论后开始diss我,然后他就又推翻自己了。(写博客时wangziye想要以陈述事实罪迫害关心我)

预计:100pts

T2
在左下角是 (𝟎, 𝟎),右上角是 (W, H)的网格上,有 (W + 1) × (H + 1) 个格点。
现在要在格点上找 N个不同的点,使得这些点在一条直线上。并且在这条直线上,相邻点之间的距离不小于𝐃。求方案数模 1e9 + 7。

右边wangziye打代码的速度跟足力健穿在手上一样,于是吓得我果断跑路。

T3

俗话说得好:考试就是赌博,赌赢你吃饭,赌输同学老师吃席。把最后的时间赌在了T3。

牛牛有一棵 n 个点的有根树,根为 1。
我们称一个长度为 m 的序列 a 是好的,当且仅当:
• ∀𝑖 ∈ (1, 𝑚],ai为 ai−1的祖先或 ai−1是 ai的祖先;
• ∀1 ≤ 𝑖 < 𝑗 ≤ 𝑚, 𝑎𝑖 ≠ 𝑎𝑗 。
你需要帮助牛牛求出最长的序列长度。

一开始题读错了,把祖先就认成父亲(就是你查族谱发现祖先只有你爸,你爷爷之类的不叫祖先),接着好好反思了一下自己的语文老师(突然想到:嗯,又没交作业),终于看懂了。

然后发现每一个节点可以在自己的儿子里选2个合为一体(儿子也可以是和自己儿子合为一体),找出最大的一体就OK。

大样例挂了,发现是在自己的子树内找最长的2个合为一体……

预计:40pts

T4

不会太难就不放了吧……

分析数据规模发现输出0有5分……

预计:5pts

看成绩 145 p t s / r a n k 2 145pts/rank 2 145pts/rank2,把把脉,嗯阳寿又透支了。

还没讲题调了一下2个月都没调A的KM模板,然后发现初始化没写……

调完AC的我:

开始讲题……

T2正解

肝快废了,我歇会儿(槽:这方格取命)。

1中方案数的计算:

看作除去结束点外每1个点后面要和k-1个点看作一个点

总点数变化为
g − 1 − ( n − 1 ) ∗ ( k − 1 ) g-1-(n-1)*(k-1) g1(n1)(k1)
减去的1为必选的结束点。

从中选出 n − 2 n-2 n2个点(减去必选点),排列为:

( g − 1 − ( n − 1 ) ∗ ( k − 1 ) n − 2 ) {g-1-(n-1)*(k-1) \choose n-2} (n2g1(n1)(k1))

变形后和题解是一样滴。

2的转变:

枚举结束点,开始点固定为 ( 0 , 0 ) (0,0) (0,0),然后平移到矩阵的不同位置就会得到不同方案。答案最后要乘2,因为我们考虑的情况只有从左下斜着向右上的方案,对于从右上斜着向左下的情况对称转移乘2。

#include<bits/stdc++.h>
using namespace std;

#define ll long long

ll mod=1e9+7;

ll n,w,h,d;
ll c[505][505];

double cacl(ll x,ll y){return sqrt(x*x*1.0+y*y);}

ll sg553(ll x,ll y)
{
    if(!x&&!y) return 0;
    ll g=__gcd(x,y);
    ll k=(ll)(ceil(d/cacl(x/g,y/g)));
    if(k*(n-1)>g) return 0;
    ll ans=c[g-1-(n-3)*(k-1)-2*(k-1)][n-2];
    if(x&&y) ans*=2;
    ans%=mod;
    return ans*(w-x+1)*(h-y+1)%mod;
}

int main()
{
    ll t;
    scanf("%lld",&t);
    for(ll i=0;i<=500;i++)
    {
        c[i][0]=c[i][i]=1;
        for(ll j=1;j<i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
    }

    while(t--)
    {
        scanf("%lld%lld%lld%lld",&n,&w,&h,&d);
        if(n==1)
        {
            printf("%lld\n",(w+1)*(h+1));
            continue;
        }
        ll ans=0;
        for(ll i=0;i<=w;i++)
        {
            for(ll j=0;j<=h;j++) ans=(ans+sg553(i,j))%mod;
        }
        printf("%lld\n",ans);
    }
}

注释就不写了。

T3正解
还是长

接上文,我们可以dfs序建线段树维护字树内接节点数最多的节点(同一颗子树编号是连续的),一次自己子树中选两个后代更新自己点,同时要把选中的后代接的节点值改为0,防止被反复被剥削做贡献。

#include<bits/stdc++.h>
using namespace std;

const int maxn=2e5+5;

struct node
{
    int to,nxt;
}edge[maxn];
struct node1
{
    int l,r,mpos,mx;
}tree[maxn*3];


int n,tot,ans;
int head[maxn],p[maxn],fp[maxn],ed[maxn],f[maxn];

bool vis[maxn];

void mc()//多组样例清0
{
    n=tot=ans=0;
    memset(vis,0,sizeof(vis));
    memset(head,0,sizeof(head));
    memset(p,0,sizeof(p));
    memset(fp,0,sizeof(fp));
    memset(ed,0,sizeof(ed));
    memset(f,0,sizeof(f));
    memset(edge,0,sizeof(edge));
    memset(tree,0,sizeof(tree));
}

void add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].nxt=head[x];
    head[x]=tot;
}
void dfs(int x)//dfs序
{
    vis[x]=true;
    tot++;
    p[x]=tot;
    fp[tot]=x;
    ed[x]=tot;
    for(int i=head[x];i;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(vis[v]) continue;
        dfs(v);
        ed[x]=max(ed[x],ed[v]);
    }
}
void build(int p,int l,int r)
{
    int mid=(l+r)/2;
    tree[p].l=l;
    tree[p].r=r;
    if(l==r)
    {
        tree[p].mpos=l;
        return;
    }
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
}

void updata(int p,int x,int y)//一个原树中点对应一个线段树中叶子节点,单点修改
{
    if(tree[p].l>x||tree[p].r<x) return;
    if(tree[p].l==x&&tree[p].r==x)
    {
        tree[p].mx=y;
        return;
    }
    updata(p*2,x,y);
    updata(p*2+1,x,y);
    if(tree[p*2].mx>tree[p*2+1].mx)
    {
        tree[p].mx=tree[p*2].mx;
        tree[p].mpos=tree[p*2].mpos;
    }
    else
    {
        tree[p].mx=tree[p*2+1].mx;
        tree[p].mpos=tree[p*2+1].mpos;
    }
}
int query(int p,int l,int r)//返回最大值在树中的位置,方便清0
{
    if(r<tree[p].l||l>tree[p].r) return 0;
    if(l<=tree[p].l&&r>=tree[p].r) return tree[p].mpos;
    int lm=query(p*2,l,r),rm=query(p*2+1,l,r);
    if(f[fp[lm]]>f[fp[rm]]) return lm;
    return rm;
}
void find(int x)//更新一个节点可以接的最大值,叶子节点为1
{
    vis[x]=true;
    for(int i=head[x];i;i=edge[i].nxt) if(!vis[edge[i].to]) find(edge[i].to);

    int sum=0,mx=0;
    for(int i=1;i<=2;i++)
    {
        mx=fp[query(1,p[x],ed[x])];
        if(mx==0) continue;
        sum+=f[mx];
        f[mx]=0;
        updata(1,p[mx],f[mx]);//清0
    }
    f[x]=1+sum;
    ans=max(ans,f[x]);
    updata(1,p[x],f[x]);
}

int main()
{
    int t;
    scanf("%d",&t);
    while (t--)
    {
        mc();
        scanf("%d",&n);
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            add(u,v);
            add(v,u);
        }

        tot=0;
        dfs(1);
        build(1,1,n);

        memset(vis,0,sizeof(vis));
        find(1);
        printf("%d\n",ans);
    }
}

细节看代码吧,有点抽象。

T4正解

不会

day-3 (3.7周二)

山外青山楼外楼,比赛暴力更上头。

T1
n名选手正在参加一个神秘比赛,比赛按照名次发放奖金。比赛中有两道程序设计题目,每道题目的满分为 10000 分,在比赛结束时,可以从榜单上看到每位选手的得分情况,第i位选手的两道题目的得分分别为ai,1, ai,2 (1 ≤ i ≤ n)。虽然比赛已经结束,但排名却并非最终确定,因为这个比赛为每道题目都设置了最终测
试,如果没有通过最终测试,得分将会归 0。具体来说,如果第i位选手通过了第j道题目的最终测试,则他该题的得分为ai,j,否则,他该题的得分为 0。
通过占卜大法,你获知每个人每道题目都独立地有50%的概率能通过最终测试,有50%不能通过。现在你想要知道,每一位选手的期望的排名是多少。
排名的定义为,假设有r名选手最终的总分数高于第i名选手最终的总分数,则第i 名 选 手 的 排 名 为 r + 1 , 比 如 4 位 选 手 的 最 终 总 分 数 分 别 为5000,15000,20000,15000,则他们的排名分别是 4,2,1,2。

比赛时根本没有想法,数学这种东西就是来劝退我的。

预计:30pts(暴力)

T2
牛妹在无穷多个平行宇宙中跳跃,每个宇宙都有一个整数编号,且对于每个整数n ∈ Z,都有唯一一个宇宙的编号是n。
牛妹掌握了三种空间跳跃技术,分别是:
1. 如果当前在编号为n的宇宙,且min(|𝑚|, |𝑚 − 𝑑|) ≤ 𝑙,则她可以跳跃到编号为n − d的宇宙,其中d 和 l都是给定的正整数。
2. 如果当前在编号为n的宇宙,则她可以跳跃到编号为2n的宇宙;
3. 如果当前在编号为n的宇宙,且n ≡ 1(mod3),(即 n =. . . , −5, −2,1,4, . ..时)则她可以跳跃到编号为(n−1)/3 的宇宙;
牛妹一开始在编号为1的宇宙中,她可以随意的使用这三种空间跳跃技术在宇宙中跳跃。她有q 个想去的目的地n1, n2, . . . , nq,她想知道对于每个i (1 ≤ i ≤ q),她需要怎么从编号为1的宇宙跳跃到编号为ni的宇宙。由于每次跳跃需要巨大的能量,所以每次牛妹只能跳跃不超过1500 次。

比赛时想到吧一个宇宙看作一个点,不同的跳跃方法看作边。但实际上不方便写,而且也过不了多少。

预计:0pts

T3

题面拷贝有问题

日常思念语文老师。

赛时思路太乱了,结果最后还是暴力。

预计:40pts

T4

题面太长就不放了,写了一个自己都证明有问题的dp,已经没有想法了。

预计:rp

中午补了下数学作业,不得不说,数学这毒品少用一点就会发现你和智障没有区别。

下午成绩出了,同年级 110 p t s / r a n k 4 110pts / rank 4 110pts/rank4(虽然就6个)。不过好像挺多大佬把第1题A了。

T1正解
难懂

个人理解吧,看不看得懂看运气,觉得不是你的问题。

第j个人和第i个人比较会产生16种情况,当j的分数比i大时贡献1,比i小时贡献0,也就是j对i期望(应该是期望吧?)排名影响为总贡献除以16,把所有的j对i的期望加起来,在加一(起码有1名),就是答案。

O ( n ) O(n) O(n)的代码实现把每一个的4种情况放到一个数组里排序求排名就OK,注意减去自己一种情况比自己一种情况大的计算。

T2正解
离谱

反过来是角谷猜想,负数的情况把绝对值变到-100以内在加d使其大于等于1,用角谷猜想求到1的路径,记录一下。

真的有人做题时想这些东西吗?

T3正解

我肝长出来再说……

day-2 (3.8 周三)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值