AtCoder Beginner Contest 370

A.Raise Both Hands(思维)

题意:

高桥决定做章鱼小丸子并把它端给Snuke。高桥告诉Snuke,如果他想吃章鱼小丸子,只需举起左手,不想吃只需举起右手。

已知两个整数 L L L R R R用于表示Snuke正在举起的手是哪只手。当且仅当 L = 1 L=1 L=1时,他正在举起左手;当且仅当 R = 1 R=1 R=1时,他正在举起右手。他可能不遵循指示,同时抬起双手或根本不抬任何一只手。

如果Snuke只抬了一只手,请输出Yes表示他想吃章鱼小丸子,输出No表示不想吃。如果他同时抬双手或者没有抬任何一只手,请输出Invalid

假设Snuke只抬了一只手,则始终遵循指示。

分析:

按照题意,分三种情况判断即可。

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=100;
const int MOD=1000000007;

void solve(){
    int l,r;
    cin>>l>>r;
    if(l==1 && r==0)
        cout<<"Yes"<<endl;
    else if(l==0 && r==1)
        cout<<"No"<<endl;
    else
        cout<<"Invalid"<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	solve();
    return 0;
}

B.Binary Alchemy(模拟)

题意:

N N N种编号为 1 , 2 , … , N 1,2,\ldots,N 1,2,,N的元素。

元素之间可以相互组合。当元素 i i i j j j组合在一起时,如果 i ≥ j i\geq j ij则变为元素 A _ i , j A\_{i,j} A_i,j,如果 i < j i\lt j i<j则变为元素 A _ j , i A\_{j,i} A_j,i

从元素 1 1 1开始,依次与元素 1 , 2 , … , N 1,2,\ldots,N 1,2,,N结合。求最后得到的元素。

分析:

按照题意查表,模拟合成过程。

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=100;
const int MOD=1000000007;

void solve(){
    int n;
    cin>>n;
    vector<vector<int>>a(n);
    for (int i=0;i<n;i++) {
        a[i].resize(i+1);
        for(auto& x:a[i]) {
            cin>>x;
            --x;
        }
    }
    int tmp=0;
    for(int i=0;i<n;++i) {
        int x=tmp,y=i;
        if(x<y)
            swap(x,y);
        tmp=a[x][y];
    }
    cout<<tmp+1<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	solve();
    return 0;
}

C.Word Ladder(贪心)

题意:

给你两个由小写英文字母组成的字符串 S S S T T T。其中, S S S T T T的长度相等。

X X X为空字符串,重复以下操作,直到 S S S等于 T T T

  • 更改 S S S中的一个字符,并将 S S S追加到 X X X的末尾。

找出这样得到的元素个数最少的字符串数组 X X X。如果有多个元素个数相同的数组,请找出其中字典序最小的一个。

分析:

对于总数只要 T _ i ≠ S _ i T\_i≠S\_i T_i=S_i那它就可以改,所以只要 T _ i ≠ S _ i T\_i≠S\_i T_i=S_i答案就加一。因此答案为 S S S T T T S _ i ≠ T _ i S\_i≠T\_i S_i=T_i的个数。

考虑操作的顺序。当 S _ i S\_i S_i被替换为 T _ i T\_i T_i时,如果是 S _ i < T _ i S\_i\lt T\_i S_i<T_i,那么字符串在字典序上会变大;如果是 S _ i > T _ i S\_i>T\_i S_i>T_i,那么字符串在字典序上会变小。

因此,我们先从头到尾遍历一遍 S S S T T T,如果 S _ i > T _ i S\_i>T\_i S_i>T_i那么就将 i i i存入数组。接着,我们从尾到头遍历一次,如果 S _ i < T _ i S\_i\lt T\_i S_i<T_i那么将 i i i存入数组。

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=20010;
const int MOD=1000000007;
int a[N],cnt;

void solve(){
	string sa,sb;
	cin>>sa>>sb;
	int n=sa.length();
	for(int i=0;i<=n-1;i++){
		if(sa[i]>sb[i])
			a[++cnt]=i;
	}
	for(int i=n-1;i>=0;i--){
		if(sa[i]<sb[i])
			a[++cnt]=i;
	}
	cout<<cnt<<endl;
	for(int i=1;i<=cnt;i++){
		sa[a[i]]=sb[a[i]];
		cout<<sa<<endl;
	 } 
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	solve();
    return 0;
}

