E
Content
给一个 \(H\times W\) 的棋盘,\((x_1,y_1)\) 上有一个车(一次可以走到所在横行竖列的任意一格),求它经过 \(K\) 步走到 \((x_2,y_2)\) 的方案数对 \(998244353\) 取模。
Sol
对每一步单独考虑。
分为:
\(S_{0,0}=\{(x_1,y_1)\}\)
\(S_{0, 1} = \{ (x_1, y) \, \vert \, y \neq y_1 \}\)
\(S_{1, 0} = \{ (x, y_1) \, \vert \, x \neq x_1 \}\)
\(S_{1, 1} = \{ (x, y) \, \vert \, x \neq x_1 \land y \neq y_1 \}\)
四种情况,每种情况集合里的所有点的方案数都是相同的。
设 \(g_{k,a,b}\) 为情况 \(S_{a,b}\) 的方案数,有:
\(g_{k,0,0}=g_{k−1,0,1}+g_{k−1,1,0}\)
\(g_{k, 0, 1} = (M - 1) g_{k - 1, 0, 0} + (M - 2) g_{k - 1, 0, 1} + g_{k - 1, 1, 1}\)
\(g_{k, 1, 0} = (H - 1) g_{k - 1, 0, 0} + (H - 2) g_{k - 1, 1, 0} + g_{k - 1, 1, 1}\)
\(g_{k, 1, 1} = (H - 1)g_{k - 1, 0, 1} + (M - 1)g_{k - 1, 1, 0} + (H - 2 + M - 2) g_{k - 1, 1, 1}\)
设 \((x_2,y_2)\) 在集合 \(S_{a,b}\) 内,则答案为 \(\frac{g_{k,a,b}}{|S_{a,b}|}\)。
这就是个矩阵幂,可以用矩阵加速优化至 \(O(logK)\)。
Code
//O(K)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
int n,m,K;
int g[2][2][2];
inline int qpow(int x,int y,int res=1){
for(;y;y>>=1){
if(y&1) res=res*x%mod;
x=x*x%mod;
}
return res;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m>>K;
int cnt[2][2]={{1,m-1},{n-1,n*m-n-m+1}};
g[0][0][0]=1;
for(int i=1;i<=K;++i){
g[i&1][0][0]=(g[!(i&1)][0][1]+g[!(i&1)][1][0])%mod;
g[i&1][0][1]=((m-1)*g[!(i&1)][0][0]%mod+(m-2)*g[!(i&1)][0][1]%mod+g[!(i&1)][1][1])%mod;
g[i&1][1][0]=((n-1)*g[!(i&1)][0][0]%mod+(n-2)*g[!(i&1)][1][0]%mod+g[!(i&1)][1][1])%mod;
g[i&1][1][1]=((n-1)*g[!(i&1)][0][1]%mod+(m-1)*g[!(i&1)][1][0]%mod+(n-2+m-2)*g[!(i&1)][1][1]%mod)%mod;
}
int x1,x2,y1,y2;
cin>>x1>>y1>>x2>>y2;
bool a=x1!=x2,b=y1!=y2;
cout<<g[K&1][a][b]*qpow(cnt[a][b]%mod,mod-2)%mod;
return 0;
}
关于矩阵快速幂做法,目标矩阵如下
\[\begin{bmatrix} g_{0,0} & g_{0,1} & g_{1,0} & g_{1,1} \end{bmatrix} \]构造如下初始矩阵
\[\begin{bmatrix} 0 & m-1 & n-1 & 0\\ 1 & m-2 & 0 & n-1\\ 1 & 0 & n-2 & m-1\\ 0 & 1 & 1 & n-2+m-2 \end{bmatrix} \]//O(logK)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4,mod=998244353;
int n,m,K;
struct mat{int r,c,a[N][N];};
mat operator *(mat a,mat b){
mat c;memset(&c,0,sizeof(c));
for(int i=0;i<a.r;++i)
for(int j=0;j<b.c;++j)
for(int k=0;k<a.c;++k)
c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod;
c.r=a.r,c.c=b.c;
return c;
}
inline int qpow(int x,int y,int res=1){
for(;y;y>>=1){
if(y&1) res=res*x%mod;
x=x*x%mod;
}
return res;
}
inline void qpow(){
int y;
cin>>y;
int G[4][4]={{0,m-1,n-1,0},{1,m-2,0,n-1},{1,0,n-2,m-1},{0,1,1,n-2+m-2}};
mat res,x,ans;
memset(&ans,0,sizeof(ans));
memset(&x,0,sizeof(x));
memset(&res,0,sizeof(res));
x.r=4,x.c=4;
res.r=4,res.c=4;
for(int i=0;i<4;++i){
res.a[i][i]=1;
for(int j=0;j<4;++j) x.a[i][j]=G[i][j];
}
for(;y;y>>=1){
if(y&1) res=res*x;
x=x*x;
}
ans.r=1,ans.c=4;
ans.a[0][0]=1;
ans=ans*res;
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
int a=((x1!=x2)<<1)+(y1!=y2);
int cnt=(x1!=x2?n-1:1)*(y1!=y2?m-1:1)%mod;
cout<<ans.a[0][a]*qpow(cnt,mod-2)%mod;
}
signed main(){
cin>>n>>m;
qpow();
return 0;
}
F
Content
给两个长度为 \(N\) 的序列 \(A,B\),对于序列 \(A\) 有如下两种操作:
- 选一个 \(i \in [1,N]\),花费 \(X\) 日元使 \(A_i \pm 1\)
- 选一个 \(i\in[1,N-1]\),花费 \(Y\) 日元交换 \(A_i,A_{i+1}\)
求最少花费多少钱使得 \(A,B\) 序列相同。
Sol
我们可以把所有单点修改操作放到一起处理,把所有的交换操作放到一起处理,这样是无影响的,且确定了一个位置,以后都不会改它。
我们设最终答案处理完交换操作后的顺序由 \(A_1,A_2,...,A_N\) 变为了 \(A_{P_1},A_{P_2},...A_{P_N}\),\(P\) 是 \([1,N]\) 的一个排列,每次可以交换相邻两个数,那么最少只用交换 \(P\) 的逆序对个数次(一个套路的结论)
考虑把逆序对的贡献分到每个位置上,方便我们和单点修改一起按位考虑算贡献。假设我们按下标从小到大确定每个位置,不难发现当前要确定的这一位对逆序对的贡献为:这一位放的位置之前有几个还没有放的(这些位置只能是之后放入的更大的元素,又因为这些位置比当前位置前,所以构成逆序对)。
于是做法就来了,设 \(S\) 为一个 \(n\) 位的二进制数,为 \(1\) 的位表示这一位被确定了,反之没确定,\(f(S)\) 表示状态为 \(S\) 的最小花费。
我们按从小到大的顺序放元素 \(P\) 中,枚举它要放到哪个位置,设函数 \(inv(S,i)\) 为状态 \(S\) 中前 \(i-1\) 位中有多少位没被确定,则有:
\(f(S')=f(S)+|A_i-B_{now}|\times X+inv(S,i)\times Y\)
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF=1e18;
int n,X,Y;
int a[20],b[20];
int f[1<<18];
inline int inv(int S,int x,int res=0){
for(int i=1;i<x;++i) res+=!(S&(1<<i-1));
return res;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>X>>Y;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i) cin>>b[i];
f[0]=0;
for(int i=1;i<1<<n;++i) f[i]=INF;
for(int i=0;i<1<<n;++i){
int cnt=__builtin_popcount(i);
for(int j=1;j<=n;++j){
if(i&(1<<j-1)) continue;
f[i|(1<<j-1)]=min(f[i|(1<<j-1)],f[i]+abs(a[j]-b[cnt+1])*X+inv(i,j)*Y);
}
}
cout<<f[(1<<n)-1];
return 0;
}