2024初三年后集训模拟测试2

前言

没什么好说的,丫的为啥不用开 \(freopen\) ,我全开了 qwq

很好的全部爆零,他题面里明明要求 \(freopen\) ,但是标准输入输出,没这个经验,长个见识吧就当。

去了 \(freopen\) 后:

image

  • \(T1\) 大模拟,不好打但肯调就能出。
  • 剩下的都不是很会,打到 \(T4\) 愣了半天题面就结束比赛了,\(T3\) 该死的捆绑测试,所以没骗来分。

\(\text{NOIP}\) 模拟赛,头一回做这种难度的,能做出来一道知足了。


T1 小P的2048

  • 题意:

    题面挺长的,里面有一些地方不太容易懂,多读几遍理解题意后就没那么难了。

    把图粘过来吧,方便解释:

    image

    1. 平移:

      他这个平移不是平移某一个棋子,是把整个棋盘的所有棋子都平移。

      以向上平移为例,就是把所有棋子向上挪直到全部顶到头,\(eg:\)

      image

    2. 合并:

      不能连续合并,却能同时合并多对,具体是什么意思?

      \(eg:\)

      image

  • 数据范围:

    \(2\leq n\leq 8\) ,随便跑就完事了,不用担心时间复杂度。

  • 解法:

    按照题意大模拟即可。

    原矩阵为 \(a_{i,j}\) ,定义新矩阵为 \(b_{i,j}\) ,用于存移动后的状况,移动完后另 \(a=b\)\(b\) 清空。

    • 处理平移与合并:

      直接看代码理解比较方便:

      1. 上移:

        if(x==0)
        {
            for(int j=1;j<=n;j++)
            {
                int cnt=0;//全部顶到头。
                for(int i=1;i<=n;i++)
                    if(a[i][j]!=0)
                    {
                        if(b[cnt][j]==a[i][j]&&num==0)//合并,不可连续合并。
                            b[cnt][j]+=a[i][j],
                            ans+=b[cnt][j],
                            num++;
                        else 
                        {
                            if(num>0) num=0;
                            b[++cnt][j]=a[i][j];
                        }
                    }
            }
        }
        
      2. 下移:

        else if(x==1)
        {
            for(int j=1;j<=n;j++)
            {
                int cnt=n+1;
                for(int i=n;i>=1;i--)
                    if(a[i][j]!=0)
                    {
                        if(b[cnt][j]==a[i][j]&&num==0)
                            b[cnt][j]+=a[i][j],
                            ans+=b[cnt][j],
                            num++;
                        else 
                        {
                            if(num>0) num=0;
                            b[--cnt][j]=a[i][j];
                        }
                    }
            }
        }
        
      3. 左移:

        else if(x==2)
        {
            for(int i=1;i<=n;i++)
            {
                int cnt=0;
                for(int j=1;j<=n;j++)
                    if(a[i][j]!=0)
                    {
                        if(b[i][cnt]==a[i][j]&&num==0)
                            b[i][cnt]+=a[i][j],
                            ans+=b[i][cnt],
                            num++;
                        else 
                        {
                            if(num>0) num=0;
                            b[i][++cnt]=a[i][j];
                        }
                    }
            }
        }
        
      4. 右移:

        else if(x==3)
        {
            for(int i=1;i<=n;i++)
            {
                int cnt=n+1;
                for(int j=n;j>=1;j--)
                    if(a[i][j]!=0)
                    {
                        if(b[i][cnt]==a[i][j]&&num==0)
                            b[i][cnt]+=a[i][j],
                            ans+=b[i][cnt],
                            num++;
                        else 
                        {
                            if(num>0) num=0;
                            b[i][--cnt]=a[i][j];
                        }
                    }
            }
        }
        
    • 处理新加的点:

      先一遍循环,处理出剩几个空点,按照题面定义 \(s=1+k\bmod sum\)\(k\) 见题面,为输入进来的)。

      再来一遍循环,到第 \(s\) 个为 \(0\)\(a_{i,j}\) 改成 \(v\) 即可( \(v\) 也是输进来的)。

    • 处理何时结束:

      很简单,如果进行过第 \(i\) 遍时 \(a\) 和上一次没有任何变化,那第 \(i\) 次操作就是无用的,也就是共可以进行 \(i-1\) 步,输出,结束程序即可。

      具体只需要在将 \(b\) 复制到 \(a\) 前比较 \(a,b\) 数组即可。

  • 代码如下:

#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
using namespace std;
const int N=10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,m,a[N][N],num,ans,sum,b[N][N];
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n),read(m);
    int x,y,z;
    for(int i=1;i<=2;i++)   
        read(x),read(y),read(z),
        a[x][y]=z;
    for(int t=1;t<=m;t++)
    {
        read(x),read(y),read(z);
        memset(b,0,sizeof(b));
        num=0;
        if(x==0)
        {
            for(int j=1;j<=n;j++)
            {
                int cnt=0;
                for(int i=1;i<=n;i++)
                    if(a[i][j]!=0)
                    {
                        if(b[cnt][j]==a[i][j]&&num==0)
                            b[cnt][j]+=a[i][j],
                            ans+=b[cnt][j],
                            num++;
                        else 
                        {
                            if(num>0) num=0;
                            b[++cnt][j]=a[i][j];
                        }
                    }
            }
        }
        else if(x==1)
        {
            for(int j=1;j<=n;j++)
            {
                int cnt=n+1;
                for(int i=n;i>=1;i--)
                    if(a[i][j]!=0)
                    {
                        if(b[cnt][j]==a[i][j]&&num==0)
                            b[cnt][j]+=a[i][j],
                            ans+=b[cnt][j],
                            num++;
                        else 
                        {
                            if(num>0) num=0;
                            b[--cnt][j]=a[i][j];
                        }
                    }
            }
        }
        else if(x==2)
        {
            for(int i=1;i<=n;i++)
            {
                int cnt=0;
                for(int j=1;j<=n;j++)
                    if(a[i][j]!=0)
                    {
                        if(b[i][cnt]==a[i][j]&&num==0)
                            b[i][cnt]+=a[i][j],
                            ans+=b[i][cnt],
                            num++;
                        else 
                        {
                            if(num>0) num=0;
                            b[i][++cnt]=a[i][j];
                        }
                    }
            }
        }
        else if(x==3)
        {
            for(int i=1;i<=n;i++)
            {
                int cnt=n+1;
                for(int j=n;j>=1;j--)
                    if(a[i][j]!=0)
                    {
                        if(b[i][cnt]==a[i][j]&&num==0)
                            b[i][cnt]+=a[i][j],
                            ans+=b[i][cnt],
                            num++;
                        else 
                        {
                            if(num>0) num=0;
                            b[i][--cnt]=a[i][j];
                        }
                    }
            }
        }
        bool flag=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(a[i][j]!=b[i][j])
                {
                    flag=1;
                    break;
                }
        if(!flag) cout<<t-1<<endl<<ans,exit(0);
        flag=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(a[i][j]!=0)
                {
                    flag=1;
                    break;
                }
        if(!flag) cout<<t-1<<endl<<ans,exit(0);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                a[i][j]=b[i][j];
        sum=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(a[i][j]==0)
                    sum++;
        int s=1+(y%sum);
        sum=0;
        for(int i=1;i<=n;i++)   
            for(int j=1;j<=n;j++)
            {
                if(a[i][j]==0) 
                {
                    sum++;
                    if(sum==s) 
                    {
                        a[i][j]=z;
                        break;
                    }
                }
            }
    }
    cout<<m<<endl<<ans;
}