D.Cross Explosion(暴力)

题意:

有一个网格,网格中有 H H H行和 W W W列。 ( i , j ) (i,j) (i,j)表示从上往下第 i i i行和从左往上第 j j j列的单元格。

最初,每个单元格中都有一面墙。按照下面给出的顺序处理 Q Q Q个查询后,求剩余墙的数量。

在第 q q q次查询中,我们给出了两个整数 R _ q R\_q R_q C _ q C\_q C_q。 在 ( R _ q , C _ q ) (R\_q,C\_q) (R_q,C_q)处放置炸弹来摧毁墙壁。结果会发生以下情况:

  • 如果 ( R _ q , C _ q ) (R\_q,C\_q) (R_q,C_q)处有一堵墙,则摧毁这堵墙并结束进程。

  • 分类讨论:

    • 如果存在 i < R _ q i\lt R\_q i<R_q,在所有 i < k < R _ q i\lt k\lt R\_q i<k<R_q中, ( i , C _ q ) (i,C\_q) (i,C_q)处有一堵墙,而 ( k , C _ q ) (k,C\_q) (k,C_q)处没有墙,则摧毁 ( i , C _ q ) (i,C\_q) (i,C_q)处的墙。

    • 如果存在 i > R _ q i\gt R\_q i>R_q,使得在所有 R _ q < k < i R\_q\lt k\lt i R_q<k<i中, ( i , C _ q ) (i,C\_q) (i,C_q)处有一堵墙,而 ( k , C _ q ) (k,C\_q) (k,C_q)处没有墙,则破坏 ( i , C _ q ) (i,C\_q) (i,C_q)处的墙。

    • 如果存在 j < C _ q j\lt C\_q j<C_q,使得在所有 j < k < C _ q j\lt k\lt C\_q j<k<C_q中, ( R _ q , j ) (R\_q,j) (R_q,j)处有一堵墙,而 ( R _ q , k ) (R\_q,k) (R_q,k)处没有墙,则破坏 ( R _ q , j ) (R\_q,j) (R_q,j)处的墙。

    • 如果存在 j > C _ q j\gt C\_q j>C_q,使得在 ( R _ q , j ) (R\_q,j) (R_q,j)处有一堵墙,而在所有的 C _ q < k < j C\_q\lt k\lt j C_q<k<j中,在 ( R _ q , k ) (R\_q,k) (R_q,k)处没有墙,则破坏 ( R _ q , j ) (R\_q,j) (R_q,j)处的墙。

分析:

观察数据范围发现题目保证 h × w ≤ 4 × 1 0 5 h×w≤4×10^5 h×w4×105,所以可以将所有点都存起来。

对每一行和每一列开一个set,存储这一行或这一列中墙的位置。用 h _ i h\_i h_i表示第 i i i行的set,用 w _ i w\_i w_i表示第 i i i列的set

对于每次询问,可以查找 h _ x h\_x h_x中是否有存在 y y y,如果有 y y y,删掉 h _ x h\_x h_x中的 y y y,删掉 w _ y w\_y w_y中的 x x x

如果没有 y y y,找到 h _ x h\_x h_x中第一个大于 y y y的数,删去这个数并删掉这个数前面的那个数。

同理,找到 w _ y w\_y w_y中第一个大于 x x x的数,删去这个数并删掉这个数前面的那个数。

最后剩下的墙的数量就是每一行set的大小之和。

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=4e5+10;
const int MOD=1000000007;
set <int> h[N], w[N];
int n,m,q;

