AtCoder Beginner Contest 242

AtCoder Beginner Contest 242

A T-shirt

题意

排名在 \(A\) 及以上的参赛选手,一定能获得一件 T 恤,排名在 \([A+1,B]\) 的选手,其中的 \(C ( 1 \leq C \leq B-A)\) 个参赛选手等概率获得一件 T 恤,求排名为 \(x\) 的参赛选手,能获得 T 恤的概率。

数据满足:\(1 \leq A,B,C,x \leq 1000\)

题解

  • 判断 \(x \leq A\),则必然获得一件T恤,输出 \(1\)
  • 判断 \(A + 1 \leq x \leq B\),由于其中的 \(C\) 个人等概率获得T恤,因此其中一个人获得T恤的概率为\(\frac{C}{B-A}\)
  • 判断\(x \geq B+1\),则不可能获得一件T恤,输出\(0\)

时间复杂度\(O(1)\)

C++代码实现

# include <bits/stdc++.h>
using namespace std;
int main() {
	int a,b,c,x; cin>>a>>b>>c>>x;
	if (x<=a) puts("1");
	else if (x>=a+1&&x<=b){
		double ans = c/(double)(b-a);
		printf("%.10lf\n",ans);
	}else puts("0");
	return 0;
}

B Minimize Ordering

题意

长度为 \(n\) 的字符串 \(s\),求出重排所有字符后,能获得字典序最小的字符串\(s'\)

数据满足:\(1 \leq n \leq 2\times 10^5\)

题解

把字符串看做字符数组,重排字符让字典序最小,等价于将这个字符数组排序。

时间复杂度\(O(n \log_2 n)\)

C++代码实现

# include <bits/stdc++.h>
using namespace std;
int main() {
	string s; cin>>s;
	sort(s.begin(),s.end());
	cout<<s<<endl;
	return 0;
}

C 1111gal password

题意

求出 \(n\) 位十进制数的个数,满足每个数位上的数字在\([1,9]\),且相邻数位相差的绝对值不超过\(1\),答案对 \(998244353\) 取模。
数据满足:\(2 \leq n \leq 10^6\)

题解

考虑数位DP,由于不存在前导零,这个问题就变的更加简单了。

\(f[i][j]\) 表示考虑到前 \(i\) 个数位,第 \(i\) 个数位上填写的数字为 \(j\) 的数的个数。

  • 转移方程:\(f[i][j] = f[i-1][k])\) 其中 \(\max(1,j-1)\leq k \leq \min(n,j+1)\)

时间复杂度\(O(kn)\),这里\(k = 9\)

C++代码实现

# include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int mo = 998244353;
int f[N][10];
int main() {
	int n; cin>>n;
	for (int i=1;i<=9;i++) f[1][i]=1;
	for (int i=2;i<=n;i++)
		for (int j=1;j<=9;j++)
			for (int k=j-1;k<=j+1;k++) {
				if (k<1||k>9) continue;
				(f[i][j]+=f[i-1][k])%=mo;
			}
	int ans = 0;
	for (int i=1;i<=9;i++) (ans+=f[n][i])%=mo;
	cout<<ans<<endl;		
	return 0;
}

D ABC Transform

题意

给出一个只包含ABC三种字符的字符串\(s\) (下标从 \(1\) 开始),做 \(k\) 次如下变换,记作\(s^{(k)}\)

  • 对于\(k = 0\),变换后结果为 \(s\) 本身。
  • 对于\(k \geq 1\),变换后结果为字符串 \(s^{(k-1)}\)A替换为BCB替换为CAC替换为AB后新生成字符串的值。

\(Q\) 组询问,每组询问用 \(t_i \ k_i\) 表示,求出 \(s^{(t_i)}[k_i]\) 的值。
数据满足:\(2 \leq |s|,Q \leq 10^5,0 \leq t_i \leq 10^{18} ,1 \leq k_i \leq \min(10^{18},|s^{(t_i)}|)\)

题解

