Educational Codeforces Round 126 (Rated for Div. 2)(A-E)

A-Array Balancing

题意:给定两个数组a,b,a数组第i个数可以和b数组的第i个数交换,最终求

a1−a2|+|a2−a3|+⋯+|an−1−an||a1−a2|+|a2−a3|+⋯+|an−1−an| ++ |b1−b2|+|b2−b3|+⋯+|bn−1−bn||b1−b2|+|b2−b3|+⋯+|bn−1−bn|的最小值;

总结:第一道就dp一开始不太敢写

思路:对于数组a1 a2 a2....... an

b1 b2 b3........bn

当我们遍历到第i个数时,第i-1有两种情况分别为交换和不交换,i也有两种情况为交换和不交换。

于是我们可以将问题分解为多个子问题,对于第i对数,我们选择不交换的话,就是取i-1中交换与不交换两者中的最小值,同理选择交换的话也是,于是我们可以写出状态转移方程(代码容易看懂)

tip:注意开long long ,别问问就是wa了一发

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main(){
	int t;
	cin>>t;
	while(t--){
		ll n,a[30],b[30],dp[30][2]={0};
		cin>>n;
		for(int i=1;i<=n;i++)cin>>a[i];
		for(int i=1;i<=n;i++)cin>>b[i];
		for(int i=2;i<=n;i++){
			dp[i][0]=min(dp[i-1][0]+abs(a[i]-a[i-1])+abs(b[i]-b[i-1]),dp[i-1][1]+abs(a[i]-b[i-1])+abs(b[i]-a[i-1]));
			dp[i][1]=min(dp[i-1][0]+abs(a[i]-b[i-1])+abs(b[i]-a[i-1]),dp[i-1][1]+abs(a[i]-a[i-1])+abs(b[i]-b[i-1]));
		}
		ll ans=min(dp[n][0],dp[n][1]);
		cout<<ans<<endl;
	}
} 

B-Getting Zero

题意:给定一个整数v,可以进行两个操作,操作1:v=(v+1)%32768,操作二:v=(2*v)%32768;

给定n个整数ai,0<=ai<=32768;问最少要多少次操作可以把这个数变为0;

思路:对于这道题我们必须从32768这个特殊的数字考虑,为什么偏偏是他呢,他肯定有特别的地方,于是我们尝试分解他之后发现他恰好是2^15,知道这个性质这个题就好做了,对于任意一个数,他最多也就进行15次操作二后得到0,但这并一定是最优解呀,我们发现如果我们将一个数分解成(2^cnt)*k,那么这个数就只需要进行15-cnt次了,所有对于给定的ai,我们只需要遍历ai~ai+15,再对这个数字求2的因子的个数后求总的操作次数,就可以得到每个数的答案了,然后在这些答案中取最小值即可。

tip:注意0的时候操作次数就是0的而不是15,所以要特判掉。(别问问就是wa了一发)

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main(){
	int n;
	cin>>n;
	while(n--){
		int a;
		cin>>a;
		int ans=15;
		for(int i=a;i<=a+15;i++){
			if(i>0){
				int tmp=i;
				int cnt=0;
				while(tmp%2==0)cnt++,tmp/=2;
				int res=i-a+(15-cnt);
				ans=min(ans,res);
			}
		}
		if(a==0)ans=0;
		cout<<ans<<" ";
	}
} 

C-Water the Trees

题意:给定n个数,从第一天开始若该天为奇数则可以对一个数加1,若该天为偶数则可以说对一个数加2,问最少需要多少天才可以将所有数都变为相同的数,注意对该天我们可以选择不操作,即不对任何数加数。

总结:爆哭,12.37分过的35分交的代码打错了一个数字呜呜呜

思路:这道题是贪心的思路,表面上看挺复杂的,但我们将问题简化一下就比较容易看出问题,简化完将会变成我们常见的一个贪心问题,首先我们需要知道所有数只能加不能减啊,所有最终的答案所有序列至少也得是原序列中的最大值,但是一定是原序列中的最大值吗,这就不一定啦,比如有一个序列7778777,对于这个序列我们发现最终答案序列为9的比8的序列需要的天数还要小,如果为10呢?10的话相当于在原序列上增加3332333,对比8的答案我们发现8是1110111,10的答案要比8的答案每个数都多增加了2,肯定会比8的答案多,于是我们可以知道最终答案是在最大值和最大值加1的序列中的最小值。

这里我们设最大值为ma;

