orangejuice's blog

挂了请务必叫我

HDU-5869 Different GCD Subarray Query(树状数组)

先不考虑查询的区间 \([L,R]\)

首先我们枚举一个 \(\gcd\) 区间的 \(l\) ,考虑不同的 \(\gcd(l..r)\) 实际上只有 \(\log n\) 个,因为每次改变, \(\gcd\) 的值至少减少一倍

维护一个倍增数组,可以 \(\log n\) 二分出下一个 \(\gcd\) 不同的 \(r\) ,统计出每一个的 \(r\) ,那么就能得到 \(n\log n\) 个不同的区间

问题就转化为求 \([L,R]\) 包含的权值不同的 \([l,r]\) 个数

那么可以把同一种权值的区间拉出来,离线之后,按照 \(l\)\(L\) 倒序,每次对于 \([l,r]\) 更新的区间就是 \(r\) 到这个权值之前出现过的最小 \(r\)

一共有 \(n\log n\) 个更新,用树状数组维护区间更新,复杂度 \(O(n\log^2n)\)

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
const int N=1e5+10,P=1e9+7;

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 gcd(int a,int b){ return b==0?a:gcd(b,a%b); }

int n,m;
int a[N];

struct BIT{
int s[N];
void clear(){ rep(i,1,n) s[i]=0; }
void Add(int p,int x) { while(p<=n) s[p]+=x,p+=p&-p; }
int Que(int p){
int res=0;
while(p) res+=s[p],p-=p&-p;
return res;
}
}Tree;

struct Query{ int p,id; };
struct Update{ int p,x; };
vector <Query> Q[N];
vector <Update> U[N];
int A[N],ans[N],G[N][18];


int apr[N],apc,mk[N*10];
void AddUpdate(int l,int r,int x) {
if(!mk[x]) apr[++apc]=x,mk[x]=n+1;
if(r>=mk[x]) return;
U[l].pb((Update){r,1}),U[l].pb((Update){mk[x],-1});
mk[x]=r;
}

int main(){
while(~scanf("%d%d",&n,&m)) {
rep(i,1,n) {
A[i]=rd(),Q[i].clear(),U[i].clear();
}
drep(i,n,1) {
G[i][0]=A[i];
for(int j=1;i+(1<<j)<=n+1;++j) G[i][j]=gcd(G[i][j-1],G[i+(1<<(j-1))][j-1]);
int x=A[i],r=i;
while(r<=n) {
AddUpdate(i,r,x);
drep(j,17,0) if(r+(1<<j)<=n+1 && G[r][j]%x==0) r+=1<<j;
x=gcd(x,A[r]);
}
}
rep(i,1,m) {
int l=rd(),r=rd();
Q[l].pb((Query){r,i});
}
Tree.clear();
drep(i,n,1) {
for(auto j:U[i]) Tree.Add(j.p,j.x);
for(auto j:Q[i]) ans[j.id]=Tree.Que(j.p);
}
rep(i,1,m) printf("%d\n",ans[i]);
rep(i,1,apc) mk[apr[i]]=0; apc=0;
}
}



#[HDU-6791] 2020HDU多校第三场T1(回文自动机)

前置知识:

1.字符串的 \(\text{Border}\)

2.回文自动机

3.回文串与 \(\text{Border}\)

3.1:回文串的 \(\text{Border}\) 也是回文串

若有回文串 \(S\) 的一个 \(\text{Border} :T\) ,则 \(S_{1,|T|}=S_{|S|-|T|+1,|S|}=reverse(S_{1,|T|})\)

\(T\) 也是一个回文串

3.2:遍历回文自动机的 \(fail\) 链,能得到当前串的所有 \(\text{Border}\) (基于3.1得到)

约定:串 \(S\)\(\text{Border}\) 集合为 \(B(S)\) ,字符集为 \(\Sigma\)

题意:

设随机空串末尾添加 \(\Sigma\) 中的字符,第一次出现子串 \(S\) 的期望长度为 \(E(S)\)

给定一个串,每次查询它的两个回文子串 \(A,B\) ,比较 \(E(A),E(B)\)

起源?

一切的起源都是" 国家集训队论文2018 :1-浅谈生成函数在掷骰子问题上的应用 "的一个结论。。。

还有为什么会是回文子串呢?因为只有回文自动机能访问子串的所有 \(\text{Border}\) 。。。

结论 以及 口胡证明?

\(\begin{aligned}E(S)=\sum_{T\in B(S)}|\Sigma|^{|T|}\end{aligned}\) (???)

