AtCoder Beginner Contest 370

A.Raise Both Hands(思维)

题意:

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

已知两个整数LLLRRR用于表示Snuke正在举起的手是哪只手。当且仅当L=1L=1L=1时,他正在举起左手;当且仅当R=1R=1R=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(模拟)

题意:

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

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

从元素111开始,依次与元素1,2,…,N1,2,\ldots,N1,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(贪心)

题意:

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

XXX为空字符串,重复以下操作,直到SSS等于TTT

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

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

分析:

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

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

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

代码:

#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(暴力)

题意:

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

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

在第qqq次查询中,我们给出了两个整数R_qR\_qR_qC_qC\_qC_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_qi\lt R\_qi<R_q,在所有i<k<R_qi\lt k\lt R\_qi<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_qi\gt R\_qi>R_q,使得在所有R_q<k<iR\_q\lt k\lt iR_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_qj\lt C\_qj<C_q,使得在所有j<k<C_qj\lt k\lt C\_qj<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_qj\gt C\_qj>C_q,使得在(R_q,j)(R\_q,j)(R_q,j)处有一堵墙,而在所有的C_q<k<jC\_q\lt k\lt jC_q<k<j中,在(R_q,k)(R\_q,k)(R_q,k)处没有墙,则破坏(R_q,j)(R\_q,j)(R_q,j)处的墙。

分析:

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

对每一行和每一列开一个set,存储这一行或这一列中墙的位置。用h_ih\_ih_i表示第iii行的set,用w_iw\_iw_i表示第iii列的set

对于每次询问,可以查找h_xh\_xh_x中是否有存在yyy,如果有yyy,删掉h_xh\_xh_x中的yyy,删掉w_yw\_yw_y中的xxx

如果没有yyy,找到h_xh\_xh_x中第一个大于yyy的数,删去这个数并删掉这个数前面的那个数。

同理,找到w_yw\_yw_y中第一个大于xxx的数,删去这个数并删掉这个数前面的那个数。

最后剩下的墙的数量就是每一行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(动态规划)

题意:

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

在这里,将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+11=i\_1\lt i\_2\lt ⋯\lt i\_k\lt i\_{k+1}=N+11=i_1<i_2<<i_k<i_k+1=N+1

  • 对于每个1≤n≤k1≤n≤k1nk,第nnn个子序列是通过取AAA中第i_ni\_ni_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_if\_if_i表示前iii个数的答案。若不考虑不合法情况,显然f_i=∑_j=1i−1f_j+1f\_i=\sum \_{j=1}^{i-1}f\_j+1f_i=_j=1i1f_j+1

但是如果出现s_i−k=s_js\_i−k=s\_js_ik=s_jsss表示前缀和),则f_if\_if_i不能从f_jf\_jf_j转移过来。

所以f_if\_if_i是否转移与s_is\_is_i有关。可以用map储存每个sss对应的f_if\_if_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(二分)

题意:

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

蛋糕块和切割线按顺时针顺序编号为1,2,…,N1,2,\ldots,N1,2,,N,蛋糕块iii的质量为A_iA\_iA_i。蛋糕块111也被称为蛋糕块N+1N+1N+1

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

我们想把这个蛋糕分给KKK个人,条件如下。设w_iw\_iw_iiii这个人得到的蛋糕的质量总和。

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

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

  • 在上述两个条件下,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)的值,以及满足条件的划分中从未被切割的切割线的数量。如果iiii+1i+1i+1被分给了不同的人,那么切割线iii就被认为是切割的。

分析:

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

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

每次二分时,预处理出每个位置的最近的一个位置使得这一段的和大于等于midmidmid,将这个最近的位置设置为父节点。然后用倍增预处理,那么查询时就可以做到log_2klog\_2klog_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、付费专栏及课程。

余额充值