• 3月15日考试 题解(数学+背包+线段树)


    这次好不容易AK了一次(虽然题有点白给。有的题还是比较考验思路的。

    --------------------------------

    T1 放棋子

    给一个n*n的棋盘,有n个棋子。棋盘的副对角线不能放棋子(即$i+j=n+1$的位置)。每行每列只能摆一个棋子。每个棋子都视为是不同的。问有多少种摆法。

    $nleq 1314520$。答案对1e8+7取模。

    --------------------------

    典型的错排问题。

    我们将题目转化:有$n$个数,标号为$1$-$n$。第$i$个数不能放到第$i$个位置。有多少种方法。

    考虑递推:设$f_{i}$为放i个棋子的方案数。假设将第$i$个棋子放到第$k$个位置上。

    如果将第$k$个棋子放到第$i$个位置上,则此时方案数为$f_{i-2}$。

    如果将第$k$个棋子放到其他位置上,则方案数为$f_{i-1}$。

    因为把$i$放到$k$上有$i-1$种方案。所以$f_{i}=(i-1) imes (f_{i-1}+f_{i-2})$。

    特别地,有$f[1]=0,f[2]=1$。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int mod=100000007;
    unsigned long long f[1314525]; 
    unsigned long long jie=1;
    int main()
    {
        int n;scanf("%d",&n);
        f[1]=0;f[2]=1;
        if (n==1){
            printf("orzlxx");
            return 0;
        }
        for (int i=3;i<=n;i++)
        {
            unsigned long long tmp=(f[i-1]+f[i-2])%mod;
            f[i]=(i-1)*tmp;
            f[i]%=mod;
        }
        for (int i=1;i<=n;i++){
            jie*=i;
            jie%=mod;
        }
        f[n]%=mod;f[n]=f[n]*jie;f[n]%=mod;
        printf("%lld",f[n]%mod);
        return 0;
    }

    T2 金明的预算方案

    尼玛竟然考了原题,这就很离谱。

    --------------------------------------

    题目描述

    --------------------

    对于一个物品,无非只有五种情况:

    1.不买这个物品。

    2.买这个物品和它的第一个附件。

    3.买这个物品和它的第二个附件。

    4.全都买

    5.都不买

    如果并没有第$i$个物品直接把$price[i]$和$im[i]$设为0即可。

    也可以先把主件和附件背包一次,然后再进行分组背包。我这里采用的是第二种方法。

    ------------------------------------------

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    struct yyy
    {
        int w,v,fa;
    }a[60],chi[60][60];
    int n,m,b[80][10],t[80],c[80][10],cnt[80],f[32005],ans;//n为价钱,m为数量 
    int main()
    {
        cin>>n>>m;
        for (int i=1;i<=m;i++)//预先把主件和附件分到一个组里 
        {
            cin>>a[i].w>>a[i].v>>a[i].fa;
            if (a[i].fa!=0)
            {
                t[a[i].fa]++;
                chi[a[i].fa][t[a[i].fa]].v=a[i].v;
                chi[a[i].fa][t[a[i].fa]].w=a[i].w;
                chi[a[i].fa][t[a[i].fa]].fa=a[i].fa;
            }
        }
        for (int i=1;i<=m;i++)//对于有依赖的背包问题,可以事先将他们分到一个组里,用“01背包”的方法取得局部最优值 
        {
            if (t[i])//如果是附件 
            {
                memset(f,-1,sizeof(f));
                f[0]=0;
                for (int j=1;j<=t[i];j++)
                    for (int k=n-a[i].w;k>=chi[i][j].w;k--)
                        if (f[k]<f[k-chi[i][j].w]+chi[i][j].w*chi[i][j].v&&f[k-chi[i][j].w]!=-1) //恰好背包的判断 
                            f[k]=f[k-chi[i][j].w]+chi[i][j].w*chi[i][j].v;
                for (int j=0;j<=n-a[i].w;j++)
                    if (f[j]!=-1)//恰好背包的判断 
                    {
                        cnt[i]++;
                        b[i][cnt[i]]=j+a[i].w;
                        c[i][cnt[i]]=a[i].v*a[i].w+f[j];
                    }
                
            }
            if (!a[i].fa)//如果不是附件 
            {
                cnt[i]++;
                b[i][cnt[i]]=a[i].w;
                c[i][cnt[i]]=a[i].w*a[i].v;
            }
        }
        memset(f,0,sizeof(f));
        for (int i=1;i<=m;i++)//这时候就是分组背包问题了 
            for (int j=n;j>=0;j--)
                for (int k=1;k<=cnt[i];k++)
                    if (j>=b[i][k])
                        f[j]=max(f[j],f[j-b[i][k]]+c[i][k]);
        for (int i=0;i<=n;i++)
            ans=max(ans,f[i]);//保险起见,又遍历了一遍 
        cout<<ans;
        return 0;
    }

    T3 线段树大综合

    题目大意:让你区间修改,区间查询。

    内容包括:

    1.修改:统一加$x$或统一重新赋值为$x$。

    2.区间求和,最大值或最小值。

    ---------------------------------------------

    对于修改,我们要维护两个tag,分别对应重新赋值和加权。

    pushdown的顺序是先维护重新赋值的tag再维护加权的tag。因为先前不管怎么加最后重新赋值和直接赋值是一样的。所以优先重新赋值。

    pushdown的代码:

    void pushdown(int k)
    {
         int a=t[k].atag,c=t[k].ctag,x=t[k].r-t[k].l+1;
         if(c!=-1){
         t[k<<1].ctag=t[k<<1|1].ctag=c;
         t[k<<1].atag=t[k<<1|1].atag=0;
         t[k<<1].sum=c*(x-(x>>1));
         t[k<<1|1].sum=c*(x>>1);
         t[k<<1].mn=t[k<<1|1].mn=t[k<<1].mx=t[k<<1|1].mx=c;
         t[k].ctag=-1;
         }
         if(a!=0){
         t[k<<1].atag+=a;t[k<<1|1].atag+=a;
         t[k<<1].sum+=a*(x-(x>>1));t[k<<1|1].sum+=a*(x>>1);
         t[k<<1].mn+=a;t[k<<1|1].mn+=a;
         t[k<<1].mx+=a;t[k<<1|1].mx+=a;
         t[k].atag=0;
         }
    }

    然后就是线段树板子题了,熟练的话20min打完+调完。

    AC代码:

    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    int n,m;
    struct data{int l,r,sum,mn,mx,atag,ctag;}t[4000001];
    void pushup(int k)
    {
        t[k].sum=t[k<<1].sum+t[k<<1|1].sum;
        t[k].mn=min(t[k<<1].mn,t[k<<1|1].mn);
        t[k].mx=max(t[k<<1].mx,t[k<<1|1].mx);
     }
    void build(int k,int l,int r)
    {
        t[k].ctag=-1;t[k].l=l;t[k].r=r;
        if(l==r){scanf("%d",&t[k].sum);t[k].mn=t[k].mx=t[k].sum;return;}
        int mid=(l+r)>>1;
        build(k<<1,l,mid);build(k<<1|1,mid+1,r);
        pushup(k);
    }
    void pushdown(int k)
    {
         int a=t[k].atag,c=t[k].ctag,x=t[k].r-t[k].l+1;
         if(c!=-1){
         t[k<<1].ctag=t[k<<1|1].ctag=c;
         t[k<<1].atag=t[k<<1|1].atag=0;
         t[k<<1].sum=c*(x-(x>>1));
         t[k<<1|1].sum=c*(x>>1);
         t[k<<1].mn=t[k<<1|1].mn=t[k<<1].mx=t[k<<1|1].mx=c;
         t[k].ctag=-1;
         }
         if(a!=0){
         t[k<<1].atag+=a;t[k<<1|1].atag+=a;
         t[k<<1].sum+=a*(x-(x>>1));t[k<<1|1].sum+=a*(x>>1);
         t[k<<1].mn+=a;t[k<<1|1].mn+=a;
         t[k<<1].mx+=a;t[k<<1|1].mx+=a;
         t[k].atag=0;
         }
    }
    void change(int k,int x,int y,int z)
    {
         pushdown(k);
         int l=t[k].l,r=t[k].r;
         if(l==x&&y==r){
                if(l!=r)t[k].ctag=z;
                t[k].mn=t[k].mx=z;
                t[k].sum=(r-l+1)*z;
                return;}
         int mid=(l+r)>>1;
         if(mid>=y)change(k<<1,x,y,z);
         else if(mid<x)change(k<<1|1,x,y,z);
         else {
              change(k<<1,x,mid,z);
              change(k<<1|1,mid+1,y,z);}
         pushup(k);
    }
    void add(int k,int x,int y,int z)
    {
     
         pushdown(k);
         int l=t[k].l,r=t[k].r;
         if(l==x&&y==r){
                if(l!=r)t[k].atag=z;
                t[k].sum+=(r-l+1)*z;
                t[k].mn+=z;t[k].mx+=z;
                return;}
         int mid=(l+r)>>1;
         if(mid>=y)add(k<<1,x,y,z);
         else if(mid<x)add(k<<1|1,x,y,z);
         else {
              add(k<<1,x,mid,z);
              add(k<<1|1,mid+1,y,z);
              }
         pushup(k);
    }
    int ask(int k,int x,int y,int f)
    {
        pushdown(k);
        int l=t[k].l,r=t[k].r;
        if(l==x&&r==y)
        {
            if(f==1)return t[k].sum;
            else if(f==2)return t[k].mn;
            else return t[k].mx;
        }
        int mid=(l+r)>>1;
        if(mid>=y)return ask(k<<1,x,y,f);
        else if(mid<x)return ask(k<<1|1,x,y,f);
        else {
             if(f==1)return (ask(k<<1,x,mid,f)+ask(k<<1|1,mid+1,y,f));
             else if(f==2)return min(ask(k<<1,x,mid,f),ask(k<<1|1,mid+1,y,f));
             else return max(ask(k<<1,x,mid,f),ask(k<<1|1,mid+1,y,f));
             }
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        build(1,1,n);
        for(int i=1;i<=m;i++)
        {
            int c,dir,x,y,v;
            scanf("%d",&c);
            if(c==1){scanf("%d%d%d",&dir,&x,&y);printf("%d
    ",ask(1,x,y,dir));}
            else
            {
                 scanf("%d%d%d%d",&dir,&x,&y,&v);
                 if(dir==1)add(1,x,y,v);
                 else change(1,x,y,v); 
             }
        }
        return 0;
    }

     后记:这次考试总体难度一般吧。平时认真做题独立思考的不出意外150+。T1有难度,转化那一步不太容易想到。T2白给题。T3恶心题,写错调半年。

    希望我的状态能保持下去。

  • 相关阅读:
    Java必会之多线程
    第一周JVM核心技术-工具与GC策略
    JVM核心技术(第一篇)
    SpringCloudAlibaba使用seata做分布式事务
    Go中的interface(接口)
    快排与堆排
    优先队列原来不难!带你手写一个
    【LeetCode】557. 反转字符串中的单词 III
    【LeetCode】214. 最短回文串
    【LeetCode】17. 电话号码的字母组合(回溯)
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/12500905.html
Copyright © 2020-2023  润新知