T2 子集

  • 题意:

    给定两正整数 \(n,k\)\(k|n\)

    已知集合为 \(1\sim n\) ,求出 \(k\) 个互无交集的子集,且子集内元素和均相等。

  • 解法:

    打表

    \(m=\dfrac{n}{k}\) ,表示每个子集中元素的数量。

    • 判无解:

      1. 首先如果 \(n\neq 1,k=n\) ,则一定无解,很显然。

      2. \(n\) 为偶数,且 \(m\) 为奇数,则无解:

        \(n|m\)\(n\) 为偶数,\(m\) 为奇数;设 \(n=2a\times (2b+1)\)\(2a=k,2b+1=m\) .

        \(\sum\limits_{i=1}^ni=\dfrac{(n+1)n}{2}=\dfrac{[2a(2b+1)+1]\times 2a(2b+1)}{2}\)

        则平均到 \(2a\) 个子集,每个子集元素和为 \(\dfrac{[2a(2b+1)+1]\times 2a(2b+1)}{2\times 2a}=a(2b+1)^2+\dfrac{2b+1}{2}\) ,显然这个数不是整数,所以无解。

    • 简单——\(m\) 为偶数:

      显然输出一个 \(i\) ,一个 \(n-i+1\) 就行了,部分分就是这么来的。

    • 难点——\(n,m\) 均为奇数:

      极其巧妙的做法:先按照上面方式搞,直到每一组剩下 \(3\) 个元素未填。

      接下来的问题就转换成如何排这 \(m=3\) 的情况。

      \(n=15,k=5\) 为例:

      image

      1. 先将最大的 \(k\) 个分别按从大到小顺序排到每一组,因为不会存在这 \(k\) 个中有 \(2\) 个在同一组。(排序是为了方便)。

      2. 根据公式 \(\dfrac{(n+1)n}{2k}\) 求出每个子集元素和 \(sum\) ,那么此时求出放完第一列后,每一组还需要多少,记作 \(val_i\) ,设 \(val_i=a_i+b_i\) ,分别表示 \(2,3\) 列。

      3. 接下来的步骤就变成了如何用剩下两列凑出每个 \(val_i\) ,此处需要分类讨论(接下来思路不易推出,可以结合上面的图):

        • \(i\) 为奇数:

          为什么要分类讨论呢?

          显然的,\(val_{i+1}-val_i=1\) ,∴ \(val_{i+2}-val_i=2\) ,这样的话就可以使 \(a_{i+2}-a_i=1,b_{i+2}-b_i=1\) 。这样的话 \(a,b\) 分别从 \(1,val_1-1\) 开始递增即可,参考上图。

        • \(i\) 为偶数:

          和上面一样的,但从剩下的数中选,\(a,b\)\(\dfrac{n+3}{2},val_2-(\dfrac{n+3}{2})\) 递增即可,参考上图。

          具体为什么是从 \(\dfrac{n+3}{2}\) 开始,∵ \(n,k\) 均为奇数,那么 \(i\) 为奇数的情况用去了 \(1\sim \dfrac{n+1}{2}\) ,所以到偶数就从 \(\dfrac{n+3}{2}\) 开始轮了。

      好了现在规律找出来了,怎么输出还是个问题。

      所以就需要将上面的规律转化为能用 \(i,k\) 表示的公式,依旧是分类讨论,按照上面的思路来:

      • \(i\) 为奇数:

        1. 输出第一列:\(3k-i+1\) ,在这里 \(3k\) 就是上面 \(n\) 的意思了。
        2. 输出第二列:\(\dfrac{i+1}{2}\)
        3. 输出第三列:\(\dfrac{3k+i}{2}\)
      • \(i\) 为偶数:

        1. 输出第一列:\(3k-i+1\) ,和上面一样的。
        2. 输出第二列:\(\dfrac{k+i+1}{2}\)
        3. 输出第三列:\(\dfrac{2k+i}{2}\)

      这里需要解释一下:

      首先在第一列的数为 \(2k+1\sim 3k\) ,第二列为 \(1\sim k\) ,第三列为 \(k+1\sim 2k\)

      参照上面的思路与图片,不难发现第二列是 \(i\) 为奇数优先,而第三列是 \(i\) 为偶数优先。

      • 在第二列时:

        奇数优先从 \(1\sim\dfrac{k+1}{2}\) 中选,则转化为 \(i\) 就是 \(\dfrac{i+1}{2}\)

        而偶数则是在 \(\dfrac{k+3}{2}\sim k\) 中选,原本按奇数那个来就是 \(\dfrac{i}{2}\) ,但是每一个都对应加上 \(\dfrac{k+1}{2}\) ,就变为 \(\dfrac{k+i+1}{2}\)

      • 在第三列时:

        偶数优先从 \(k+1\sim \dfrac{3k-1}{2}\) 中选,则转化为 \(i\) 就是 \(k+\dfrac{i}{2}=\dfrac{2k+i}{2}\)

        而奇数则是在 \(\dfrac{3k+1}{2}\sim 2k\) 中选,若按偶数那个来就是 \(\dfrac{i+1}{2}\) ,单是每一个都加上 \(\dfrac{3k-1}{2}\) ,就变为 \(\dfrac{3k+i}{2}\)

      • 最后证明:

        由于 \(n,m,k\) 均为奇数,所以 \(i\) 为奇数的情况占 \(\dfrac{k+1}{2}\) ,而 \(i\) 为偶数的情况占 \(\dfrac{k-1}{2}\) ,上面奇偶分别所选范围就是这么来的。

      只需要先把这三列先输出,剩下的按照 \(m\) 为偶数那么来就可以了,当然这两部也可以调换,不过会稍微麻烦一点点,需要找到这三列里第一个数 \(s\) ,把上面的公式都加上 \(s-1\) 即可;若不换就直接用上面公式。

  • 代码如下:

#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
using namespace std;
const int N=10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int t,n,m,k;
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(t);
    while(t--)
    {
        read(n),read(k);
        
        int m=n/k;
        if((n%2==0&&m&1)||(m==1&&n!=1)) 
        {
            cout<<"No"<<endl;
            continue;
        }
        cout<<"Yes"<<endl;
        if(k==1)
        {
            for(int i=1;i<=n;i++) cout<<i<<' ';
            cout<<endl;
            continue;
        }
        if(m%2==0) 
        {
            int cnt=0;
            for(int i=1;i<=k;i++)
            {
                for(int j=1;j<=m/2;j++)
                    cout<<++cnt<<' '<<n-cnt+1<<' ';
                cout<<endl;
            }   
        }
        else 
        {
            int cnt=0,s=(n-k)/2-k+1;
            for(int i=1;i<=k;i++)
            {
                for(int j=1;j<=(m-3)/2;j++)
                    cout<<++cnt<<' '<<n-cnt+1<<' ';
                cout<<3*k+s-i<<' ';
                if(i&1) cout<<s+(i-1)/2<<' '<<s+k-1+(k+i)/2<<endl;
                else cout<<s+(k-1)/2+i/2<<' '<<s+k-1+i/2<<endl;//就是上面的公式加上 s-1 。
            }
        }
    }
}

