orangejuice's blog

挂了请务必叫我

「GXOI / GZOI2019」宝牌一大堆

麻将.jpg

观察牌型和计算方法可知,选择一个杠与选择一个面子对于牌型的贡献是等价的

而选择一个杠的答案一定没有选择一个刻子优,因此是没有任何意义的

除去 "七对子" "国士无双" 的特殊情况后,此外的情况就是选择 4个面子 + 1个雀头

容易想到对于每个花色dp得到选择 \(i\) 个面子 \(j\) 个雀头的答案,然后背包合并

为了 \(dp\) 顺子的情况,可以存储前面两个位置作为顺子头的个数

\(dp_{i,j,a,b}\) 表示当前选了 \(i\) 个面子, \(j\) 个雀头,上两个位置选了 \(a\) 个顺子,上一个位置选了 \(b\) 个顺子

暴力维护dp即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
const int INF=1e9+10;

int min(int x,int y,int z){ return min(min(x,y),z); }

int n,m;
int C[5][5],c[4][12],mk[4][12];
ll ans,val[5][2];

void Check7(){
static int val[40],cnt;
cnt=0;
rep(i,0,3) rep(j,0,9) if(c[i][j]>=2) val[++cnt]=C[c[i][j]][2]<<(mk[i][j]*2);
if(cnt<7) return;
sort(val+1,val+cnt+1,greater<int>());
ll res=7;
rep(i,1,7) res*=val[i];
cmax(ans,res);
}

void Check13(){
ll res=13,x=0,y=0;
auto chk=[&](ll a,ll b) {
if(!x) x=a,y=b;
if(x*b<a*y) x=a,y=b;
};
rep(i,0,2) {
if(!c[i][0] || !c[i][8]) return;
res*=c[i][0]*c[i][8];
res<<=(mk[i][0]+mk[i][8]);
if(c[i][0]>=2) chk(C[c[i][0]][2]<<(mk[i][0]*2),c[i][0]<<mk[i][0]);
if(c[i][8]>=2) chk(C[c[i][8]][2]<<(mk[i][8]*2),c[i][8]<<mk[i][8]);
}
rep(i,0,6) {
if(!c[3][i]) return;
res<<=mk[3][i];
res*=c[3][i];
if(c[3][i]>=2) chk(C[c[3][i]][2]<<(mk[3][i]*2),c[3][i]<<mk[3][i]);
}
if(!x) return;
cmax(ans,res*x/y);
}

void Work(int *cnt,int *mk,ll res[5][2]){
static ll dp[2][5][2][5][5],w[5];
int cur=0;
memset(dp,0,sizeof dp),dp[cur][0][0][0][0]=1;
rep(t,0,8) {
int x=cnt[t]; ll val;
memset(dp[!cur],0,sizeof dp[!cur]);
rep(i,0,x) w[i]=C[x][i]<<(mk[t]*i);
rep(i,0,4) rep(j,0,1) rep(a,0,x) rep(b,0,x-a) if((val=dp[cur][i][j][a][b])) {
int d=a+b,y=x-d,u=min(cnt[t+1]-b,cnt[t+2]);

rep(k,0,min(3-i,y-3,u))
cmax(dp[!cur][i+k+1][j][b][k],val*w[d+k+3]); // 刻 + 顺

rep(k,0,min(4-i,y,u))
cmax(dp[!cur][i+k][j][b][k],val*w[d+k]); // 顺

if(j) continue;
rep(k,0,min(4-i,y-2,u))
cmax(dp[!cur][i+k][j+1][b][k],val*w[d+k+2]); // 雀 + 顺
}
cur^=1;
}
drep(i,4,0) drep(j,1,0) if(res[i][j]) {
rep(a,0,4-i) rep(b,0,1-j)
if(dp[cur][a][b][0][0])
cmax(res[i+a][j+b],res[i][j]*dp[cur][a][b][0][0]);
}
}

Pii Read(){
static char O[5];
scanf("%s",O);
if(*O=='0') return mp(-1,0);
if(isalpha(*O)) {
if(*O=='E') return mp(3,0);
if(*O=='S') return mp(3,1);
if(*O=='W') return mp(3,2);
if(*O=='N') return mp(3,3);
if(*O=='Z') return mp(3,4);
if(*O=='B') return mp(3,5);
if(*O=='F') return mp(3,6);
return mp(-1,-1);
}
if(O[1]=='m') return mp(0,*O-'1');
if(O[1]=='p') return mp(1,*O-'1');
if(O[1]=='s') return mp(2,*O-'1');
return mp(-1,-1);
}

void Solve(){
ans=0,memset(val,0,sizeof val),memset(mk,0,sizeof mk),val[0][0]=1;
rep(i,0,2) rep(j,0,8) c[i][j]=4;
rep(i,0,6) c[3][i]=4;
while(1) {
Pii T=Read();
if(T.first==-1) break;
c[T.first][T.second]--;
}
while(1) {
Pii T=Read();
if(T.first==-1) break;
mk[T.first][T.second]=1;
}
Check7(),Check13();
rep(i,0,2) Work(c[i],mk[i],val);
rep(i,0,6) {
static ll w[5];
int x=c[3][i];
rep(j,0,x) w[j]=C[x][j]<<(j*mk[3][i]);
drep(a,4,0) drep(b,1,0) if(val[a][b]) {
if(b<1 && x>=2) cmax(val[a][b+1],val[a][b]*w[2]); // 雀
if(a<4 && x>=3) cmax(val[a+1][b],val[a][b]*w[3]); // 刻
if(a<4 && x>=4) cmax(val[a+1][b],val[a][b]*w[4]); // 杠
}
}
cmax(ans,val[4][1]);
printf("%lld\n",ans);
}

int main(){
rep(i,0,4) rep(j,C[i][0]=1,i) C[i][j]=C[i-1][j]+C[i-1][j-1];
int T; scanf("%d",&T);
while(T--) Solve();
}

「SDOI2017」树点涂色(LCT+线段树)

可以发现更新操作就是 \(\text{LCT}\)\(\text{Access}\) 操作,这个操作复杂度是 \(O(n\log n)\)

因此,考虑对于每次的 \(\text{Access}\) 操作,维护每个点到根的路径上不同的权值个数

每次 \(\text{Access}\) 操作只设计到合并两个链/断开一条链两种操作,可以通过线段树维护子树修改

那么修改的复杂度就是 \(O(n\log^2 n)\)

对于二操作,自己模拟一下就知道,就是两个点的答案-2 \(\cdot \text{LCA}\) 答案+1

实现上有一些细节,就是更新的子树根节点是需要查询得到的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
const int N=1e5+10;

int n,m;
vector <int> G[N];
int L[N],R[N],dfn;
int son[N][2],fa[N],tfa[N][18],dep[N],id[N];
int s[N<<2],t[N<<2],mi[N];

void Upd(int p,int l,int r,int ql,int qr,int x) {
if(ql<=l && r<=qr) {
t[p]+=x;
return;
}
int mid=(l+r)>>1;
if(ql<=mid) Upd(p<<1,l,mid,ql,qr,x);
if(qr>mid) Upd(p<<1|1,mid+1,r,ql,qr,x);
s[p]=max(t[p<<1]+s[p<<1],t[p<<1|1]+s[p<<1|1]);
}
int Que(int p,int l,int r,int ql,int qr) {
if(ql<=l && r<=qr) return s[p]+t[p];
int mid=(l+r)>>1,res=-1e9;
if(ql<=mid) cmax(res,Que(p<<1,l,mid,ql,qr));
if(qr>mid) cmax(res,Que(p<<1|1,mid+1,r,ql,qr));
return res+t[p];
}
int Que(int p,int l,int r,int x) {
int res=0;
while(1) {
if(l==r) return res+s[p]+t[p];
int mid=(l+r)>>1;
res+=t[p];
if(x<=mid) p<<=1,r=mid;
else p=p<<1|1,l=mid+1;
}
}
void Build(int p,int l,int r) {
if(l==r) { s[p]=dep[id[l]]+1; return; }
int mid=(l+r)>>1;
Build(p<<1,l,mid),Build(p<<1|1,mid+1,r);
s[p]=max(s[p<<1],s[p<<1|1]);
}