于是我们可以开始简化问题,对于一个序列,我们比较关心的是他可以进行加二的操作的次数,因为对于加1的操作不能由加2实现,但加2的操作却可以由加1实现。所有我们就可以对整个序列进行遍历啦,如果该数为奇数,那么可以加1的操作数就加1,最终得到cnt1,我们还要处理出最终达到答案序列总共需要的数字和totle,加2的操作数就为(totle-cnt1)/2;

到这里我们就可以很明显得看出来是一个贪心问题了,我们需要判断的是进行两天(即加3)的一个循环的次数,即totle/3,但所有的循环次数里又不一定每一次都能实现加2的操作,此时cnt2的作用就出来了。我们贪心最大的二的操作次数,剩余的数我们就只能进行循环每两天加1了。

总而言之就是分类讨论,还是比较容易讨论的,主要是简化问题比较复杂,代码中的分类讨论已经很清晰了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=3e5+10;
ll h[N];
int main(){
     ll t;
     cin>>t;
     while(t--){
     	ll n,ma=-1;
     	cin>>n;
     	for(int i=1;i<=n;i++)cin>>h[i],ma=max(ma,h[i]);
        //最大值为ma的操作
     	ll ans=0,cnt1=0,cnt2=0,totle=0;
     	for(int i=1;i<=n;i++){
     	   	ll cha=ma-h[i];
     	   	if(cha%2==1)cnt1++;//可以进行+1的操作
     	   	totle+=cha;//处理出总共需要的数字和
		}
		cnt2=(totle-cnt1)/2;//可以进行+2的操作
		if(totle/3<=cnt2){//此时每两天一个循环中所有操作都能进行(即加3)
		  ans=totle/3*2;//循环次数*2(即为天数)
		  if(totle%3==1)ans++;//剩余1个,那直接加1天即可
		  else if(totle%3==2&&cnt2>totle/3)ans+=2;//剩余两个,就得看cnt2了
		  else if(totle%3==2&&cnt2==totle/3)ans+=3;
	    }
	    else {
	    	ans=totle/3*2;
	    	ll less=totle-cnt2*2-totle/3;//剩余的数字和
	    	ans+=2*less-1;//只能每两天加1,最后减掉最后一个循环的偶数天
		}
        //最大值为ma+1的操作
	    ll ans1=0;
     	cnt1=0;cnt2=0;totle=0;
     	for(int i=1;i<=n;i++){
     	   	ll cha=ma-h[i]+1;
     	   	if(cha%2==1)cnt1++;
     	   	totle+=cha;
		}
		cnt2=(totle-cnt1)/2;
		if(totle/3<=cnt2){
		  ans1=totle/3*2;
		  if(totle%3==1)ans1++;
		  else if(totle%3==2&&cnt2>totle/3)ans1+=2;
		  else if(totle%3==2&&cnt2==totle/3)ans1+=3;
	    }
	    else {
	    	ans1=totle/3*2;
	    	ll less=totle-cnt2*2-totle/3;
	    	ans1+=2*less-1;
		}
		cout<<min(ans1,ans)<<endl;
	 }
} 

星期一补D题今晚先学专业课吧呜呜呜

来啦补题

D. Progressions Covering

题意:给定一个全为0的a序列和一个b序列,每次可以对a序列选择一串小于等于k的字串按顺序加上1,2,3,4,5......k,问最少进行多少次操作能使得a数组的每一个都大于等于b数组的每一个。

知乎上看的题解有有线段树的和没线段树的,想到线段树可能比较好理解就是代码难搞点,还是搞线段树吧,好久没用过线段树都快忘了,顺便熟悉一下线段树板子吧。

