Educational Codeforces Round 162 (Rated for Div. 2)(A-D)

91 阅读10分钟

前言

这是一场很适合我的div2div2,可惜昨天由于在宿舍有点吵所以没打QAQQAQ,今天来vpvp了一下,发现貌似如果打的话能上个几十分,感觉不懂啊,每次打的都是抽象场,唯独这次没打的场。然后就是,教育场真的很有教育意义,(AC)(A-C)送分,(D)(D)送命是吧(bushi)(bushi)

传送门

题解

AA:不懂啊,这个AA就很有教育意义,一眼读成了DD的感觉,然后第一发还交到BB上去了,难蚌。AA的话看样例不难发现,这个问题等价于我们可以将连续每段连续的11看成1111,然后求一下每个11之间的00的个数,求个和即可。

代码:

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long 
#define lowbit(x) x&(-x)
#define x first
#define y second
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr); 
#define two(x) __builtin_popcount(x)
using namespace std;
typedef pair<ll,ll> PII;
const int mod=998244353;
//ll qmi(ll a,ll b){ll res=1ll;while(b){if(b&1) res=res*a;b>>=1;a=(a)*(a);}return res;}
ll qmi(ll a,ll b,ll mod){ll res=1ll;while(b){if(b&1) res=(res%mod)*a%mod;b>>=1;a=(a%mod)*(a%mod)%mod;}return res%mod;}
//const int N=2001020;
//ll gcd(ll x,ll y){return y?gcd(y,x%y):x;} //最大公约数 
//ll fact[N],infact[N];
//void c(ll mod){fact[0]=infact[0]=1;for(ll i=1;i<=N-5;i++){fact[i]=(i%mod*fact[i-1]%mod)%mod;infact[i]=(infact[i-1]%mod)*(qmi(i,mod-2,mod)%mod)%mod;}}
//ll C(ll a,ll b,ll mod){return ((fact[a]%mod*infact[b]%mod)%mod*(infact[a-b]%mod)%mod)%mod;}//组合数 

const int N=100010;
int a[N]; 
void solve(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	int id=-1;
	for(int i=1;i<=n;i++){
		if(a[i]==1){
			id=i;
			break;
		}
	}
	if(id==-1){
		cout<<0<<"\n";
		return;
	}
	int cnt0=0,ans=0;
	for(int i=id;i<=n;i++){
		if(a[i]==1){
			ans+=cnt0;
			cnt0=0;
		}
		else{
			cnt0++;
		}
	}
	cout<<ans<<"\n";
}

int main(){
	//IOS
	int t;
	t=1;
	cin>>t;
  // f(N-2);
//     for(int i=1;i<=cnt;i++){
//         cout<<primes[i]<<" ";
//     }
//	c(mod);
	while(t--) solve();
	return 0;
}

BB:BB这个题感觉其难度要超过CCBB其实是一个贪心的过程,我们不难发现,其实在数轴左侧的点上的怪等价于其绝对值上(也就是数轴右边)的怪,然后血量可以叠加,这是解决这个问题的重点(原因是我们的每秒kk发子弹可以自己定义射击谁),而且距离也一样,因此我们只需要依次考虑数轴右侧的怪物(按距离),用一个ansans来记录我们每次杀死前一只怪物剩余的子弹量,以及当前怪物与前一只怪物的距离distance×k+ansdistance×k+ans的子弹量是否的血量即可。时间复杂度O(n)O(n)代码:

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long 
#define lowbit(x) x&(-x)
#define x first
#define y second
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr); 
#define two(x) __builtin_popcount(x)
using namespace std;
typedef pair<ll,ll> PII;
const int mod=998244353;
//ll qmi(ll a,ll b){ll res=1ll;while(b){if(b&1) res=res*a;b>>=1;a=(a)*(a);}return res;}
ll qmi(ll a,ll b,ll mod){ll res=1ll;while(b){if(b&1) res=(res%mod)*a%mod;b>>=1;a=(a%mod)*(a%mod)%mod;}return res%mod;}
//const int N=2001020;
//ll gcd(ll x,ll y){return y?gcd(y,x%y):x;} //最大公约数 
//ll fact[N],infact[N];
//void c(ll mod){fact[0]=infact[0]=1;for(ll i=1;i<=N-5;i++){fact[i]=(i%mod*fact[i-1]%mod)%mod;infact[i]=(infact[i-1]%mod)*(qmi(i,mod-2,mod)%mod)%mod;}}
//ll C(ll a,ll b,ll mod){return ((fact[a]%mod*infact[b]%mod)%mod*(infact[a-b]%mod)%mod)%mod;}//组合数 

const int N=300010;
int a[N],k,n,x[N]; 
map<ll,ll> mp; 
void solve(){
	cin>>n>>k;
	mp.clear();
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>x[i];
		mp[abs(x[i])]+=a[i];
	}
	vector<PII> v;
	for(auto it:mp){
		v.push_back({it.x,it.y});
	}
