Loading

题解-ARC113

ARC113

铭记这场暂时没有补的 F 题和场上没有做出的 E 题。

说句闲话,不知道有没有人和我一样想:Codeforces 查黑这么严,都有许多人开黑,许多成功。那么这个 AtCoder 很多时候场上觉得不简单的题评成菜色是不是也有原因啊。


ARC113A A*B*C

\[\begin{aligned} ans =& \sum_{i = 1} ^ K \sum_{j = 1} ^ {\lfloor K/i\rfloor} d(j)\\ =& \sum_{i = 1} ^ K \sum_{j = 1} ^ {\lfloor K/i\rfloor} \lfloor\frac{K}{ij}\rfloor \end{aligned}\]

整除分块套整除分块即可,时间复杂度 \(\Theta(K)\)

int n;
 
i64 sum(int n) {
	i64 res=0;
	for (int l=1,r;l<=n;l=r+1) {
		r=n/(n/l);
		res+=1ll*(r-l+1)*(n/l);
	}
	return res;
}
 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin>>n;
	i64 res=0;
	for (int l=1,r;l<=n;l=r+1) {
		r=n/(n/l);
		res+=sum(n/l)*(r-l+1);	
	}
	cout<<res<<'\n';
	return 0;
}

ARC113B A^B^C

容易发现,任意一个 \(k : 1\le k\le 9\) 的循环节必然如下:

\[1, a, b, c, d, a, b, c, d, \dots \]

有可能 \(d = 1\),也可能 \(b = d\) 甚至 \(a = b = c = d\)

由于 \(B ^ C > 0\),可以求出 \(P = B ^ C \bmod 4 + 4\),然后答案就是 \(A ^ P \bmod 10\)

int a,b,c;

int mypow(int a,int x,int mod) {
	int res=1;
	for (;x;x>>=1,a=a*a%mod)
		if (x&1) res=res*a%mod;
	return res;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin>>a>>b>>c,a%=10,b%=4;
	cout<<mypow(a,4+mypow(b,c,4),10)<<'\n';
	return 0;
}

ARC113C String Invasion

从右往左贪心,每次遇到两个相邻的字符,就把右边都涂成这个字符。

然后可以得到右边与这个字符不相同的字符的个数个贡献。

具体实现可以维护当前右边每个字符有多少个。

const int xn=2e5,xc=26;
int n,last[xc];
string s;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin>>s,n=sz(s);
	rep(c,xc) last[c]=0;
	i64 res=0;
	per(i,n) {
		rep(c,xc) if (s[i]-'a'!=c)
			++last[c];
		if (i>=2 and s[i-1]==s[i-2]
		and s[i-1]!=s[i]) {
			res+=last[s[i-1]-'a'];
			rep(c,xc) last[c]=n-i;
			last[s[i-1]-'a']=0;
		}
	}
	cout<<res<<'\n';
	return 0;
}

ARC113D Sky Reflector

没法直接处理限制必然从分析性质入手。

那么先设这个 Grid\(C\) 吧。

对于每个 \(i\)\(A[i] = \min_{j = 1} ^ m C[i][j]\)

所以 \(\forall j \in [1,m] : C[i][j] \ge A[i]\)

同理,\(C[i][j] \le B[j]\),所以 \(\forall i, j : B[j] \ge A[i]\)

如果 \(n \ge 2, m \ge 2\),这里必然可以挑选出每行一个格子染成红色,每列一个格子染成蓝色,使得没有格子被染成两种颜色。

然后这样就只需要统计满足上述条件的序列数了,具体见代码。

所以特殊处理 \(n = 1\)\(m = 1\) 的情况即可。

这里可以图个方便,注意到 \(n, m\)\(m, n\) 是一样的,令 \(n \le m\) 可以少特判一次。

int n,m,k;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin>>n>>m>>k;
	if (n>m) swap(n,m);
	int res=0;
	if (n==1) {
		if (m==1) cout<<k<<'\n';
		else cout<<mypow(k,m)<<'\n';
		return 0;
	}
	rep(i,k) res=(res+(mint (mypow(i+1,n))
	-mypow(i,n))*mypow(k-i,m)).x;
	cout<<res<<'\n';
	return 0;
}

ARC113E Rvom and Rsrev

这题其实没那么可怕吧,细节和重复工作有点多而已,好久没写代码,场上没调出来 /kk

大致分析一下,最后有几种可能(易证其他的都是可以转化得更优的):

a,全 b,一堆 b 中一个 a,一堆 b 然后一堆 a

