省赛第一次训练赛题解

文章包含多道编程竞赛题目,主要涉及字符串的字符排序与操作、数组处理中的双指针应用、数列构造以及图论中的拓扑排序问题。通过对字符出现次数的统计和比较,以及数组元素的移动和区间划分,解决了一系列算法问题。
摘要由CSDN通过智能技术生成

省赛第一次训练赛题解

A(CF1451C)

由于没有操作次数的限制,很容易想到对于任意 k 个相同的字符,他们都可以通过交换换到一起之后修改。而对于两个按照字母顺序排序之后相同的字符串,从第一个也可以经过若干交换换到第二个。

所以问题变成了:在两个字符序列中,一次可将 k 个相同的字母变为它的下一个,问是否能把第一个变成第二个。

注意数据,不能直接a,b进行排序。但是可以统计字母出现次数,然后从字母 a 开始到字母 z,每次把第一个串中当前字母的个数,通过变成下一个字母,变得和第二个串的个数一样,如果不可行就输出 No。这样就线性通过此题。

#include<bits/stdc++.h>
using namespace std;
int a[100010];
void slove(){
    string s1,s2;
    int n,k;
    cin>>n>>k;
    cin>>s1>>s2;
    int a[30]={0},b[30]={0};
    for(auto ch:s1){
        a[ch-'a']++;
    }
    for(auto ch:s2){
        b[ch-'a']++;
    }
    for(int i=0;i<26;i++)
        if(a[i]>=b[i]){
            if((a[i]-b[i])%k!=0) {
                cout << "No" << '\n';
                return ;
            }
            else
                a[i+1]+=a[i]-b[i];
        }
    else {
            cout << "No" << '\n';
            return ;
        }
    cout<<"Yes"<<'\n';
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
   int t;
   cin>>t;
   while(t--){
       slove();
   }
    return 0;
}

B(CF1793C)

这题本来是个蛮简单的题,但是审题没审清楚,没注意这是一个排列,一直在处理重复元素,hash,单调栈,map什么的都尝试过

很典的一个双指针,用两个变量,一个记录最大值一个记录最小值,然后双指针从左右两侧向中间靠,再讨论四种情况,不断更新左右最大值最小值

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int a[N];
int n;
int main()
{
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(false);
	int t;
	cin >> t;
	while (t--)
	{
		cin >> n;		
		for (int i = 1; i <= n; i++)
		{
			cin >> a[i];			
		}
		int ma = n;
		int mi = 1;
		int l = 1, r = n;
		int ansl = 0, ansr = 0;
		while (r - l + 1 >= 4)
		{
			bool flag = 0;//判断当前区间是否满足四种情况中的一种
			while (a[r] == ma && r - l + 1 >= 4)
			{//右端点是最大值
				r--;//右端点左移
				ma--;//最大值-1
				flag = 1;
			}
			if (r - l + 1 < 4)
				break;
			while (a[r] == mi && r - l + 1 >= 4)
			{//右端点是最小值
				r--;//右端点左移
				mi++;//最小值+1
				flag = 1;
			}
			if (r - l + 1 < 4)
				break;
			while (a[l] == ma && r - l + 1 >= 4)
			{//左端点是最大值
				l++;//左端点右移
				ma--;//最大值-1
				flag = 1;
			}
			if (r - l + 1 < 4)
				break;
			while (a[l] == mi && r - l + 1 >= 4)
			{//左端点是最小值
				mi++;//最小值+1
				l++;//左端点右移
				flag = 1;
			}
			if (!flag)
			{//四种情况都没有发生
				ansl = l, ansr = r;
				break;
			}
		}
		if (!ansl)
		{
			cout << -1 << endl;
		}
		else
		{
			cout << ansl << " " << ansr << endl;
		}
	}
	return 0;
}
 

C(CF1738B)

  • 先从s的后k项判断一下a的后面k-1项是不是满足递增的

  • 然后数学推导一下会发现a的前n-k+1项,最大值就两种情况,s(n-k+1)/(n-k+1)或者s(n-k+1)/(n-k+1)+1,这里的/都是向下取整

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    ll s[200010];
    void slove(){
        int n,k;
        cin>>n>>k;
        ll pre=0;
        for(int i=1;i<=k;i++) {
            cin >> s[i];
        }
        pre=s[2]-s[1];
        for(int i=3;i<=k;i++){
          if(s[i]-s[i-1]<pre){
              cout<<"No"<<'\n';
              return;
          }
          else
              pre=s[i]-s[i-1];
        }
        if(k==1) {
            cout << "Yes" << '\n';
        }
        else if(s[1]>=0){
            if(s[1]%(n-k+1)){
                if(s[1]/(n-k+1)+1<=s[2]-s[1])
                    cout<<"Yes"<<'\n';
                else
                    cout<<"No"<<'\n';
            }
            else{
                if(s[1]/(n-k+1)<=s[2]-s[1])
                    cout<<"Yes"<<'\n';
                else
                    cout<<"No"<<'\n';
            }
        }
        else{
            if(s[1]/(n-k+1)<=s[2]-s[1])
                cout<<"Yes"<<'\n';
            else
                cout<<"No"<<'\n';
        }
    }
    int main(){
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
        int t;
        cin>>t;
        while(t--){
            slove();
        }
        return 0;
    }
    