在原论文给出了生成函数性的证明,实际可以直接口胡(好吧也差不多),大致分成两个步骤

  1. \(E(S)=\sum_{i=0}^{\infty}\) 长度为 \(i\) 依然不包含 \(S\) 的概率(即把长度为 \(i\) 时恰好合法转化为了 \(0..i-1\) 时不合法)

2.设所有长度下不合法的串集合为 \(G\) (每个不合法串有概率 \(G(T)\) ),合法的串集合为 \(F\) (每个合法串也有概率 \(F(T)\) )

由第一步 \(E(S)=\sum G(T)\) ,合法串的概率不会重复,所以 \(\sum F(T)=1\)

考虑 \(G\) 中所有的串,如果在后面接上 \(S\) 必然合法,但是可能在更早的时候就结束了,这是必然满足接上的前缀是 \(\text{Border}\)

也就是说,在 \(G\) 集合后面接上 \(S\) 后,不仅会得到 \(F\) 集合,还会得到 \(F\) 集合后面额外接上 \(|S|-|R|,(R\in B(S))\) 长度字符的状态

所以有 \(\sum G(T)\cdot (\frac{1}{|\Sigma|})^{|S|}=\sum_{R\in B(S)}\sum F(T)\cdot (\frac{1}{|\Sigma|})^{|S|-|R|}\)

化简且带入 \(\sum F(T)=1\) ,得到 \(E(S)=\sum G(T)=\begin{aligned}\sum_{R\in B(S)}|\Sigma|^{|R|}\end{aligned}\)

那么比较问题就落到了比较 \(\text{Border}\) 上面

视答案为为一个 \(26\) 进制数从高位到低位比较,转化为直接从大到小比较 \(\text{Border}\) 序列的字典序即可

建出回文自动机后,倍增找到当前查询串对应的状态,所有的 \(\text{Border}\) 就是 \(fail\) 链上所有非空状态长度

比较字典序可以

1.倍增+hash

2.可以根据 \(\text{Border}\) 的性质分解为等差数列后暴力比较

3.像后缀数组一样,倍增地去为所有节点的字典序排序,这样查询是 \(O(1)\)

hash应该细节比较少,但是常数大

以下是hash版本

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
#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)

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=1e5+10;
const ll P1=1e9+13,P2=19260817;
const ll K1=123213,K2=342525;

int n;
char s[N];
int now,len[N],fail[N],nxt[N][26],pos[N],cnt;
int Pow1[N],Pow2[N];
int fa[N][18],h1[N][18],h2[N][18];

void Init(){
rep(i,0,cnt) memset(nxt[i],fail[i]=0,104);
len[1]=-1;
fail[now=0]=fail[1]=cnt=1;
}
int Find(int x,int y){
while(s[y]!=s[y-len[x]-1]) x=fail[x];
return x;
}
void Extend(int i,int c){
now=Find(now,i);
if(!nxt[now][c]){
fail[++cnt]=nxt[Find(fail[now],i)][c];
len[nxt[now][c]=cnt]=len[now]+2;
}
pos[i]=now=nxt[now][c];
}
int Que(int l,int p){
l=p-l+1,p=pos[p];
drep(i,17,0) if(len[fa[p][i]]>=l) p=fa[p][i];
return p;
}

int main(){
rep(i,Pow1[0]=Pow2[0]=1,N-1) Pow1[i]=1ll*Pow1[i-1]*K1%P1,Pow2[i]=Pow2[i-1]*K2%P2;
rep(kase,1,rd()){
Init(),n=rd(),scanf("%s",s+1);
rep(i,1,n) Extend(i,s[i]-'a');
rep(i,2,cnt) {
fa[i][0]=fail[i],h1[i][0]=h2[i][0]=len[i];
rep(j,1,17){
fa[i][j]=fa[fa[i][j-1]][j-1];
if(fa[i][j]>1){
h1[i][j]=(1ll*h1[i][j-1]*Pow1[1<<(j-1)]+h1[fa[i][j-1]][j-1])%P1;
h2[i][j]=(1ll*h2[i][j-1]*Pow2[1<<(j-1)]+h2[fa[i][j-1]][j-1])%P2;
}
}
}
rep(q,1,rd()) {
int A=rd(),B=rd(),C=rd(),D=rd();
A=Que(A,B),C=Que(C,D);
drep(i,17,0) if(fa[A][i]>1 && fa[C][i]>1 && h1[A][i]==h1[C][i] && h2[A][i]==h2[C][i]) A=fa[A][i],C=fa[C][i];
A=max(len[A],0),C=max(len[C],0);
if(A==C) puts("draw");
else if(A<C) puts("sjfnb");
else puts("cslnb");
}
}
}


2016 多校5 ATM