void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            h[i].insert(j);
            w[j].insert(i);
        }
    cin>>q;
    while(q--) {
        int x,y;
        cin>>x>>y;
        if(h[x].find(y) != h[x].end()){
            h[x].erase(y);
            w[y].erase(x);
        }
        else{
            auto i = h[x].lower_bound(y);
            vector <int> v;
            if(i!=h[x].end()) v.push_back(*i);
            if(i!=h[x].begin()) v.push_back(*(--i));
            for(auto j : v) {
                h[x].erase (j);
                w[j].erase (x);
            }

            v.clear();
            i=w[y].lower_bound(x);
            if(i!=w[y].end()) v.push_back(*i);
            if(i!=w[y].begin()) v.push_back(*(--i));
            for(auto j : v) {
                w[y].erase(j);
                h[j].erase(y);
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++) 
        ans+=h[i].size();
    cout<<ans<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	solve();
    return 0;
}

E.Avoid K Partition(动态规划)

题意:

给你一个长度为 N N N的序列 A = ( A _ 1 , A _ 2 , … , A _ N ) A=(A\_1,A\_2,\dots,A\_N) A=(A_1,A_2,,A_N)和一个整数 K K K。 有 2 N − 1 2^{N-1} 2N1种方法可以将 A A A分成几个连续的子序列。在这些分割中,有多少个子序列的元素之和不等于 K K K?答案对 998244353 998244353 998244353取模。

在这里,将A分成几个连续的子序列意味着以下过程。

  • 自由选择子序列的数量 k ( 1 ≤ k ≤ N ) k(1≤k≤N) k(1kN)和一个整数序列 ( i _ 1 , i _ 2 , … , i _ k , i _ k + 1 ) (i\_1,i\_2,…,i\_k,i\_{k+1}) (i_1,i_2,,i_k,i_k+1),满足 1 = i _ 1 < i _ 2 < ⋯ < i _ k < i _ k + 1 = N + 1 1=i\_1\lt i\_2\lt ⋯\lt i\_k\lt i\_{k+1}=N+1 1=i_1<i_2<<i_k<i_k+1=N+1

  • 对于每个 1 ≤ n ≤ k 1≤n≤k 1nk,第 n n n个子序列是通过取 A A A中第 i _ n i\_n i_n ( i _ n + 1 − 1 ) (i\_{n+1}−1) (i_n+11)的元素所形成的。保持其顺序不变。

下面是 A = ( 1 , 2 , 3 , 4 , 5 ) A=(1,2,3,4,5) A=(1,2,3,4,5)的一些分割示例:

  • ( 1 , 2 , 3 ) , ( 4 ) , ( 5 ) (1,2,3),(4),(5) (1,2,3),(4),(5)

  • ( 1 , 2 ) , ( 3 , 4 , 5 ) (1,2),(3,4,5) (1,2),(3,4,5)

  • ( 1 , 2 , 3 , 4 , 5 ) (1,2,3,4,5) (1,2,3,4,5)

分析:

本题我们考虑动态规划,设 f _ i f\_i f_i表示前 i i i个数的答案。若不考虑不合法情况,显然 f _ i = ∑ _ j = 1 i − 1 f _ j + 1 f\_i=\sum \_{j=1}^{i-1}f\_j+1 f_i=_j=1i1f_j+1

但是如果出现 s _ i − k = s _ j s\_i−k=s\_j s_ik=s_j s s s表示前缀和),则 f _ i f\_i f_i不能从 f _ j f\_j f_j转移过来。

所以 f _ i f\_i f_i是否转移与 s _ i s\_i s_i有关。可以用map储存每个 s s s对应的 f _ i f\_i f_i的和,转移时减去。

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N=200010;
const LL MOD=998244353;
LL n,k,a[N],s,s2,f[N];
map<LL,LL>mp;

void solve(){
	cin>>n>>k;
	for(LL i=1;i<=n;i++){
		cin>>a[i];
		s+=a[i];
		f[i]=((s!=k)+s2-mp[s-k]+MOD)%MOD;
		s2=(s2+f[i])%MOD;
		mp[s]=(mp[s]+f[i])%MOD;
	}
	cout<<f[n]<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	solve();
    return 0;
}

F.Cake Division(二分)

题意:

有一个圆形蛋糕,被切割线分割成 N N N块。每条切割线都是连接圆心和弧线上一点的线段。

蛋糕块和切割线按顺时针顺序编号为 1 , 2 , … , N 1,2,\ldots,N 1,2,,N,蛋糕块 i i i的质量为 A _ i A\_i A_i。蛋糕块 1 1 1也被称为蛋糕块 N + 1 N+1 N+1