D(CF1753A1)

又审题审错了,看成了每个si都要为0,实际上是si的和为0,1200的题都没做出。。。。。

  • 如果数组长度为奇数,那不可能构造出
  • 数组长度为偶数,可以发现,ai和a(i+1)若相同,则将他们组成一个区间,若不同,则将他们分为两个长度为1的区间,这样他们的s相加也为0
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
const int maxn = 200010;

int n, a[maxn];
void solve() {
    cin>>n;
    for (int i = 1; i <= n; ++i) {
       cin>>a[i];
    }
    if (n & 1)  {
        cout<<-1<<'\n';
        return;
    }
    vector<pii> ans;
    for (int i = 1; i <= n; i += 2) {
        if (a[i] == a[i+1]) {
            ans.push_back({i, i + 1});
        } else {
            ans.push_back({i, i});
            ans.push_back({i + 1, i + 1});
        }
    }
    cout<<ans.size()<<'\n';
    for (auto p: ans) {
        cout<<p.first<<" "<<p.second<<'\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin>>t;
    int cas = 1;
    while (t--) {
        solve();
    }
    return 0;
}

E(CF1753A2)

这题是上个题的困难版本,改动的条件是a数组不止由1和-1组成,还有可能有0

和上一个题其实相差也不多,可以考虑这么构造,相邻两个相等依旧选择把他们放到一个区间,不相等就判断是不是有0,如果没有0,那就分为两个区间,有0,再往后面看一位,若这个数和原来的那个非0数相等则将三个数放入一个区间,否则三个数构成三个区间。总而言之,这里面的0是可以忽略的,那么我们把0忽略后,就变成了上面那个简单版问题

同时还看到一个更巧妙的解法:

考虑分段的本质是什么,其实就是给每个元素乘上 11 或 −1−1,不能有两个相邻的元素都乘上 −1−1。

于是考虑,初始时假设每个元素都乘上了 11,现在我们需要将其中一些元素改为乘上 −1−1,修改的两个元素不能相邻。

直接贪心地选取即可,从前往后枚举每个元素,判断若当前元素乘上 −1−1,元素和的绝对值是否会减小,若减小就操作,然后记录一下,下一个元素不能再乘上 −1−1。

贪心的正确性也很好证明,若当前元素乘上 −1−1 后,元素和的绝对值会减小,但是我们不修改当前元素,那么下一个元素最多也只会让元素和的绝对值减小相同的数,所以我们先对当前元素进行修改,一定不会更劣,就做完了。

#include <bits/stdc++.h>
using namespace std;
int wz[200005],n,a[200005],b[200005],x[200005],L[200005],R[200005];
int main(){
	int t;
	cin >> t;
	while(t--){
		int n;
		cin >> n;
		int cnt=0,ok=0,len=0;
		for (register int i=1;i<=n;i++){
			cin >> a[i];
			if (a[i]!=0) b[++cnt]=a[i],wz[cnt]=i;//记录每个非0位置的信息
		}
        if (cnt%2==1){
        	cout << -1 << endl;//无解
        	continue ;
		}
		for (register int i=1;i<=cnt;i+=2){
			if (b[i]!=b[i+1]){//第二种情况
				for (register int j=wz[i-1]+1;j<=wz[i+1];j++){
					L[++len]=R[len]=j;//每段单独分
				}
			}
			else {
				if (wz[i]+1==wz[i+1]) L[++len]=wz[i-1]+1,R[len]=wz[i+1];//相邻就直接分,注意要从上个非0位置+1开始
				else {
					for (register int j=wz[i-1]+1;j<=wz[i];j++) L[++len]=R[len]=j;//i之前全部单独分
					for (register int j=wz[i]+1;j<=wz[i+1]-2;j++) L[++len]=R[len]=j;
					L[++len]=wz[i+1]-1,R[len]=wz[i+1];//给第i+1个非零位置前留下一个0使它在区间里的长度变为偶数
				}
			}
		}
		if (R[len]<n){
			L[len+1]=R[len]+1,R[len+1]=n;
			len++;//可能最后一个非零数后面还有,单独再开一段
		}
		cout << len << '\n';
		for (register int i=1;i<=len;i++) cout << L[i] << ' ' << R[i] << endl;
	}
}
#include<bits/stdc++.h>
# define sum(x,y) (s[y]-s[x-1])
using namespace std;
inline int rd(){
	int f=1,s=0;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c<='9'&&c>='0'){s=(s<<3)+(s<<1)+(c^48);c=getchar();}
	return s*f;
}
const int N = 2e5;
int t,n,s[N+5],f[N+5],g[N+5],a[N+5];
int main(){
	t=rd();
	while(t--){
		n=rd();int sum=0,cnt=n;
		for(int i=1;i<=n;++i)
			a[i]=rd(),sum+=a[i],g[i]=0;
		
		for(int i=2;i<=n;++i)
			if(abs(sum-2*a[i])<abs(sum)&&!g[i-1])
				g[i]=1,--cnt,sum-=2*a[i];
		if(sum)printf("-1\n");
		else{
			printf("%d\n",cnt);
			g[n+1]=0;
			for(int i=1;i<=n;++i){
				if(g[i])continue;
				if(g[i+1])printf("%d %d\n",i,i+1);
				else printf("%d %d\n",i,i); 
			}	
		}
	}
	return 0;
}

