ARC 杂题
ARC150C Path and Subsequence
考试题削弱版。
给你一个无向图,点 \(i\) 有点权 \(v_i\)。给你一个长为 \(k\) 的序列 \(b\),求 \(1\to n\) 的所有路径是否全部包含子序列 \(b\)。
\(k\le n,m\le 10^5\)
枚举 \(b_i\),从 \(1\) 开始,将所有不经过点权为 \(b_i\) 的点打上标记(时间戳) \(i\),接下来再对没经过的点往下跑。若 \(n\) 的时间戳为 \(k\) 则说明包含了。时间复杂度 \(O(n+m+k)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5e5+3;
int n,m,s;
vector<int>e[maxn];
queue<int>q,p;
int vis[maxn];
int c[maxn],b[maxn];
void dfs(int u,int t){
for(int v:e[u]){
if(vis[v]==-1){
vis[v]=t;
if(c[v]!=b[t+1]){
dfs(v,t);
}else{
p.push(v);
}
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin>>n>>m>>s;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1;i<=n;i++) cin>>c[i],vis[i]=-1;
for(int i=1;i<=s;i++) cin>>b[i];
e[0].push_back(1);
vis[0]=0;
q.push(0);
for(int i=0;i<=s;i++){
while(!q.empty()){
int j=q.front();
q.pop();
vis[j]=i;
dfs(j,i);
}
q=p;
while(!p.empty()) p.pop();
}
if(vis[n]==s) cout<<"Yes";
else cout<<"No";
return 0;
}
ARC185E Adjacent GCD
给你一个数列 \(a\),对于每个 \(m\in [1,n]\),求对于每个 \(a_{[1,m]}\) 的子序列 \(S\) 相邻两项的 GCD 的和的和。
\(n\le 5\times 10^5,a_i\le 10^5\)
设 \(f(i)\) 为以 \(i\) 结尾的答案。则 \(f(i)\) 的答案可以由选/不选 \(i\) 继承过来,再计算从每个 \(j<i\) 转移过来的系数即可,即
由于有 \(\gcd(a_i,a_j)=\sum\limits_{d\mid \gcd(a_i,a_j)} \varphi(d)=\sum\limits_{d} [d\mid a_i][d\mid a_j]\varphi(d)\),所以
把与 \(j\) 无关的往前扔
记 \(g(d,i)=\sum\limits_{j<i} 2^{j-1} [d\mid a_j]\),显然这一坨可以 \(O(d(a_i))\) 动态维护,考虑每次加入 \(a_i\) 统计完当前答案后,就在 \(d\mid a_i\) 的位置加上 \(2^{i-1}\) 的贡献即可。
所以最后的式子就是 \(f(i)=2f(i-1)+\sum\limits_{d\mid a_i} \varphi(d) g(d,i)\),时间复杂度 \(O(nd(n))\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5e5+7;
const int mod=998244353;
int n;
int a[maxn];
int f[maxn],g[maxn],po2[maxn],k[maxn],val[maxn];
vector<int>v[maxn];
signed main(){
cin>>n;
po2[0]=1;
for(int i=1;i<=n;i++){
cin>>a[i];
po2[i]=po2[i-1]*2%mod;
}
for(int i=1;i<=100000;i++){
k[i]+=i; v[i].emplace_back(i);
for(int j=2*i;j<=100000;j+=i){
k[j]-=k[i];
v[j].emplace_back(i);
}
}
for(int x=1;x<=n;x++){
f[x]=2*f[x-1];
for(int d:v[a[x]]){
f[x]=(f[x]+k[d]*val[d]%mod)%mod;
val[d]=(val[d]+po2[x-1])%mod;
}
cout<<f[x]<<'\n';
}
return 0;
}
ARC183C Not Argmax
求满足 \(m\) 个形如【\([l_i,r_i]\) 中最大值的不为 \(p_{x_i}\)】的排列 \(p\) 的数量。
\(n\le 500,m\le 2\times 10^5\)
考虑没有限制时怎么 DP,设 \(f(l,r)\) 表示 \([l,r]\) 的答案,枚举区间内最大值的位置,则有
加上限制即记个标记 \(t(l,r,k)\),当满足标记时不转移即可,注意转移顺序。标记转移为 \(t(l,r,k)|=t(l+1,r,k)|t(l,r-1,k)\)。时间复杂度 \(O(n^3+m)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=507;
const int maxm=2e5+7;
const int mod=998244353;
int n,m;
int f[maxn][maxn],fac[maxn],ifac[maxn];
bitset<maxn>is[maxn][maxn];
int qpow(int a,int b){
int res=1;
for(;b;b>>=1,a=a*a%mod) if(b&1) res=res*a%mod;
return res;
}
int C(int a,int b){
if(a<b) return 0;
return fac[a]*ifac[a-b]%mod*ifac[b]%mod;
}
signed main(){
cin>>n>>m;
for(int i=1,l,r,x;i<=m;i++){
cin>>l>>r>>x;
is[l][r][x]=1;
if(l==r){
cout<<"0\n";
return 0;
}
}
for(int len=2;len<=n;len++)
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
is[l][r]|=is[l+1][r];
is[l][r]|=is[l][r-1];
}
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
ifac[n]=qpow(fac[n],mod-2);
for(int i=n-1;~i;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
f[1][0]=1;
for(int i=1;i<=n;i++) f[i][i]=f[i+1][i]=1;
for(int len=2;len<=n;len++)
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
for(int k=l;k<=r;k++) if(!is[l][r][k])
f[l][r]=(f[l][r]+f[l][k-1]*f[k+1][r]%mod*C(r-l,k-l)%mod)%mod;
}
cout<<f[1][n];
return 0;
}
ARC186B Typical Permutation Descriptor
给你一个序列 \(a\) 满足 \(a_i<i\),求满足以下条件的排列 \(p\) 的数量:
- \(p_j>p_i>p_{a_i}(j\in(a_i,i))\)
\(n\le 3\times 10^5\),保证有解
由于保证有解,考虑观察有解的情况所带来的性质:
- 区间 \([a_i,i]\) 要么把前面的若干区间完全包含,要么左端点与相邻区间端点相交;
由性质 1 与偏序关系可知,假如以偏序关系(大于号连接的两边)连边,\(p_i\) 为点权,以 \(p_0\) 为根,则形成一棵满足 \(u\) 子树内的点权大于 \(u\) 点权的树。
这棵树的性质很好啊,当你用拓扑序遍历这棵树他一定合法,即为充分必要条件了,虽然我没看出来。
接下来就是一个裸的树上拓扑序计数了,也是个结论,即
证明:
考虑树形 DP。设 \(f(u)\) 为以 \(u\) 为根子树的拓扑序数量。
考虑合并两棵子树 \(v_1,v_2\),先把两棵子树的方案数乘起来然后考虑顺序,即在 \(siz(v_1)+siz(v_2)\) 个数里选掉 \(siz(v_1)\) 个数。更一般的,多个子树相当于叠加,而且组合约掉了,则有转移\[\begin{aligned} f(u)&=\binom{siz(v_1)+siz(v_2)}{siz(v_1)}\binom{siz(v_1)+siz(v_2)+siz(v_3)}{siz(v_1)+siz(v_2)}\cdots\binom{siz(u)-1}{siz(v_1)+siz(v_2)+\cdots+siz(v_{x-1})}\prod\limits_{v\in son(u)}f(v)\\ &=\frac{(siz(u)-1)!}{\prod\limits_{v\in son(u)}siz(v)!}\prod\limits_{v\in son(u)}f(v)\\ &=\frac{(siz(u)-1)!}{\prod\limits_{v\in son(u)}siz(v)!}\prod\limits_{v\in son(u)}f(v)\\ \end{aligned}\]考虑把每个 \((siz(u)-1)!\) 与 \(siz(v)!\) 相抵消,剩下 \(\prod\limits_{i=2}^n\frac{1}{siz(i)}\) 以及 \((siz(1)-1)!\),写得好看点,都乘个 \(siz(1)\),即得上式。
时间复杂度 \(O(n)\)。
ARC187B Sum of CC
给你一个长为 \(n\) 的数列 \(a\),\(a_i=-1\) 表示未确定,可以填入 \([1,m]\),对于一个填完的序列,连边 \((i,j)\) 当且仅当 \(i<j,a_i\le a_j\),求所有可能的序列的连通块数量和。
\(n,m\le 2\times 10^3\)
开始直接看错题,以为是图中的边数,直接干瞪 2h 无果。
考虑发现一些性质,手玩发现所有的连通块形成区间。且左边的最小值大于右边的最大值。这个证明随便找三个数玩一下就出来了。
所以记录前缀 \(\min\) \(p\),后缀 \(\max\) \(q\),前后缀 -1 数量 \(r,s\)。然后枚举每个断点 \(i\) 表示 \(i,i+1\) 不在同一联通块的方案数(贡献显然为多出了一个连通块)。即要求前缀 \(\min>\) 后缀 \(\max\),所以 再枚举前缀 \(\min=j\),前缀的 -1 取值范围为 \([j,m]\),后缀 -1 的取值范围为 \([1,j-1]\),但是这样会算重。记 \(g_1(j)\) 为前缀 \(\min\ge j\) 的方案数 \(=(m-j+1)^{r_i}\),\(g_2(j)\) 为后缀 \(\max\le j\) 的方案数 \(=(j-1)^{s_{i+1}}\),则有 \(g_3(j)=g_2(j)-g_2(j-1)\) 为后缀 \(\max=j\) 的方案数,这样固定一边乘以另一边就不会记重了。统计 \(g_1(j)g_3(j-1)\) 即可。时间复杂度 \(O(nm)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2007;
const int mod=998244353;
int n,m;
int a[maxn],fac[maxn],ifac[maxn];
int h[maxn],g[maxn],mx[maxn],mi[maxn],pmx[maxn],pmi[maxn];
void add(int &x,int y){x+=y;if(x>=mod) x-=mod;}
void mul(int &x,int y){x=x*y%mod;}
int qpow(int a,int b){
int res=1;
for(;b;b>>=1,a=a*a%mod) if(b&1) res=res*a%mod;
return res;
}
int C(int a,int b){
if(a<b) return 0;
return fac[a]*ifac[b]%mod*ifac[a-b]%mod;
}
signed main(){
cin>>n>>m;
fac[0]=1;
for(int i=1;i<=n;i++)
fac[i]=fac[i-1]*i%mod;
ifac[n]=qpow(fac[n],mod-2);
for(int i=n-1;~i;i--)
ifac[i]=ifac[i+1]*(i+1)%mod;
pmi[0]=m;
for(int i=1;i<=n;i++){
cin>>a[i];
g[i]=g[i-1]+(a[i]==-1);
if(a[i]!=-1) pmi[i]=min(pmi[i-1],a[i]);
else pmi[i]=pmi[i-1];
}
for(int i=n;i;i--){
pmx[i]=max(pmx[i+1],a[i]);
h[i]=h[i+1]+(a[i]==-1);
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) mi[j]=mx[j]=0;
for(int j=1;j<=pmi[i];j++) mi[j]=qpow(m-j+1,g[i]);
for(int j=pmx[i+1];j<=m;j++) mx[j]=qpow(j,h[i+1]);
for(int j=2;j<=m;j++) add(ans,mi[j]*(mx[j-1]-mx[j-2]+mod)%mod);
}
add(ans,qpow(m,g[n]));
cout<<ans;
return 0;
}