Educational Codeforces Round 107 (Rated for Div. 2) 题解


Educational-Round-107

A. Review Site

都给了两台机子,直接把所有只会投②的扔到一台,其余的全是另一台

就是类型一和类型三的数量求和

#include <cstdio>
int T, n, ans;

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d", &n );
		ans = 0;
		for( int i = 1, x;i <= n;i ++ ) {
			scanf( "%d", &x );
			if( x == 2 ) continue;
			else ans ++;
		}
		printf( "%d\n", ans );
	}
	return 0;
}

B. GCD Length

直接强制 g c d = 1 0 c − 1 gcd=10^{c-1} gcd=10c1,假设 x x x的位数小于 y y y的位数,则 x = 1 0 a − 1 , y = 1 0 b − 1 + g c d x=10^{a-1},y=10^{b-1}+gcd x=10a1,y=10b1+gcd

加上 g c d gcd gcd恰好保证了 g c d ( x g c d , y g c d ) = 1 gcd(\frac{x}{gcd},\frac{y}{gcd})=1 gcd(gcdx,gcdy)=1的性质

#include <cstdio>
#include <iostream>
using namespace std;
int T, n, a, b, c, A, B, C;

int qkpow( int x, int y ) {
	int ans = 1;
	while( y ) {
		if( y & 1 ) ans = ans * x;
		x = x * x;
		y >>= 1;
	}
	return ans;
}

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d %d %d", &a, &b, &c );
		bool flag = 0;
		if( a > b ) swap( a, b ), flag = 1;
		A = qkpow( 10, a - 1 );
		C = qkpow( 10, c - 1 );
		B = qkpow( 10, b - 1 );
		if( flag ) printf( "%d %d\n", B + C, A );
		else printf( "%d %d\n", A, B + C );
	}
	return 0;
}

C. Yet Another Card Deck

真正有用的只有颜色第一次出现的位置

对于一个颜色第一次被操作,可以用树状数组统计该位置后面有多少颜色被操作过,那么最初其位置就会后移

对于多次被操作的颜色,因为颜色不超过五十种,显然可以暴力移动颜色

#include <cstdio>
#define maxn 300005
int n, Q, x;
int a[maxn], pos[maxn], vis[maxn], t[maxn], ans[maxn];

int lowbit( int x ) {
	return x & ( -x );
}

void add( int x ) {
	x = n - x + 1;
	for( int i = x;i <= n;i += lowbit( i ) )
		t[i] ++;
}

int query( int x ) {
	x = n - x + 1; int ans = 0;
	for( int i = x;i;i -= lowbit( i ) )
		ans += t[i];
	return ans;
}



int main() {
	scanf( "%d %d", &n, &Q );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%d", &a[i] );
		if( ! pos[a[i]] ) pos[a[i]] = i;
	}
	int cnt = 0;
	for( int i = 1;i <= Q;i ++ ) {
		scanf( "%d", &x );
		if( ! vis[x] ) {
			printf( "%d\n", pos[x] + query( pos[x] ) );
			cnt ++;
			for( int j = cnt;j > 1;j -- )
				ans[j] = ans[j - 1];
			ans[1] = x;
			add( pos[x] );
			vis[x] = 1;
		}
		else {
			for( int j = 1;j <= cnt;j ++ ) 
				if( ans[j] == x ) {
					printf( "%d\n", j );
					for( int k = j;k > 1;k -- )
						ans[k] = ans[k - 1];
					ans[1] = x;
					break;
				}
		}
	}
	return 0;
}

D. Min Cost String

找规律a ab ac ad ae ...a[k] b bc bd be bf... b[k]...[k]

高级解释:剩余系(同余)

#include <cstdio>
#define maxn 200005
int n, k, cnt;
char s[maxn];

int main() {
	scanf( "%d %d", &n, &k );
	for( int i = 0;i < k;i ++ ) {
		s[cnt ++] = i + 'a';
		for( int j = i + 1;j < k;j ++ )
			s[cnt ++] = i + 'a', s[cnt ++] = j + 'a';
	}
	int ip = 0;
	for( int i = 1;i <= n;i ++ )
		printf( "%c", s[ip % cnt] ), ip ++;
	return 0;
}

E. Colorings and Dominoes

利用概率论求解应该是更简单的,求出一张多米诺骨牌放在 i , j i,j i,j位置的概率乘总方案数就是贡献

行多米诺骨牌只能用红色,列多米诺骨牌只能用蓝色

因此行列是彼此独立的,可以分开做,下面以行为例

考虑 D P DP DP求概率,设 d p i , 0 / 1 : i dp_{i,0/1}:i dpi,0/1:i 位置是一张多米诺骨牌的左边0/右边1