首先发现的一个性质是:每多进行 \(1\) 次操作,字符串的长度会加倍。

因此,如果我们依次写出进行 \(1...t\) 次变换的所有所有变化的话,可以发现,这些变化形成了 \(n\) 棵以原字符串的 \(n\) 个位置为根节点,深度为 \(t\) 的完全二叉树(根节点深度为 \(0\)

每个根节点都对应一段最后生成字符串中的一个长度为 \(2^t\) 的连续区间(叶节点),因此给出一个 \(k\) 我们可以在\(O(\log_2 n)\)的时间复杂度内,定位编号为 \(k\) 的这个叶节点是哪个根节点派生的。

这样,问题就转化到一棵完全二叉树上了。

如果把一棵完全二叉树每层的节点从左往右从 \(1\) 开始依次编号,那么如果一个非根节点的编号为 \(x\):

  • \(x\) 为奇数,那么该节点是从上层节点的左儿子。
  • \(x\) 为偶数,那么该节点是从上层节点的右儿子。
  • \(x\) 的父节点编号为 \(\lfloor \frac{x-1}{2} \rfloor + 1\)

考虑从编号为 \(k\) 的这个叶子节点往上去寻找从根节点的唯一路径,这条路径就可以推出当前叶子节点的答案。
模拟上述过程,可以发现,\(k\)的规模每次减少一半,\(\log_2 k\)次计算后,必然为\(1\)
这意味着,由于 \(k\) 的规模的限制,在开始的很长一段时间内,都是往左子树走的。

然后我们发现,往左子树连续走 \(3\) 次是没有意义的,因为会重新回到根节点的值,这样每次计算的规模就被我们简化到 \(3 + \log_2 k\) 次了,这样直接模拟就可以了。

时间复杂度为\(O(|s| + Q\log_2 k)\)

C++代码实现

# include <bits/stdc++.h>
# define int long long
using namespace std;
inline char getleft(char x) {
	return (x-'A'+1)%3+'A';
}
inline char getright(char x) {
	return (x-'A'+2)%3+'A';
}
signed main() {
	std::ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	string s; cin>>s;
	int q; cin>>q;
	while (q--) {
		int t,k; cin>>t>>k;
		int id;
		if (t>=60) id=0;
		else {
			int left=1,right=(1ll<<t),x=0;
			while (true) {
				if (k>=left && k<=right) {
					id=x; k=k-left+1; break;
				}
				x++; left+=(1ll<<t); right+=(1ll<<t);
			}
		}
		int tm = 0;
		char now = s[id];
		for (int i=1;i<=t;i++) {
			if (k == 1) { tm = t-i+1; break;}
			if (k&1) now=getleft(now);
			else now = getright(now);
			k=(k-1)/2+1;
		}
		tm%=3;
		for (int i=1;i<=tm;i++) now=getleft(now);
		cout<<now<<'\n';
	}
	return 0;
}

E (∀x∀)

题意

\(T\)组数据,每组数据给出长度为 \(n\) 的大写字母字符串 \(s\),求出相同长度且字典序小于等于 \(s\) 的大写字母字符串 \(t\) 的个数,使得 \(t\) 是回文字符串,答案对 \(998244353\) 取模。

数据范围满足:\(1 \leq T \leq 250000, 1 \leq \sum n \leq 10^6\)

题解

考虑回文字符串是如何构成的,是由前面大约 \(n/2\) 个字符翻转拼接而成。

如果我们考虑前面这些字符串,字典序已经比原串的字典序小了,那么这个回文串的字典序一定比原串小。

问题转化为,前 \(n/2\) 个字符构成的字符串,有多少个字符串的字典序比它小,这是一个数位dp的问题,问题的解决方式和问题 \(C\) 一模一样。

还需考虑,如果前 \(n/2\) 和原字符串字典序相同,还可能构成 \(1\) 个字符串,这样我们需要把这个字符串构造出来,和原串比较字典序即可。

时间复杂度\(O(\sum n)\)

C++代码实现

# include <bits/stdc++.h>
using namespace std;
const int mo = 998244353;
const int N=1e6+10;
string s;
int n,l;
int f[N][2];
int dfs(int now,bool lim) {
	if (now == l+1) return 1;
	if (f[now][lim]!=-1) return f[now][lim];
	int res = 0;
	for (char i='A';i<=(lim?s[now]:'Z');i++) 
		(res+=dfs(now+1,lim&&i==s[now]))%=mo;
	return f[now][lim] = res;
}
int main() {
	int t; cin>>t;
	while (t--) {
		cin>>n; cin>>s;
		for (int i=0;i<=n;i++)
			f[i][0]=f[i][1] = -1;
		l = (s.length()-1)/2;
		int ans = dfs(0,1);
		string t = "";
		if (s.length()&1) {
			for (int i=0;i<=l;i++) t+=s[i];
			for (int i=l-1;i>=0;i--) t+=s[i];
		} else {
			for (int i=0;i<=l;i++) t+=s[i];
			for (int i=l;i>=0;i--) t+=s[i]; 
		}
		if (t > s) {
			ans=(ans-1+mo)%mo;
		}
		cout<<ans<<endl;	
	}
	return 0;
}

F Black and White Rooks

题意

\(n \times m\) 的棋盘上,放 \(b\) 个黑棋和 \(w\) 个白棋,要求白棋和黑棋互不攻击。
如果两个棋子,位于同一行或同一列,则视为可以相互攻击。
输出棋盘上放置黑棋和白棋的方案数,答案对 \(998244353\) 取模。

数据范围满足:\(1 \leq n,m \leq 50, 1 \leq b+w \leq n\times m\)

题解

容易发现,如果一种放置的方案合法,那么黑棋和白棋会在不同的行和列上。

  • \(f[x][y]\)表示,\(b\)个黑棋放置在\(x\)个不同的行,\(y\)个不同的列上,不相互攻击的方案数。
  • \(g[x][y]\)表示,\(w\)个白棋放置在\(x\)个不同的行,\(y\)个不同的列上,不相互攻击的方案数。

那么最后的答案就是:\(\sum\limits_{1 \leq x_1,x_2 \leq n, 1 \leq y_1,y_2 \leq m, x_1+x_2 \leq n, y_1+y_2 \leq m} \binom{n}{x_1} \binom{n-x_1}{x_2} \binom{m}{y_1} \binom{m-y_1}{y_2}\cdot f[x_1][y_1] \cdot g[x_2][y_2]\)

考虑如何求 \(f[i][j]\)\(g[i][j]\),我们以前者为例。

首先,如果在这个 \(i\times j\) 个格子中任意选择 \(b\) 个格子,摆放上黑子,方案数为\(\binom{i\cdot j}{b}\)

这些方案中,有一些方案,会有空的行或者列,也就意味着实际上的黑子集中在这样一个 \(p\times q\) 的格子中(\(1 \leq p \leq i, 1 \leq q \leq j\),且 \(p = i\)\(q = j\) 不同时成立)。

这样,原来的问题就化归到若干个规模更小的子问题上。即:

  • \(f[x][y] = \binom{x\cdot y}{b} - \sum_\limits{1 \leq i \leq x,1 \leq j \leq y,(i,j)\neq (x,y) } f[i][j]\)

同理,

  • \(g[x][y] = \binom{x\cdot y}{w} - \sum_\limits{1 \leq i \leq x,1 \leq j \leq y,(i,j)\neq (x,y) } g[i][j]\)

如果我们通过杨辉三角,预处理出 \(nm \times nm\) 规模的组合数,就可以快速计算组合数的值。

时间复杂度为 \(O(n^2m^2)\)

C++代码实现

# include <bits/stdc++.h>
# define int long long
using namespace std;
const int mo = 998244353;
const int N=55;
int c[N*N][N*N];
int f[N][N],g[N][N];
signed main() {
	c[0][0]=1;
	for (int i=1;i<=50*50;i++) {
		c[i][0] = c[i][i] = 1;
		for (int j=1;j<=50*50;j++)
			c[i][j] = (c[i-1][j-1]+c[i-1][j])%mo;
	}
	int n,m,b,w; cin>>n>>m>>b>>w;
	for (int i=1;i<=n;i++)
	for (int j=1;j<=m;j++) {
		f[i][j] = c[i*j][b];
		for (int f1=1;f1<=i;f1++)
		for (int f2=1;f2<=j;f2++) {
			if (f1 == i && f2 == j) continue;
			f[i][j]=((f[i][j] - f[f1][f2]*c[i][f1]%mo*c[j][f2]%mo)%mo+mo)%mo;
		}	
	}
	for (int i=1;i<=n;i++)
	for (int j=1;j<=m;j++) {
		g[i][j] = c[i*j][w];
		for (int f1=1;f1<=i;f1++)
		for (int f2=1;f2<=j;f2++) {
			if (f1 == i && f2 == j) continue;
			g[i][j]=((g[i][j] - g[f1][f2]*c[i][f1]%mo*c[j][f2]%mo)%mo+mo)%mo;
		}
	}
	int ans = 0;
	for (int p1=1;p1<=n;p1++) for (int p2=1;p1+p2<=n;p2++)
	for (int q1=1;q1<=m;q1++) for (int q2=1;q1+q2<=m;q2++) {
		(ans+=c[n][p1]*c[n-p1][p2]%mo*c[m][q1]%mo*c[m-q1][q2]%mo*f[p1][q1]%mo*g[p2][q2]%mo)%=mo;
	}
	cout<<ans<<endl;			
	return 0;
}

G Range Pairing Query

题意

\(n\) 个人,每个人穿着 \(a_i\) 颜色的衣服,有 \(Q\) 个询问,每次询问区间 \([l_i,r_i]\) 中最多能选取多少对穿着颜色相同衣服的人。

数据范围满足:\(1 \leq n \leq 10^5, 1 \leq Q \leq 10^6,1 \leq a_i \leq n\)

题解

考虑莫队。

如果区间中有 \(x\) 个人穿着同一个颜色的衣服,那么最多可以找出 \(\lfloor \frac{x}{2} \rfloor\) 对人穿同样的衣服。

穿不同衣服的人,答案简单相加即可,各自独立。

因此,我们可以在 \(O(1)\) 的复杂度内计算出某个颜色新增一个人和新减一个人后,答案的值。

利用莫队算法,可以在 \(O(Q\sqrt{n})\) 的时间复杂度内求解。

C++代码实现

# include <bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10;
struct rec{
    int p,bl,l,r;
}q[MAXN];
int n,m,a[MAXN],answer,b[MAXN];
int ans[MAXN];
bool cmp(rec a,rec b)
{
    return (a.bl<b.bl||(a.bl==b.bl&&a.r<b.r));
}
int calc(int x) {
	return x/2;
}
void add(int pos){
	answer-=calc(b[a[pos]]);
	b[a[pos]]++;
	answer+=calc(b[a[pos]]);
}
void del(int pos){ 
	answer-=calc(b[a[pos]]);
	b[a[pos]]--;
	answer+=calc(b[a[pos]]);
}
int main()
{
    scanf("%d",&n); int block=sqrt(n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    scanf("%d",&m);
    for (int i=1;i<=m;i++) {
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].p=i; q[i].bl=(q[i].l-1)/block+1;
    }
    sort(q+1,q+1+m,cmp);
    int curL=0,curR=0; 
    for (int i=1;i<=m;i++) {
        int L=q[i].l,R=q[i].r;
        while (curL>L) add(--curL);
        while (curL<L) del(curL++);
        while (curR>R) del(curR--);
        while (curR<R) add(++curR);
        ans[q[i].p]=answer;
    }
    for (int i=1;i<=m;i++)
     printf("%d\n",ans[i]);
    return 0;
}
posted @ 2022-03-06 01:46  Maystern  阅读(202)  评论(0编辑  收藏  举报