切割线 i i i位于蛋糕块 i i i i + 1 i+1 i+1之间,它们按顺时针顺序排列为:蛋糕块 1 1 1,切割线 1 1 1,蛋糕块 2 2 2,切割线 2 2 2 … \ldots ,蛋糕块 N N N,切割线 N N N

我们想把这个蛋糕分给 K K K个人,条件如下。设 w _ i w\_i w_i i i i这个人得到的蛋糕的质量总和。

  • 每个人都会得到一块或多块连续的蛋糕。

  • 不存在有人没得到蛋糕。

  • 在上述两个条件下, min ⁡ ( w _ 1 , w _ 2 , … , w _ K ) \min(w\_1,w\_2,\ldots,w\_K) min(w_1,w_2,,w_K)最大。

求满足条件的划分中 min ⁡ ( w _ 1 , w _ 2 , … , w _ K ) \min(w\_1,w\_2,\ldots,w\_K) min(w_1,w_2,,w_K)的值,以及满足条件的划分中从未被切割的切割线的数量。如果 i i i i + 1 i+1 i+1被分给了不同的人,那么切割线 i i i就被认为是切割的。

分析:

首先将环断开为链。然后问题转化成了在链上有多少个节点满足 1 ≤ i ≤ n 1≤i≤n 1in,且 i + 1 i+1 i+1 i + n i+n i+n划分出来的段和的最小值最大。先考虑求一个询问 x x x的时候如何处理。很明显可以二分。

但对于每个断点都二分一次是无法通过的,因此考虑整体二分。对于当前答案区间 l , r l,r l,r,对于每个询问考虑从后往前跳,每次找到最近的一个满足当前段和大于等于 m i d mid mid的位置,并调到那个位置上。如果一个位置 i + n i+n i+n k k k次后仍然位于KaTeX parse error: Can't use function '\]' in math mode at position 9: [i+1,i+n\̲]̲这个区间上,那么 i i i的答案就大于 m i d mid mid。每次对于当前可能贡献最优解的位置集合都判断一次,如果有答案大于 m i d mid mid的位置就往大的递归,否则往小的递归。

每次二分时,预处理出每个位置的最近的一个位置使得这一段的和大于等于 m i d mid mid,将这个最近的位置设置为父节点。然后用倍增预处理,那么查询时就可以做到 l o g _ 2 k log\_2k log_2k

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=1e6+5;

LL n,k;
LL pr[N];
LL a[N];
bool in[N];
int fat[N][21];
int sol1(int x,int y){
	for(int i=20;i>=0;i--){
		if(y&(1<<i)){
			x=fat[x][i];
		}
	}return x;
}
void deal(LL l,LL r,vector<int>q){
	LL mid=(l+r+1)>>1;
	if(l==r){
		cout<<l<<" ";
		for(int i=0;i<q.size();i++){
			in[q[i]]=true;
		}
		cout<<n-q.size();
		return;
	}
	for(int i=1;i<=n*2;i++){
		int L=1,R=i;
		fat[i][0]=0;
		if(pr[i]<mid){
			continue;
		}
		while(L<R){
			int mi=(L+R+1)>>1;
			if(pr[i]-pr[mi-1]>=mid){
				L=mi;
			}else{
				R=mi-1;
			}
		}
		fat[i][0]=L-1;
	}
	for(int i=1;i<=20;i++){
		for(int j=1;j<=2*n;j++){
			fat[j][i]=fat[fat[j][i-1]][i-1];
		}
	}
	vector<int>t1,t2;
	for(int i=0;i<q.size();i++){
		if(sol1(q[i]+n,k)>=q[i]){
			t2.push_back(q[i]);
		}else{
			t1.push_back(q[i]);
		}
	}
	if(t2.empty()){
		deal(l,mid-1,t1);
	}else{
		deal(mid,r,t2);
	}

}

void solve(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i+n]=a[i];
	}
	for(int i=1;i<=n*2;i++)
        pr[i]=pr[i-1]+a[i];
	vector<int>t;
	for(int i=1;i<=n;i++){
		t.push_back(i);
	}
	deal(1,pr[n],t);
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	solve();
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值