2024初三集训模拟测试3

2024初三集训模拟测试3

\(T1\) 计蒜客 T3726 排序 \(0pts\)

  • 考虑先将 \(a\) 数组进行升序排列。然后题意转化为了把 \(1 \sim 2n\) 平均分成 \(2\) 组,使得每组乘积之和最小;把 \(2n+1 \sim 4n\) 平均分成 \(2\) 组,使得每组乘积之和最大。

  • 对于 \(x_{1},x_{2},x_{3},x_{4}\) 若存在 \(x_{1}<x_{2}<x_{3}<x_{4}\) 则有 \(x_{1}x_{2}+x_{3}x_{4}>x_{1}x_{3}+x_{2}x_{4}>x_{1}x_{4}+x_{2}x_{3}\) ,若加上等于号则有 \(x_{1}x_{2}+x_{3}x_{4} \ge x_{1}x_{3}+x_{2}x_{4} \ge x_{1}x_{4}+x_{2}x_{3}\)

  • 故最终 \(\sum\limits_{i=2n+1}^{4n}[i \bmod 2=1] \times x_{i}x_{i+1}-\sum\limits_{i=1}^{n}x_{i}x_{2n-i+1}\) 即为所求。

    点击查看代码
    ll a[500001];
    int main()
    {
        ll n,ans=0,i;
        cin>>n;
        for(i=1;i<=4*n;i++)
        {
            cin>>a[i];
        }
        sort(a+1,a+1+4*n);
        for(i=1;i<=n;i++)
        {
            ans-=a[i]*a[2*n-i+1];
        }
        for(i=2*n+1;i<=4*n;i+=2)
        {
            ans+=a[i]*a[i+1];
        }
        cout<<ans<<endl;
        return 0;
    }
    

\(T2\) 计蒜客 T3727 牛吃草 \(0pts\)

  • 部分分

    • \(90pts\)
      • 为使最小覆盖大小最大,考虑二分答案,设当前二分出来的答案为 \(mid\)

      • \(f_{i}\) 表示对 \([1,i]\) 完成最小覆盖大小 \(\ge mid\) 的覆盖后的最大总覆盖大小,状态转移方程为 \(\begin{aligned} f_{i}=\max(f_{i-1},\max\limits_{j=i-w_{i}}^{i-mid} \{ f_{j}+(i-j) \})=\max(f_{i-1}, \max\limits_{j=i-w[i]}^{i-mid} \{ f_{j}-j \}+i) \end{aligned}\) 。暴力进行转移即可。

        点击查看代码
        int w[600000],f[600000];
        bool check(int mid,int n,int m)
        {
            int i,j;
            memset(f,0,sizeof(f));
            for(i=1;i<=n;i++)
            {
                f[i]=f[i-1];
                if(i-w[i]>=0)
                {
                    for(j=i-w[i];j<=i-mid;j++)
                    {
                        f[i]=max(f[i],f[j]+(i-j));
                    }
                }
            }
            return f[n]>=m;
        }
        int main()
        {
            int n,m,s,l=1,r,mid,i;
            cin>>n;
            for(i=1;i<=n;i++)
            {
                cin>>w[i];
            }
            cin>>s;
            r=m=ceil(1.0*n*s/100);
            while(l<=r)
            {
                mid=(l+r)/2;
                if(check(mid,n,m)==true)
                {
                    l=mid+1;
                }
                else
                {
                    r=mid-1;
                }
            }
            cout<<r<<endl;
            return 0;
        }
        
  • 正解

    • 由于 \(w_{i-1} \ge w_{i}-1\) ,故 \((i-1)-w_{i-1} \le i-w_{i}\) ,故当 \(i\) 增大时,有 \(j\) 的上界增加了 \(1\) ,下界减少了 \(w_{i-1}-(w_{i}-1)\) ,使用单调队列维护即可。
    点击查看代码
    int w[600000],f[600000];
    bool check(int mid,int n,int m)
    {
        deque<int>q;
        for(int i=1;i<=n;i++)
        {
            f[i]=f[i-1];
            if(i-mid>=0)
            {
                while(q.empty()==0&&f[q.back()]-q.back()<=f[i-mid]-(i-mid))
                {
                    q.pop_back();
                }
                q.push_back(i-mid);
                while(q.empty()==0&&q.front()<=i-w[i]-1)
                {
                    q.pop_front();
                }
                if(q.empty()==0)
                {
                    f[i]=max(f[i],f[q.front()]-q.front()+i);
                }
            }
        }
        return f[n]>=m;
    }
    int main()
    {
        int n,m,s,l=1,r,mid,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>w[i];
        }
        cin>>s;
        r=n;
        m=ceil(1.0*n*s/100);
        check(1,n,m);
        while(l<=r)
        {
            mid=(l+r)/2;
            if(check(mid,n,m)==true)
            {
                l=mid+1;
            }
            else
            {
                r=mid-1;
            }
        }
        cout<<r<<endl;
        return 0;
    }
    