int dir(int x){ return son[fa[x]][1]==x; }
int isroot(int x){ return !fa[x] || (son[fa[x]][0]!=x && son[fa[x]][1]!=x); }
void Up(int p) { mi[p]=son[p][0]?mi[son[p][0]]:p; }
void rotate(int u) {
int f=fa[u],ff=fa[f],d=dir(u);
fa[u]=ff; if(!isroot(f)) son[ff][dir(f)]=u;
son[f][d]=son[u][!d]; if(son[u][!d]) fa[son[u][!d]]=f;
son[u][!d]=f,fa[f]=u; Up(f),Up(u);
}
void Splay(int x){ for(;!isroot(x);rotate(x)) if(!isroot(fa[x])) rotate((dir(x)^dir(fa[x]))?x:fa[x]); }
void Access(int x) {
for(int t=0,y;x;t=x,x=fa[x]) {
Splay(x),y=son[x][1];
if(y) Upd(1,1,n,L[mi[y]],R[mi[y]],1);
if(t) Upd(1,1,n,L[mi[t]],R[mi[t]],-1);
son[x][1]=t,Up(x);
}
} // LCT模板

void dfs(int u,int f) {
mi[u]=u,fa[u]=tfa[u][0]=f,id[L[u]=++dfn]=u;
for(int i=1;(1<<i)<=dep[u];++i) tfa[u][i]=tfa[tfa[u][i-1]][i-1];
for(int v:G[u]) if(v!=f) dep[v]=dep[u]+1,dfs(v,u);
R[u]=dfn;
}
int LCA(int x,int y) {
if(dep[x]<dep[y]) swap(x,y);
for(int i=0,del=dep[x]-dep[y];(1<<i)<=del;++i) if(del&(1<<i)) x=tfa[x][i];
if(x==y) return x;
drep(i,17,0) if(tfa[x][i]!=tfa[y][i]) x=tfa[x][i],y=tfa[y][i];
return tfa[x][0];
}

int main(){
n=rd(),m=rd();
rep(i,2,n) {
int u=rd(),v=rd();
G[u].pb(v),G[v].pb(u);
}
dfs(1,0),Build(1,1,n);
rep(i,1,m) {
int opt=rd();
if(opt==1) Access(rd());
else if(opt==2) {
int x=rd(),y=rd(),z=LCA(x,y);
printf("%d\n",Que(1,1,n,L[x])+Que(1,1,n,L[y])-2*Que(1,1,n,L[z])+1);
} else {
int x=rd();
printf("%d\n",Que(1,1,n,L[x],R[x]));
}
}
}



[补]「WC2021」括号路径

注意到到达关系是相互的,因此可以把能够互相到达的点放到同一集合中

因此只需要考虑最简单的到达情况,发现实际上当一个点有两条同色入边时,可以将这两条边对应的点合并

对于每个集合,维护一个颜色出边的集合,可以用 \(\text{std::map}\) 实现,每次合并两个点用并查集处理集合关系

然后用启发式合并的方式维护集合的边,即可做到 \(O(m\log^2 m)\)

用线段树合并的方式维护同样的东西即可做到 \(O(m\log k)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<bits/stdc++.h>
using namespace std;
enum{N=300010};
int n,m,i,u,v,w,F[N],S[N];
int Find(int x){ return F[x]==x?x:F[x]=Find(F[x]); }
map <int,int> M[N];
void U(int x,int y){
x=Find(x),y=Find(y);
if(x==y) return;
if(M[x].size()>M[y].size()) swap(x,y);
F[x]=y,S[y]+=S[x];
for(auto i:M[x]) M[y].emplace(i);
for(auto i:M[x]) U(M[y][i.first],i.second);
}
main(){
for(scanf("%d%d%*d",&n,&m),i=1;i<=n;++i) S[F[i]=i]=1;
while(m--){
scanf("%d%d%d",&v,&u,&w),u=Find(u),v=Find(v);
if(M[u].count(w)) U(M[u][w],v);
else M[u][w]=v;
}
int64_t ans=0;
for(i=1;i<=n;++i) if(Find(i)==i) ans+=1ll*S[i]*(S[i]-1)/2;
printf("%lld\n",ans);
}

「WC2021」表达式求值

直接枚举每一位求值显然至少是 \(O(n|S|)\) 的,为了减少计算次数,考虑对于 \(n\) 个不同数组的情况归纳出一些通用情况

对于一个数组,考虑计算答案 \(\ge A_i\) 的方案数,那么有一部分数 \(\ge A_i\)

直接状压 \(\ge A_i\) 的数的集合,对于的数不同二进制表示就可以得到 \(2^m\) 种不同的状态

在计算时,只需要考虑是否 \(\ge A_i\) ,分为两种值, \(O(1)\) 合并即可

预处理出每个二进制对应的值,然后对于每个 \(A_i\) 计算答案即可

复杂度为 \(O(|S|2^m+nm\log m)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)

char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}

const int N=5e4+10,P=1e9+7;

int n,m;
int A[N][10];
char s[N];
struct Node{
int a[2];
Node(){}
Node(int x){ a[!x]=0,a[x]=1; }
Node operator < (const Node x) const {
Node res;
res.a[0]=(1ll*a[0]*(x.a[0]+x.a[1])+1ll*a[1]*x.a[0])%P;
res.a[1]=1ll*a[1]*x.a[1]%P;
return res;
}
Node operator > (const Node x) const {
Node res;
res.a[0]=1ll*a[0]*x.a[0]%P;
res.a[1]=(1ll*a[1]*(x.a[0]+x.a[1])+1ll*a[0]*x.a[1])%P;
return res;
}
Node operator + (const Node x) const {
int l=a[0]+a[1],r=x.a[0]+x.a[1];
Node res;
rep(i,0,1) res.a[i]=(1ll*a[i]*r+1ll*l*x.a[i])%P;
return res;
}
} X[N];
int I[10],Y[N],T,val[N];

void Work(int S){
T=0;
for(int i=1;s[i];++i) {
if(isdigit(s[i])) X[++T]=Node((S>>(s[i]-'0'))&1),Y[T]=0;
else if(s[i]=='<') Y[++T]=1;
else if(s[i]=='>') Y[++T]=2;
else if(s[i]=='?') Y[++T]=3;
else if(s[i]=='(') Y[++T]=4;
else X[T-1]=X[T],Y[T-1]=Y[T],T--;
if(T>2 && !Y[T] && !Y[T-2]){
if(Y[T-1]==1) X[T-2]=X[T-2]<X[T];
if(Y[T-1]==2) X[T-2]=X[T-2]>X[T];
if(Y[T-1]==3) X[T-2]=X[T-2]+X[T];
T-=2;
}
}
val[S]=X[1].a[1];
}

int main(){
n=rd(),m=rd();
rep(i,0,m-1) rep(j,1,n) A[j][i]=rd();
scanf("%s",s+1);
rep(S,1,(1<<m)-1) Work(S);
int ans=0;
rep(i,1,n) {
rep(j,0,m-1) I[j]=j;
sort(I,I+m,[&](int x,int y){ return A[i][x]<A[i][y]; });
int S=0;
for(int j=m-1;~j;--j){
S|=1<<I[j];
ans=(ans+1ll*(A[i][I[j]]-(j?A[i][I[j-1]]:0))*val[S])%P;
}
}
printf("%d\n",ans);
}

「ZJOI2018」树

前言

置换同构计数真是令人头大,感觉依然不是特别懂

\[ \ \]

分析与初步构想


设按照题意生成的 \(n\) 个节点有标号有根树族为 \(\mathcal{T}_n\) ,对于某种树形 \(T\) 的生成方案数为 \(c(T)\)

则答案显然是 \(\cfrac{\sum _{T\in \mathcal{T}_n} c^k(T)}{(n-1)!^k}\)

\(k\) 的量级意味着我们无法完成有关 \(c^k(T)\) 的展开,因此要在最开始的计算中就将 \(k\) 这个幂次加入

为此定义 \(F_{i,n}=\sum _{T\in \mathcal{T}_n} c^{ik}(T)\)

则我们要计算的答案就是 \(\cfrac{F_{1,n}}{(n-1)!^k}\)

容易发现有意义的 \(F_{i,j}\) 规模为 \(O(\sum \sum [ij\leq n])=O(n\ln n)\)

关于为什么 \(F_{i,n}\) 会有第一维,会在下面出现

ps: 我的两个维度好像和别人是反的



计算过程分析


考虑递推计算 \(F_{i,n}\)

容易发现对于任意一个 \(F_{i,n}\) 的计算只需要令 \(k\rightarrow ik\)

为了简化描述,我们先以计算 \(F_{1,...}\) 为例

对于同构树计数,子树的叠加是无序的背包问题,树形之间存在着置换同构

而每棵子树的编号之间可以任意归并,因此需要 \(\text{EGF}\) 的背包合并每棵子树

显然同构仅出现在同种大小的子树中,因此可以对于不同大小的子树分离

假设对于大小为 \(n\) 的子树,出现了 \(m\) 种不同的树形 \(T_1,T_2,\ldots T_m\) ,每种出现了 \(a_i\)

则答案应该为 \(\displaystyle \sum_{T_i\ne T_j} \frac{1}{(n!)^m}\prod \frac{c^{a_ik}(T_i)}{(a_i!)^k}\)

