【2023.10.25】NOIP2023模拟试题-24
T1
题目大意
给定长度为 \(n\) 的数组 \(a\) 与长度为 \(m\) 的数组 \(b\) ,在 \(n\times m\) 的矩阵中,需要满足第 \(i\) 列的最大值为 \(a_i\) ,第 \(j\) 行的最大值为 \(b_i\) ,且任意一格的值不小于 \(0\) ,求满足条件的矩阵个数。
思路分析
稍微手推一下可以发现最大值更小的限制不受最大值更大的限制影响,而且最大值最小的几行(列)一定是完整的,所以我们可以从最小的开始计算组合数,每次算完就删除这一行(列),来保证下一个要计算的行(列)也是完整的。
比如这个表,最小的 \(1\) 所在的列是完整的,我们可以先计算这一列的值然后删掉
b\a | 3 | 1 | 4 |
---|---|---|---|
3 | 3 | 1 | 3 |
2 | 2 | 1 | 2 |
4 | 3 | 1 | 4 |
b\a | 3 | 4 |
---|---|---|
3 | 3 | 3 |
2 | 2 | 2 |
4 | 3 | 4 |
然后删掉 \(2\) 所在的行
b\a | 3 | 4 |
---|---|---|
3 | 3 | 3 |
4 | 3 | 4 |
然后删掉 \(3\) 所在的行列,然后删掉 \(4\) ,我们就完成统计了。
接下来我们考虑怎么用容斥计算值相同的多行多列的组合方式,最终的答案 \(=\) 每一个值的组合方式之积。
假设有 \(r\) 个行限制与 \(c\) 个列限制,它们限定的最大值都是 \(mx\) ,那么就是说这些位置的值不能超过 \(mx\) ,并且每一行每一列至少有一个位置恰好取到 \(mx\)。
考虑用容斥原理计算这些限制的贡献,枚举 \(i\) 和 \(j\) 表示钦定其中有 \(i\) 行 \(j\) 列都没有取到 \(mx\) ,那么贡献就是 \((-1)^{i+j}\left(\begin {array}{c}r\\ i \end{array} \right) \left(\begin {array}{c}c\\ j \end{array} \right) (mx-1)^t(mx)^{tot-t}\),其中 \(t\) 表示 \(i\) 行 \(j\) 列覆盖的所有位置数,而 \(tot\) 表示 \(r\) 行 \(c\) 列覆盖的所有位置数。
展开代码
#include<bits/stdc++.h>
using namespace std;
#define N 2005
#define Z 10005
#define P 1000000009
#define ll long long
ll qpow(ll a,ll b){
ll c=1;
while(b){
if(b&1)c=c*a%P;
b>>=1;
a=a*a%P;
}
return c;
}
ll inv(ll x){
return qpow(x,P-2);
}
ll fac[Z];
ll C(ll n,ll m){
return fac[n]*inv(fac[m])%P*inv(fac[n-m])%P;
}
int a[N],b[N],n,m;
ll fa[Z],fb[Z];
ll ans=1,jied;
int main(){
freopen("mat.in","r",stdin);
freopen("mat.out","w",stdout);
scanf("%d",&n);
fac[0]=1;
for(int i=1;i<Z;++i){
fac[i]=fac[i-1]*i%P;
}
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
++fa[a[i]];
}
scanf("%d",&m);
for(int i=1;i<=m;++i){
scanf("%d",&b[i]);
++fb[b[i]];
}
for(int c=0;c<Z;++c){
if(fa[c]+fb[c]>0){
jied=0;
ll ara=(fa[c]*m+fb[c]*n-fa[c]*fb[c])%P;
ll niet,f,tmp;
for(ll i=0;i<=fa[c];++i){
for(ll j=0;j<=fb[c];++j){
niet=i*m+j*n-i*j;
f=(((i+j)&1)==1?-1:1);
tmp=C(fa[c],i)*C(fb[c],j)%P*qpow(c+1,(ara-niet)%P)%P*qpow(c,niet)%P;
// printf("值=%d 行=%lld/%lld 列=%lld/%lld 组合=%lld\n",c,j,fb[c],i,fa[c],f*tmp);
jied=(jied+tmp*f+P)%P;
}
}
n-=fa[c],m-=fb[c];
ans=ans*jied%P;
}
}
printf("%lld",ans);
return 0;
}
T3 [ JOI Open 2018] 冒泡排序 2
题目描述
冒泡排序是一个对序列排序的算法。现在我们要将一个长度为 \(N\) 的序列 \(A_0,A_1,\ldots ,A_{N-1}\) 按不降顺序排序。当两个相邻的数没有按正确顺序排列时,冒泡排序会交换这两个数的位置。每次扫描这个序列就进行这种交换。更确切地说,在一趟扫描中,对于 \(i=0,1,\ldots ,N-2\),并按这个顺序,如果 \(A_i>A_{i+1}\),那么我们就交换这两个数的位置。众所周知任何序列经过有限趟扫描后一定可以按非降顺序排好序。对于一个序列 \(A\),我们定义用冒泡排序的扫描趟数为使用如上算法使得 \(A\) 排好序的情况下所扫描的趟数。
JOI 君有一个长度为 \(N\) 的序列 \(A\)。他打算处理 \(Q\) 次修改 \(A\) 的值的询问。更明确地说,在第 \((j+1)\ (0\le j\le Q-1)\) 次询问,\(A_{X_j}\) 的值会变为 \(V_j\)。
JOI 君想知道处理每次修改之后,用冒泡排序的扫描趟数。
思路分析
维护每个数之前的比他大的数的个数,全局最大值就是答案。
为啥呢?这些比他大的数都要到他右边去,一趟最多带一个过去,所以个数就是趟数。
考虑 \(f(i) = i - g(i)\) ,其中 \(g(i)\) 表示第 \(1\) ~ \(i\) 个数中 \(≤ a_i\) 的个数。而如果有两个数 \(i < j, a_i ≥ a_j\) ,显然 \(f (i) < f (j)\),即 \(f(i)\) 不可能成为答案。于是可以将 \(g(i)\) 的定义改为所有数中 \(≤ a_i\) 的个数,这只会导致原本就不可能成为答案的某些 \(f(i)\) 变小,不会影响答案 \(max\{f(i)\}\) 。
修改定义后的 \(f(i) = i - g(i)\) 只需要用一棵权值线段树进行维护,时间复杂度 \(O(n \log n)\) 。
接下来考虑怎么维护权值线段树。
我们只需要维护每一个数字的 ƒ 就行了。具体地,我们发现删除一个数时,这个数对应的线段树节点存储的 \(f\) 需要减去这个数在原数组中的位置,并将值大于等于自己的所有数的 \(f\) 加上 \(1\)。加入一个数时,这个数对应的线段树节点存储的 \(f\) 需要加上这个数在原数组中的位置,再给大于等于自己的所有数的 \(f\) 减上 \(1\) 。
展开代码
#include<bits/stdc++.h>
using namespace std;
#define N 1500005
#define time tptptp
#define fs fsyfsy
#define tm tmmtmt
#define x0 weti36
#define x1 fdsuhg
#define y0 igriog
#define y1 nsoggn
#define ws jioreo
#define id gjidfi
#define prev fjfjfj
#define next gjoire
#define at aegvji
#define in inindf
#define qi russia
#define qn etytyj
#define rank hfwehi
#define heap hiphop
int m;
int n;
pair<int,int>a[N],poi[N*5];
int at[N],pcnt,qi[N],qn[N],pre[N];
struct node{
int l,r;
int mx,add;
}tr[N*32];
#define lson(x) ((x)<<1)
#define rson(x) (lson(x)|1)
#define Lson(x) tr[lson(x)]
#define Rson(x) tr[rson(x)]
struct TREE{
void pushdown(int x){
if(tr[x].l==tr[x].r)return;
if(tr[x].add==0)return;
Lson(x).add+=tr[x].add;
Rson(x).add+=tr[x].add;
Lson(x).mx+=tr[x].add;
Rson(x).mx+=tr[x].add;
tr[x].add=0;
return;
}
void pushup(int x){
if(tr[x].l==tr[x].r)return;
tr[x].mx=max(Lson(x).mx,Rson(x).mx);
}
void build(int x,int l,int r){
tr[x].l=l;tr[x].r=r;
if(l==r)return;
int mid=(l+r)>>1;
build(lson(x),l,mid);
build(rson(x),mid+1,r);
pushup(x);
return;
}
void update(int x,int l,int r,int val){
if(tr[x].l>r||tr[x].r<l)return;
if(tr[x].l>=l&&tr[x].r<=r){
tr[x].add+=val;
tr[x].mx+=val;
return;
}
pushdown(x);
update(lson(x),l,r,val);
update(rson(x),l,r,val);
pushup(x);
}
void out(int x,int lev){
pushdown(x);
for(int i=1;i<lev;++i)putchar('\t');
printf("[%d,%d] : mx=%d\n",tr[x].l,tr[x].r,tr[x].mx);
if(tr[x].l==tr[x].r)return;
out(lson(x),lev+1);
out(rson(x),lev+1);
return;
}
}tree;
int main(){
// freopen("sort.in","r",stdin);
// freopen("sort.out","w",stdout);
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&a[i].first);
a[i].second=i;
poi[++pcnt]=a[i];
}
for(int i=1;i<=m;++i){
scanf("%d %d",&qi[i],&qn[i]);
++ qi[i];
poi[++pcnt]=make_pair(qn[i],i+n);
}
sort(poi+1,poi+pcnt+1);
for(int i=1;i<=pcnt;++i){
if(poi[i].first==poi[i-1].first){
pre[i]=pre[i-1];
}else{
pre[i]=i;
}
}
tree.build(1,1,pcnt);
for(int i=1;i<=n;++i){
int id=lower_bound(poi+1,poi+pcnt+1,a[i])-poi;
tree.update(1,id,id,i);
tree.update(1,pre[id],pcnt,-1);
}
// tree.out(1,1);
for(int i=1;i<=m;++i){
int id=lower_bound(poi+1,poi+pcnt+1,a[qi[i]])-poi;
tree.update(1,id,id,-qi[i]);
tree.update(1,pre[id],pcnt,1);
a[qi[i]]=make_pair(qn[i],n+i);
id=lower_bound(poi+1,poi+pcnt+1,a[qi[i]])-poi;
tree.update(1,id,id,qi[i]);
tree.update(1,pre[id],pcnt,-1);
printf("%d\n",tr[1].mx);
// tree.out(1,1);
}
return 0;
}
return;