F(CF1761C)

一个图论的构造题,先建图把优先级确定,让真子集指向父集合,初始可以把集合1到n初始值都赋为自己的下标i,如集合3初始值3,这样就能避免重复了,接着这就是一个标准的拓扑排序的题了,只不过在拓扑排序的过程中我们要进行集合加法,这个用set可以很容易解决

#include<iostream>
#include<queue>
#include<set>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 110;
int  ind[maxn];
void slove() {
    int n, x;
    cin >> n;
    vector<int>g[maxn];
    vector<set<int>>ans(maxn);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) {
            scanf("%1d", &x);
            if (x)
                ind[j]++, g[i].push_back(j);
        }
    for(int i=1;i<=n;i++)
        ans[i].insert(i);
    queue<int>q;
    for (int i = 1; i <= n; i++)
        if (ind[i]==0)
            q.push(i);
    while (q.size()) {
        int u = q.front();
        q.pop();
        for (int i = 0; i < g[u].size(); i++) {
            if ((--ind[g[u][i]]) == 0) {
                q.push(g[u][i]);
            }
            for(auto it:ans[u])
                ans[g[u][i]].insert(it);
        }
    }
    for (int i = 1; i <= n; i++) {
        cout<<ans[i].size();
        for(auto it:ans[i])
            cout<<" "<<it;
        cout<<endl;
    }
    for (int i = 1; i <= n; i++)
        ind[i] = 0;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--) {
        slove();
    }
    return 0;
}

G(CF1788C)

构造方法:根据等差数列求和公式可以判断构造是否存在,以及第一项的值a,接着第一项选取1和a-1,然后选取3,a-1,也就是第一项加2,第二项-1,这样就可以等差数列的条件了,当第一个数大于等于第二个数时停止上诉操作,然后选取第一项为2,重复第一项+2,第二项-1的操作,可以数学证明,能够取完所有的数

#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
typedef long long ll;
int main()
{
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(false);
	int t;
	cin >> t;
	while (t--)
	{
		ll n;
		cin >> n;
		if (n & 1)
		{
			cout << "YES" << endl;
			ll b = 2 * n - n / 2;//s/n-n/2-1
			ll a = 1;
			for (int i = 1; i <= (n + 1) / 2; i++)
			{//遍历n范围内的所有奇数,构成第1个到中间的数对
				cout << a << " " << b << endl;
				a += 2;
				b--;
			}
			a = 2, b = 2 * n;//2和s/n+1-2
			for (int i = 1; i <= n / 2; i++)
			{//遍历n范围内所有偶数
				cout << a << " " << b << endl;
				a += 2;
				b--;
			}
		}
		else
		{
			cout << "NO" << endl;
		}
	}
	return 0;
}

H(CF1741D)

因为只能交换子树,所以可以从叶子节点往上走,每次都更新子树中的数的左右边界,如果两个子树的区间出现了交叉,那么必然不可以通过交换满足题目要求,反之就判断谁在前谁在后,也就是是否需要交换

#include<iostream>
#include<queue>
#include<set>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=3e5+10;
struct {
    int l=INT_MAX,r=0;
}tree[maxn*2];
int m,ans=0,flag=0;
void dfs(int u){
    if(u>=m||flag)
        return;
    dfs(u*2);
    dfs(u*2+1);
    if(tree[u*2].l<=tree[u*2+1].r&&tree[u*2].r>=tree[u*2+1].l||tree[u*2+1].l<=tree[u*2].r&&tree[u*2+1].r>=tree[u*2].l)
        flag=1;
    else if(tree[u*2].l>=tree[u*2+1].r)
        ans++;
}
void slove(){
    cin >> m;
    for(int i=m;i<2*m;i++)
        cin>>tree[i].l,tree[i].r=tree[i].l;
    for(int i=m;i<2*m;i+=2){
        int a=i/2;
        while(a){
            tree[a].l=min(tree[a*2].l,tree[a*2+1].l),
                    tree[a].r=max(tree[a*2].r,tree[a*2+1].r);
            a/=2;
        }
    }
    dfs(1);
    if(flag)
        cout<<-1<<'\n';
    else
        cout<<ans<<'\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--) {
        slove();
        flag=0;
        ans=0;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值