其中 \((a_i!)^k\) 除去同构树之间无排列序的方案数

普通的背包计数难以处理 \(T_i\ne T_j\) 的限制

因此考虑用 \(\text{Burnside}\) 引理解决置换同构问题



同构出现于 \(m\) 棵树之间,因此我们置换群为对于 \(m\) 的排列置换群

显然任意一个排列置换的的结果是若干个置换环,环上的树树形相同

则对于一个置换 \(f\) ,设其生成了大小分别为 \(b_1,b_2,\ldots, b_m\) 的置换环

理想情况下其不动点的式子计算如下

\(\displaystyle \text{fix}(f)=\frac{(n\sum b_i)!}{(n!)^{\sum b_i}}\cdot \frac{F_{b_i,n}}{(b_i!)^k}\)

(这就是为什么要给 \(F\) 添加第一维)

其中 \(\cfrac{(n\sum b_i)!}{(n!)^{\sum b_i}}\) 处理了 \(\sum b_i\) 棵树的点编号,实际上这两个权值可以在计算的最后加入,因此下面忽略掉

而实际上,在不动点中直接加入 \(\cfrac{1}{(b_i!)^k}\) 是错的

原因在于让这样的 置换环权值 带入 \(\text{Burnside}\) 引理之后

最终的 同构类权值 并不是我们想要的 \(\cfrac{1}{(a_i!)^k}\)



考虑 待定求解 一个置换环系数的 \(\text{EGF}\) ,设其为 \(A(x)\)

相较于上面的枚举 \(f\) 的式子,这里的计算中系数还需要考虑对于每种 \(b_i\) ,等价的置换 \(f\) 个数

这同样需要对于每棵子树的 \(\text{EGF}\) 合并,系数为 \(\cfrac{1}{b_i!}\)

而一个环上的点存在一个环排列 \((b_i-1)!\) ,两者合并即 \(\cfrac{1}{b_i}\)

注意这一部分并未被加入 \(A(x)\)

然而这也恰好使得 \(\text{Burnside}\) 引理的系数 \(\frac{1}{m!}\) 和置换元素的 \(\text{EGF}\) 系数相抵消

不妨设添加环系数 \(\text{EGF}\) 的变换为 \(\hat A_i=\cfrac{A_i}{i}\)

所以,则最终的计算中 \(\text{Burnside}\) 引理的答案就是 \(\text{exp}(\hat A(x))\)



我们希望最终一个 合法的同构类 的权值为 \(\cfrac{1}{(a_i!)^k}\)

而容易发现实际上 最终的同构类 是由我们初始枚举的置换环\(\text{exp}\)

因此 \(\cfrac{1}{a_i!}\) 应当是置换环系数经过环元素 \(\text{exp}\) 叠加的结果

\(B(x)=\cfrac{x^i}{(i!)^k},C_i=\cfrac{A_i}{F_{n,i}}\)

\(B(x)=\text{exp}(\hat C(x))\) ,对于 \(B(x)\)\(\ln\) 得到 \(\hat C(x)\)

加入系数得到我们前面所待定的 \(A(x)\) ,即可进行最后的 \(\text{exp}(\hat A(x))\) 计算

ps:

你会发现可以直接忽略环 \(\text{EGF}\) 变换,全程只有 \(\hat C(x),\hat A(x)\) ,在过渡中这个变换的系数直接消失了

在这里提到这个系数是为了避免不必要的误解,同时也强调其他时候使用 \(\text{Burnside}\) 引理需要添加这个系数



算法实现简谈


倒序枚举 \(F_{i,j}\)\(i\) ,正序枚举大小 \(j\) ,边界条件自然是 \(F_{i,1}=1\)

确定了一个 \(i\) 后,就可以预处理 \(\ln\) 求出置换环系数,这里有 \(\frac{n}{i}\)

按照每个 \(i\) ,上面式子中的 \(k\rightarrow ik\)

按照 \(j\) 从小到大计算 \(F_{i,j}\) ,每次得到 \(F_{i,j}\) 之后

计算关于大小为 \(j\) 的树的背包系数,这里系数的个数为 \(l=\frac{n}{ij}\)

将先前的系数补上 \(F_{d,j}\) ,再做 \(\text{exp}\) ,最后把前面扔掉的 \(\cfrac{1}{(n!)^{\sum a_i}}\) 补上,( \((n\sum a_i)!\) 直接作为后面 \(\text{EGF}\) 合并的系数)

然后将它补进前面累和的背包里,就能得到这一项的值

注意前面算的式子都是计算儿子的,最后还要加上自己的大小1

当然,计算 \(\text{exp},\ln\) 需要下面的帮助


\(\text{exp}\)\(O(n^2)\) 方法

\(F(x)=\text{exp}(G(x))\)

\(F'(x)=\text{exp}(G(x))G'(x)\)

\(F'(x)=F(x)G'(x)\)

先计算出 \(G'(x)\) ,然后 \(O(n^2)\) 依次得到 \(F'(x)\) 的第 \(i\) 项,就能知道 \(F(x)\) 的第 \(i+1\)


\(\ln\)\(O(n^2)\) 方法

\(F(x)=\ln G(x)\)

\(F'(x)G(x)=G'(x)\)

边界 \([x^0]G(x)=1,[x^0]F(x)=0\)

暴力推 \(F'(x)\) 的每一项即可



进一步优化

上面的计算时,每次求得一个 \(\hat A(x)\) ,都做一次 \(\text{exp}\) ,然后背包合并

但是实际上,我们可以先将 \(\hat A(x)\) 放在一起,然后一起做 \(\text{exp}\)

具体的,每次得到 \(F_{i,j}\) 之和,我们就可以确定 \(\sum \hat A(x)\) 的第 \(j\)

那么在维护 \(\sum \hat A(x)\) 的同时,也依次递推 \(\text{exp}(\sum \hat A(x))\)\(j\)

这样不仅去掉的背包的过程,也少了很多次 \(\text{exp}\)

Montegomery

最后是喜闻乐见的套板子时间

\[ \ \]

复杂度分析


未优化

相对于 \(\text{exp}\) ,求 \(\ln\) 的复杂度可以忽略

\(\text{exp}\) 每次大小是 \(\frac{n}{ij}\) ,即 \(O(\sum \sum \cfrac{n^2}{i^2j^2})=O(n^2)\)

最后的复杂度反而在于背包合并 \(\text{EGF}\) ,为 \(O(n^2\ln n)\)

优化后

同步求 \(\text{exp}\) 的复杂度为 \(O((\cfrac{n}{i})^2)=O(n^2)\)

Code1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
const int N=2010;

int n,k,P;
ll qpow(ll x,ll k=P-2) {
ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}
int F[N][N],T[N],H[N],C[N];
int I[N],J[N],Inv[N];
void Exp(int *a,int n){
static int b[N];
rep(i,1,n) b[i-1]=1ll*a[i]*i%P;
rep(i,0,n) a[i]=0;
a[0]=1;
rep(i,0,n-1) {
int s=0;
rep(j,0,i) s=(s+1ll*a[i-j]*b[j])%P;
a[i+1]=1ll*s*Inv[i+1]%P;
}
}

void Ln(int *a,int n){
static int b[N];
rep(i,0,n) b[i]=a[i];
rep(i,0,n-1) {
int s=1ll*b[i+1]*(i+1)%P;
rep(j,0,i-1) s=(s-1ll*a[j]*b[i-j])%P;
Mod2(s),a[i]=s;
}
drep(i,n,1) a[i]=1ll*a[i-1]*Inv[i]%P;
a[0]=0;
}

int IK[N],JK[N];

int main(){
n=rd(),k=rd(),P=rd();
Inv[0]=Inv[1]=1;
rep(i,2,n) Inv[i]=1ll*(P-P/i)*Inv[P%i]%P;
rep(i,*I=*J=1,n) I[i]=1ll*I[i-1]*qpow(Inv[i],k)%P,J[i]=1ll*J[i-1]*qpow(i,k)%P;

drep(i,n,1) {
int m=n/i;
rep(j,*H=1,m) H[j]=0;
rep(j,0,m) IK[j]=qpow(I[j],i),JK[j]=qpow(J[j],i);
rep(j,*C=1,m) C[j]=IK[j];
Ln(C,m);
rep(j,1,m) {
F[i][j]=1ll*H[j-1]*JK[j-1]%P;

int u=m/j;
T[0]=0;
rep(x,1,u) T[x]=1ll*C[x]*F[i*x][j]%P;
Exp(T,u);
int t=1;
rep(x,1,u) t=1ll*t*IK[j]%P,T[x]=1ll*T[x]*t%P;
drep(x,m,1) rep(y,1,x/j) H[x]=(H[x]+1ll*H[x-y*j]*T[y])%P;
}
}
int ans=1ll*F[1][n]*I[n-1]%P;
printf("%d\n",ans);
}

Code2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<cstdio>
using namespace std;
typedef long long ll;
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
enum{N=2010};
int n,k,P;
ll qpow(ll x,ll k=P-2) {
ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}
int H[N],F[N][N],T[N],C[N];
int I[N],J[N],Inv[N],Fac[N];
int IK[N],JK[N];
void Ln(int *a,int n){
static int b[N];
rep(i,0,n) b[i]=a[i];
rep(i,0,n-1) {
int s=1ll*b[i+1]*(i+1)%P;
rep(j,0,i-1) s=(s-1ll*a[j]*b[i-j])%P;
Mod2(s),a[i]=s;
}
drep(i,n,1) a[i]=1ll*a[i-1]*Inv[i]%P;
a[0]=0;
}


int main(){
scanf("%d%d%d",&n,&k,&P);
Inv[0]=Inv[1]=1;
rep(i,2,n) Inv[i]=1ll*(P-P/i)*Inv[P%i]%P;
rep(i,*I=*J=1,n) I[i]=1ll*I[i-1]*qpow(Inv[i],k)%P,J[i]=1ll*J[i-1]*qpow(i,k)%P;
rep(i,*Fac=1,n) Fac[i]=1ll*Fac[i-1]*i%P;

drep(i,n,1) {
int m=n/i;
rep(j,*H=1,m) H[j]=T[j]=0;
rep(j,0,m) IK[j]=qpow(I[j],i),JK[j]=qpow(J[j],i);
rep(j,*C=1,m) C[j]=IK[j];
Ln(C,m);
rep(j,1,m) {
F[i][j]=1ll*H[j-1]*JK[j-1]%P;
int u=m/j,t=1;
T[0]=0;
rep(x,1,u) t=1ll*t*IK[j]%P,T[x*j]=(T[x*j]+1ll*C[x]*F[i*x][j]%P*t%P*x*j)%P;
rep(x,1,j) H[j]=(H[j]+1ll*H[j-x]*T[x])%P;
H[j]=1ll*H[j]*Inv[j]%P;
}
}
int ans=1ll*F[1][n]*I[n-1]%P;
printf("%d\n",ans);
}

Code3:

Loj Submission

吐槽:实际上套了板子之后已经比loj上的所有人都快了

但是由于新旧评测机的问题~~~,总时间就显得慢了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)