题意:

有个人富到不知道自己有多少钱,但是知道钱数 \(x\in \Z \cap [0,K]\)

它最多可以有 \(W\) 次查询超过钱数, \(W\ge 1\)

要求在最优决策的情况下,最小次数取出所有钱的期望次数

\[ \ \]

\(K,W\) 上界为 \(O(n)\)

先考虑边界情况,如果它手里有 \(0\) 块钱,那么需要查询一次才知道自己吃土了

如果手头 \(W=1\) ,那么只能每次取 \(1\) ,否则就可能被抓走

众所周知,情况个数有限的期望问题,可以先直接计数然后除掉情况数,所以

定义 \(dp_{i,j}\) 为已知手头的票子上限 \(i\) ,且还剩 \(j\) 次会被抓去干奇怪的事情的最小代价总和

对于 \(j>1\) 的情况,我们需要决策这一次选出多少钱

假设这一次我们选择取出 \(k\) 块钱

1.那么对于实际钱数为 \([0,k-1]\) 的部分,查询会超限,并且知道上界变为 \(k-1\)

2.对于实际钱数 \([k,i]\) 的部分,上界变为 \(i-k\)

而这次决策产生的代价要计算所有情况的代价,即为 \(i+1\)

因此,转移的表达式应是 \(\begin{aligned} dp_{i,j}=\min_{k=1}^i\lbrace dp_{k-1,j-1}+dp_{i-k,j}+i+1\rbrace\end{aligned}\)

因为不是期望而是计数,决策应该更好理解了吧

直接转移,复杂度为 \(O(n^3)\)

优化1

不会证明,但是 \(dp_{k-1,j-1}+dp_{i-k,j}\) 构成了关于 \(k\)斜率单调非递减的函数(俗称单峰函数),可以直接三分

复杂度为 \(O(n^2\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
#include<bits/stdc++.h>
using namespace std;
typedef long double ldb;
#define reg register
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,const T &b){ ((a>b)&&(a=b)); }

const int N=2e3+10;

int n,m;
int dp[N][N];

int main(){
rep(i,1,N-1) dp[1][i]=2;
rep(i,1,N-1) dp[i][1]=i*(i+1)/2+i;
rep(i,2,N-1) rep(j,2,N-1) {
dp[i][j]=1e9;
int l=1,r=i;
cmin(dp[i][j],dp[0][j-1]+dp[i-1][j]+i+1);
cmin(dp[i][j],dp[i-1][j-1]+dp[0][j]+i+1);
while(l<r) {
int a=(l+r)>>1,b=a+1;
int x=dp[a-1][j-1]+dp[i-a][j]+i+1,y=dp[b-1][j-1]+dp[i-b][j]+i+1;
cmin(dp[i][j],x),cmin(dp[i][j],y);
if(x>=y) l=b;
else r=a;
}
}
while(~scanf("%d%d",&n,&m)) printf("%.6Lf\n",(ldb)dp[n][m]/(n+1));
}


优化2

感性理解,钱的上界越大,显然我们每次最优决策要取出的也就越多

\(dp_{i,j}\) 的最优决策位置关于 \(i\) 单调非递减

由于转移的式子比较奇怪,这个题目不好使用决策单调性的分治优化方法(或许很简单吗)

但是由于转移是一个单峰函数,不存在波动的问题,所以可以直接记录决策位置 \(g_{i,j}\)

或者说,就是单峰函数的最值位置是递增的,每次从 \(g_{i-1,j}\) 的最优位置开始向后找到 \(g_{i,j}\) 的峰的位置即可停止

对于每个 \(j\)\(g_{i,j}\) 最多从 \(1\) 移动到 \(K\) ,所以复杂度为 \(O(n^2)\)

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
#include<bits/stdc++.h>
using namespace std;
typedef long double ldb;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,const T &b){ ((a>b)&&(a=b)); }

const int N=2e3+10;

int n,m;
int dp[N][N],G[N][N];

int main(){
rep(i,1,N-1) dp[1][i]=2;
rep(i,1,N-1) dp[i][1]=i*(i+1)/2+i;
rep(i,2,N-1) rep(j,2,N-1) {
dp[i][j]=1e9;
rep(k,max(G[i-1][j],1),i) { // 从上一个决策位置开始for
int x=dp[k-1][j-1]+dp[i-k][j]+i+1;
if(x<=dp[i][j]) G[i][j]=k,dp[i][j]=x;
else break;
}
}
while(~scanf("%d%d",&n,&m)) printf("%.6Lf\n",(ldb)dp[n][m]/(n+1));
}


优化3:第二维大小的优化

