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

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

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

T1 放棋子

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

$n\leq 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)\times (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\n",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恶心题,写错调半年。

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

posted @ 2020-03-15 22:57  我亦如此向往  阅读(137)  评论(0编辑  收藏  举报