• 【学习笔记】ARC150/ARC151


    ARC150

    Path and Subsequence

    不妨把每个点看作 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
    #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";
    }
    
    • 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

    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
    #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;
    }
    
    • 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

    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
    #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;
    	}
    	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;
    		}
    	}
    	res+=L;cout<<res%mod;
    }
    
    • 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

    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
    #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;
    }
    
    • 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

    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
    #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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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
    #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]<<' ';
    }
    
    • 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
  • 相关阅读:
    云服务器重启后无法获取IP地址怎么办?
    DPDK环境搭建
    【广州华锐互动】智能家居设计3D虚拟还原系统
    JS引擎(2):Java平台上JavaScript引擎—Rhino/Nashorn概述
    2022Q3手机配件增长榜:手机壳、数据线等供求不断增加
    【数据处理】用python将西班牙语的特殊符号替换成相应的英文字符
    Linux 软件安装目录
    为什么我的视频播放量上不去?
    Springboot如何整合Kafka
    在报表开发工具Stimulsoft Report报表设计中使用存储过程?
  • 原文地址:https://blog.csdn.net/cqbzlydd/article/details/127430085