T3 混凝土粉末

  • 题意:

    直接看吧:

    image

  • 部分分—— \(84pts\)

    在线做法(暴力)。

    • 修改:

      仅存下来即可,不做其他操作。

    • 询问:

      遍历之前所有的修改操作,找到第一个使高度 \(>y\) 的修改下标。

      具体的,若 \(x\)\(l_i,r_i\) 之间,定义一计数器 \(sum+=h_i\) ,当 \(sum>=y\) 的那一刻,\(ans=id_i\) ,看代码理解吧。

    • 最坏情况下,复杂度为 \(O(\dfrac{1}{4}q^2)\)

    • 代码如下:

    #include<bits/stdc++.h>
    #define int long long 
    #define endl '\n'
    using namespace std;
    const int N=1e6+10;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=1;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    int n,m,op,x,y,z,sum,cnt,ans;
    struct aa
    {
        int l,r,h,id;
    }e[N];
    signed main()
    {
        #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
        #endif
        read(n),read(m);
        for(int i=1;i<=m;i++)
        {
            read(op);
            if(op==1)
                read(x),read(y),read(z),
                e[++cnt]={x,y,z,i};
            else
            {
                read(x),read(y);
                ans=sum=0;
                for(int j=1;j<=cnt;j++)
                {
                    if(e[j].l<=x&&x<=e[j].r)
                    {
                        sum+=e[j].h;
                        if(sum>=y)
                        {
                            ans=e[j].id;
                            break;
                        }
                    }
                }
                cout<<ans<<endl;
            }
        }
    }
    
  • 正解:

    离线做法

    树状数组+二分 维护。

    和上面思路是一样的,但是把上面的操作都存下来,进行离线操作。

    而离线后用什么来维护,可以用树状数组。而用其维护的目的还有就是可以对其进行二分。

    具体的,看代码理解会更方便,这里简单阐述一下:

    • 保存操作:

      1. 开一个结构体,分别存上时间 \(id\) 和高度 \(h\) 。开 \(2\) 个面向该结构体的 \(vector\)\(ch,qu\) ,分别存上修改和查询;同时,对于用来判断修改还是查询的 \(op\) 也要存成 \(op_i\)
      2. 对于存修改,既然使用树状数组,自然需要差分,对应的 \(ch_l+h,ch_{r+1}-h\) ;具体如下:
      if(op[i]==1)
              read(x),read(y),read(z),
              ch[x].push_back({i,z}),
              ch[y+1].push_back({i,-z});
      
      1. 对于存查询,只需要将要查的高度和对应时间压入 \(vector\) 即可:
      else
              read(x),read(y),
              qu[x].push_back({i,y});
      

    然后树状数组离线维护即可,具体看代码理解吧,不想写了

  • 代码如下:

#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
using namespace std;
const int N=1e6+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,m,x,y,z,sum,cnt;
int op[N],c[N],ans[N];
struct aa
{
    int id,h;
};
vector<aa>ch[N],qu[N];
int l,r;
int lowbit(int x) {return x&-x;}
void change(int x,int v)
{
    for(;x<=m;x+=lowbit(x)) c[x]+=v;
}
int ask(int x)
{
    int ans=0;
    for(;x;x-=lowbit(x)) ans+=c[x];
    return ans;
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n),read(m);
    for(int i=1;i<=m;i++)
    {
        read(op[i]);
        if(op[i]==1)
            read(x),read(y),read(z),
            ch[x].push_back({i,z}),
            ch[y+1].push_back({i,-z});
        else
            read(x),read(y),
            qu[x].push_back({i,y});
    }
    for(int i=1;i<=n;i++)
    {
        for(aa j:ch[i]) change(j.id,j.h);
        for(aa j:qu[i])
        {
            l=1;r=j.id;
            ans[j.id]=0;
            while(l<=r)
            {
                int mid=l+r>>1;
                if(ask(mid)<j.h) l=mid+1;
                else ans[j.id]=mid,r=mid-1;
            }
        }
    }
    for(int i=1;i<=m;i++)
        if(op[i]==2)
            cout<<ans[i]<<endl;
}

T4 排水系统

  • 概率期望,感觉不是给我做的,把题面粘上跑路了……

image

总结

有大样例好评。

头一次做 \(\text{NOIP}\) 难度的题,确实好难, \(T4\) 直接先不管了。

看到 \(\Huge{标准输入输出}\) 就不要打 \(freopen\) !!!

大模拟题只要耐心不放弃就好。

构造题一定要思路转换,找好规律,打 \(T2\) 时真的硬生生自己推了好久,光直到规律不从头推怎么来的有什么用。

搞出来 \(T3\) 之后感觉不难啊 \(qwq\)

运气真好(密码是歪的两个数字)

posted @ 2024-02-20 09:56  卡布叻_周深  阅读(57)  评论(1)    收藏  举报