d p i , 0 = ( d p i − 1 , 1 + 1 2 ) × 1 2 dp_{i,0}=(dp_{i-1,1}+\frac{1}{2})\times \frac{1}{2} dpi,0=(dpi1,1+21)×21 1 2 \frac{1}{2} 21是有可能 i − 1 i-1 i1是蓝色,天然隔绝一行中的两段, i i i此时满足做左边的条件

d p i , 1 = d p i − 1 , 0 × 1 2 dp_{i,1}=dp_{i-1,0}\times \frac{1}{2} dpi,1=dpi1,0×21

1 2 \frac{1}{2} 21是表示 i i i为红色的概率

这个 D P DP DP状态转移其实只与长度有关,因此只需要提取出一行中连续段长度即可

#include <cstdio>
#define int long long
#define mod 998244353
#define maxn 300000
int n, m, cnt;
char s[maxn];
int row[maxn + 5], col[maxn + 5];
int dp[maxn + 5][2];

int id( int i, int j ) {
	return i * m + j;
}

int qkpow( int x, int y ) {
	int ans = 1;
	while( y ) {
		if( y & 1 ) ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

signed main() {
	scanf( "%lld %lld", &n, &m );
	for( int i = 0;i < n;i ++ ) {
		scanf( "\n" );
		for( int j = 0;j < m;j ++ ) {
			scanf( "%c", &s[id( i, j )] );
			cnt += ( s[id( i, j )] == 'o' );
		}
	}
	int inv = qkpow( 2, mod - 2 ), All = qkpow( 2, cnt );
	dp[1][0] = inv; 
	for( int i = 2;i <= maxn;i ++ ) {
		dp[i][0] = ( dp[i - 1][1] + inv ) * inv % mod;//还有可能是i-1为蓝色(概率为1/2)断开了行的两段
		dp[i][1] = dp[i - 1][0] * inv % mod;
	}
	int ans = 0;
	for( int i = 0;i < n;i ++ ) {
		for( int j = 0;j < m;j ++ ) {
			row[id( i, j )] = col[id( i, j )] = 1;
			if( i > 0 && s[id( i - 1, j )] == 'o' ) row[id( i, j )] = row[id( i - 1, j )] + 1;
			if( j > 0 && s[id( i, j - 1 )] == 'o' ) col[id( i, j )] = col[id( i, j - 1 )] + 1;
			if( s[id( i, j )] == 'o' ) ans = ( ans + dp[row[id( i, j )]][1] + dp[col[id( i, j )]][1] ) % mod;
		} 
	}
	printf( "%lld\n", ans * All % mod );
	return 0;
}

F. Chainword

对单词建立trie

当在s的末尾增加一个字符的时候

相当于在 trie树上从一个结点u沿着一条转移边走到另外一个结点v

当然,也可以在某个字符串的结束位置不继续往下走,而是跳回到根节点

f i , u , v : f_{i,u,v}: fi,u,v: 表示 s s s加入了 i i i个字符,上方的线在trie的结点 u u u上,下方的线在``trie的结点v`上 的方案数

发现这个 D P DP DP i i i没什么关系,可以使用矩阵快速幂优化

进行有效状态合并后才能转移不超时

  • S S S r o o t → u root\rightarrow u rootu的路径, T T T r o o t → v root\rightarrow v rootv的路径,那么 S S S一定是 T T T的前缀/后缀
  • f i , u , v = f i , v , u ⇒ ( u , v ) ( v , u ) f_{i,u,v}=f_{i,v,u}\Rightarrow (u,v)(v,u) fi,u,v=fi,v,u(u,v)(v,u)可以视为一个状态

找到满足第一种要求的状态的点:从 ( 0 , 0 ) (0,0) (0,0)开始 b f s bfs bfs,每次转移的时候枚举最后一个字符,所有能访问到的结点

#include <map>
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define int long long
#define mod 998244353
#define Pair pair < int, int >
queue < Pair > q;
map < Pair, int > mp;
int n, m;
char s[10];

struct matrix {
	int v[170][170];
	matrix() {
		memset( v, 0, sizeof( v ) );
	}
	matrix operator * ( matrix &t ) const {
		matrix ans;
		for( int i = 0;i <= 160;i ++ )
			for( int j = 0;j <= 160;j ++ )
				for( int k = 0;k <= 160;k ++ )
					ans.v[i][j] = ( ans.v[i][j] + v[i][k] * t.v[k][j] % mod ) % mod;
		return ans;
	}
}O;

matrix qkpow( matrix x, int y ) {
	matrix ans;
	for( int i = 0;i <= 160;i ++ )
		ans.v[i][i] = 1;
	while( y ) {
		if( y & 1 ) ans = ans * x;
		x = x * x;
		y >>= 1;
	}
	return ans;
}

struct tree {
	int End, son[26];
	tree() {
		memset( son, -1, sizeof( son ) );
	}
}trie[170];

int cnt;

void insert() {
	int len = strlen( s + 1 ), now = 0;
	for( int i = 1;i <= len;i ++ ) {
		if( trie[now].son[s[i] - 'a'] == -1 )
			trie[now].son[s[i] - 'a'] = ++ cnt;
		now = trie[now].son[s[i] - 'a'];
	}
	trie[now].End = 1;
}

int ID( int x, int y ) {
	if( x > y ) swap( x, y );
	if( ! mp.count( make_pair( x, y ) ) ) {
		mp[make_pair( x, y )] = mp.size();
		q.push( make_pair( x, y ) );
	}
	return mp[make_pair( x, y )];
}

signed main() {
	scanf( "%lld %lld", &n, &m );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%s", s + 1 );
		insert();
	}
	mp[make_pair( 0, 0 )] = 0;
	q.push( make_pair( 0, 0 ) );
	while( ! q.empty() ) {
		Pair now = q.front(); q.pop();
		int id = ID( now.first, now.second );
		for( int i = 0;i < 26;i ++ ) {
			int x = trie[now.first].son[i], y = trie[now.second].son[i];
			if( x == -1 || y == -1 ) continue;
			O.v[id][ID( x, y )] ++;
			if( trie[x].End ) O.v[id][ID( 0, y )] ++;
			if( trie[y].End ) O.v[id][ID( x, 0 )] ++;
			if( trie[x].End && trie[y].End ) O.v[id][ID( 0, 0 )] ++;
		}
	}
	printf( "%lld\n", qkpow( O, m ).v[0][0] );
	return 0;
}

G. Chips on a Board

两人在玩Nim博弈游戏,有异或结论:在 [ l , r ] [l,r] [l,r]范围内所有棋子到 l l l的距离的异或和

0 0 0则后手胜,不为 0 0 0则先手胜

距离差在异或下的运算法则不好维护

考虑拆成二进制下每位独立维护

发现可以用倍增(倍增的本质就是二进制下每一位单独贡献)

d p i , j : [ i , i + 2 j ) dp_{i,j}:[i,i+2^j) dpi,j:[i,i+2j)区间内的棋子到 i i i的距离异或和

最高位的贡献是独立于所有比其小的贡献的

c n t i : cnt_i: cnti: i i i列的棋子个数

d p i , j = d p i , j − 1 ⨁ d p i + 2 j − 1 , j − 1 ⨁ [ c n t i + 2 j − 1 − c n t i + 2 j − 1 − 1 m o d    2 = 1 ] 2 j − 1 dp_{i,j}=dp_{i,j-1}\bigoplus dp_{i+2^{j-1},j-1}\bigoplus [cnt_{i+2^j-1}-cnt_{i+2^{j-1}-1}\mod 2 =1]2^{j-1} dpi,j=dpi,j1dpi+2j1,j1[cnti+2j1cnti+2j11mod2=1]2j1

最后求答案异或和,也是枚举每位单独计算贡献

#include <cstdio>
#define maxn 200005
int n, m, Q;
int dp[maxn][20];
int bit[maxn], cnt[maxn];

int main() {
	scanf( "%d %d", &n, &m );
	for( int i = 1, x;i <= n;i ++ )
		scanf( "%d", &x ), cnt[x] ++;
	for( int i = 1;i <= m;i ++ )
		cnt[i] += cnt[i - 1];
	bit[0] = -1;
	for( int i = 1;i <= m;i ++ )
		bit[i] = bit[i >> 1] + 1;
	for( int j = 1;j < 20;j ++ )
		for( int i = 1;i <= m;i ++ )
			if( i + ( 1 << j ) - 1 <= m )
				dp[i][j] = dp[i][j - 1] ^ dp[i + ( 1 << j - 1 )][j - 1] ^ ( ( ( cnt[i + ( 1 << j ) - 1] - cnt[i + ( 1 << j - 1 ) - 1] ) & 1 ) * ( 1 << j - 1 ) );
	scanf( "%d", &Q );
	while( Q -- ) {
		int ans = 0, l, r;
		scanf( "%d %d", &l, &r );
		for( int i = bit[m];~ i;i -- )
			if( l + ( 1 << i ) <= r ) {
				ans ^= dp[l][i];
				l += ( 1 << i );
				if( ( cnt[r] - cnt[l - 1] ) & 1 )
					ans ^= ( 1 << i );
			}
		printf( "%c", ans ? 'A' : 'B' );
	}
	return 0;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值