\(T3\) 计蒜客 T3728 树上的宝藏 \(0pts\)

  • 部分分

    • \(60pts\) :设 \(f_{x,0},f_{x,1}\) 分别表示以 \(x\) 为根节点的子树内,不选和选 \(x\) 的方案数。状态转移方程为 \(\begin{cases} f_{x,0}=\prod\limits_{y \in Son(x)}^{}(f_{y,0}+f_{y,1}) \\ f_{x,1}=\prod\limits_{y \in Son(x)}^{}f_{y,0} \end{cases}\) 。对于一条边 \((x,y)\) ,砍断这条边后,进行树形 \(DP\) ,此时 \(f_{x,0}f_{y,1}+f_{x,1}f_{y,0}+f_{x,1}f_{y,1}\) 即为所求。
  • 正解

    • 发现砍断操作可以用换根 \(DP\) 优化。
      • 钦定 \(1\) 为根节点。
      • 第一遍 \(DFS\) 时,处理出 \(f_{x,0},f_{x,1}\)
      • 第二遍 \(DFS\) 时,进行换根。设 \(g_{x,0},g_{x,1}\) 表示以 \(x\) 为整棵树的根节点时,不选和选 \(x\) 的方案数,仍钦定 \(1\) 为根节点。考虑根节点由 \(fa_{x}\) 变为 \(x(x \ne 1)\) 的过程中,以 \(x\) 为根的子树内的节点对答案的贡献不变,要消除以 \(x\) 为根的子树内的节点对以 \(fa_{x}\) 为根的子树的影响,故状态转移方程为 \(\begin{cases} g_{x,0}=f_{x,0} \times (\frac{g_{fa_{x},0}}{f_{x,0}+f_{x,1}}+\frac{g_{fa_{x},1}}{f_{x,0}}) \\ g_{x,1}=f_{x,1} \times \frac{g_{fa_{x},0}}{f_{x,0}+f_{x,1}} \end{cases}\)
    • 对于一条边 \((x,y)\) ,进行分类讨论。
      • 若不选 \(x\) ,选 \(y\) ,则有 \(f_{y,1} \times \frac{g_{x,0}}{f_{y,0}+f_{y,1}}\) 即为所求。
      • 若选 \(x\) ,不选 \(y\) ,则有 \(f_{y,0} \times \frac{g_{x,1}}{f_{y,0}}=g_{x,1}\) 即为所求。
      • 若选 \(x,y\) ,则有 \(f_{y,1} \times \frac{g_{x,1}}{f_{y,0}}\) 即为所求。
    点击查看代码
    const ll p=998244353;
    struct node
    {
        ll nxt,to;
    }e[700000];
    ll head[700000],dep[700000],u[700000],v[700000],g[700000][2],f[700000][2],cnt=0;
    void add(ll u,ll v)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    ll qpow(ll a,ll b,ll p)
    {
        ll ans=1;
        while(b>0)
        {
            if(b&1)
            {
                ans=ans*a%p;
            }
            b>>=1;
            a=a*a%p;
        }
        return ans;
    }
    void dfs(ll x,ll fa)
    {
        f[x][0]=f[x][1]=1;
        dep[x]=dep[fa]+1;
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                dfs(e[i].to,x);
                f[x][0]=f[x][0]*((f[e[i].to][0]+f[e[i].to][1])%p)%p;
                f[x][1]=f[x][1]*f[e[i].to][0]%p;
            }
        }
    }
    void reroot(ll x,ll fa)
    {
        if(x!=1)
        {
            ll f0=g[fa][0]*qpow((f[x][0]+f[x][1])%p,p-2,p)%p,f1=g[fa][1]*qpow(f[x][0],p-2,p)%p;
            g[x][0]=f[x][0]*((f0+f1)%p)%p;
            g[x][1]=f[x][1]*f0%p;
        }
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                reroot(e[i].to,x);
            }
        }
    }
    int main()
    {
        ll n,sum1,sum2,sum3,i;
        cin>>n;
        for(i=1;i<=n-1;i++)
        {
            cin>>u[i]>>v[i];
            add(u[i],v[i]);
            add(v[i],u[i]);
        }
        dfs(1,0);
        g[1][0]=f[1][0];
        g[1][1]=f[1][1];
        reroot(1,0);
        for(i=1;i<=n-1;i++)
        {
            if(dep[v[i]]<dep[u[i]])
            {
                swap(u[i],v[i]);
            }
            sum1=((f[v[i]][1]*g[u[i]][0])%p)*qpow((f[v[i]][0]+f[v[i]][1])%p,p-2,p)%p;
            sum2=g[u[i]][1];
            sum3=((f[v[i]][1]*g[u[i]][1])%p)*qpow(f[v[i]][0]%p,p-2,p)%p;
            cout<<(((sum1+sum2)%p)+sum3)%p<<endl;
        }
        return 0;
    }
    