我们知道,存在一种决策方法即每次二分上界,可以取到一个较优值

满足这个决策只需要 \(W\ge \log_2 k\) ,大致可以认为 \(W\ge 10\)

在最优决策的情况下,一定可以在 \(10\) 次错误的范围内查出结果,即 \(W\ge 10\) 之后 \(W\) 的值已经不会影响答案了

所以直接上优化,转移复杂度就是 \(O(n^2\log n)\)

加上决策单调性的优化,就是 \(O(n\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
#include<bits/stdc++.h>
using namespace std;
#define reg register
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,const T &b){ ((a>b)&&(a=b)); }

const int N=2e3+10;

int n,m;
int dp[N][11],G[N][11];

int main(){
rep(i,1,10) dp[1][i]=2;
rep(i,1,N-1) dp[i][1]=i*(i+1)/2+i;
rep(i,2,N-1) rep(j,2,10) {
dp[i][j]=1e9;
rep(k,max(G[i-1][j],1),i) {
int x=dp[k-1][j-1]+dp[i-k][j]+i+1;
if(x<=dp[i][j]) G[i][j]=k,dp[i][j]=x;
else break;
}
}
while(~scanf("%d%d",&n,&m)) printf("%.6lf\n",1.0*dp[n][min(m,10)]/(n+1));
}

以下是来自地狱的魔改代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<cstdio>
#define rep(i,a,b) for(i=a;i<=b;++i)
enum{N=2000};
int n,m,dp[11][N|1],i,j,k,x;
main(){
rep(i,1,N) dp[1][i]=i*(i+1)/2+i;
rep(j,2,10) rep(i,dp[j][k=1]=2,N) {
dp[j][i]=1e9;
for(;k<=i && (x=dp[j-1][k-1]+dp[j][i-k]+i+1)<=dp[j][i];++k) dp[j][i]=x;
--k;
}
while(~scanf("%d%d",&n,&m))printf("%.6lf\n",1.0*dp[m>10?10:m][n]/(n+1));
}

[HDU - 6833] A Very Easy Math Problem (莫比乌斯反演)

\(\gcd\) 有关的问题,很容易想到莫比乌斯反演

\(G(a,n)=(\sum_{i=1}^{\lfloor \frac{n}{a} \rfloor } (ai)^k)^x\)

\(Ans=\sum_{g=1}^{n} g\cdot f(g)\cdot \sum _{d=1}^{\lfloor\frac{n}{g}\rfloor} \mu(d) G(gd,n)\)

对于单组询问,显然可以 \(O(n\ln n)\) 求解

考虑优化

可以在 \(O(n\ln n)\) 的时间内,对于每个 \(i\) ,求出 \(F(i)=\sum_{d|i}\mu(d)\cdot \frac{i}{d} f(\frac{i}{d})\)

对于 \(G(a,n)\) 的求解,参数分离后发现 \(G(a,n)=a^{kx}(\sum_{i=1}^{\lfloor \frac{n}{a} \rfloor } i^k)^x\)

可以预处理出 \(S(n)=\sum_{i=1}^n i^{kx}\cdot F(i)\) 前缀和以及 \(A(n)=(\sum_{i=1}^{n}i^k)^x\) ,对于每个 \(\lfloor \frac{n}{a}\rfloor\) 考虑即可

数论分段的复杂度为单组查询 \(O(\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
#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)

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=2e5+10,P=1e9+7;

int T,n,k,x;
int mk[N],notpri[N],pri[N],pc,w[N];
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 s[N],F[N],S[N],A[N];


int main(){
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];
}
}
for(int i=2;i*i<N;++i) for(int j=i*i;j<N;j+=i*i) mk[j]=1;
rep(i,1,N-1) if(!mk[i]) rep(j,1,(N-1)/i) F[i*j]=(F[i*j]+i*w[j]+P)%P;
T=rd(),k=rd(),x=rd();
rep(i,1,N-1) S[i]=(S[i-1]+F[i]*qpow(i,1ll*k*x))%P;
rep(i,1,N-1) A[i]=(A[i-1]+qpow(i,k))%P;
rep(i,1,N-1) A[i]=qpow(A[i],x);
rep(kase,1,T) {
n=rd();
ll ans=0;
for(int i=1,j;i<=n;i=j+1) {
j=n/(n/i);
ans=(ans+1ll*(S[j]-S[i-1])*A[n/i])%P;
}
ans=(ans%P+P)%P;
printf("%lld\n",ans);
}
}

[HDU-6834] Yukikaze and Smooth numbers

题意:计算 \([1,n]\) 中只包含 \([1,k]\) 的质因数的数个数

