题解-ARC113
铭记这场暂时没有补的 F
题和场上没有做出的 E
题。
说句闲话,不知道有没有人和我一样想:Codeforces
查黑这么严,都有许多人开黑,许多成功。那么这个 AtCoder
很多时候场上觉得不简单的题评成菜色是不是也有原因啊。
ARC113A A*B*C
整除分块套整除分块即可,时间复杂度 \(\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\) 的循环节必然如下:
有可能 \(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
很简单:把中间挡住后面的 b
的 a
干掉 或 把中间的 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
。