\(T4\) 计蒜客 T3729 MEX \(0pts\)

  • Special Judgeluogu P4137 Rmq Problem / mex

  • 部分分

    • \(10pts\)

      • 对于每个区间枚举左右端点,进行暴力求解。

        点击查看代码
        ll a[5000000],vis[5000000],ans[5000000];
        int main()
        {
            ll n,m,p,q,l,r,i;
            cin>>n>>m;
            for(i=1;i<=n;i++)
            {
                cin>>a[i];
            }
            for(l=1;l<=n;l++)
            {
                for(r=l;r<=n;r++)
                {
                    for(i=l;i<=r;i++)
                    {
                        vis[a[i]]=1;
                    }
                    for(i=0;i<=m;i++)
                    {
                        if(vis[i]==0)
                        {
                            ans[i]++;
                            break;
                        }
                    }
                    for(i=0;i<=m;i++)
                    {
                        vis[i]=0;
                    }
                }
            }
            cin>>p>>q;
            for(i=p;i<=q;i++)
            {
                cout<<ans[i]<<" ";
            }
            return 0;
        }
        
    • \(20pts\)

      • 使用莫队进行统计答案。

        点击查看代码
        int a[3000010],cnt[3000010],lscnt[3000010],ans[3000010],L[3000010],R[3000010],pos[3000010],klen,ksum;
        struct ask
        {
            int l,r,id;
        }q[3000010];
        bool q_cmp(ask a,ask b)
        {
            return (pos[a.l]==pos[b.l])?(a.r>b.r):(a.l<b.l);
        }
        void init(int n,int m)
        {
            klen=n/sqrt(m)+1;
            ksum=n/klen;
            for(int i=1;i<=ksum;i++)
            {
                L[i]=R[i-1]+1;
                R[i]=R[i-1]+klen;
            }
            if(R[ksum]<n)
            {
                ksum++;
                L[ksum]=R[ksum-1]+1;
                R[ksum]=n;
            }
            for(int i=1;i<=ksum;i++)
            {
                for(int j=L[i];j<=R[i];j++)
                {
                    pos[j]=i;
                }
            }
        }
        void add(int cnt[],int x)
        {
            cnt[a[x]]++;
        }
        void del(int cnt[],int x,int &sum)
        {
            cnt[a[x]]--;
            sum=(cnt[a[x]]==0)?min(sum,a[x]):sum;
        }
        int main()
        {
            int n,mm,m=0,x,y,l=1,r,anss=0,sum=0,tmp=0,lastblock=0,i,j;
            cin>>n>>mm;
            r=n;
            for(i=1;i<=n;i++)
            {
                cin>>a[i];
            }
            for(i=1;i<=n;i++)
            {
                for(j=i;j<=n;j++)
                {
                    m++;
                    q[m].l=i;
                    q[m].r=j;
                    q[m].id=m;
                }
            }
            init(n,m);
            sort(q+1,q+1+m,q_cmp);
            for(i=1;i<=n;i++)
            {
                add(cnt,i);
            }
            while(cnt[anss]>=1)
            {
                anss++;
            }
            for(i=1;i<=m;i++)   
            {
                if(pos[q[i].l]==pos[q[i].r])
                {
                    sum=0;
                    for(j=q[i].l;j<=q[i].r;j++)
                    {
                        add(lscnt,j);
                    }
                    while(lscnt[sum]>=1)
                    {
                        sum++;
                    }
                    for(j=q[i].l;j<=q[i].r;j++)
                    {
                        lscnt[a[j]]--;
                    }
                }
                else
                {
                    if(lastblock!=pos[q[i].l])
                    {
                        while(r<n)
                        {
                            r++;
                            add(cnt,r);
                        }
                        while(l<L[pos[q[i].l]])
                        {
                            del(cnt,l,anss);
                            l++;
                        }
                        tmp=anss;
                        lastblock=pos[q[i].l];
                    }
                    while(r>q[i].r)
                    {
                        del(cnt,r,tmp);
                        r--;
                    }
                    sum=tmp;
                    while(l<q[i].l)
                    {
                        del(cnt,l,sum);
                        l++;
                    }
                    while(l>L[pos[q[i].l]])
                    {
                        l--;
                        add(cnt,l);
                    }
                }
                ans[sum]++;
            }
            cin>>x>>y;
            for(i=x;i<=y;i++)
            {
                cout<<ans[i]<<" ";
            }
            return 0;
        }
        
    • \(30pts\)

      点击查看 APJifengc 写的题解

  • 正解

    点击查看 APJifengc 写的题解

    const int MAXN = 1e6 + 5;
    int n, m;
    int arr[MAXN];
    int last[MAXN];
    int nxt[MAXN];
    long long answer[MAXN];
    
    struct SegmentTree {
    public:
        int min, num;
        long long sum;
        class LazyTag {
        public:
            int cover, tim;
            long long add;
            inline LazyTag() {
                cover = -1;
                add = 0;
                tim = 0;
            }
            inline LazyTag(const int cover): cover(cover) {
                if (cover == -1) {
                    tim = 1;
                }
            }
        } tag;
    } sgt[MAXN << 2];
    
    inline void PushUp(const int now) {
        sgt[now].min = min(sgt[now << 1].min, sgt[now << 1 | 1].min);
    }
    void Build(const int now = 1, const int left = 0, const int right = m) {
        if (left == right) {
            sgt[now].num = sgt[now].min = last[left];
            return;
        }
    
        Build(now << 1, left, (left + right) >> 1);
        Build(now << 1 | 1, ((left + right) >> 1) + 1, right);
        PushUp(now);
    }
    inline void Down(const SegmentTree::LazyTag tag,
                    const int now, const int left, const int right) {
        sgt[now].sum += 1ll * sgt[now].num * tag.tim + tag.add;
    
        if (~tag.cover) {
            if (~sgt[now].tag.cover) {
                sgt[now].tag.add += 1ll * sgt[now].tag.cover * tag.tim + tag.add;
            } else {
                sgt[now].tag.tim += tag.tim;
                sgt[now].tag.add = tag.add;
            }
    
            sgt[now].num = sgt[now].min = tag.cover;
            sgt[now].tag.cover = tag.cover;
        } else {
            if (~sgt[now].tag.cover) {
                sgt[now].tag.add += 1ll * sgt[now].tag.cover * tag.tim;
            } else {
                sgt[now].tag.tim += tag.tim;
            }
        }
    }
    inline void PushDown(const int now, const int left, const int right) {
        Down(sgt[now].tag, now << 1, left, (left + right) >> 1);
        Down(sgt[now].tag, now << 1 | 1, ((left + right) >> 1) + 1, right);
        sgt[now].tag = SegmentTree::LazyTag();
    }
    void Updata(const int now_left, const int now_right,
                const int cover, const int now = 1,
                const int left = 0, const int right = m) {
        if (now_right < left || right < now_left) {
            return;
        }
    
        if (now_left <= left && right <= now_right) {
            Down(SegmentTree::LazyTag(cover), now, left, right);
            return;
        }
    
        PushDown(now, left, right);
        Updata(now_left, now_right, cover, now << 1, left, (left + right) >> 1);
        Updata(now_left, now_right, cover,
            now << 1 | 1, ((left + right) >> 1) + 1, right);
        PushUp(now);
    }
    inline void Time() {
        Down(SegmentTree::LazyTag(-1), 1, 1, m);
    }
    int Find(const int now_left, const int now_right,
            const int val, const int now = 1,
            const int left = 0, const int right = m) {
        if (now_right < left || right < now_left || val <= sgt[now].min) {
            return -1;
        }
    
        if (left == right && now_left <= left && right <= now_right) {
            return left;
        }
    
        int result(Find(now_left, now_right, val, now << 1 | 1,
                        ((left + right) >> 1) + 1, right));
        return ~result ? result : Find(now_left, now_right,
                                    val, now << 1, left, (left + right) >> 1);
    }
    void GetAnswer(const int now = 1, const int left = 0, const int right = m) {
        if (left == right) {
            answer[left] = sgt[now].sum;
            return;
        }
    
        PushDown(now, left, right);
        GetAnswer(now << 1, left, (left + right) >> 1);
        GetAnswer(now << 1 | 1, ((left + right) >> 1) + 1, right);
    }
    
    int main() {
        scanf("%d%d", &n, &m);
        int last0 = 0;
        long long answer0 = 0;
    
        for (int i = 1; i <= n; i++) {
            scanf("%d", &arr[i]);
    
            if (arr[i] == 0) {
                answer0 += (i - last0 - 1ll) * (i - last0) >> 1;
                last0 = i;
            }
        }
    
        answer0 += (n - last0) * (n + 1ll - last0) >> 1;
    
        for (int i = 0; i <= m; i++) {
            last[i] = n + 1;
        }
    
        for (int i = n; i >= 1; i--) {
            nxt[i] = last[arr[i]];
            last[arr[i]] = i;
        }
    
        for (int i = 1; i <= m - 1; i++) {
            last[i] = max(last[i], last[i - 1]);
        }
    
        Build();
    
        for (int i = 1; i <= n; i++) {
            Time();
            int f = Find(arr[i], m, nxt[i]);
    
            if (~f) {
                Updata(arr[i], f, nxt[i]);
            }
        }
    
        GetAnswer();
        int l, r;
        scanf("%d%d", &l, &r);
    
        if (!l) {
            printf("%lld ", answer0);
            l = 1;
        }
    
        for (int i = l; i <= r; i++) {
            printf("%lld ", answer[i] - answer[i - 1]);
        }
    
        return 0;
    }
    
    

总结

  • \(T1\) 场上结论没有猜出来,导致心态炸了。
  • \(T2\) 场上只看出来了二分答案,没有再接着去想,而且貌似还读错题了。
  • \(T3\) 场上读假题了。
  • \(T4\) 场上部分分没打,亏了。
posted @ 2024-02-22 07:00  hzoi_Shadow  阅读(39)  评论(0编辑  收藏  举报
扩大
缩小