让人联想到Min25筛的 \(dp\) 模型

\(m=\sqrt n\) ,可以对于 \(k > m\)\(k\leq m\) 讨论

Case1: \(k\leq m\)

此时可以直接套用类似Min25筛的 \(dp\) 模型求解

\(dp_{i,j}\)\([1,j]\) 只包含 \([1,i]\) 的质因数的数个数

\(dp_{i,j}=\sum_k dp_{i-1,\lfloor \frac{j}{prime_i^k}\rfloor }\)

要求的是 \(dp_{k,n}\) ,第二维状态是 \(O(m)\) 级别的

直接写当然是近似于 \(O(m\cdot \pi(n))=O(\frac{n}{\log n})\) 级别的

加上Min25筛的优化,令 \(dp_i,j\) 不包含单质数和1的情况,以减少转移情况

如果从大到小考虑每个质数,那么只需要考虑 \(j\ge prime_i^2\) 的第二维状态,以减少很多的 \(dp\) 时间

沿用Min25筛复杂度证明,是 \(O(\frac{n^{\frac{3}{4}}}{\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
#define id(x) (x<=m?x:cnt-n/x+1)

int dp[N],g[N],st[N],cnt;

if(k==1){ puts("1"); continue; }
m=sqrt(n),cnt=0;
rep(i,1,n) st[++cnt]=i=n/(n/i),dp[cnt]=0; // 不包括质数本身和1
int sz=1;
while(pri[sz+1]<=k) sz++;
int p=0;
rep(i,1,cnt){
while(p<sz && pri[p+1]<=st[i]) p++;
g[i]=p;
}
rep(i,1,cnt) for(ll x=pri[sz]*pri[sz];x<=st[i];x*=pri[sz]) dp[i]++;
for(reg int i=sz-1;i;--i) {
for(reg int j=cnt,tmp=pri[i]*pri[i];st[j]>=tmp;--j) {
reg int x=st[j];
while(x>=tmp) {
x/=pri[i];
dp[j]+=dp[id(x)]+g[id(x)]-i+1;
}
}
}
printf("%d\n",dp[cnt]+sz+1);

\[\ \]

Case2 : \(k> m\)

可以把问题转化为求不合法部分,即 \(\sum_{prime_i>k}\lfloor \frac{n}{prime_i}\rfloor\)

采用数论分段计算 \(\lfloor \frac{n}{i}\rfloor\) ,那么剩下的问题就是要求一段区间内的质数个数

同样采用类似上面的模型,

\(dp_{i,j}\)\([1,j]\) 内与前 \([1,i]\) 内质数互质的个数以及这些质数本身,不包括1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int n,m;
int dp[N],g[N],st[N],cnt;
#define id(x) (x<=m?x:cnt-n/x+1)

int Count(int n) {
if(n<N) return pcount[n];
::n=n,m=sqrt(n),cnt=0;
rep(i,1,n) st[++cnt]=i=n/(n/i),dp[cnt]=i-1;
for(reg int i=1;pri[i]<=m;++i) {
for(reg int j=cnt,tmp=pri[i]*pri[i];st[j]>=tmp;--j) {
reg int k=st[j]/pri[i];
dp[j]-=dp[id(k)]-(i-1);
}
}
return dp[cnt];
}

具体复杂度没有算过,应该不会太高

HDU-6801 2020HDU多校第三场T11 (生成函数)

题解又给式子不解释了。。

设未被选中的概率 \(q=1-p\)

\(a_i\)\(c\) 号点被选中前有 \(i\) 个点被选中的概率,它的普通生成函数为 \(A(x)\)

考虑枚举 \(c\) 在第 \(i\) 次被访问到时被选中

\(c\) 前面的 \(c-1\) 个点在转的过程中被访问了 \(i\) 次,后面的 \(n-c\) 个点被访问了 \(i-1\) 次(可能在没有经过这么多次时就已经被删掉了,但是不影响概率的计算),因此可以简单考虑这两种点在 \(c\) 之前被选中的概率

得到一个 \(A(x)\) 的表示

\(\begin{aligned} A(x)=\sum_{i=1}^{\infty}p\cdot q^{i-1}\cdot (q^i+(1-q^i)x)^{c-1}\cdot (q^{i-1}+(1-q^{i-1}x))^{n-c}\end{aligned}\)

也即题解中的式子

\(\begin{aligned} A(x)=\sum_{i=0}^{\infty}p\cdot q^i\cdot (q^{i+1}+(1-q^{i+1})x)^{c-1}\cdot (q^i+(1-q^ix))^{n-c}\end{aligned}\)

然后是暴力展开

\(\begin{aligned} A(x)=\sum_{i=0}^{\infty}p\cdot q^i\cdot (q^{i+1}(1-x)+x)^{c-1}\cdot (q^i(1-x)+x)^{n-c}\end{aligned}\)

\(\begin{aligned} A(x)=\sum_{i=0}^{\infty}p\cdot q^i\cdot (\sum_{j=0}^{c-1}C(c-1,j)\cdot q^{(i+1)j}(1-x)^jx^{c-1-j})\cdot (\sum_{k=0}^{n-c}C(n-c,k)\cdot q^{ik}(1-x)^kx^{n-c-k})\end{aligned}\)

\(\begin{aligned} A(x)=\sum_{i=0}^{\infty}p\cdot q^i\cdot \sum_{j=0}^{c-1}\sum_{k=0}^{n-c} C(c-1,j)\cdot C(n-c,k)\cdot q^{(i+1)j+ik}(1-x)^{j+k}x^{n-j-k-1}\end{aligned}\)

这个式子极其反人类,把 \(i\) 换到右边化掉

\(\begin{aligned} A(x)=\sum_{j=0}^{c-1}\sum_{k=0}^{n-c} C(c-1,j)\cdot C(n-c,k)\cdot (1-x)^{j+k}x^{n-j-k-1}\cdot \sum_{i=0}^{\infty}p\cdot q^i q^{(i+1)j+ik}\end{aligned}\)

右边是一个收敛的无穷等比数列

\(\begin{aligned} A(x)=\sum_{j=0}^{c-1}\sum_{k=0}^{n-c} C(c-1,j)\cdot C(n-c,k)\cdot (1-x)^{j+k}x^{n-j-k-1}\cdot \frac{pq^j}{1-q^{j+k+1}}\end{aligned}\)

\(\begin{aligned} A(x)=\sum_{j=0}^{c-1}\sum_{k=0}^{n-c} C(c-1,j)\cdot C(n-c,k)\cdot x^{n-j-k-1}\frac{pq^j}{1-q^{j+k+1}}\cdot (\sum_{i=0}^{j+k} C(j+k,i) \cdot (-x)^i)\end{aligned}\)

虽然有三个循环,但是很显然可以先对于 \(j,k\) 进行一次卷积,然后再对于 \(j+k,-i\) 进行一次卷积得到

Code:

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
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef double db;
typedef unsigned long long ull;
typedef pair <int,int> Pii;
#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())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=1<<21,P=998244353;


int n,c,p,q;
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 w[N],Fac[N+1],Inv[N+1],FInv[N+1],rev[N];
void Init(){
Fac[0]=Fac[1]=Inv[0]=Inv[1]=FInv[0]=FInv[1]=1;
rep(i,2,N) {
Fac[i]=1ll*Fac[i-1]*i%P;
Inv[i]=1ll*(P-P/i)*Inv[P%i]%P;
FInv[i]=1ll*FInv[i-1]*Inv[i]%P;
}
w[N>>1]=1; ll t=qpow(3,(P-1)/N);
rep(i,(N>>1)+1,N-1) w[i]=w[i-1]*t%P;
drep(i,(N>>1)-1,1) w[i]=w[i<<1];
}
int Init(int n){
int R=1,c=-1;
while(R<n) R<<=1,c++;
rep(i,1,R-1) rev[i]=(rev[i>>1]>>1)|((i&1)<<c);
return R;
}

void NTT(int n,int *a,int f) {
rep(i,1,n-1) if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=1;i<n;i<<=1) {
int *e=w+i;
for(int l=0;l<n;l+=i*2) {
for(int j=l;j<l+i;++j) {
int t=1ll*a[j+i]*e[j-l]%P;
a[j+i]=a[j]-t; Mod2(a[j+i]);
a[j]+=t; Mod1(a[j]);
}
}
}
if(f==-1) {
reverse(a+1,a+n);
rep(i,0,n-1) a[i]=1ll*a[i]*Inv[n]%P;
}
}

int A[N],B[N],C[N];

int main(){
Init();
rep(kase,1,rd()) {
n=rd(),p=rd(),q=rd(),p=p*qpow(q)%P,q=P+1-p,c=rd();
int R=Init(n),t=1;
rep(i,0,R-1) A[i]=B[i]=C[i]=0;
rep(i,0,c-1) {
A[i]=1ll*Fac[c-1]*FInv[i]%P*FInv[c-1-i]%P*t%P;
t=1ll*t*q%P; // t= q^i
}
rep(i,0,n-c) B[i]=1ll*Fac[n-c]*FInv[i]%P*FInv[n-c-i]%P;
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);

R=Init(n*2);
rep(i,n,R-1) A[i]=0;
rep(i,0,R-1) B[i]=0;
t=1;
rep(i,0,n-1) {
t=1ll*t*q%P; // t=q^{i+1}
A[i]=1ll*A[i]*Fac[i]%P*qpow(P+1-t)%P;
}
rep(i,0,n) B[n-i]=(i&1)?P-FInv[i]:FInv[i];
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);
rep(i,1,n) printf("%d\n",int(1ll*FInv[n-i]*A[2*n-i]%P*p%P));
}
}


