【考试总结】2022-03-24
小 F 与游戏
\(A\) 序列 \(1\) 球后面的元素的一部分将在 \(B\) 序列中出现在 \(1\) 前面
将 \(A\) 序列里面在 \(1\) 后面的数字涂上黑色,前面的数字涂上白色
在 \(B\) 序列中前 \(k\) 个数字中的黑数和白数构成两个下降子序列,在第 \(k\) 个数后面的数满足是同色的,而且前 \(k-1\) 个数字中两个下降子序列末尾较大的子序列的末尾一定大于 \(1\) 后面数字的最大值
避免重复计数,我们强制两个下降序列的值域没有除较大者后缀和较小者前缀交之外的交
枚举 \(f_{i,j}\) 表示长度为 \(i\) 能分成两个下降子序列的序列中较大末尾者末尾为 \(j\) 的方案数,这里值域是不连续的,但是将位次作为新的权值就可以变成连续值域了
对于长度为 \(i\) 能分成两个递降序列的序列,如果 \(1\) 是序列末尾的元素,可以去掉之再计算,推广而言,可以删掉序列末尾形如 \(d,d-1,\dots,2,1\) 的连续子段
枚举 \(d\),设 \(g_{i,k}\) 表示末尾长度为 \(i\),末尾权值为 \(k\) 的可以分成两个递降序列的序列数,那么存在如下事实:
\[f_{i,k}=\sum_{len根据 【冒泡排序】 一题的结论:\(g_{i-len,k-len}\) 的前缀和可以表达为卡特兰数的形式,剩下的工作就是组合数行求和以及范德蒙德卷积了
记得将得数 \(\binom{n-k+2}{k-1}-\binom{n-k+2}{k-2}\) 乘 \([k+1,n]\) 中球的放置方案数 \(2^{\max(n-k-1,0)}\)
Code Display
const int N=1e6+10;
int n,ans,k,fac[N],ifac[N];
inline int C(int n,int m){return m<0||n=1;--i) ifac[i-1]=mul(ifac[i],i);
print(mul(ksm(2,max(0ll,n-k-1)),del(C(n+k-2,k-1),C(n+k-2,k-2))));
return 0;
}
小 Z 与函数
考虑维护 \(1\sim i\) 的递降序列,那么考虑加入 \(a_{i+1}\) 的时候序列会找到一个首个元素小于 \(a_{i+1}\) 的极长递降序列,序列中元素的 vs
置 \(1\) 并产生一次交换
尝试将答案分成顺序对和 vs
元素为 \(1\) 的 \(i\) 的数量,不重顺序对容易使用树状数组解决,尝试观察 vs
元素计算在暴力过程中的冗余
这其实就是如果之前有 \(yvs=1
那么 \(x\) 对该位置的 vs
的变化不产生贡献
所以对于每个元素 \(x\) 的第一次出现找到后面第一个大于 \(x\) 的数字的出现位置 \(i\),通过维护 \(x\) 的排名可以得到 \(x\) 在 \(1\sim i-1\) 都排好序之后的位置 \(p\),这时候 \(y\) 给 \(p\) 的 vs
置 \(1\)
那么使用 std::set
维护查找过程,如果小于 \(a_i\) 的元素 \(x\) 所在的位置 vs=1
那么它被换到的其它位置也不再能产生贡献了,删掉即可
Code Display
const int N=5e5+10;
int ans,n,a[N];
bool vis[N];
struct Fenwick_Tree{
int c[N];
inline void insert(int x,int v){for(;x<=n;x+=x&(-x)) c[x]++; return ;}
inline int query(int x){int res=0; for(;x;x-=x&(-x)) res+=c[x]; return res;}
inline void clear(){memset(c,0,sizeof(c));}
}T,rk;
signed main(){
freopen("function.in","r",stdin); freopen("function.out","w",stdout);
int Test=read(); while(Test--){
memset(vis,0,sizeof(vis));
T.clear(); rk.clear();
n=read();
int ans=0;
set st;
for(int i=1;i<=n;++i){
a[i]=read();
set::iterator iter=st.begin();
for(;iter!=st.end();){
if(*iter>=a[i]) break;
int cur=i-rk.query(*iter);
++iter;
if(!vis[cur]) vis[cur]=1,++ans;
else st.erase(prev(iter));
}
ans+=T.query(a[i]-1);
if(T.query(a[i])==T.query(a[i]-1)){
T.insert(a[i],1);
st.insert(a[i]);
}
rk.insert(a[i],1);
print(ans);
} putchar('\n');
}
return 0;
}
小 W 与骑士
列出两个二元一次方程之后判断是否系数矩阵线性相关:
-
只考察直线上的点即可,那么 \((n,m)\) 不和 \(\vec{A}\) 共线方案数也就是 \(0\),否则将直线上的点 \(V\) 和 \(V+\vec{A},V+\vec{B}\) 连边,如果 \((0,0)\) 到 \((n,m)\) 不连通方案数也是 \(0\),如果路径上存在强连通分量那么方案数就是无穷
存在方案数且有限的情况使用平凡 \(\rm DP\) 解决即可,注意可能走到的点的值域开到 \(|S|^2\)
-
矩阵满秩:那么可以将所有的障碍点和 \((n,m)\) 使用唯一分解定理得到走到之使用的 \(\vec{A},\vec{B}\) 的数量
使用满足 \(x\vec{A}+y\vec{B}=(i,j)\) 的数对 \((x,y)\) 来代替 \((i,j)\),那么使用钦定经过一些非法点的容斥即可计算答案
这个部分里面方案数一定是有限的,为 \(0\) 的条件也容易发现是 \((n,m)\) 唯一分解系数不全非负
Code Display
const int N=2010,U=1000;
int fac[N*1000],inv[N*1000],ifac[N*1000];
int n,m,K,ax,ay,bx,by;
inline int C(int x,int y){ return x G[N],revG[N];
inline void tarjan(int x){
dfn[x]=low[x]=++tim; stk[++top]=x; vis[x]=1;
for(auto t:G[x]){
revG[t].emplace_back(x);
if(!dfn[t]) tarjan(t),ckmin(low[x],low[t]);
else if(vis[t]) ckmin(low[x],dfn[t]);
}
if(low[x]==dfn[x]){
scc++;
do{
siz[scc]++;
vis[stk[top]]=0;
bel[stk[top]]=scc;
}while(stk[top--]!=x);
} return ;
}
bool flag;
inline void check_inf(int x){
if(vis[x]==1) return ; vis[x]=1;
if(flag||!dfn[x]) return ;
if(siz[bel[x]]>1) return flag=1,void();
for(auto t:revG[x]) check_inf(t);
return ;
}
vector ban(2010);
vector dp(3000010);
inline int DP(int x){
if(x>3000000||x<0||ban[x-1000000+1000]) return 0;
if(x==n+1e6) return 1;
if(~dp[x]) return dp[x];
int sum=add(DP(x+ax),DP(x+bx));
return dp[x]=sum;
}
signed main(){
freopen("knight.in","r",stdin); freopen("knight.out","w",stdout);
fac[0]=inv[0]=1;
for(int i=1;i<=2000000;++i) fac[i]=mul(fac[i-1],i);
ifac[2000000]=ksm(fac[2000000],mod-2);
for(int i=2000000;i>=1;--i) ifac[i-1]=mul(ifac[i],i),inv[i]=mul(ifac[i],fac[i-1]);
int T=read(); while(T--){
n=read(),m=read(),K=read();
ax=read(),ay=read(),bx=read(),by=read();
if((ax==0&&ay==0)&&(bx==0&&by==0)){
if(n==0&&m==0) puts("-1");
else puts("0");
continue;
}
if(n==0&&m==0){
if((ax==0&&ay==0)||(bx==0&&by==0)) puts("-1");
else puts("1");
continue;
}
bool swp=0;
if(!n){
swp=1;
swap(ax,ay);
swap(n,m);
swap(bx,by);
}
if(ax*by==ay*bx){
if(n*ay!=m*ax){puts("0"); continue;}
rep(i,0,2000) ban[i]=0;
for(int i=1;i<=K;++i){
int x=read(),y=read();
if(swp) swap(x,y);
if(ax*y==ay*x&&x+U>=0&&x+U<=2000) ban[x+U]=1;
}
memset(stk,0,sizeof(stk));
memset(siz,0,sizeof(siz));
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(vis,0,sizeof(vis));
memset(bel,0,sizeof(bel));
scc=tim=top=0;
for(int i=0;i<=2000;++i) revG[i].clear(),G[i].clear();
for(int i=0;i<=2000;++i) if(!ban[i]){
if(i+ax>=0&&i+ax<=2000&&!ban[i+ax]) G[i].push_back(i+ax);
if(i+bx>=0&&i+bx<=2000&&!ban[i+bx]) G[i].push_back(i+bx);
}
tarjan(U);
if(!dfn[n+U]){puts("0"); continue;}
flag=0;
check_inf(n+U);
if(flag){puts("-1"); continue;}
if(ax==bx){puts("1"); continue;}
for(auto &t:dp) t=-1;
print(DP(1e6));
}else{
vector > obs;
auto ins=[&](int n,int m){
if((n*ay-m*ax)%abs(bx*ay-by*ax)) return ;
int j=(n*ay-m*ax)/(bx*ay-by*ax);
if((n-bx*j)%abs(ax)) return ;
int i=(n-bx*j)/ax;
if(i<0||j<0) return ;
obs.emplace_back(i,j);
};
ins(0,0);
ins(n,m);
pair tar=obs.back();
if(obs.size()<2){puts("0"); continue;}
for(int i=1;i<=K;++i){
int x=read(),y=read();
if(swp) swap(x,y);
ins(x,y);
}
sort(obs.begin(),obs.end());
vector dp(obs.size());
dp[0]=mod-1;
for(int i=1;i