文章目录
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=10c−1,假设 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=10a−1,y=10b−1+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=(dpi−1,1+21)×21 加 1 2 \frac{1}{2} 21是有可能 i − 1 i-1 i−1是蓝色,天然隔绝一行中的两段, 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=dpi−1,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 root→u的路径, T T T为 r o o t → v root\rightarrow v root→v的路径,那么 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,j−1⨁dpi+2j−1,j−1⨁[cnti+2j−1−cnti+2j−1−1mod2=1]2j−1
最后求答案异或和,也是枚举每位单独计算贡献
#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;
}