文章目录
- A. Review Site
- B. GCD Length
- C. Yet Another Card Deck
- D. Min Cost String
- E. Colorings and Dominoes
- F. Chainword
- G. Chips on a Board
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
直接强制gcd=10c−1gcd=10^{c-1}gcd=10c−1,假设xxx的位数小于yyy的位数,则x=10a−1,y=10b−1+gcdx=10^{a-1},y=10^{b-1}+gcdx=10a−1,y=10b−1+gcd
加上gcdgcdgcd恰好保证了gcd(xgcd,ygcd)=1gcd(\frac{x}{gcd},\frac{y}{gcd})=1gcd(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,ji,ji,j位置的概率乘总方案数就是贡献
行多米诺骨牌只能用红色,列多米诺骨牌只能用蓝色
因此行列是彼此独立的,可以分开做,下面以行为例
考虑DPDPDP求概率,设dpi,0/1:idp_{i,0/1}:idpi,0/1:i 位置是一张多米诺骨牌的左边0
/右边1
dpi,0=(dpi−1,1+12)×12dp_{i,0}=(dp_{i-1,1}+\frac{1}{2})\times \frac{1}{2}dpi,0=(dpi−1,1+21)×21 加12\frac{1}{2}21是有可能i−1i-1i−1是蓝色,天然隔绝一行中的两段,iii此时满足做左边的条件
dpi,1=dpi−1,0×12dp_{i,1}=dp_{i-1,0}\times \frac{1}{2}dpi,1=dpi−1,0×21
乘12\frac{1}{2}21是表示iii为红色的概率
这个DPDPDP状态转移其实只与长度有关,因此只需要提取出一行中连续段长度即可
#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
当然,也可以在某个字符串的结束位置不继续往下走,而是跳回到根节点
设fi,u,v:f_{i,u,v}:fi,u,v: 表示sss加入了iii个字符,上方的线在trie
的结点uuu上,下方的线在``trie的结点
v`上 的方案数
发现这个DPDPDP与iii没什么关系,可以使用矩阵快速幂优化
进行有效状态合并后才能转移不超时
- 设SSS为root→uroot\rightarrow uroot→u的路径,TTT为root→vroot\rightarrow vroot→v的路径,那么SSS一定是TTT的前缀/后缀
- fi,u,v=fi,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)开始bfsbfsbfs,每次转移的时候枚举最后一个字符,所有能访问到的结点
#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]范围内所有棋子到lll的距离的异或和
为000则后手胜,不为000则先手胜
距离差在异或下的运算法则不好维护
考虑拆成二进制下每位独立维护
发现可以用倍增(倍增的本质就是二进制下每一位单独贡献)
设dpi,j:[i,i+2j)dp_{i,j}:[i,i+2^j)dpi,j:[i,i+2j)区间内的棋子到iii的距离异或和
最高位的贡献是独立于所有比其小的贡献的
cnti:cnt_i:cnti: 前iii列的棋子个数
dpi,j=dpi,j−1⨁dpi+2j−1,j−1⨁[cnti+2j−1−cnti+2j−1−1mod2=1]2j−1dp_{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;
}