实际跑两次卷积,写得丑几乎就是顶着时限过去的。。

[HDU-6848] Expectation (2020多校7T5) (dp)

比赛时疯狂脑抽写了3个小时祭

考虑计算每条 \(x_i\rightarrow x_{i+1}\) 的边被在所有情况下被经过的次数总和

\(dp[i][j]\) 为有 \(i\) 个球时, \(x_j\rightarrow x_{j+1}\) 这段被经过的次数总和( \(j\leq 2i\) )

考虑转移,对于 \(dp[i]\) ,枚举每个球向左或者右走,发现把两边的部分拉拢过来后,合并形成一条包含了原先 \(3\) 条边的新边,变成了 \(i-1\) 阶的子问题

画图理解,发现 \(j\) 这条边,在 \(i-1\) 阶的子问题上对应的编号只可能是 \(j,j-1,j-2\)

视选择了 \(j\) 这条边为将边一端的球滚进另一端的洞里

那么对于任意一条编号为 \(j\) 的边

\(j\) 变为编号为 \(j-2\) 的情况为选择了编号 \([1,j-1]\) 范围内的边

\(j\) 变为编号为 \(j-1\) 的情况为选择了编号为 \(j\) 的边

\(j\) 变为编号为 \(j\) 的情况为选择了编号为 \([j+1,2i]\) 的边