//	for(int i=0;i<v.size();i++){
		//cout<<v[i].x<<" "<<v[i].y<<"\n";
//	}
	ll distance=0,ans=0;;
	for(int i=0;i<v.size();i++){
		//cout<<ans<<"\n";
		if(ans+k*(v[i].x-distance)>=v[i].y){
			ans=ans+k*(v[i].x-distance)-v[i].y;
			distance=v[i].x;
		}else{
			cout<<"NO\n";
			return;
		}
	}
	cout<<"YES\n";
}

int main(){
	//IOS
	int t;
	t=1;
	cin>>t;
  // f(N-2);
//     for(int i=1;i<=cnt;i++){
//         cout<<primes[i]<<" ";
//     }
//	c(mod);
	while(t--) solve();
	return 0;
}

CC:这个CC是个有点意思但又比较简单的思维题,开始想了很多种乱搞的方法,但是都被我一一passpass掉,最后冷静下来想了下,发现这个题的难点在于所有数都要不一样但是和又要相等,就说明所有的数字都要在自身进行相应的加减操作(且操作之后这个数还要是正整数),于是我先想如果对序列进行排序,那么显然是可以的(当然并不是真排序,因为排序之后数字还都不变,那我们答案只要一一对应原来数字就行了),那么从贪心的角度来考虑,我们想要最大程度的满足条件二(每个数都不相等),我们一定是对每个数在自身进行加减11操作,这样一定是最优,那么对于>1>1的每个数字,我们本身都可以进行加减11的操作,唯独对于11,我们无法进行减11操作,那么我们可以让其他所有数都提供减11操作之和sumsum,统计一下序列内所有11的个数cnt1cnt1,那么就变成了判断是否cnt1sumcnt1≤sum,求cnt1cnt1可以用一个类似于dpdp的数组,求sumsum那自然就是前缀和了,时间复杂度O(q)O(q)

代码:

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long 
#define lowbit(x) x&(-x)
#define x first
#define y second
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr); 
#define two(x) __builtin_popcount(x)
using namespace std;
typedef pair<ll,ll> PII;
const int mod=998244353;
//ll qmi(ll a,ll b){ll res=1ll;while(b){if(b&1) res=res*a;b>>=1;a=(a)*(a);}return res;}
ll qmi(ll a,ll b,ll mod){ll res=1ll;while(b){if(b&1) res=(res%mod)*a%mod;b>>=1;a=(a%mod)*(a%mod)%mod;}return res%mod;}
//const int N=2001020;
//ll gcd(ll x,ll y){return y?gcd(y,x%y):x;} //最大公约数 
//ll fact[N],infact[N];
//void c(ll mod){fact[0]=infact[0]=1;for(ll i=1;i<=N-5;i++){fact[i]=(i%mod*fact[i-1]%mod)%mod;infact[i]=(infact[i-1]%mod)*(qmi(i,mod-2,mod)%mod)%mod;}}
//ll C(ll a,ll b,ll mod){return ((fact[a]%mod*infact[b]%mod)%mod*(infact[a-b]%mod)%mod)%mod;}//组合数 

const int N=300010;
ll a[N],k,n,x[N],dp[N],q,s[N];
map<ll,ll> mp; 
void solve(){
	mp.clear();
	cin>>n>>q;
	for(int i=1;i<=n;i++){
		cin>>a[i],dp[i]=0,s[i]=0;
	}
	dp[0]=0,s[0]=0;
	for(int i=1;i<=n;i++){
		s[i]=s[i-1]+a[i];
		if(a[i]==1) dp[i]=dp[i-1]+1;
		else dp[i]=dp[i-1];
	}
	for(int i=1;i<=q;i++){
		int l,r;
		cin>>l>>r;
		int len=r-l+1;
		if(len==1){
			cout<<"NO\n";
		}else{
			ll cnt1=dp[r]-dp[l-1];
			ll ans=s[r]-s[l-1]-len;
			if(ans>=cnt1){
				cout<<"YES\n";
			}else cout<<"NO\n";
		}
	}
}

int main(){
	//IOS
	int t;
	t=1;
	cin>>t;
  // f(N-2);
//     for(int i=1;i<=cnt;i++){
//         cout<<primes[i]<<" ";
//     }
//	c(mod);
	while(t--) solve();
	return 0;
}

DD:DD的话,开始不是特别会,然后听RegenFallen大佬讲了一半,就会了,然后就润去写了个很抽象的东西,在WAWA了一发之后过了。这个题其实是个怎么回事呢,是这么回事,我们经过模拟不难发现我们要解决以下问题:

  • 什么样的史莱姆不可以被吃掉?
  • 在能被吃掉的情况下,怎么求最小的步骤?

