AGC061 F Perfect String
毒瘤出题人,史诗加强 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\):
\]
其中 \(z_{i,j}=F(x_i,x_j)\)。
具体怎么计算这个式子呢?
我们考虑拉格朗日插值类似 \(\text{IDFT}\) 是一个线性变换,相当于乘上了一个范德蒙德矩阵的逆矩阵。
类似二元 \(\text{DFT}\) 或者 \(\text{FWT}\) 处理高维多项式的思想,各维之间独立,那么我们先对一维进行线性变换,然后再对进行线性变换后的结果对另一维执行线性变换。
就比如说这里 \(z_{i,j}\),我们先把 \(z\) 看成 \(n+1\) 个行向量,对这个行向量进行拉格朗日插值,把插值得到的系数塞回原来的位置,然后把 \(z\) 竖着的列看成列向量,再拉格朗日插值一遍塞回原来的位置。此时 \(z\) 就变成了二元多项式的系数表示。
这启发我们 \(\text{FWT}\) 的核心思想就是线性变换关于各维独立,所以逐维进行线性变换就是对的,对一维做完后把结果塞回原来的位置再做另一维。
还有不清楚的可以直接推式子:
\]
发现括号内的东西就是一元拉插的式子,设进行插值后的结果是 \(G_i(y)=\sum_{j=0}^m g_{i,j} y^j\)。
=\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的更多相关文章
- Codeforces Round #582 (Div. 3) F. Unstable String Sort
传送门 题意: 你需要输出一个长度为n的字符序列(由小写字母组成),且这个字符串中至少包含k个不同的字符.另外题目还有要求:给你两个长度为p和q的序列,设字符序列存在s中 那么就会有s[Pi]< ...
- CF - 1117 F Crisp String
题目传送门 题解: 枚举非法对. 如果 ‘a' 和 ’b' 不能相邻的话,那么删除 'a' 'b'之间的字符就是非法操作了. 假设题目给定的字符串为 "acdbe",所以删除cd ...
- String字符串的完美度
题目详情: 我们要给每个字母配一个1-26之间的整数,具体怎么分配由你决定,但不同字母的完美度不同, 而一个字符串的完美度等于它里面所有字母的完美度之和,且不在乎字母大小写,也就是说字母F和f的完美度 ...
- 关于string.format() 转
string.format()函数用来生成具有特定格式的字符串,这个函数有两个参数,第一个参数为格式化串:由指示符和控制格式的字符组成.第二个参数是对应格式中每个代号的各种数据. 格式字符串可能包含以 ...
- 【C#】 格式化说明符 string.Format WriteLine
定义 格式说明符的语法由3个字段组成:索引号.对齐说明符和格式字段.String.Format和WriteLine都遵守同样的格式化规则. 对齐说明符 对齐说明符表示了字段中字符的最小宽度.对齐说明符 ...
- String、StringBuffer、StringBuilder的一些小经验……
一说String.StringBuffer和StringBuilder,想必大家都很熟悉,这三者经常在我们的面试题中出现,我也是看到了关于这三个的经典面试题,才触动了我之前工作中的一些经历,故而根据我 ...
- java中遇到过的String的一些特性
1.string对象是final的? String str="asdfdf"; str.replace("as",""); System.o ...
- C++中int,float,string,char*的转换(待续)
//float转string char a[100]; float b = 1.234; sprintf(a, "%f", b); string result(a); //int转 ...
- Java中的Scanner类和String类
1:Scanner的使用(了解) (1)在JDK5以后出现的用于键盘录入数据的类. (2)构造方法: A:讲解了System.in这个东西. 它其实是标准的输入流,对应于键 ...
- 【Java】Java创建String时,什么情况放进String Pool?
对Java创建String是否放入String pool作代码性的试验. 参考的优秀文章 JAVA面试题解惑系列(二)——到底创建了几个String对象? public String(String o ...
随机推荐
- webpack 3/4踩坑,我太难了,从安装、卸载、到使用,各相应的版本号,sass-loader报错-版本的原因,webpack -v 不识别,没卸载干净
-先说卸载: wabpack@4对应的每个插件的版本号都在最后 1 全局安装的话,npm uninstall webpack -g 有时候并不能卸载干净, 2 webpack -v 可判断是否安装成 ...
- 【帆吖】Java学习零基础22
Java数组
- bilibili视频常用快捷键
Esc退出全屏 Q点赞.长按三连 W投币 E收藏 D开/关弹幕 F开/关全屏 M静音 [ 多P 上一个 ] 多P下一个 Enter发弹幕 Space播放/暂停 →单次快进5s,长按倍速播放 ←快退5s ...
- vs2019配置boost库(转载)
网址:https://blog.csdn.net/qq_42214953/article/details/105087015 关于途中的执行文件,可以使用b2.exe,不用跟着教程走. 如果本来就有b ...
- Vue中 ref、$refs区别与使用
定义2个组件: 子组件ChildrenSubRef.vue: 1 <template> 2 <div> 3 4 </div> 5 </template> ...
- MarkDown基本用法学习
一级标题 语法:# +内容 二级标题1 语法:## +内容 二级标题2 三级标题 语法:### +内容 字体 加粗 语法:** +内容+ **(中间无空格) 效果:粗体 斜体 语法 * +内容+ *( ...
- modbus通讯协议详解
1.Modbus 协议简介 Modbus协议是一种已广泛应用于当今工业控制领域的通用通讯协议.通过此协议,控制器相互之间.或控制器经由网络(如以太网)可以和其它设备之间进行通信.Modbus协议使用 ...
- mysql统计特定字符串出现次数
其中'test'为原始字符串,'t'为特定字符串 SELECT floor((char_length('test') - char_length(replace('test', 't', '')) ...
- 使用golang+antlr4构建一个自己的语言解析器(一)
Antlr4 简介 ANTLR(全名:ANother Tool for Language Recognition)是基于LL(*)算法实现的语法解析器生成器(parser generator),用Ja ...
- vue之写发表评论思路
后端接口 var express = require('express'); const sql = require('../sql') const Comment = require('../sql ...