题解:Toyota Programming Contest 2024#9(AtCoder Beginner Contest 370)
总体情况
这次手速够快:ABC in 10min,ABCDE in 30min。
%%%%%%%%
%%%%%%%%
A - Raise Both Hands
思路
分类讨论很简单的。
注意一定要判断 \(0,0\) 这种情况。
Code
// Problem: A - Raise Both Hands
// Contest: AtCoder - Toyota Programming Contest 2024#9(AtCoder Beginner Contest 370)
// URL: https://atcoder.jp/contests/abc370/tasks/abc370_a
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
//#define int long long
namespace gtx{
// Fast IO
void read(int &x){
x = 0;int h = 1;char tmp;
do{tmp=getchar();if(tmp=='-')h*=-1;}while(!isdigit(tmp));
while(isdigit(tmp)) x*=10,x+=tmp-'0',tmp=getchar();
x*=h;
}
void read(char &x){do{x=getchar();}while(x==' '||x=='\n'||x=='\r');}
void write(char x){putchar(x);}
void write(int x){
if(x<0) putchar('-'),x=-x;int st[200]={0},tot=0;
do{st[++tot]=x%10,x/=10;} while(x);
while(tot){putchar(st[tot--]+'0');};
}
void write(int x,char y){write(x);write(y);}
signed main(){
int a,b;
cin >> a>>b;
cout << (a&&b?"Invalid":(a?"Yes":(b?"No":"Invalid")));
return 0;
}
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// gtx::read(T);
while(T--) gtx::main();
return 0;
}
B - Binary Alchemy
思路
这个直接模拟。
Code
// Problem: B - Binary Alchemy
// Contest: AtCoder - Toyota Programming Contest 2024#9(AtCoder Beginner Contest 370)
// URL: https://atcoder.jp/contests/abc370/tasks/abc370_b
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
//#define int long long
namespace gtx{
// Fast IO
void read(int &x){
x = 0;int h = 1;char tmp;
do{tmp=getchar();if(tmp=='-')h*=-1;}while(!isdigit(tmp));
while(isdigit(tmp)) x*=10,x+=tmp-'0',tmp=getchar();
x*=h;
}
void read(char &x){do{x=getchar();}while(x==' '||x=='\n'||x=='\r');}
void write(char x){putchar(x);}
void write(int x){
if(x<0) putchar('-'),x=-x;int st[200]={0},tot=0;
do{st[++tot]=x%10,x/=10;} while(x);
while(tot){putchar(st[tot--]+'0');};
}
void write(int x,char y){write(x);write(y);}
const int MAXN =120;
int n,a[MAXN][MAXN];
signed main(){
read(n);
for(int i = 1;i<=n;i++){
for(int j = 1;j<=i;j++) read(a[i][j]);
}
int now = 1;
for(int i = 1;i<=n;i++){
if(now>=i) now = a[now][i];
else now = a[i][now];
}
write(now);
return 0;
}
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// gtx::read(T);
while(T--) gtx::main();
return 0;
}
C - Word Ladder
思路
最小此时明显就是每一个位置上面不一样的位置之和。
于是我们只需要保证字典序最小就行。
考虑怎么做到字典序最小。
- 对于 \(a_i>b_i\) 的操作,它一定会将字典序变小,我们一定要先执行这些操作,并按照 \(i\) 从小到大执行。
- 对于 \(a_i<b_i\) 的操作,它一定会将字典序变大,我们一定要后执行这些操作,并按照 \(i\) 从大到小执行。
Code
// Problem: C - Word Ladder
// Contest: AtCoder - Toyota Programming Contest 2024#9(AtCoder Beginner Contest 370)
// URL: https://atcoder.jp/contests/abc370/tasks/abc370_c
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
//#define int long long
namespace gtx{
// Fast IO
void read(int &x){
x = 0;int h = 1;char tmp;
do{tmp=getchar();if(tmp=='-')h*=-1;}while(!isdigit(tmp));
while(isdigit(tmp)) x*=10,x+=tmp-'0',tmp=getchar();
x*=h;
}
void read(char &x){do{x=getchar();}while(x==' '||x=='\n'||x=='\r');}
void write(char x){putchar(x);}
void write(int x){
if(x<0) putchar('-'),x=-x;int st[200]={0},tot=0;
do{st[++tot]=x%10,x/=10;} while(x);
while(tot){putchar(st[tot--]+'0');};
}
void write(int x,char y){write(x);write(y);}
string a,b;
vector<int> v1,v2;
signed main(){
cin >> a >>b;
for(int i = 0;i<a.size();i++){
if(a[i]==b[i]) continue;
if(a[i]<b[i]) v2.push_back(i);
else v1.push_back(i);
}
reverse(v2.begin(),v2.end());
cout << v1.size()+v2.size() << endl;
for(int i:v1){
a[i] = b[i];
cout << a << endl;
}
for(int i:v2){
a[i] = b[i];
cout << a << endl;
}
return 0;
}
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// gtx::read(T);
while(T--) gtx::main();
return 0;
}
D - Cross Explosio
思路
对于每一个 \(x\)、\(y\) 都建立一个 set,直接使用模拟即可。
如果存在点,我们直接删除。
如果不存在这个点,对于 \(x\) 上面,找到前面的一个下标和后面的一个下标删除点就行,在 \(y\) 上同理。
Code
// Problem: D - Cross Explosion
// Contest: AtCoder - Toyota Programming Contest 2024#9(AtCoder Beginner Contest 370)
// URL: https://atcoder.jp/contests/abc370/tasks/abc370_d
// Memory Limit: 1024 MB
// Time Limit: 4000 ms
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
//#define int long long
namespace gtx{
// Fast IO
void read(int &x){
x = 0;int h = 1;char tmp;
do{tmp=getchar();if(tmp=='-')h*=-1;}while(!isdigit(tmp));
while(isdigit(tmp)) x*=10,x+=tmp-'0',tmp=getchar();
x*=h;
}
void read(char &x){do{x=getchar();}while(x==' '||x=='\n'||x=='\r');}
void write(char x){putchar(x);}
void write(int x){
if(x<0) putchar('-'),x=-x;int st[200]={0},tot=0;
do{st[++tot]=x%10,x/=10;} while(x);
while(tot){putchar(st[tot--]+'0');};
}
void write(int x,char y){write(x);write(y);}
const int MAXN = 4e5+10;
int n,m,q;
set<int> stx[MAXN],sty[MAXN];
signed main(){
read(n);read(m);read(q);
for(int i = 1;i<=n;i++){
for(int j = 1;j<=m;j++){
stx[i].insert(j);
sty[j].insert(i);
}
}
for(int i = 1;i<=q;i++){
int a,b;
// int ans = 0;
// for(int i = 1;i<=n;i++) ans += stx[i].size();
// write(ans,endl);
read(a);read(b);
if(stx[a].find(b)!=stx[a].end()){
stx[a].erase(b);
sty[b].erase(a);
}else{
if(1){
auto tmp2 = stx[a].upper_bound(b);
if(tmp2!=stx[a].begin()){
auto tmp1 = --tmp2;tmp2++;
stx[a].erase(*tmp1);
sty[*tmp1].erase(a);
}
if(tmp2!=stx[a].end()){
stx[a].erase(*tmp2);
sty[*tmp2].erase(a);
}
}
if(1){
auto tmp2 = sty[b].upper_bound(a);
if(tmp2!=sty[b].begin()){
auto tmp1 = --tmp2;tmp2++;
sty[b].erase(*tmp1);
stx[*tmp1].erase(b);
}
if(tmp2!=sty[b].end()){
sty[b].erase(*tmp2);
stx[*tmp2].erase(b);
}
}
}
// ans = 0;
// for(int i = 1;i<=n;i++) ans += stx[i].size();
// write(ans,endl);
}
int ans = 0;
for(int i = 1;i<=n;i++) ans += stx[i].size();
write(ans);
return 0;
}
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// gtx::read(T);
while(T--) gtx::main();
return 0;
}
E - Avoid K Partition
思路
一眼 DP。跟这道题差不多。
定义 \(f_i\) 表示的是以 \(i\) 结尾的方案数。
则可以设计转移:
其中 \(s(i,j)\) 代表 \([i,j]\) 的数的和。
这个是 \(O(n^3)\) 的,明显会炸。
首先我们可以改变整个 DP 的转移。
就是所有方案之和减去可以使得 \(s(j,i)=k\) 的方案数。
可以使用前缀和拆分开,\(sum_i-sum_{j-1}=k\) 的方案数,就是求 \(sum_{j-1}=sum_i-k\) 的方案数,可以使用一个桶进行记录。定义 \(g_i\) 代表的是 \(sum_j=i\) 的方案数,于是:
但是,\(sum_i-k\) 一定不能取模,我们稍微算一下,\(1\times 10^{15} \cdot 2\times 10^5=2\times 10^{20}\),是需要开 __int128
的。
于是时间复杂度变为 \(O(n^2)\)。
其中 \(\displaystyle \sum_{j\in[1,i)} f_j\) 直接使用变量 \(summ\) 存储就行。
时间复杂度:\(O(n)\)。
(不要看见这些怎么多的推理,其实考试的时候一念之间就从 \(O(n^3)\) 优化到了 \(O(n)\))。
Code
// Problem: E - Avoid K Partition
// Contest: AtCoder - Toyota Programming Contest 2024#9(AtCoder Beginner Contest 370)
// URL: https://atcoder.jp/contests/abc370/tasks/abc370_e
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
namespace gtx{
// Fast IO
void read(int &x){
x = 0;int h = 1;char tmp;
do{tmp=getchar();if(tmp=='-')h*=-1;}while(!isdigit(tmp));
while(isdigit(tmp)) x*=10,x+=tmp-'0',tmp=getchar();
x*=h;
}
void read(char &x){do{x=getchar();}while(x==' '||x=='\n'||x=='\r');}
void write(char x){putchar(x);}
void write(int x){
if(x<0) putchar('-'),x=-x;int st[200]={0},tot=0;
do{st[++tot]=x%10,x/=10;} while(x);
while(tot){putchar(st[tot--]+'0');};
}
void write(int x,char y){write(x);write(y);}
const int MAXN = 2e5+10;
const int MOD = 998244353;
int n,k,sum;
__int128 summ;
int f,a[MAXN];
map<__int128,int> mp;
signed main(){
read(n);read(k);
sum = 1;
mp[0] = 1;
for(int i = 1;i<=n;i++){
read(a[i]);
summ += a[i];
f = sum-mp[summ-k];
mp[summ] += f;
mp[summ] %= MOD;
sum += f;
sum %= MOD;
// cout << f << " " << sum << " " << summ << endl;
}
write((f%MOD+MOD)%MOD);
return 0;
}
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// gtx::read(T);
while(T--) gtx::main();
return 0;
}
F - Cake Division
思路
首先,对于这个问题,一看就知道要使用二分答案来解决。
于是我们就将问题转换成将蛋糕分给 \(k\) 个人,使得每个人都能至少得到权值为 \(w\) 的方案是否可行。如果可以,那么那些边一定不能删。
我们先来看是否可行的问题。
首先我们可以先破环为链,预处理出来从第 \(i(i\in[1,n])\) 个蛋糕开始满足 \(sum(i,j)\ge w\) 的最小值,记录为 \(f(i)\),如果不存在,就是 \(f(i)=\infty\)。我们枚举第一个人先拿的是那一块蛋糕,那么我们一定会给他分配到第 \(f(i)\) 块蛋糕,接着第二个人就从 \(f(i)+1\) 开始拿蛋糕,一直到 \(f(f(i)+1)\) 块蛋糕,可以证明这个定是分配时最优秀的情况。只要有一个 \(i\) 满足要求,那么这个 \(w\) 就是可行的。
但是暴力这样的时间复杂度是 \(O(n^2\log_2 n+nk\log_2 n)\) 的,会超时。
于是我们可以试着给 \(i\) 和 \(f(i)+1\) 连一条有向边,那么求得就是 \(i\) 向前走的第 \(k-1\) 个父亲。这个可以使用倍增解决。
而关于怎么求 \(f(i)\),直接使用双指针求就行,这样时间复杂度被我们降为了 \(O(n\log^2_2 n)\) 的,非常理想。
所以我们就已经知道了最大的 \(w\),已经解决了第一问。
对于最大的 \(w\),我们已经知道 \(w\) 对于那个(些)\(i\) 是满足限制的。我们可以对 \(i\) 打一个标记,于是问题变成了在一个 DAG 里面,有一些点上面有标记,标记会顺着边一路往前,求有那些点没有被打过标记。
这个问题很经典,就是一个拓扑序 DP。实际上我们只关心 \(i\) 的下一个是谁,这样就可以递推进行了。
我的思路很复杂,代码可能有一点难写。
Code
// Problem: F - Cake Division
// Contest: AtCoder - Toyota Programming Contest 2024#9(AtCoder Beginner Contest 370)
// URL: https://atcoder.jp/contests/abc370/tasks/abc370_f
// Memory Limit: 1024 MB
// Time Limit: 5000 ms
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
//#define int long long
#pragma GCC optimize(2)
namespace gtx{
// Fast IO
void read(int &x){
x = 0;int h = 1;char tmp;
do{tmp=getchar();if(tmp=='-')h*=-1;}while(!isdigit(tmp));
while(isdigit(tmp)) x*=10,x+=tmp-'0',tmp=getchar();
x*=h;
}
void read(char &x){do{x=getchar();}while(x==' '||x=='\n'||x=='\r');}
void write(char x){putchar(x);}
void write(int x){
if(x<0) putchar('-'),x=-x;int st[200]={0},tot=0;
do{st[++tot]=x%10,x/=10;} while(x);
while(tot){putchar(st[tot--]+'0');};
}
void write(int x,char y){write(x);write(y);}
const int MAXN = 4e5+10;
const int LOGN = log2(MAXN)+2;
const int INF = 0x3f3f3f3f;
int n,k;
int fath[MAXN][LOGN],sum[MAXN];
bitset<MAXN> b;
int get(int x,int w){
int k = 0;
while(w){
if(w&1){
if(x!=INF) x = fath[x][k];
}
w>>=1;
k++;
}
return x;
}
bool check(int w){
for(int i = 1;i<=2*n+5;i++) for(int j = 0;j<LOGN;j++) fath[i][j] = INF;
int end=1;
for(int i = 1;i<=2*n;i++){
while(end<=2*n&&(sum[end]-sum[i-1])<w) end++;
end = min(end,2*n);
if((sum[end]-sum[i-1])<w) fath[i][0] = INF;
else fath[i][0] = end+1;
}
for(int j = 1;j<LOGN;j++){
for(int i = 1;i<=2*n;i++){
fath[i][j] = (fath[i][j-1]==INF)?INF:fath[fath[i][j-1]][j-1];
}
}
for(int j = 1;j<=n;j++){
if(get(j,k)-1<j+n) return true;
}
return false;
}
signed main(){
read(n);read(k);
int p = 0;
for(int i = 1;i<=n;i++){
read(sum[i]);
sum[i+n] = sum[i];
p+=sum[i];
}
for(int i = 1;i<=2*n;i++) sum[i]+=sum[i-1];
int l = 0,r = p/k,ans = 0;
while(l<=r){
int mid = (l+r)>>1;
if(check(mid)){
l = mid+1;
ans = mid;
}else r= mid-1;
}
write(ans,' ');
for(int i = 1;i<=2*n+5;i++) for(int j = 0;j<LOGN;j++) fath[i][j] = INF;
int end=1;
for(int i = 1;i<=2*n;i++){
while(end<=2*n&&(sum[end]-sum[i-1])<ans) end++;
end = min(end,2*n);
if((sum[end]-sum[i-1])<ans) fath[i][0] = INF;
else fath[i][0] = end+1;
}
for(int j = 1;j<LOGN;j++){
for(int i = 1;i<=2*n;i++){
fath[i][j] = (fath[i][j-1]==INF)?INF:fath[fath[i][j-1]][j-1];
}
}
for(int j = 1;j<=n;j++){
if(get(j,k)-1<j+n){
b.set(j);
}
}
for(int j = 1;j<=2*n;j++){
if(b[j]&&fath[j][0]!=INF) b[(fath[j][0]>n)?fath[j][0]-n:fath[j][0]]=1;
}
write(n-(int)b.count());
return 0;
}
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// gtx::read(T);
while(T--) gtx::main();
return 0;
}