enum{N=2010};
int n,k,P;
using u32=uint32_t;
using i32=int32_t;
using u64=uint64_t;
using i64=int64_t;

static u32 m,m2,inv,r2;
u32 getinv(){
u32 inv=m;
for(int i=0;i<4;++i) inv*=2-inv*m;
return inv;
}
struct Mont{
private :
u32 x;
public :
static u32 reduce(u64 x){
u32 y=(x+u64(u32(x)*inv)*m)>>32;
return i32(y)<0?y+m:y;
}
Mont(){ ; }
Mont(i32 x):x(reduce(u64(x)*r2)) { }
Mont& operator += (const Mont &rhs) { return x+=rhs.x-m2,i32(x)<0&&(x+=m2),*this; }
Mont& operator -= (const Mont &rhs) { return x-=rhs.x,i32(x)<0&&(x+=m2),*this; }
Mont& operator *= (const Mont &rhs) { return x=reduce(u64(x)*rhs.x),*this; }
friend Mont operator + (Mont x,const Mont &y) { return x+=y; }
friend Mont operator - (Mont x,const Mont &y) { return x-=y; }
friend Mont operator * (Mont x,const Mont &y) { return x*=y; }
i32 get(){
u32 res=reduce(x);
return res>=m?res-m:res;
}
} H[N],F[N][N],T[N],C[N],I[N],J[N],Inv[N],IK[N],JK[N];
Mont qpow(Mont x,ll k=P-2) {
Mont res(1);
for(;k;k>>=1,x*=x) if(k&1) res*=x;
return res;
}
void Init(int m) {
::m=m,m2=m*2;
inv=-getinv();
r2=-u64(m)%m;
}

void Ln(Mont *a,int n){
static Mont b[N];
rep(i,0,n) b[i]=a[i];
rep(i,0,n-1) {
Mont s=b[i+1]*(i+1);
rep(j,0,i-1) s-=a[j]*b[i-j];
a[i]=s;
}
drep(i,n,1) a[i]=a[i-1]*Inv[i];
a[0]=0;
}

int main(){
scanf("%d%d%d",&n,&k,&P),Init(P);
Inv[0]=Inv[1]=1;
rep(i,2,n) Inv[i]=(P-P/i)*Inv[P%i];
I[0]=J[0]=1;
rep(i,1,n) I[i]=I[i-1]*qpow(Inv[i],k),J[i]=J[i-1]*qpow(i,k);

drep(i,n,1) {
int m=n/i;
rep(j,1,m) T[j]=0;
rep(j,0,m) IK[j]=qpow(I[j],i),JK[j]=qpow(J[j],i);
C[0]=H[0]=1;
rep(j,1,m) C[j]=IK[j];
Ln(C,m);
rep(j,1,m) {
F[i][j]=H[j-1]*JK[j-1];
Mont t=1;
rep(x,1,m/j) t=t*IK[j],T[x*j]+=C[x]*F[i*x][j]*t*(x*j);
H[j]=0;
rep(x,1,j) H[j]+=H[j-x]*T[x];
H[j]=H[j]*Inv[j];
}
}
Mont ans=F[1][n]*I[n-1];
printf("%d\n",ans.get());
}

「NOI2020」时代的眼泪

前言

这种东西看到就给人一种

分块分块分块分块分块分块!

啊啊啊啊啊啊啊啊啊啊啊

\[\ \]

问题分析

这是一个二维区间顺序对问题,对于普通的区间顺序对问题,我们有简单分块解法

预处理整块的答案,有 \(n\sqrt n\) 个数要插入预处理,也就是有 \(O(\sqrt n)\) 个区间查询

对于散点暴力求,也是 \(n\sqrt n\) 个区间查询问题

那么离线+分块就可以做到 \(O(\sqrt n)\) 插入一个数, \(O(\sqrt 1)\) 查询,并且有办法将空间实现到 \(O(n)\)

那么对于二维区间考虑部分沿用上面的思路

\[ \ \]

Solution

首先对于散块的部分,是完全一样的处理,可以 \(O(n)\) 内存实现

具体的:

散点之间可以暴力 \(for\) 答案,每次还需要一个二维区间个数查询

每次需要查询的散点又是一段区间

可以描述为 \(O(m)\) 个查询,总共查询 \(O(m\sqrt n)\) 个散点

\[ \ \]

问题在于整块部分的查询 \([p1,p2],[u,d]\)

对于同一个块内的答案,可以暴力预处理出来

\[ \ \]

而块之间,可以转化为 \([1,d]-[1,u-1]-[1,u-1]\times [u,d]\)

前面两个前缀型问题,可以用如下方法实现:

按照 \(p_i\) 从小到大插入,同时维护每个块内已经出现的个数

每次插入 \(i\) 后,对于 \(i\) 前面的块,会产生 \(O(\sqrt n)\) 对 顺序对

我们要查询的是一个块编号 \([p1,p2]\) 内块的关系,这是一个二维前缀和

可以把两个维度的前缀和分开给插入和查询

具体的,在插入时,处理 \(S_{l,r}=\sum_{i\ge l} C_{i,r}\)

查询 \([p1,p2]\) 时,就可以暴力求 \(S_{l,i}i\in[l,r]\) 的和

这样可以分摊复杂度为 \(O(n\sqrt n)\) ,并且内存为 \(O(n)\) ,常数较小

\[ \ \]

对于 \([1,u-1]\times [u,d]\) ,从左到右一段段 查询过来,每次查询块内 \([1,u-1]\) \(,[u,d]\) 个数即可

这个统计和上面的块内答案统计都需要预处理每个数在块内排名

但是也可以通过离线去掉这个步骤,避免了一个 \(O(n\sqrt n)\) 的数组

\[ \ \]

实际实现时,发现散块暴力的部分枚举起来实在太慢,所以块开大了一点,加了一点底层玄学优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair <int,int> Pii;
typedef vector <int> V;
#define reg register
#define mp make_pair
#define pb push_back
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) f|=IO=='-';
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=1e5+10,M=2e5+10,S=1000;

