毒瘤出题人,史诗加强 AGC 的 F……

然而我连原题都不会,所以只学了原题做法。

翻译一下题意就是给定一张循环网格图,求经过 \((0,0)\) 的闭合回路条数。

由于网格图中每一个位置都等价,所以转化成求所有闭合回路长度之和。

考虑回路的形态,由于必须要走到右边界/下边界才可以回到起点,所以相当于是从左边界/上边界的若干个插头走到右边界/下边界的若干个插头且路径不交的方案数。

这是经典的 \(\text{LGV Lemma}\) 的形式。显然插头只会按照顺序匹配,否则一定有交叉。我们对相对的行和相对的列的插头编上同样的编号(这样子做的原因以下会说),那么 \((-1)^{\mathrm{inv}(p)}\) 始终是 \((-1)^{ij}\)。

有些插头的放置方案会导致连出的不止一个环,然而由于插头总是按顺序匹配,所以连出一个环当且仅当 \(\gcd(r,c)=1\),其中 \(r,c\) 是放插头的行数和列数。

直接做对于每一个行的子集和列的子集求答案,指数级显然不现实。

然而一组子集的贡献只与 \(r,c\) 有关,所以我们考虑计算一个改造一下原 \(\text{DAG}\)。

对每一个插头新建原汇点,然后加入额外边,如果走新加入的额外边代表不选这个行/列,否则乘上一个占位符 \(x/y\)。

加入额外边逆序对数看起来会变,然而由于我们相对的插头编号一样,走额外边相当于加入自环,排列的奇偶性又只跟环的个数及环长有关,所以加入一些自环永远不会改变排列的奇偶性。

接下来总不至于带着占位符求行列式吧?二元多项式相乘还要求逆想想就可怕。

然而我们可以求出这个二元多项式 \((n+1)\times (m+1)\) 个点值进行二元多项式插值。

具体地,我们仿照一元拉格朗日插值构造二元多项式 \(F\):

\[F(x,y)=\sum_{i=0}^n \sum_{j=0}^m z_{i,j} \prod_{i'\neq i} \frac{x-x_{i'}}{x_i-x_{i'}} \prod_{j'\neq j} \frac{y-y_{j'}}{y_j-y_{j'}}
\]

其中 \(z_{i,j}=F(x_i,x_j)\)。

具体怎么计算这个式子呢?

我们考虑拉格朗日插值类似 \(\text{IDFT}\) 是一个线性变换,相当于乘上了一个范德蒙德矩阵的逆矩阵。

类似二元 \(\text{DFT}\) 或者 \(\text{FWT}\) 处理高维多项式的思想,各维之间独立,那么我们先对一维进行线性变换,然后再对进行线性变换后的结果对另一维执行线性变换。

就比如说这里 \(z_{i,j}\),我们先把 \(z\) 看成 \(n+1\) 个行向量,对这个行向量进行拉格朗日插值,把插值得到的系数塞回原来的位置,然后把 \(z\) 竖着的列看成列向量,再拉格朗日插值一遍塞回原来的位置。此时 \(z\) 就变成了二元多项式的系数表示。

这启发我们 \(\text{FWT}\) 的核心思想就是线性变换关于各维独立,所以逐维进行线性变换就是对的,对一维做完后把结果塞回原来的位置再做另一维。

还有不清楚的可以直接推式子:

\[F(x,y)=\sum_{i=0}^n (\sum_{j=0}^m z_{i,j} \prod_{j'\neq j} \frac{y-y_{j'}}{y_j-y_{j'}}) \prod_{i'\neq i} \frac{x-x_{i'}}{x_i-x_{i'}}
\]

发现括号内的东西就是一元拉插的式子,设进行插值后的结果是 \(G_i(y)=\sum_{j=0}^m g_{i,j} y^j\)。

\[F(x,y)=\sum_{i=0}^n \sum_{j=0}^m g_{i,j} y^j \prod_{i'\neq i} \frac{x-x_{i'}}{x_i-x_{i'}}
=\sum_{j=0}^m y^j (\sum_{i=0}^n g_{i,j} \prod_{i'\neq i} \frac{x-x_{i'}}{x_i-x_{i'}})
\]

括号又是一元拉插的式子,再插一下就做完了。

#include <cstdio>
#include <algorithm>
using namespace std;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=(x<<1)+(x<<3)+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
const int P=998244353;
const int N=103,Lim=1003;
typedef long long ll;
int n,m;
int fac[Lim],fiv[Lim],inv[Lim];
int qp(int a,int b=P-2){
int res=1;
while(b){
if(b&1) res=(ll)res*a%P;
a=(ll)a*a%P;b>>=1;
}
return res;
}
int comb(int a,int b){
return (ll)fac[a+b]*fiv[a]%P*fiv[b]%P;
}
void inc(int &x,int v){if((x+=v)>=P) x-=P;}
void dec(int &x,int v){if((x-=v)<0) x+=P;}
int a[N][N];
int deter(int len){
bool sgn=0;
for(int i=1;i<=len;++i){
int p=i;
while(p<=len&&!a[p][i]) ++p;
if(p!=i){
for(int j=i;j<=len;++j) swap(a[i][j],a[p][j]);
sgn=!sgn;
}
int iv=qp(a[i][i]);
for(int j=i+1;j<=len;++j){
int tmp=(ll)iv*a[j][i]%P;
for(int k=i;k<=len;++k)
dec(a[j][k],(ll)tmp*a[i][k]%P);
}
}
int res=1;
if(sgn) res=P-1;
for(int i=1;i<=len;++i) res=(ll)res*a[i][i]%P;
for(int i=1;i<=len;++i)
for(int j=1;j<=len;++j) a[i][j]=0;
return res;
}
int solve(int x,int y){
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j)
a[i][j+n]=(ll)comb(n-i,j-1)*x%P;
for(int j=i;j<=n;++j)
a[i][j]=((ll)comb(j-i,m-1)*x+(i==j))%P;
}
for(int i=1;i<=m;++i){
for(int j=1;j<=n;++j)
a[i+n][j]=(ll)comb(j-1,m-i)*y%P;
for(int j=i;j<=m;++j)
a[i+n][j+n]=((ll)comb(n-1,j-i)*y+(i==j))%P;
}
return deter(n+m);
}
void init(int len){
inv[1]=1;
for(int i=2;i<=len;++i) inv[i]=(ll)inv[P%i]*(P-P/i)%P;
fac[0]=1;
for(int i=1;i<=len;++i) fac[i]=(ll)fac[i-1]*i%P;
fiv[len]=qp(fac[len]);
for(int i=len;i;--i) fiv[i-1]=(ll)fiv[i]*i%P;
}
int b[N][N];
int c[N],d[N],e[N],sz;
void mul(int v,int s){
for(int i=++sz;i;--i){
d[i]=(ll)d[i]*v%P;
inc(d[i],d[i-1]);
}
d[0]=(ll)d[0]*v%P;
for(int i=0;i<=sz;++i) d[i]=(ll)d[i]*s%P;
}
void lagrange(int len){
for(int i=0;i<=len;++i) e[i]=0;
for(int i=0;i<=len;++i){
d[sz=0]=c[i];
for(int j=0;j<i;++j) mul(P-j,inv[i-j]);
for(int j=len;j>i;--j) mul(P-j,P-inv[j-i]);
for(int j=0;j<=len;++j) inc(e[j],d[j]);
while(sz) d[sz--]=0;
}
}
int gcd(int a,int b){
if(!b) return a;
return gcd(b,a%b);
}
int main(){
init(1000);
n=read();m=read();
if(n==1||m==1){puts("2");return 0;}
if(n==2){printf("%d\n",(qp(2,m-1)+qp(2,m-2)+1)%P);return 0;}
if(m==2){printf("%d\n",(qp(2,n-1)+qp(2,n-2)+1)%P);return 0;}
for(int i=0;i<=n;++i){
for(int j=0;j<=m;++j) c[j]=solve(i,j);
lagrange(m);
for(int j=0;j<=m;++j) b[i][j]=e[j];
}
for(int j=0;j<=m;++j){
for(int i=0;i<=n;++i) c[i]=b[i][j];
lagrange(n);
for(int i=0;i<=n;++i) b[i][j]=e[i];
}
int res=0;
for(int i=0;i<=n;++i)
for(int j=0;j<=m;++j){
if(!i&&!j) continue;
if(gcd(i,j)==1)
if(i&j&1) dec(res,(ll)(i*m+j*n)*b[i][j]%P);
else inc(res,(ll)(i*m+j*n)*b[i][j]%P);
}
res=(ll)res*qp(n*m)%P;
printf("%d\n",res);
return 0;
}

拉插实现的比较丑,只写了 \(O(n^4)\),可以做到更优复杂度但没必要。

AGC061 F Perfect String的更多相关文章

  1. Codeforces Round #582 (Div. 3) F. Unstable String Sort

    传送门 题意: 你需要输出一个长度为n的字符序列(由小写字母组成),且这个字符串中至少包含k个不同的字符.另外题目还有要求:给你两个长度为p和q的序列,设字符序列存在s中 那么就会有s[Pi]< ...

  2. CF - 1117 F Crisp String

    题目传送门 题解: 枚举非法对. 如果 ‘a'  和 ’b' 不能相邻的话,那么删除 'a' 'b'之间的字符就是非法操作了. 假设题目给定的字符串为 "acdbe",所以删除cd ...

  3. String字符串的完美度

    题目详情: 我们要给每个字母配一个1-26之间的整数,具体怎么分配由你决定,但不同字母的完美度不同, 而一个字符串的完美度等于它里面所有字母的完美度之和,且不在乎字母大小写,也就是说字母F和f的完美度 ...

  4. 关于string.format() 转

    string.format()函数用来生成具有特定格式的字符串,这个函数有两个参数,第一个参数为格式化串:由指示符和控制格式的字符组成.第二个参数是对应格式中每个代号的各种数据. 格式字符串可能包含以 ...

  5. 【C#】 格式化说明符 string.Format WriteLine

    定义 格式说明符的语法由3个字段组成:索引号.对齐说明符和格式字段.String.Format和WriteLine都遵守同样的格式化规则. 对齐说明符 对齐说明符表示了字段中字符的最小宽度.对齐说明符 ...

  6. String、StringBuffer、StringBuilder的一些小经验……

    一说String.StringBuffer和StringBuilder,想必大家都很熟悉,这三者经常在我们的面试题中出现,我也是看到了关于这三个的经典面试题,才触动了我之前工作中的一些经历,故而根据我 ...

  7. java中遇到过的String的一些特性

    1.string对象是final的? String str="asdfdf"; str.replace("as",""); System.o ...

  8. C++中int,float,string,char*的转换(待续)

    //float转string char a[100]; float b = 1.234; sprintf(a, "%f", b); string result(a); //int转 ...

  9. Java中的Scanner类和String类

    1:Scanner的使用(了解)    (1)在JDK5以后出现的用于键盘录入数据的类. (2)构造方法: A:讲解了System.in这个东西.            它其实是标准的输入流,对应于键 ...

  10. 【Java】Java创建String时,什么情况放进String Pool?

    对Java创建String是否放入String pool作代码性的试验. 参考的优秀文章 JAVA面试题解惑系列(二)——到底创建了几个String对象? public String(String o ...

随机推荐

  1. TFlite 多线程并行

    import numpy as np from multiprocessing import Process, Queue def process_data(data, model_TSNet, ts ...

  2. mysql数据迁移,通用windows->linux,linux->windows

    1. 将用到的数据库文件夹直接拷贝到目标文件夹,mysql5.7在linux中默认在var/lib/mysql,将data下ibdata1也要拷贝进去 2. linux下需要将所有者root改为mys ...

  3. Python第一次作业(20220909)

    实例01:根据身高.体重计算BMI指数 ①:分别定义两个变量"height"和"weight",用于储存身高(单位:米)和体重(单位:千克).使用内置的prin ...

  4. Switch问题

    package com.company;public class Main { public static void main(String[] args) { Income[] incomes = ...

  5. Pinia使用技巧

    vue2使用的vuex,是一个状态管理器,现在vue3出了最新的pinia,今年偿试一下. 首先是安装,这里要注意一下,有一个持久化插件,如果不用的话,页面一刷新,状态会消失. npm install ...

  6. Eigen 中的 conservativeResize 和 resize 操作

    Eigen 中的 conservativeResize 和 resize 操作 对于能够改变大小的动态矩阵,一般会有 resize() 操作. resize() 如果不改变原矩阵的大小,则原矩阵大小和 ...

  7. 【事故】记一次意外把企业项目放到GitHub并被fork,如何使用DMCA下架政策保障隐私

    前言 缘由 在一个月黑风高的夜晚,正准备休息的我突然接到之前外包老总的亲切问候.一顿输出才知道三年前为了搭建流程化部署,将甲方的测试代码放到github上后忘记删除.现在被甲方的代码扫描机制扫到,并且 ...

  8. apt-get update报“Temporary failure resolving '***.com/cn'

    解决办法: 1.打开/etc/resolv.conf: $sudo vim /etc/resolv.conf 2.修改nameserver即DNS服务器: 我这里使用腾讯云和阿里云的DNS 加入: n ...

  9. tidyr包几个函数的用法

    在R语言中,tidyr主要提供了一个类似Excel中数据透视表 (pivottable)的功能; gather和spread函数将数据在长格式和宽格式之间相互转化,应用在比如稀疏矩阵和稠密矩阵之间的转 ...

  10. pysimplegui之画布,图形,表格和树结构元素

    画布元素 在我看来,tkinter Canvas 小部件是 tkinter 小部件中功能最强大的.虽然我尽我所能将用户与任何与 tkinter 相关的东西完全隔离,但 Canvas 元素是一个例外.它 ...