【学习笔记】ARC150/ARC151

ARC150

Path and Subsequence

没想到是 d p dp dp

不妨把每个点看作 A i A_i Ai,那么一条路径就是经过点的 A i A_i Ai构成的序列。记 d i d_i di表示 1 → i 1\to i 1i路径构成的所有序列中与 B B B序列 L C S LCS LCS的最小值(必须是 B B B序列的前缀)。只需判断 d n = K d_n=K dn=K

转移类比 LCS \text{LCS} LCS。事实上这是 L C S LCS LCS在无向图中的形式 。那么 d i = min ⁡ ( d j + [ B d j + 1 = A i ] ) d_i=\min(d_j+[B_{d_j+1}=A_i]) di=min(dj+[Bdj+1=Ai])。只需每次取出 d i d_i di最小的节点拓展。跑 dijkstra \text{dijkstra} dijkstra即可。

#include<bits/stdc++.h> #define ll long long #define fi first #define se second #define pb push_back using namespace std; int n,m,K,dp[200005],a[200005],vis[200005],b[200005]; vector<int>g[200005]; priority_queue<pair<int,int>>q; int main(){ cin.tie(0),cout.tie(0); ios::sync_with_stdio(false); cin>>n>>m>>K; for(int i=1;i<=m;i++){ int u,v;cin>>u>>v,g[u].pb(v),g[v].pb(u); } for(int i=1;i<=n;i++)cin>>a[i]; for(int i=1;i<=K;i++)cin>>b[i]; memset(dp,0x3f,sizeof dp),dp[1]=(a[1]==b[1]); q.push(make_pair(dp[1],1)); while(q.size()){ int u=q.top().se;q.pop(); if(vis[u])continue;vis[u]=1; for(auto v:g[u]){ int w=dp[u]+(a[v]==b[dp[u]+1]); if(w<dp[v])dp[v]=w,q.push(make_pair(-dp[v],v)); } } if(dp[n]==K)cout<<"Yes"; else cout<<"No"; }

Removing Gacha

v i v_i vi表示节点 i i i被操作的期望次数。答案是 ∑ i = 1 n v i \sum_{i=1}^nv_i i=1nvi

感性理解一下,此时只需考虑发生在路径 1 → i 1\to i 1i上的操作。问题转述为每次从 i i i个点中随机选一个,所有点都被选中时 i i i节点的操作次数(注意如果操作的是好的点也算,相当于样本容量变大了,但是期望不变)。注意到 i i i个节点的操作次数应该是相等的,因此只需算总操作次数,也就是 ∑ j = 1 i i j \sum_{j=1}^i\frac{i}{j} j=1iji。只需预处理 ∑ j = 1 i 1 j \sum_{j=1}^i\frac{1}{j} j=1ij1即可。

#include<bits/stdc++.h> #define ll long long #define fi first #define se second #define pb push_back using namespace std; const int mod=998244353; int n; ll f[200005],dep[200005]; ll res; vector<int>g[200005]; ll fpow(ll x,ll y){ ll z(1); for(;y;y>>=1){ if(y&1)z=z*x%mod; x=x*x%mod; }return z; } void dfs(int u,int topf){ dep[u]=dep[topf]+1;res=(res+f[dep[u]])%mod; for(auto v:g[u])dfs(v,u); } int main(){ cin.tie(0),cout.tie(0); ios::sync_with_stdio(false); cin>>n;for(int i=1;i<=n;i++)f[i]=(f[i-1]+fpow(i,mod-2))%mod; for(int i=2;i<=n;i++){ int j;cin>>j,g[j].pb(i); }dfs(1,0); cout<<res; }

Weathercock

思维题。非常考验观察能力。

f i f_i fi表示 1 ∼ i 1\sim i 1i R R R的数目减去 L L L的数目。不妨假设 R R R的总数目大于 L L L的总数目(反之将原串翻转并取反即可),那么 f n k > 0 f_{nk}>0 fnk>0

那么 S i S_i Si反转的情况为:

1.1 1.1 1.1 S i = L S_i=L Si=L ,并且 f i ≥ 0 f_i\ge0 fi0
1.2 1.2 1.2 S i = R S_i=R Si=R,并且 f n k − f i < 0 f_{nk}-f_i<0 fnkfi<0 。稍作变形得到 f i > f n k f_i>f_{nk} fi>fnk

不难发现对于直线 y = f n k y=f_{nk} y=fnk上方的边都会被反转,包括一些满足 f i ≥ 0 f_i\ge 0 fi0的下降边。