再分析一下删 a 转和删 b 转分别的目的是什么:

a 很简单:把中间挡住后面的 ba 干掉 或 把中间的 a 甩到结尾去。

这里需要区分:中间的 a 是狼,是野兽,会伤害它们后面的 b

结尾的 a 是狗,是宠物,因为 a 的字典序比空位大。

b 就是很无奈的选择了,最多一次。必然是恰有一个 a 卡在中间,然后为了把这个 a 甩到结尾牺牲两个 b

然后这题萌新认为最大的难点,就是要发现,这样删 b 的作用可能不止把一个 a 甩到后面去,前面一些被删掉的中间的 a,也可以复活,然后搭便车,逃到结尾。

具体实现见代码和注释。

int n;
string s;

int mymain() {
	cin >> s, n = sz(s);
	int la = -1, lb = -1; // 最后的 a 和 b 的位置
	int ca = 0, cb = 0; // a 和 b 初始的数量
	rep(i, 0, n) {
		if (s[i] == 'a') la = i, ++ca;
		else lb = i, ++cb;
	}
	if (cb == 0 or ca == 0)
		return cout << s << '\n', 0; // 全 a 或全 b
	if (lb == n - 1) { // b 结尾,a 没法靠删 a 甩到后面
		if (ca & 1) { // 中间的 a 删不光,那么留下最后一个 a
			int rcb = n - 1 - la; // 最后的 a 右边的 b 数
			int lcb = cb - rcb;  // a 左边的 b 数
			if (rcb <= 2 or !lcb) { // 决策删 b,前者删了不如不删,后者没法删,那么不删
				rep(i, 0, lcb) cout << 'b';
				cout << 'a';
				rep(i, 0, rcb) cout << 'b';
				cout << '\n';
			} else { // 决定删 b,统计搭便车
				int ba = -1, baa = -1; // 找到一个 bab 或 baa 的位置,至少有一个
				/* 
				※:这里用到了有点重要的贪心性质,下面也会用到:
				如果一段 a 只有 1 个,那么删一个后面的 a 和这里的 a 把 a 甩到后面去不优
				否则是优的。所以这些单个的 a 可以自相残杀只留最后一个 
				*/
				rep(i, 0, n - 2) {
					if (s[i] == 'a') continue;
					if (s[i+1] == 'b') continue;
					if (s[i+2] == 'a') baa = i;
					else ba = i;
				}
				if (~baa) ba = baa; // 第一次用删 b 找甩点甩同样遵循 ※
				int ab = ba + 1;
				for (; s[ab] == 'a'; ++ab); // 找到这段 a 的长度
				int ra = ab - ba - 1, oa = 0; // 得出当前后缀 a 的长度,是否剩余单个 a
				for (int i = 0, j; i < ba; i = j) {
					j = i + 1;
					if (s[i] == 'b') continue;
					for (; s[j] == 'a'; ++j);
					if (j - i >= 2) ra += j - i - 2;
					else oa ^= 1;
				} // 统计删 b 甩点 ba 的左边
				for (int i = ab, j; i < n; i = j) {
					j = i + 1;
					if (s[i] == 'b') continue;
					for (; s[j] == 'a'; ++j);
					if (j - i >= 2) ra += j - i - 2;
					else oa ^= 1;
				} // 统计删 b 甩点 ba 的右边
				ra -= oa; // 此时 ra 必然 >= 1,这样就是浪费一个后缀 a 消掉中间 a
				rep(i, 0, cb - 2) cout << 'b';
				rep(i, 0, ra) cout << 'a';
				cout << '\n';
			}
		} else { // 中间的 a 删光了,不用牺牲 b 了
			rep(i, 0, cb) cout << 'b';
			cout << '\n';
		}
	} else { // 本来就有后缀 a
		int ra = n - 1 - lb, oa = 0; // 当前后缀 a 长度,有没有中间的单个 a
		for (int i = 0, j; i < lb; i = j) {
			j = i + 1;
			if (s[i] == 'b') continue;
			for (; s[j] == 'a'; ++j);
			if (j - i >= 2) ra += j - i - 2;
			else oa ^= 1; 
		}
		ra -= oa; // 同理,消掉中间的 a
		rep(i, 0, cb) cout << 'b';
		rep(i, 0, ra) cout << 'a';
		cout << '\n';		
	}
	return 0;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int cas; cin >> cas;
	while (cas--) mymain();
	return 0;
}

本人已在加速退役,大家敬请随便爆 D

posted @ 2021-02-22 12:08  George1123  阅读(438)  评论(0编辑  收藏  举报