int n,m,A[N],len,P[N];
struct Blocker{
int s[M],t[N];
void clear(){ memset(s,0,(n+1)<<2),memset(t,0,(n+1)<<2); }
void Add(int x){
int p=x/len;
rep(i,x,(p+1)*len-1) s[i]++;
rep(i,p+1,n/len) t[i]++;
}
int operator [](const int &x) const{ return s[x]+t[x/len]; }
} B;
int L[M],R[M],U[M],D[M],p1[M],p2[M],I[M],T[M];
ll Ans[M];
struct Que{ int l,r,k,id; };
vector <Que> Q[N];
// 处理散点
void SolvePoints(){
rep(i,1,n) {
B.Add(A[i]);
for(Que x:Q[i]) {
rep(j,x.l,x.r) {
int u=U[x.id],d=D[x.id];
if(A[j]<u || A[j]>d) continue;
if(j>i) cmin(d,A[j]-1);
else cmax(u,A[j]+1);
Ans[x.id]+=x.k*(B[d]-B[u-1]);
}
}
}
}

vector <Pii> E[N];
// 处理块区间的 前缀逆序对
void SolveB1(){
static ll s[S][S],c[S];
rep(k,1,n) {
int i=P[k],t=0,p=i/len;
c[p]++;
drep(j,p-1,0) t+=c[j],s[j][p]+=t;
for(Pii x:E[k]) {
int u=x.first,l=p1[u]+1,r=p2[u]-1;
rep(j,l+1,r) Ans[u]+=s[l][j]*x.second;
}
}
}

//处理块内答案
void SolveB2(){
static int s[S][S],C[N];
rep(i,0,n/len) {
int l=max(1,i*len),r=min(n,(i+1)*len-1);
rep(j,1,n) C[j]=C[j-1]+(l<=P[j] && P[j]<=r);
int L=C[n];
rep(a,1,L+1) rep(b,a-1,L+1) s[a][b]=0;
rep(a,l,r) rep(b,a+1,r) if(A[a]<=A[b]) s[C[A[a]]][C[A[b]]]++;
drep(a,L,1) rep(b,a,L) s[a][b]+=s[a+1][b]+s[a][b-1]-s[a+1][b-1];
rep(j,1,m) if(p1[j]<i && i<p2[j]) {
Ans[j]+=s[C[U[j]-1]+1][C[D[j]]];
Ans[j]-=1ll*T[j]*(C[D[j]]-C[U[j]-1]);
T[j]+=C[U[j]-1];
}
}
}

// 本来是暴力for l,r内的逆序对的,但是太慢,加了一点底层优化
int Que(int i,int l,int r,int u,int d){
if(r-l>45) {
int mid=(l+r*3)/4;
Q[l-1].pb({mid+1,r,-1,i});
Q[mid].pb({mid+1,r,1,i});
return Que(i,l,mid,u,d)+Que(i,mid+1,r,u,d);
}
int ans=0;
rep(i,l,r) if(u<=A[i] && A[i]<=d) rep(j,i+1,r) ans+=A[i]<=A[j] && A[j]<=d;
return ans;
}

int main(){
freopen("tears.in","r",stdin),freopen("tears.out","w",stdout);
n=rd(),m=rd(),len=ceil(sqrt(n/4.0));
fprintf(stderr,"Block len=%d ,Block Count=%d\n",len,n/len);
rep(i,1,n) P[A[i]=rd()]=i;
clock_t ti=clock();
rep(i,1,m) {
I[i]=i,L[i]=rd(),R[i]=rd(),U[i]=rd(),D[i]=rd();
p1[i]=L[i]/len,p2[i]=R[i]/len;
if(p1[i]==p2[i]){ Ans[i]=Que(i,L[i],R[i],U[i],D[i]); continue; }
Ans[i]=Que(i,L[i],(p1[i]+1)*len-1,U[i],D[i])+Que(i,p2[i]*len,R[i],U[i],D[i]);
Q[L[i]-1].pb({p2[i]*len,R[i],-1,i});
Q[p2[i]*len-1].pb({p2[i]*len,R[i],1,i});
if(p1[i]<p2[i]-1) {
Q[(p1[i]+1)*len-1].pb({L[i],(p1[i]+1)*len-1,-1,i});
Q[p2[i]*len-1].pb({L[i],(p1[i]+1)*len-1,1,i});
E[D[i]].pb(mp(i,1));
E[U[i]-1].pb(mp(i,-1));
}
}
fprintf(stderr,"Part0 %d\n",int(clock()-ti)),ti=clock();
SolvePoints();
fprintf(stderr,"Part1 %d\n",int(clock()-ti)),ti=clock();
sort(I+1,I+m+1,[&](int x,int y){ return L[x]<L[y]; });
SolveB1();
fprintf(stderr,"Part2 %d\n",int(clock()-ti)),ti=clock();
SolveB2();
fprintf(stderr,"Part3 %d\n",int(clock()-ti)),ti=clock();
rep(i,1,m) printf("%lld\n",Ans[i]);
}

「ZJOI2019」开关

神题


前言