并且一次操作后, f n k f_{nk} fnk成为最大值,这意味着只会存在往上翻而不会存在往下翻的情况。只要存在 y = 0 y=0 y=0上方的下降边就会被翻上去,因此最终所有边都会被翻上去,只有 y = 0 y=0 y=0下方的前缀能存活。

模拟即可。复杂度 O ( n ) O(n) O(n)

#include<bits/stdc++.h> #define ll long long #define pb push_back #define fi first #define se second #define db double #define int ll #define inf 0x3f3f3f3f3f3f3f3f using namespace std; const int mod=998244353; int n,K,m,f[200005],res; string s; int qry(int i,int x){ return max(0ll,K-max(0ll,(int)ceil((x-f[i])*1.0/m))); } signed main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>K>>s;for(int i=0;i<n;i++)m+=(s[i]=='R')?1:-1; if(m<0){ m=-m; reverse(s.begin(),s.end());for(int i=0;i<n;i++)s[i]=(s[i]=='R')?'L':'R'; } f[0]=(s[0]=='R')?1:-1;for(int i=1;i<n;i++)f[i]=f[i-1]+((s[i]=='R')?1:-1); if(m==0){ for(int i=0;i<n;i++)if(f[i]>0||f[i]==0&&s[i]=='L')res++; cout<<res*K%mod; return 0; } assert(f[n-1]==m&&m>0); int L=0,R=0; for(int i=0;i<n;i++){ if(f[i]<=0){ if(s[i]=='L')L+=K-1; else R+=K-1; } else { for(int j=i;j<n;j++){ if(s[j]=='L')L+=K; else R+=K; } break; } } for(int i=0;i<n;i++){ if(s[i]=='L'){ int tmp=qry(i,0); L-=tmp,R+=tmp,res+=tmp; } else { int tmp=qry(i,m*K+1); L+=tmp,R-=tmp,res+=tmp; } }assert(L>=0),assert(R>=0); res+=L;cout<<res%mod; }

ARC151

A < AP

B题还要想半天。

假设第i位时第一次出现不同。可以得到如下限制:

a 1 = a p 1 , a 2 = a p 2 , . . . , a i − 1 = a p i − 1 , a i ≠ a p i a_1=a_{p_1},a_2=a_{p_2},...,a_{i-1}=a_{p_{i-1}},a_i\ne a_{p_i} a1=ap1,a2=ap2,...,ai1=api1,ai=api

如果在图论中来看的话,首先 i i i p i p_i pi不在同一连通块,其次如果有 k k k个连通块,那么方案数 m k − m k − 1 2 \frac{m^k-m^{k-1}}{2} 2mkmk1

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include<bits/stdc++.h> #define ll long long #define fi first #define se second using namespace std; const int mod=998244353; int n,m,K,p[200005],fa[200005]; ll res; ll fpow(ll x,ll y){ ll z(1); for(;y;y>>=1){ if(y&1)z=z*x%mod; x=x*x%mod; }return z; } int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} int main(){ cin>>n>>m,K=n;for(int i=1;i<=n;i++)cin>>p[i],fa[i]=i; for(int i=1;i<=n;i++){ if(find(i)!=find(p[i])){ res=(res+(fpow(m,K)-fpow(m,K-1))*fpow(2,mod-2)%mod)%mod; fa[fa[i]]=fa[p[i]],K--; } }cout<<(res+mod)%mod; }

01 Game

前置知识: S G SG SG函数。

可以把每个状态视为一个节点,再从每个状态向它的后继状态连边,定义 S G ( x ) = mex ( S G ( y 1 ) , S G ( y 2 ) , . . . , S G ( y k ) ) SG(x)=\text{mex}(SG(y_1),SG(y_2),...,SG(y_k)) SG(x)=mex(SG(y1),SG(y2),...,SG(yk))。显然当 S G ( x ) = 0 SG(x)=0 SG(x)=0时必败, S G ( x ) ≠ 0 SG(x)\ne 0 SG(x)=0时必胜。

一般的,如果由 n n n个有向图游戏组成,那么必胜的充要条件是 ⊕ i = 1 n S G ( x i ) ≠ 0 \oplus_{i=1}^nSG(x_i)\ne 0 i=1nSG(xi)=0

道理很简单。 S G ( x i ) SG(x_i) SG(xi) S G SG SG函数大的方向转移是无效的(因为可以被转移回来),因此 S G ( x i ) SG(x_i) SG(xi)只能转移到 0 ∼ S G ( x i ) − 1 0\sim SG(x_i)-1 0SG(xi)1,而这和 n i m nim nim游戏类似,最终所有 S G ( x i ) SG(x_i) SG(xi)都变成 0 0 0,也就是都不能操作的情况。