思路:如果我们从前往后枚举,则第一个数加1,第二数加2一直累加,我们发现对于遍历到的这个数并不是最优解,但是从后往前的话,第i个数加k,第i-1个数加k-1一直累积,这样子的贡献在保证能把这个数加到最大的同时还能对前面的数实现最大的贡献,于是这是最优解。我们又发现这是一个1,2,3的序列,恰好是一个差分序列为1的数组,我们只需要用线段树访问该差分数组前缀和就可以找到他当前的值,再进行模拟即可。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define int long long
const ll N=3e5+10;
ll b[N];
struct node{
	ll l,r;
	ll sum,add;
}tr[N<<2];
void pushup(int u){
	tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum; 
}
void bt(int u,int l,int r){
	if(l==r)tr[u]={l,l,0,0};
	else {
		tr[u]={l,r,0,0};
		int mid=l+r>>1;
		bt(u<<1,l,mid);
		bt(u<<1|1,mid+1,r);
		pushup(u);
	}
}
void pushdown(int u){
	 tr[u<<1].sum+=tr[u].add*(tr[u<<1].r-tr[u<<1].l+1);
	 tr[u<<1|1].sum+=tr[u].add*(tr[u<<1|1].r-tr[u<<1|1].l+1);
	 tr[u<<1].add+=tr[u].add;
     tr[u<<1|1].add+=tr[u].add;
     tr[u].add=0;
}
void update(int u,int l,int r,int d){
	if(l<=tr[u].l&&tr[u].r<=r){
		tr[u].sum+=d*(tr[u].r-tr[u].l+1);
		tr[u].add+=d;
	}else {
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid)update(u<<1,l,r,d);
		if(r>mid)update(u<<1|1,l,r,d);
		pushup(u);
	}
}
ll query(int u,int l,int r){
	if(l<=tr[u].l&&tr[u].r<=r){
		return tr[u].sum;
	}
	else {
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		ll res=0;
		if(l<=mid)res+=query(u<<1,l,r);
		if(r>mid)res+=query(u<<1|1,l,r);
		return res;
	}
}
signed main(){
	int n,k;
	ll ans=0;
	cin>>n>>k;
	bt(1,1,n);
	for(int i=1;i<=n;i++)cin>>b[i];
	for(int i=n;i>=1;i--){
		ll now=query(1,1,i);
		if(now>=b[i])continue;
		int last=min(k,i);//注意这里要判断他的区间是否会小于1。
		ll time=(b[i]-now+last-1)/last;
		time=max(time,0ll);
		ans+=time;
		update(1,i-last+1,i,time);
	}
	cout<<ans;
}

加油加油,e题这两天看看能不能补吧,上一场div2还没写题解呜呜呜。最晚周四补掉e题,好像挺难的,还是周五吧呜呜呜。也是线段树呜呜呜。

来啦明天没课但是我今晚补掉了,并查集加线段树的做法我是真的不会呜呜呜,好难。

于是上知乎学了一个,不过也学到了一个询问区间线段个数的前缀和技巧,不亏不亏。

来补啦,可以看这个Educational Codeforces Round 126 (Rated for Div. 2) A~E - 知乎

我写写我的总结呜呜呜。

E-      

Narrow Components


 

题意:给定一个3*n的01矩阵,进行q个询问,问3*(l,r)的这个区间内有多少个1的连通块

思路:上面发的知乎的题解非常棒。在此我为了更容易理解加入一些解释。

首先,我们可以比较清晰的一点是,对于我们要查询的区间,会影响答案的只能是在两端,中间的连通块是可以直接由前缀o(1)求出的,

其次,对于两端,我们要考虑有什么情况会影响到答案,我们暂且只考虑左边,对于左边

l-1为0 0 0,肯定不会影响答案,

l-1为1 0 0,呢?我们稍微思考可以发现 无论l是什么答案都不会被这个影响;

l-1为0 1 0,呢?同样不会

        0 0 1  不会

        1 1 0 不会

        1 0 1 不会 

        0 1 1 不会

        1 1 1 会

 我们发现只有111才可能使得答案被影响,那他是如何影响的呢

   l      r

1 1 1 1 1

1 0 0 0 0

1 1 1 1 1 我们发现只有这样的情况才可能在截取区间时将原本的一块的答案变成两块

对于右边也是一样。

那介于两者之间呢?

   l      r

1 1 1 1 1

1 0 0 0 1

1 1 1 1 1

我们发现这种情况就更加特殊了,只有介于两者中间才会使一块的答案变成两块.

所有我们最终要处理的东西就只有这三种特殊的情况,其他的都可以用一般的线段求前缀来得到。

