【学习笔记】ARC题目选做02

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

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]<<' ';
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值