对于问题11我开始的想法也是非常的朴素,一样就可以发现如果最大的那个史莱姆不小于所有的其他的和,那么这个一定不会被吃掉,但是这个情况并不是很全面,如果所有的数都相等,那么我们必然一个都不会被吃掉,再仔细观察和模拟样例,我们可以发现这样一个事情:对于从22~n1n-1的所有史莱姆ii,大小为a[i]a[i],他们如果能被吃掉,必然有两种大情况:

  • 左边有一个史莱姆在左边不停扩充(如果本身大也可以不扩充)至比a[i]a[i]大。
  • 右边有一个史莱姆在右边不停扩充(如果本身大也可以不扩充)至比a[i]a[i]大。

11只有情况22,nn只有情况11然后如果左右都没有这样的史莱姆,那么它必然不可能被吃掉。

但是问题又来了,左右两边的史莱姆怎么扩充呢,他们的扩充过程肯定也是符合以上两种情况的,那么如果这么考虑的话,情况就非常非常多,肯定是不利于解题的。因此我们可以考虑这么单一的情况,就拿左边来举例吧,在左边我们选定一个区间[l,i1][l,i-1],左端点ll可以变化,但是右端点一定是i1i-1(因为要扩充到ii),对于这么一个区间,我们不难发现这个区间只要不是所有数都相同,那么一定存在一种吞并(哇,这个词真好)方式,使得最后合并成一个大小为j=li1a[j]\sum\limits_{j=l}^{i-1}a[j]的史莱姆,而且操作必然是区间长度len=illen=i-l,但是如果区间所有数都想等,那么我们必然没有办法操作,因此核心问题解决了,我们可以用dpdp来判断区间是否所有数都相等(O(n))(O(n)),再对每个史莱姆进行两次二分搜索求解最优的合并步骤(单个O(logn)O(logn)),注意11nn要特判,总时间复杂度O(nlogn)O(nlogn)

代码

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long 
#define lowbit(x) x&(-x)
#define x first
#define y second
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr); 
#define two(x) __builtin_popcount(x)
using namespace std;
typedef pair<ll,ll> PII;
const int mod=998244353;
//ll qmi(ll a,ll b){ll res=1ll;while(b){if(b&1) res=res*a;b>>=1;a=(a)*(a);}return res;}
ll qmi(ll a,ll b,ll mod){ll res=1ll;while(b){if(b&1) res=(res%mod)*a%mod;b>>=1;a=(a%mod)*(a%mod)%mod;}return res%mod;}
//const int N=2001020;
//ll gcd(ll x,ll y){return y?gcd(y,x%y):x;} //最大公约数 
//ll fact[N],infact[N];
//void c(ll mod){fact[0]=infact[0]=1;for(ll i=1;i<=N-5;i++){fact[i]=(i%mod*fact[i-1]%mod)%mod;infact[i]=(infact[i-1]%mod)*(qmi(i,mod-2,mod)%mod)%mod;}}
//ll C(ll a,ll b,ll mod){return ((fact[a]%mod*infact[b]%mod)%mod*(infact[a-b]%mod)%mod)%mod;}//组合数 

const int N=300010;
int n;
ll a[N],ans[N],dp[N],s[N];
bool check1(int x,int id){//id大 x小 
	if(s[id-1]-s[x-1]>a[id] && dp[id-1]!=dp[x]) return true;
	return false;
}
bool check2(int x,int id){ //id大 x小 
	if(s[id]-s[x]>a[x] && dp[id]!=dp[x+1]) return true; 
	return false;
}
void solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],s[i]=0,dp[i]=0,ans[i]=1e18;
	if(n==1){
		cout<<"-1\n";
		return;
	}
	for(int i=1;i<=n;i++){
		if(i==1){
			if(a[i+1]>a[i]) ans[i]=1;
		}else if(i==n){
			if(a[i-1]>a[i]) ans[i]=1;
		}else if(a[i-1]>a[i] || a[i+1]>a[i]){
			ans[i]=1;
		}
	}
	dp[0]=s[0]=0;
	for(int i=1;i<=n;i++){
		if(a[i]!=a[i-1]){
			dp[i]=dp[i-1]+1;
		}else dp[i]=dp[i-1];
		s[i]=s[i-1]+a[i];
	}
	for(ll i=1;i<=n;i++){
		if(ans[i]==1) continue;	
		ll l=1,r=i-1;
		bool ok=false;
		if(i>1){
			
			while(l<r){
				ll mid=l+r+1>>1;
				if(check1(mid,i)) l=mid,ok=true;
				else r=mid-1;
			}
			if(check1(l,i)) ans[i]=min(ans[i],i-l);
		}
		if(i<n){
			l=i+1,r=n;
			while(l<r){
				int mid=l+r>>1;
				if(check2(i,mid)) r=mid,ok=true;
				else l=mid+1;
			}
			if(check2(i,l)) ans[i]=min(ans[i],l-i);
		}	
		if(ans[i]==1e18) ans[i]=-1; 
	}
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<" ";
	}
	cout<<"\n";
}

int main(){
	IOS
	int t;
	t=1;
	cin>>t;
	while(t--) solve();
	return 0;
}

感悟

DD真不错!