代码:

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define pb push_back
const int N=5e5+10;
int n,dx[]={-1,0,0,1},dy[]={0,-1,1,0},pre1[3][N],pre2[3][N];
vector<pii> v[3];
bool vis[3][N];
string s[3];
int pos[N];
pii bfs(int x,int y,int col){
	int l=y,r=y;
	vis[x][y]=true;
	queue<pii>q;
	q.push({x,y});
	while(q.size()){
		pii tmp=q.front();q.pop();
		int xx=tmp.first,yy=tmp.second;
		for(int i=0;i<4;i++){
			int nx=xx+dx[i],ny=yy+dy[i];
			if(nx>=0&&nx<3&&ny>=0&&ny<n&&!vis[nx][ny]&&s[nx][ny]=='1'){
				q.push({nx,ny});
				vis[nx][ny]=col;
				l=min(l,ny);
				r=max(r,ny);
			}
		}
	}
	pii line={l,r};
	return line;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=0;i<=n;i++)pos[i]=1e9; 
	for(int i=0;i<3;i++)cin>>s[i];
	//先用bfs处理出所有存在的线段。 
	for(int i=0;i<3;i++){
		for(int j=0;j<n;j++){
			if(!vis[i][j]&&s[i][j]=='1')v[0].pb(bfs(i,j,i*n+j+1));
		} 
	}
	//for(int i=0;i<v[0].size();i++)cout<<v[0][i].first<<" "<<v[0][i].second<<endl;
	//接着找到第二种情况,即为101的每一列的模拟
	int cnt=0;//记录连续的101的个数 
	for(int i=0;i<=n;i++){
		if(i!=n&&s[0][i]=='1'&&s[1][i]=='0'&&s[2][i]=='1'&&vis[0][i]==vis[2][i])cnt++;
		else if(cnt>0){
			int l=i-cnt;
			int r=i-1;
			int is_l= l>0&&s[0][l-1]=='1'&&s[1][l-1]=='1'&&s[2][l-1]=='1';
		    int is_r= r<n-1&&s[0][r+1]=='1'&&s[1][r+1]=='1'&&s[2][r+1]=='1';
			if(is_l && is_r){
		       for(int i=r;i>=l;i--)pos[i]=l;	 
		    }//pos[r]=l代表右端点是r是左端点至少得是l才会有加一的贡献;
			//如 1 1 1 1 1
		    //   1 0 0 0 1
			//   1 1 1 1 1
	        //   l       r 
	        // L必须大于l才会有加一贡献 (即将整个1串分成两部分)
			if(is_l && !is_r)v[1].pb({l,r});
			//   1 1 1 1 1 ?
			//   1 0 0 0 0 ?
			//   1 1 1 1 1 ?
			//   l       r
			//   这种情况可以同样可以用线段前缀的方式来得到答案 
			if(!is_l && is_r)v[2].pb({l,r}); //同上不过是在r右边
			cnt=0; 
		} 
	}
	//处理前缀
	for(int i=0;i<3;i++){
		for(int j=0;j<v[i].size();j++){
			int l=v[i][j].first,r=v[i][j].second;
			pre1[i][l]++;//在l处进入的线段 
			pre2[i][r+1]++;//在r处离开的线段 
		}
		for(int j=1;j<n;j++)pre1[i][j]+=pre1[i][j-1],pre2[i][j]+=pre2[i][j-1];
	}
	int q;cin>>q;
	while(q--){
		int x,y;
		cin>>x>>y;
		x--;y--;
		int ans;
		ans = pre1[0][y] - pre2[0][x] + pre1[1][x] - pre2[1][x] +pre1[2][y] - pre2[2][y] ; 
		if(x>=pos[y])ans++;
		cout<<ans<<endl;
	}
} 

f就算了,明天起来补上一场的

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"educational codeforces round 103 (rated for div. 2)"是一个Codeforces平台上的教育性比赛,专为2级选手设计评级。以下是有关该比赛的回答。 "educational codeforces round 103 (rated for div. 2)"是一场Codeforces平台上的教育性比赛。Codeforces是一个为程序员提供竞赛和评级的在线平台。这场比赛是专为2级选手设计的,这意味着它适合那些在算法和数据结构方面已经积累了一定经验的选手参与。 与其他Codeforces比赛一样,这场比赛将由多个问题组成,选手需要根据给定的问题描述和测试用例,编写程序来解决这些问题。比赛的时限通常有两到三个小时,选手需要在规定的时间内提交他们的解答。他们的程序将在Codeforces的在线评测系统上运行,并根据程序的正确性和效率进行评分。 该比赛被称为"educational",意味着比赛的目的是教育性的,而不是针对专业的竞争性。这种教育性比赛为选手提供了一个学习和提高他们编程技能的机会。即使选手没有在比赛中获得很高的排名,他们也可以从其他选手的解决方案中学习,并通过参与讨论获得更多的知识。 参加"educational codeforces round 103 (rated for div. 2)"对于2级选手来说是很有意义的。他们可以通过解决难度适中的问题来测试和巩固他们的算法和编程技巧。另外,这种比赛对于提高解决问题能力,锻炼思维和提高团队合作能力也是非常有帮助的。 总的来说,"educational codeforces round 103 (rated for div. 2)"是一场为2级选手设计的教育性比赛,旨在提高他们的编程技能和算法能力。参与这样的比赛可以为选手提供学习和进步的机会,同时也促进了编程社区的交流与合作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值