对于 \(j\) 在子问题中被访问的次数可以直接 \(O(1)\) 继承过来

同时,考虑当第一次就选了 \(j\) 时,后面的操作随意,即加上 \((i-1)!\cdot 2^{i-1}\)

于是得到一个 \(O(n^2)\)\(dp\) 预处理

而对于每个询问,求解 \(n\) 阶的答案复杂度为 \(O(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
#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)

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=3010,P=998244353;

int n;
int dp[N][N*2],Fac[N];
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 main(){
rep(i,Fac[0]=1,N-1) Fac[i]=1ll*i*Fac[i-1]%P;
dp[1][1]=dp[1][2]=1;
int t=1;
rep(i,2,N-1) {
t=1ll*t*(i-1)*2%P;
rep(j,1,i*2) {
dp[i][j]=(1ll*(i*2-j)*dp[i-1][j]+1ll*dp[i-1][j-1]+1ll*(j-1)*(j>=2?dp[i-1][j-2]:0)+t)%P;
}
}

rep(kase,1,rd()) {
n=rd();
int ans=0,x=rd();
rep(i,1,n*2) {
int y=rd();
ans=(ans+1ll*(y-x)*dp[n][i])%P;
x=y;
}
ans=ans*qpow((P+1)/2,n)%P*qpow(Fac[n])%P;
printf("%d\n",ans);
}
}

[HDU-6854] Kcats (2020多校7 T11) (笛卡尔树+区间dp)

前缀 \(p_1,p_2,\cdots,p_i\) 的单调栈大小,即 \(i\) 号节点在全局的笛卡尔树上对应的位置的所有在左边的祖先个数

因此,区间 \(dp\) 笛卡尔树的树形,合并时,为了满足题目的限制,只需要记录左边的祖先个数 \(d\)

即定义 \(dp[l][r][d]\) 为区间 \(l,r\) 对应笛卡尔树子树,且根节点左祖先个数为 \(d\) 的方案数

合并两个子树时注意补上组合数,且自己这个点对于左儿子深度没有贡献

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
#include<bits/stdc++.h>
using namespace std;
#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())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

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

int n,a[N],C[N][N],dp[N][N][N];

int main(){
rep(i,0,N-1) rep(j,C[i][0]=1,i) C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;
rep(kase,1,rd()) {
rep(i,1,n=rd()) a[i]=rd();
memset(dp,0,sizeof dp);
drep(i,n,1) rep(j,i,n) rep(k,i,j)
rep(d,~a[k]?a[k]:1,~a[k]?a[k]:n)
dp[i][j][d]=(dp[i][j][d]+1ll*(i<k?dp[i][k-1][d]:1)*(k<j?dp[k+1][j][d+1]:1)%P*C[j-i][k-i])%P;
printf("%d\n",dp[1][n][1]);
}
}




[HDU-6883] Coin Game(2020HDU多校第十场T7)

题目给出的模型看起来比较奇怪,但是简单推理后,发现可以转化为一个简单的01背包问题

对于题目给定的权值 \(a_i,b_i\) ,分为 \(a_i,a_i+b_i\) 两个物品,发现可以得到这个机器的所有合法贡献情况

也就是说,有两种大小分别为 \(1,2\) 的物品,要做01背包

这个刚刚在WC2020考过。。。

设两类转化后的权值分别为 \(a_i,b_i\) ,则转移过程可以简单描述为

1.将两类权值分别从大到小排序

2.将dp值转化为在两个序列中分别选取一段前缀和

3.转移时枚举下一次决策的选取是那种物品,选取最优一个,记录指针转移即可

主要复杂度可能还在于排序,Trick:有一点卡内存

但是实测桶排和直接sort好像差距不大。。。

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)

const int N=5e6+10,INF=1e9;

int n,m;
int a[N],b[N];
ull k1,k2;
int Shift(){
ull k3=k1,k4=k2;
k1=k4;
k3^=k3<<23;
k2=k3^k4^(k3>>17)^(k4>>26);
return (k2+k4)%10000000+1;
}
int A[N*3],B[N*3]; // 记录在两个序列中的指针
ll dp[3]; // dp数组滚动了一下

int main(){
while(~scanf("%d%d%llu%llu",&n,&m,&k1,&k2)) {
rep(i,1,n) a[i]=Shift(),b[i]=Shift()+a[i];
sort(a+1,a+n+1,greater<int>()),sort(b+1,b+n+1,greater<int>());

A[0]=B[0]=1;
rep(i,0,2) dp[i]=0;
ll ans=0;
int cur=0;
rep(i,0,m) {
if(i+1<=m && A[i]<=n) {
int nxt=(cur+1)%3;
if(dp[cur]+a[A[i]]>dp[nxt]) dp[nxt]=dp[cur]+a[A[i]],A[i+1]=A[i]+1,B[i+1]=B[i];
}
if(i+2<=m && B[i]<=n) {
int nxt=(cur+2)%3;
if(dp[cur]+b[B[i]]>dp[nxt]) dp[nxt]=dp[cur]+b[B[i]],A[i+2]=A[i],B[i+2]=B[i]+1;
}
ans^=dp[cur];
cur=(cur+1)%3;
}
printf("%lld\n",ans);
}
}


HDU-6886 Tic-Tac-Toe-Nim(2020HDU多校第十场T10)

正如题目名字,这是一个nim游戏

观察题目条件,前两次操作一定会清空两个位置,那么考虑后面得到的状态是否先手必胜即可

对于这两个位置,发现只有两种情况

两个位置共线

此时先手者直接选择同线的另一个即可,必胜

两个位置不共线

此时还剩下一个位置与这两个空位均不共线,其他6个位置均共线

考虑可能存在的结束情况:

1.6个位置中有一个被清空,则下一个人直接清空共线的另一个位置必胜

2.不共线的位置被清空,不影响结局

考虑把这个问题转化为普通的Nim游戏

可以认为如果那6个位置均为1时游戏必败,而不共线的一个位置随意

则转化为 六个共线位置的值-1不共线位置的值 形成的普通Nim游戏,根据常识,直接异或判断即可

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
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
int rd(){
int IO,s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}

int a[4][4];
int Check(){ // 判断是否必胜
rep(i,1,3) rep(j,1,3) if(a[i][j]) {
int fl=1;
rep(k,1,3) if(j!=k && !a[i][k]) fl=0;
rep(k,1,3) if(k!=i && !a[k][j]) fl=0;
if(!fl) continue;
int x=a[i][j],ans=0;
a[i][j]=0;
rep(i,1,3) rep(j,1,3) if(a[i][j]) ans^=a[i][j]-1; // 共线位置
ans^=x,a[i][j]=x; // 不共线位置
return ans;
}
return '?';
}

int main(){
rep(kase,1,rd()) {
rep(i,1,3) rep(j,1,3) a[i][j]=rd();
int ans=0;
rep(i,1,3) rep(j,1,3) {
int t=a[i][j];
a[i][j]=0;
int fl=0;
rep(x,1,3) rep(y,1,3) { // 枚举两次初始操作,注意两次操作不共线!
if(x==i || y==j) continue;
int t=a[x][y];
a[x][y]=0;
if(!Check()) fl=1;
a[x][y]=t;
}
if(!fl) ans++;
a[i][j]=t;
}
printf("%d\n",ans);
}
}

0%