\(\text{FWT}_{\oplus}(F(x))=F'(x)\)

关于 \(\text{FWT}_{\oplus}\) 的展开式子,我发现大部分人都不晓得。。。。

\([x^S]F'(x)=\sum_T(-1)^{|S\cap T|} [x^T]F(x)\)

\(F(x)=\frac{F''(x)}{2^n}\)

详细可以看这个

定义 \(\bigoplus\) 为两个多项式的异或卷积

\(\times\) 为两个多项式对应项直接相乘

\([x^S]F(x)\) 表示 \(F(x)\) 的第 \(S\) 项的系数


正文

翻转过程是一个 \(\oplus\) 的过程,所以考虑用集合幂指数配合 \(\text{FWT}_\oplus\) 构造和求解方程

事实上问题等价于从初始状态 \(S\) 跑到 \(\empty\) 的期望次数

设从 \(S\)\(\empty\) 的期望次数生成函数为 \(F(x)\) ,其中 \([x^{\empty}]F(x)=0\)

设转移函数 \(G(x),[x^{\{i\}}]G(x)=p_i\)

那么我们的方程就是 \(F(x)\bigoplus G(x)+\sum x^S+cx^{\empty}=F(x)\)

其中 \(x^S\) 表示这次转移之后答案要加一

由于直接这样转移得到的方程显然是无穷解的,因为无法保证 \([x^{\empty}]F(x)=0\)

所以我们用一个常数项 \(cx^{\empty}\) 平衡这个问题, \(c\) 现在是未知的

\(\because F(x)\bigoplus G(x)+\sum x^S+cx^{\empty}=F(x)\)

\(\therefore (F(x)\bigoplus G(x)+\sum x^S+cx^{\empty})'=F'(x)\)

\(\therefore F'(x)\times G'(x)+(\sum x^S+cx^{\empty})'=F'(x)\)

我们模拟一下 \(G'(x)\)

\([x^S]G'(x)=\sum_i(-1)^{|S\cap \{i\}|}[x^{\{i\}}]G(x)=\sum_{i\notin S}p_i-\sum_{i\in S}p_i\)

再卷一下 \((\sum x^S+cx^{\empty})'\)

\((\sum x^S)'=\sum_S\sum_T(-1)^{|S\cap T|}x^S\)

\(\because (-1)^{|S\cap T|} =\sum_{T\subset S}(-1)^|T|\cdot 2^{n-|S|}=[S=\empty]2^{n-|S|}\)

\(\therefore (\sum x^S)'=2^n x^{\empty}\)

\((cx^{\empty})'=\sum c\cdot x^S\)

然后我们带入反解 \([x^S]F'(x)\)


\(S=\empty\) 时,

\([x^S]F'(x)\cdot [x^S]G'(x)+2^n+c=[x^S]F'(x)\)

然后发现此时 \([x^S]G'(x)=1\) ,那么就意味着 \(2^n+c=0\)

解出了 \(c=-2^n\) ,但是此时我们实际上并不知道 \([x^{\empty}]F'(x)\) 的值


\(S\ne \empty\)

\([x^S]F'(x)\cdot [x^S]G'(x)+c=[x^S]F'(x)\)

\([x^S]F'(x)(1-[x^S]G'(x))=c\)

\[[x^S]F'(x)=-\frac{2^n}{1-\sum_{i\notin S}p_i+\sum_{i\in S}p_i}=-\frac{2^n}{2\sum_{i\in S}p_i}\]


我们考虑要求出 \([x^\empty]F'(x)\) 的值

\([x^{\empty}]F(x)=2^{-n}\cdot \sum [x^S] F'(x)=0\)

\[[x^\empty]F'(x)-\sum_{S\ne \empty}\frac{2^n}{2\sum_{i\in S}p_i}=0\]

那么我们由 \(F'(x)\) 回代得到 \([x^S]F(x)(S \ne \empty)\)


\[[x^S]F(x)=2^{-n}\cdot \sum_T(-1)^{|S\cap T|}[x^T]F'(x)\]

\[=2^{-n}([x^\empty]F'(x)+\sum_{T\ne \empty}(-1)^{|S\cap T|}[x^T]F'(x))\]

\[=\sum_{T\ne\empty}\frac{1}{2\sum_{i\in T}p_i}+2^{-n}\sum_{T\ne \empty}(-1)^{|S\cap T|}[x^T]F'(x)\]

\[=\sum_{T\ne \empty,|S\cap T|\mod 2=1} \frac{1}{\sum_{i\in T} p_i}\]

下面是一个背包,跑的同时统计一下就好了 \(|S\cap T|\) 的奇偶性就好了

然后会发现事实上并没有必要特判 \(S=\empty\) 的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cctype>
#include<vector>
using namespace std;

#define reg register
typedef long long ll;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)

#define pb push_back
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
int rd(){
int s=0;
int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=5e4,P=998244353;


int n;
int s[N],a[N],sum;
int dp[110][N][2]; // 背包表示p之和为i,有j个不同的方案数
ll Inv[N];
ll c;

ll qpow(ll x,ll k) {
ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}



int main(){
freopen("switch.in","r",stdin),freopen("switch.out","w",stdout);
n=rd();
rep(i,1,n) s[i]=rd();
dp[0][0][0]=1;
rep(i,1,n) {
int x=rd();
rep(j,0,sum) rep(k,0,1) rep(d,0,1)
dp[i][j+d*x][k^(d&&s[i])]=(dp[i][j+d*x][k^(d && s[i])]+dp[i-1][j][k])%P;
sum+=x;
}
Inv[0]=Inv[1]=1;
rep(i,2,sum) Inv[i]=(P-P/i)*Inv[P%i]%P;
ll ans=0;
rep(i,1,sum) ans=(ans+Inv[i]*dp[n][i][1])%P; // 因为最后统计的时候没有空集,所以i从1开始
printf("%lld\n",ans*sum%P);
}





「ZJOI2020」抽卡

Sub1: 从 \(n\) 张卡中选取钦定的 \(m\) 张的期望次数

\(f_m\) 表示期望次数,显然 \(m>0,f_m=\frac{(n-m)f_m+mf_{m-1}}{n}+1\)

\(f_0=0,f_m=f_{m-1}+\frac{n}{m}\)

\(\displaystyle f_m=\sum_{i=1}^m \frac{n}{i}\)

Minmax容斥转化

回到原问题的 \(a_1,a_2,\ldots,a_m\) ,按照连续 \(k\) 个分段,设每段开始点 \(A_i\)

我们要求这些 \([A_i,A_i+k)\) 第一次有一个被满足的期望,这难以解决

用一个 \(\text{minmax}\) 容斥处理掉

\(\displaystyle \min\{\exists A_i合法\}=(-1)^{T+1}\sum_{T\subset S} \max\{\forall i\in T,A_i合法\}\)

这个 \(\text{max}\) 显然取决于 \(A_i\) 并的长度

\(dp_{i,j}\) 表示当前最大的选择点为 \(i\) ,选择的总长度为 \(j\) ,容易用前缀和优化到 \(O(n^2)\)

\[ \ \]

优化求解

容易想到对于每个长度 \(\ge k\) 的连续段分别求解,然后分治 \(\text{NTT}\) 背包合并

此时我们要对于连续 \(n\) 个元素可以选择的子问题求解答案生成函数 \(F_n(x)\)

\[ \ \]

推论:对于某一个长度 \(L \in[k+2,2k]\) 的且已经确定都选择的段,其容斥系数为0

考虑此时,收尾两个段必须选择,而中间的段(个数 \(>0\) )随意选择

由等式 \(\sum {T\in S} (-1)^{|T|}=[S=\empty]\) 可知,其系数为0

观察一下我们能由这个推论得到什么

\(dp_{n}\) 表示顺次覆盖前 \(n\) 个元素的系数

\(dp_{k}=-1,dp_{k+1}=-dp_{k}=1\)

\(\forall i\in[k+2,2k],dp_i=0\)

\(dp_{2k+1}=-dp_{k+1}=-1\)

\(dp_{2k+2}=-dp_{2k+1}=1\)

想必睿智的你一定已经发现了, \(dp\) 数组 \(k+1\) 一循环

实际上根据这个性质的暴力 \(dp\) 可以多10pts

由于 \(dp_{k+1}=1\) ,也可以表示为 \(dp_{i}=dp_{k+1}\cdot dp_{i-k-1}\)

因此可以把连续段的前 \(k+1\) 个分裂开来,假装不连续

此时,我们可以简单描述为:

每次选择的是一个长度为 \(k+1\) 的段

可以覆盖前 \(k\) 个,系数为 \(-1\)

也可以覆盖 \(k+1\) 个,系数为 \(1\)

并且这 \(k+1\) 个段不能相交,可以相邻

根据这样的决策,计算系数可以分为两步

\(n\) 个元素选出 \(i\) 个长度为 \(k+1\) 的段,剩下的元素可以分配到这 \(i+1\) 个间隔中

方案数为 \(\displaystyle \binom{n-i(k+1)+i+1-1}{i+1-1}=\binom{n-ik}{i}\)

一开始令这些段都选择前 \(k\) 个,然后对于某一些可以额外选择最后一个,乘上 \(-x\)

由此我们列出 \(\text{OGF}\) 表达式

\(\displaystyle G_n(x)=\sum_{i=0}^{\frac{n}{k+1}} (-1)^i\binom{n-ik}{i}x^{ik}(1-x)^i\)

\(\displaystyle G_n(x)=\sum_{i=0}^{\frac{n}{k+1}} \binom{n-ik}{i}x^{ik}(x-1)^i\)

注意边界情况是最后 \(k\) 个被选的情况不会算进去,因此实际上

\(F_n(x)=G_n(x)-x^kG_n(n-k)\)

考虑如何计算 \(G_n(x)\)

我们对于所有的 \(i\) 分治,分治到区间 \([l,r]\) 时,我们维护的是

\(\displaystyle \sum_{i=l}^{r} \binom{n-ik}{i}x^{(i-l)k}(x-1)^{(i-l)}\)

这样就能保证分治时,区间内多项式长度为 \(O((r-l+1)(k+1))\)

合并时,给右区间补上 \(x^{(mid-l+1)k}(x-1)^{mid-l+1}\) 即可

因此计算 \(F_n(x)\) 和合并 \(F_n(x)\) 的复杂度均为 \(O(n\log ^2n)\)

\(sort\) 有80pts.jpg

代码好看就完事了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}

const int N=1<<18|10,P=998244353;

int n,k;
ll qpow(ll x,ll k=P-2) {
ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}

int rev[N],I[N],J[N];
typedef vector <int> V;
void Init(){
rep(i,J[0]=1,N-1) J[i]=1ll*J[i-1]*i%P;
I[N-1]=qpow(J[N-1]);
drep(i,N-1,1) I[i-1]=1ll*I[i]*i%P;
}
int Init(int n){
int R=1,c=-1;
while(R<=n) R<<=1,c++;
rep(i,0,R-1) rev[i]=(rev[i>>1]>>1)|((i&1)<<c);
return R;
}
void NTT(int n,V &a,int f) {
static int e[N>>1];
rep(i,0,n-1) if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=e[0]=1;i<n;i<<=1) {
ll t=qpow(f==1?3:(P+1)/3,(P-1)/i/2);
for(int j=i-2;j>=0;j-=2) e[j+1]=(e[j]=e[j>>1])*t%P;
for(int l=0;l<n;l+=i*2) {
for(int j=l;j<l+i;++j) {
int t=1ll*e[j-l]*a[j+i]%P;
a[j+i]=a[j]-t,Mod2(a[j+i]);
a[j]+=t,Mod1(a[j]);
}
}
}
if(f==-1) {
ll Inv=1ll*I[n]*J[n-1]%P;
rep(i,0,n-1) a[i]=a[i]*Inv%P;
}
}

V operator * (V a,V b){
if(!a.size() || !b.size()) return {};
int n=a.size()+b.size()-1,R=Init(n);
a.resize(R),b.resize(R);
NTT(R,a,1),NTT(R,b,1);
rep(i,0,R-1) a[i]=1ll*a[i]*b[i]%P;
NTT(R,a,-1);
a.resize(n);
return a;
}
V operator + (V a,V b){
if(a.size()<b.size()) swap(a,b);
rep(i,0,b.size()-1) a[i]+=b[i],Mod1(a[i]);
return a;
}
V operator - (V a,const V &b){
if(a.size()<b.size()) a.resize(b.size());
rep(i,0,b.size()-1) a[i]-=b[i],Mod2(a[i]);
return a;
}

V operator << (V a,const int &x){
a.resize(a.size()+x);
drep(i,a.size()-1,x) a[i]=a[i-x];
rep(i,0,x-1) a[i]=0;
return a;
}

int C(int n,int m){ return n<0||m<0||n<m?0:1ll*J[n]*I[m]%P*I[n-m]%P; }
V Binom(int n){
V A(n+1);
rep(i,0,n) A[i]=(n-i)&1?P-C(n,i):C(n,i);
return A;
}
V Solve(int n,int l,int r){
if(l==r) return {C(n-l*k,l)};
int mid=(l+r)>>1;
return Solve(n,l,mid)+((Solve(n,mid+1,r)*Binom(mid-l+1))<<(k*(mid-l+1)));
}
V GetG(int n){ return Solve(n,0,n/(k+1)); }
V GetF(int n){ return GetG(n)-(GetG(n-k)<<k); }

vector <V> T;
V Solve(int l=0,int r=T.size()-1){
if(l==r) return T[l];
int mid=(l+r)>>1;
return Solve(l,mid)*Solve(mid+1,r);
}
int A[N];

int main(){
Init(),n=rd(),k=rd();
rep(i,1,n) A[i]=rd();
sort(A+1,A+n+1);
rep(i,1,n) {
int j=i;
while(A[j+1]==A[j]+1) j++;
if(j-i+1>=k) T.pb(GetF(j-i+1));
i=j;
}
V Res=Solve();
int s=0,ans=0;
rep(i,1,Res.size()-1){
s=(s+1ll*n*I[i]%P*J[i-1])%P;
ans=(ans+1ll*s*Res[i])%P;
}
ans=(P-ans)%P;
printf("%d\n",ans);
}

「HAOI2018」字串覆盖

这自然有后缀数组和后缀自动的写法,我写的是后缀数组

现对于 \(A,B\) 两串拼接后建立 \(\text{SA}\)

对于查询的四个参数 \([s,t,l,r]\) ,在 \(\text{SA}\) 上找到能够匹配 \([l,r]\)\(\text{rank}\) 区间 \([l',r']\)

这个 \([l',r']\) 就用 \(\text{SA}\)\(\text{height}\) 数组上倍增即可 \(O(\log n)\) 找到

由于 \(K>n\) ,显然覆盖就是从左到右依次匹配每个 \(\text{rank}\)\([l',r']\) 中的 \(i\) ,能放就放

数据范围提示我们切分写

Part1 \(r-l\leq 50\)

对于每种不同的 \(r-l\) ,倍增预处理

我们将依次匹配的过程描述成一个个跳跃

对于每个 \(i\) 找到后面第一个 \(j\) 满足 \(j>i+r-l,\text{LCP}(i,j)\ge r-l+1\)

具体的,将 \(\text{SA}\)\(\text{height}\) 数组按照 \(\text{height}_p\ge r-l+1\) 分成一段一段

每个连续段中的两个位置 \(\text{LCP}\ge r-l+1\)

合法的 \(i,j\) 一定出现的某个连续段中

找到每一个这样一个连续段,然后双指针得到合法的 \(i,j\) 即可

\[ \ \]

这样的跳跃关系,以及跳跃过程中的答案,可以通过倍增来维护出来

对于每个询问,可以先找到区间内第一个合法的点 \(i_0\) ,然后倍增查询答案即可

找到 \(i_0\) 可以用主席树二分出 \(\text{rank}\)\([l',r']\) 内的第一个 \(i_0\ge s\) 的位置

复杂度为 \(O(50n\log n+q\log n)\)

\[ \ \]

Part2 \(r-l>50\)

根据数据范围,这里我们只要能够暴力跳每一个合法的 \(i\) 即可

那么像上面一样,用主席树每次找 \([l',r']\) 内第一个 \(i'> i+r-l\)

每次 \(\log n\) 跳即可,复杂度为 \(O(\frac{nq}{r-l}\log n)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)

char IO;
int rd(){
int s=0,f=0;
while(!isdigit(IO=getchar())) f|=IO=='-';
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=2e5+10,M=N*18,S=60;

int n,K,q;
char s[N];
int cnt[N],rk[N<<1],tmp[N],sa[N];
int st[19][N],Log[N];
void Build() {
rep(i,1,n) cnt[(int)s[i]]++;
rep(i,1,200) cnt[i]+=cnt[i-1];
drep(i,n,1) sa[cnt[(int)s[i]]--]=i;
rep(i,1,n) rk[sa[i]]=rk[sa[i-1]]+(s[sa[i]]!=s[sa[i-1]]);
for(int m=rk[sa[n]],k=1;k<n && m<n;k<<=1,m=rk[sa[n]]) {
int h=0;
rep(i,n-k+1,n) tmp[++h]=i;
rep(i,1,n) if(sa[i]>k) tmp[++h]=sa[i]-k;

memset(cnt,0,(m+1)<<2);
rep(i,1,n) cnt[rk[i]]++;
partial_sum(cnt+1,cnt+m+1,cnt+1);
drep(i,n,1) sa[cnt[rk[tmp[i]]]--]=tmp[i];
rep(i,1,n) tmp[sa[i]]=tmp[sa[i-1]]+(rk[sa[i]]!=rk[sa[i-1]] || rk[sa[i]+k]!=rk[sa[i-1]+k]);
memcpy(rk,tmp,(n+1)<<2);
}
rep(i,2,n) Log[i]=Log[i>>1]+1;
int h=0;
rep(i,1,n) {
int j=sa[rk[i]-1];
if(h) h--;
while(s[i+h]==s[j+h]) h++;
st[0][rk[i]-1]=h;
}
rep(i,1,Log[n]) {
int len=1<<(i-1);
rep(j,1,n-len+1) st[i][j]=min(st[i-1][j],st[i-1][j+len]);
}
}

int ls[M],rs[M],c[M],rt[N],tcnt;
void Upd(int &p,int pre,int l,int r,int x){
c[p=++tcnt]=c[pre]+1,ls[p]=ls[pre],rs[p]=rs[pre];
if(l==r) return;
int mid=(l+r)>>1;
x<=mid?Upd(ls[p],ls[pre],l,mid,x):Upd(rs[p],rs[pre],mid+1,r,x);
}
int Que(int p1,int p2,int l,int r,int x){
if(c[p1]==c[p2] || r<x) return n+1;
if(l==r) return l;
int mid=(l+r)>>1,t=Que(ls[p1],ls[p2],l,mid,x);
return t<=n?t:Que(rs[p1],rs[p2],mid+1,r,x);
}

vector <int> G[S];
int A[N],B[N],L[N],R[N],X[N];
ll ans[N];
ll H[20][N]; int F[20][N];
int D[N],C;

int main(){
n=rd(),K=rd();
scanf("%s",s+1),scanf("%s",s+n+1);
n*=2,Build(),n/=2;
rep(i,1,n*2) {
rt[i]=rt[i-1];
if(sa[i]<=n) Upd(rt[i],rt[i],1,n,sa[i]);
}
rep(i,1,q=rd()) {
A[i]=rd(),B[i]=rd();
int l=rd(),x=rd()-l+1,r=l=rk[l+n];
drep(j,18,0) {
if(r+(1<<j)<=n*2 && st[j][r]>=x) r+=1<<j;
if(l>(1<<j) && st[j][l-(1<<j)]>=x) l-=1<<j;
}
L[i]=l,R[i]=r,X[i]=x;
if(n<=5000 && q<=5000) {
int p=A[i],e=B[i]-x+1;
while(p<=e) {
while(p<=e && (rk[p]<l || rk[p]>r)) p++;
if(p>e) break;
ans[i]+=K-p,p+=x;
}
} else if(X[i]>=S) {
int p=A[i],e=B[i]-x+1;
while(p<=e) {
int c=0;
while(++c<5 && p<=e && (rk[p]<l || rk[p]>r)) p++;
if(p>e) break;
if(rk[p]<l || rk[p]>r) p=Que(rt[l-1],rt[r],1,n,p);
if(p>e) break;
ans[i]+=K-p,p+=x;
}
} else G[X[i]].pb(i);
}
rep(x,1,S-1) if(G[x].size()) {
rep(i,1,n) F[0][i]=n+1;
rep(i,0,17) F[i][n+1]=n+1;
rep(i,1,n*2) {
int j=i;
while(j<n*2 && st[0][j]>=x) j++;
C=0;
rep(k,i,j) if(sa[k]<=n) D[++C]=sa[k];
if(C) {
sort(D+1,D+C+1);
int j=1;
rep(i,1,C) {
while(j<=C && D[j]-D[i]<x) j++;
if(j<=C) F[0][D[i]]=D[j];
}
}
i=j;
}
rep(i,1,n) H[0][i]=K-i;
rep(j,1,17) rep(i,1,n) {
F[j][i]=F[j-1][F[j-1][i]];
H[j][i]=H[j-1][i]+H[j-1][F[j-1][i]];
}
rep(d,0,G[x].size()-1) {
int i=G[x][d],e=B[i]-x+1;
int p=Que(rt[L[i]-1],rt[R[i]],1,n,A[i]);
if(p>e) continue;
drep(j,17,0) if(F[j][p]<=e) {
ans[i]+=H[j][p];
p=F[j][p];
}
ans[i]+=H[0][p];
}
}
rep(i,1,q) printf("%lld\n",ans[i]);
}

「十二省联考 2019」骗分过样例

很显然,这是一道有时限的提答题!!!!

但是知识点很丰富,值得一写

Case 1-3: 1_998244353

直接快速幂 \(19^x\) ,如果读入太大,指数可以 \(\mod \varphi(998244353)\) ,不谈...

Case 4: 1?

不知道模数,但是可以看出模数很小,随便选一个答案试出模数即可(145141?)

Case5: 1?+

值域非常大,就算是快速幂也显然要通过快速乘来实现,设模数为 \(P\)

把所有读入的数拿出来看,发现最相近的两个相差只有 \(2\)

意味着我们知道

\(19^x \equiv a\pmod P,19^{x+2} \equiv b \pmod P\)

很显然 \(a\cdot 19^2 \mod P=b\) ,求出 \(a\cdot 19^2-b\) ,分解质因数即可

实际实现可以用手写高精/Python

Case6: 1_wa998244353

答案的生成是,求幂次的时候乘法不开long long ,即

1
2
int n,s=1; scanf("%d",&n);
for(int i=1;i<=n;++i) s=s*19%998244353;

出题人的tips给了,自然溢出是非定义行为,甚至有可能导致RE

所以模拟的话,实际应该这样写

1
s=int(((unsigned)s*19))%P;

可以发现,这个答案的序列近似于一个伪随机数列,学过 \(\text{Pollard's Rho}\) 算法就知道,

根据生日问题/生日悖论,伪随机数列期望在 \(O(\sqrt n)\) 的时间内产生一个伪循环,因此可以快速求解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int s[300000];
int T=-1,fir=-1;
map <int,int> M;
s[0]=1,M[1]=0;
rep(i,1,300000){
s[i]=int(((unsigned)s[i-1]*19))%P;
if(M.count(s[i])) {
T=i-M[s[i]],fir=M[s[i]];
break;
}
M[s[i]]=i;
}
rep(kase,1,rd()){
ll x=rd<ll>();
printf("%d\n",s[x<=fir?x:(x-fir)%T+fir]);
}

Case8-10: 2p

判断 \([l,r]\) 内的数是否是质数,\(\text{Miller_Rabin}\) 模板

Case9-10: 2u

\([L,R]\) 内的莫比乌斯系数 \(w(n)\) ,设 \(n=\prod_1^m p_i^{c_i},c_i>0\)

\(w(n)=\left\{\begin{aligned} (-1)^m &&\nexists c_i>1,\\ 0 && \exists c_i>1\end{aligned}\right.\)

对于 \(L,R\) 达到 \(10^{18}\) 次时,考虑先用 \([1,R^{\frac{1}{3}}]\) 内的质数筛掉,那么剩下的数最多包含两个 \((R^{\frac{1}{3}},R]\) 内的质因数

可以开根号判断是否是平方数,用 \(\text{Miller_Rabin}\) 判断是否是质数

由于 \(R-L\)\(R^{\frac{1}{3}}\) 同阶复杂度为 \(O(R^{\frac{1}{3}}\log R^{\frac{1}{3}})\) ,常数较大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair <int,int> Pii;
#define pb push_back
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)

char IO;
template <class T=int> T rd(){
T s=0;int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=1e6+10,P=998244353;

ll qmul(ll x,ll y,ll P){
ll z=(long double)x/P*y;
ll t=(ull)x*y-(ull)z*P;
t=t%P; Mod2(t);
return t;
}
ll qpow2(ll x,ll k,ll P) {
ll res=1;
for(;k;k>>=1,x=qmul(x,x,P)) if(k&1) res=qmul(res,x,P);
return res;
}
ll qpow(ll x,ll k,ll P) {
if(P>2e9) return qpow2(x,k,P);
ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}

char s[20000];
void Solve19(ll P){
scanf("%s",s+1);
ll k=0;
for(int i=1;s[i];++i) k=(qmul(k,10,P-1)+s[i]-'0')%(P-1);
printf("%lld\n",qpow(19,k,P));
}
int notpri[N],pri[N],pc,w[N];
void Init(){
notpri[1]=w[1]=1;
rep(i,2,N-1) {
if(!notpri[i]) pri[++pc]=i,w[i]=-1;
for(int j=1;j<=pc && 1ll*i*pri[j]<N;++j){
notpri[i*pri[j]]=1;
if(i%pri[j]==0) {
w[i*pri[j]]=0;
break;
}
w[i*pri[j]]=-w[i];
}
}
}
int MR(ll x){
if(x==2) return 1;
if(x<=1 || ~x&1) return 0;
if(x<N) return !notpri[x];
ll s=0,t=x-1;
while(~t&1) s++,t>>=1;
rep(i,1,3) {
ll b=pri[rand()%pc+1],k;
b=qpow(b,t,x);
rep(j,1,s) {
k=qmul(b,b,x);
if(k==1 && b!=1 && b!=x-1) return 0;
b=k;
}
if(b!=1) return 0;
}
return 1;
}
int chk(ll x){ return x<N?!notpri[x]:MR(x); }
char Mo(int x){ return "-0+"[x+1]; }
int ans[N]; ll a[N];
void QueMobius(ll l,ll r){
while(l<N && l<=r) putchar(Mo(w[l++]));
if(l>r) return (void)puts("");
rep(i,0,r-l) ans[i]=1,a[i]=i+l;
rep(i,2,N-1) if(!notpri[i]) {
for(ll j=(l+i-1)/i*i;j<=r;j+=i) {
ll id=j-l;
if(!ans[id]) continue;
ll c=0;
while(a[id]%i==0) a[id]/=i,c++;
if(c>1) ans[id]=0;
else ans[id]*=-1;
}
}
rep(i,0,r-l) if(ans[i] && a[i]>1) {
ll t=round(sqrt(a[i]));
if(t*t==a[i]) ans[i]=0;
else if(chk(a[i])) ans[i]=-ans[i];
}
rep(i,0,r-l) putchar(Mo(ans[i]));
puts("");
}
void Queg(int l,int r,int P){
int t=P-1;
vector <int> Fac;
for(int i=2;i*i<=t;++i) if(t%i==0){
while(t%i==0) t/=i;
Fac.pb(i);
}
if(t>1) Fac.pb(t);

if(r!=13123110) {
rep(i,l,r){
int f=1;
for(int j:Fac) if(qpow(i,(P-1)/j,P)==1) { f=0; break; }
putchar(".g"[f]);
}
} else {
static int mk[13123120];
static bool copri[13123120];
int g=1;
rep(i,l,r){
int f=1;
for(int j:Fac) if(qpow(i,(P-1)/j,P)==1) { f=0; break; }
if(f) { g=i; break; }
}
int s=1; mk[1]=0;
rep(i,1,P-2) s=s*g%P,mk[s]=i;
copri[0]=1;
for(int i:Fac) for(int j=i;j<P;j+=i) copri[j]=1;
rep(i,1,P-1) putchar("g."[copri[mk[i]]]);
}
puts("");
}

int main(){
Init();
freopen("software.in","r",stdin); freopen("software.out","w",stdout);
string typ; cin>>typ;
if(typ=="1_998244353") rep(kase,1,rd()) Solve19(998244353);
else if(typ=="1?") rep(kase,1,rd()) Solve19(1145141);
else if(typ=="1?+") rep(kase,1,rd()) Solve19(5211600617818708273);
else if(typ=="1wa_998244353") {
static int s[300000];
int T=-1,fir=-1;
map <int,int> M;
s[0]=1,M[1]=0;
rep(i,1,300000){
s[i]=int(((unsigned)s[i-1]*19))%P;
if(M.count(s[i])) {
T=i-M[s[i]],fir=M[s[i]];
break;
}
M[s[i]]=i;
}
rep(kase,1,rd()){
ll x=rd<ll>();
printf("%d\n",s[x<=fir?x:(x-fir)%T+fir]);
}
} else if(typ=="2p") {
rep(kase,1,rd()) {
ll l=rd<ll>(),r=rd<ll>();
for(ll i=l;i<=r;++i) putchar(chk(i)?'p':'.');
puts("");
}
} else if(typ=="2u") {
rep(kase,1,rd()) {
ll l=rd<ll>(),r=rd<ll>();
QueMobius(l,r);
}
} else if(typ=="2g" || typ=="2g?") {
rep(kase,1,rd()){
int l=rd(),r=rd();
int p=1515343657;
if(l!=233333333) p=rd();
Queg(l,r,p);
}
}
}

0%