思考:如何求 S G ( x 1 , x 2 , . . . , x n ) SG(x_1,x_2,...,x_n) SG(x1,x2,...,xn)?

应该可以感性认识到就是 ⊕ i = 1 n S G ( x i ) \oplus_{i=1}^nSG(x_i) i=1nSG(xi)。因为是yy的所以没有证明。

考虑如何运用。显然每一个空白段构成一个有向图游戏,只需分析每个空白段的 S G SG SG

首先一个观察:最终不能操作的局面一定是 01 01 01交错,也就是说如果初始状态是 01 01 01交错的那么必败。

因此如果两端相同那么必胜,如果两端不同那么必败。

如果只有一端有值,假设是 1 1 1,不妨设长度为 n n n,考虑填一个 0 0 0在中间,那么 mex ( ∑ i = 0 n − 1 i ⊕ 0 ) = n \text{mex}(\sum_{i=0}^{n-1}i\oplus 0)=n mex(i=0n1i0)=n

因此计算每一段 S G SG SG值即可。

#include<bits/stdc++.h> #define ll long long #define fi first #define se second #define pb push_back using namespace std; ll n,X[200005]; int m,Y[200005]; int main(){ cin.tie(0),cout.tie(0); ios::sync_with_stdio(false); cin>>n>>m;for(int i=1;i<=m;i++)cin>>X[i]>>Y[i]; if(m==0){ cout<<((n&1)?"Takahashi":"Aoki"); return 0; }ll tmp=(X[1]-1)^(n-X[m]); for(int i=2;i<=m;i++){ if(Y[i]==Y[i-1])tmp^=1; }cout<<((tmp)?"Takahashi":"Aoki"); }

Binary Representations and Queries

纯纯的人类智慧。

考虑对操作序列进行调整。我们注意到,如果 X i ≠ X i + 1 X_i\ne X_{i+1} Xi=Xi+1,那么交换 i i i i + 1 i+1 i+1的操作顺序不会影响结果。

道理很简单。每次操作 i i i最多有两条转移路径,即 i → i i\to i ii 以及 i → i ⊕ 2 a i\to i\oplus2^a ii2a。也就是说 i i i只会转移到 { i , i ⊕ 2 a , i ⊕ 2 b , i ⊕ 2 a ⊕ 2 b } \{i,i\oplus 2^{a},i\oplus 2^b,i\oplus 2^a\oplus 2^b\} {i,i2a,i2b,i2a2b} ( a ≠ b ) (a\ne b) (a=b)。不难验证它是正确的。

据此,我们可以很自然地想到对于相同的 X i X_i Xi一起处理。而我们发现这样的转移可以通过两两分组来实现,其过程是平凡的线性递推关系,直接乘转移矩阵即可。

时间复杂度 O ( n 2 n + Q ) O(n2^n+Q) O(n2n+Q)

总结:对递推关系的考察。

我信仰什么,我便实现哪种方法。

#include<bits/stdc++.h> #define ll long long #define fi first #define se second #define pb push_back using namespace std; const int mod=998244353; int n,Q; ll a[1<<18]; struct Matrix{ ll c[2][2]; Matrix(){memset(c,0,sizeof c);} Matrix operator *(const Matrix &a)const{ Matrix r; for(int k=0;k<2;k++){ for(int i=0;i<2;i++){ for(int j=0;j<2;j++){ r.c[i][j]=(r.c[i][j]+c[i][k]*a.c[k][j]%mod)%mod; } } }return r; } }; vector<int>v[18]; int main(){ cin>>n>>Q;Matrix r1,r2;r1.c[0][0]=r1.c[0][1]=r1.c[1][1]=1;r2.c[0][0]=r2.c[1][0]=r2.c[1][1]=1; for(int i=0;i<1<<n;i++)cin>>a[i]; for(int i=1;i<=Q;i++){ int x,y;cin>>x>>y,v[x].pb(y); }for(int i=0;i<n;i++){ if(v[i].size()){ Matrix x;x.c[0][0]=x.c[1][1]=1; for(auto y:v[i]){ if(!y)x=x*r1;else x=x*r2; } for(int j=0;j<1<<n;j++){ if(!(j>>i&1)){ Matrix y;y.c[0][0]=a[j],y.c[0][1]=a[j|(1<<i)]; y=y*x;a[j]=y.c[0][0],a[j|(1<<i)]=y.c[0][1]; } } } }for(int i=0;i<1<<n;i++)cout<<a[i]<<' '; }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530088.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(26)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示