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恶心题,写错调半年。
希望我的状态能保持下去。