Codeforces Round #751 (Div. 2)

本文介绍了六种算法问题的解决方案,涉及暴力遍历、找规律、图论模型、分治策略和贪心思想。题目涵盖字符串操作、数组变换、数组归零条件、跳跃游戏、逆序对优化和登山者路径规划,展示了不同算法在解决实际问题中的应用。

A.暴力

题意:

给一个字符串sss,要求从该字符串中抽取出一个子字符串序列aaa,剩下的为bbb

要求aaa的字典序最小

我们暴力遍历一遍,找到最小的字符,让他作为aaa即可

#include<bits/stdc++.h>
using namespace std;

int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		string s;
		cin>>s;
		int id = 0;
		for (int i=0;i<s.size();++i)
			if (s[i]<s[id])id=i;
		cout<<s[id]<<" ";
		for (int i=0;i<s.size();++i)if (i!=id)cout<<s[i];
		cout<<endl;
	}
}

\newpage

B.思维+找规律

题意:

给一个数组aaa,不断进行变换。

cnt[num]cnt[num]cnt[num],为numnumnum在数组aaa中出现的次数。

每一轮变换a[i]a[i]a[i]变为cnt[a[i]]cnt[a[i]]cnt[a[i]]

每一轮变换结束后,cntcntcnt更新

给出qqq次询问,格式为

xxx kkk

要求输出在第kkk轮变换结束的时候a[x]a[x]a[x]的值

找规律,实际上在草稿纸上完了几轮后,我们便发现在过了一定的轮数后所有的值都不再改变了

因此,我们可以暴力进行2n2n2n轮,离线查询

所有轮数高于2n2n2n轮的询问,我们都按照第2n2n2n轮的结果输出

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2100;
const int maxm = 101000;
int p[maxn],a[maxn];
array<int,4> ques[maxm];
int n,q;

int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		cin>>n;
		for (int i=1;i<=n;++i)cin>>p[i];
		cin>>q;
		for (int i=1;i<=q;++i)
		{
			cin>>ques[i][0]>>ques[i][1];
			ques[i][2]=i;
		}
		sort(ques+1,ques+1+q,[](array<int,4>ar1,array<int,4> ar2)
		{
			return ar1[1]<ar2[1];
		});
		int pl = 1;
		while (pl<=q&&ques[pl][1]==0)
		{
			ques[pl][3]=p[ques[pl][0]];
			++pl;
		}
		for (int _=1;_<=2*n;++_)
		{
			for (int i=1;i<=n;++i)
			{
				a[i]=0;
			}
			for (int i=1;i<=n;++i)a[p[i]]++;
			for (int i=1;i<=n;++i)p[i]=a[p[i]];
			while (pl<=q&&ques[pl][1]==_)
			{
				ques[pl][3] = p[ques[pl][0]];
				++pl;
			}
		}
		while (pl<=q)
		{
			ques[pl][3] = p[ques[pl][0]];
			++pl;
		}
		sort(ques+1,ques+1+q,[](array<int,4>ar1, array<int,4>ar2)
		{
			return ar1[2]<ar2[2];
		});
		for (int i=1;i<=q;++i)cout<<ques[i][3]<<"\n";
	}
}

\newpage

C.思维

题意:

给一个长为nnn的数组aaa,要求选择一个数kkk

接下来可以进行有限次操作:

每次操作从aaa中选择kkk个数,减去他们的与操作和

问:是否能在有限次操作中将所有的数归零

要求求出所有能做到的kkk

结论一:kkk一定是nnn的因数

考虑到与操作的特殊性,我们对这些数归零时,一定是选择了kkk个相等的数,然后将他们一起变成零

因此,一定是kkkkkk个变成零

所以,kkk一定是nnn的因数

扩展结论一,我们按照每一位来看。

kkk一定是第iii位中为111的个数的因数

基本想法和结论一一样

因此,我们统计每一位的111的个数,求他们的gcdgcdgcd

然后输出该gcdgcdgcd所有的因数即可

特别的,如果所有的数都为000,那么答案应该为111nnn

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+100;
int a[33];
int n;
int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		cin>>n;
		memset(a,0,sizeof(a));
		for (int i=1,num;i<=n;++i)
		{
			cin>>num;
			for (int j=0;j<=30;++j)
			{
				a[j]+=(num&1);
				num>>=1;
			}
		}
		int gd = 0;
		for (int i=0;i<=30;++i)gd = __gcd(gd,a[i]);
		if (gd==0)
		{
			for (int i=1;i<=n;++i)cout<<i<<" ";cout<<endl;
			continue;
		}
		for (int i=1;i<=gd;++i)
		{
			if (gd%i==0)cout<<i<<" ";
		}cout<<endl;
	}
}

\newpage

D.图论

给一个两个长度为nnn的数组a,ba,ba,b,刚开始你再索引nnn,你的目标是越过索引111,跳出数组。

每次你可以跳000a[i]a[i]a[i]米,换而言之,身处索引iii你可以跳到[i−a[i],a[i]][i-a[i],a[i]][ia[i],a[i]]

但是,到从iii跳到jjj时,你会后退b[j]b[j]b[j]步,即最终落脚在j−b[j]j-b[j]jb[j]

要求你求出最少需要多少步才能跳出

很容易看出,这似乎是一个最短路模型

对于向下划b[j]b[j]b[j]步这个条件,我们直接将i→ji\rightarrow jij改成i→j−b[j]i\rightarrow j-b[j]ijb[j]即可

但是条件一中我们要从iiii,i−a[i]i,i-a[i]i,ia[i]a[i]a[i]a[i]条边,这实在是难以承受的开销

注意到,所有的边都是连续的,我们可以利用线段树建图

算是一种积累的建图技巧了

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e6+100;
const int inf = 1e9;
vector<array<int,3>> G[maxn];
int a[maxn],b[maxn];
int fa[maxn],pa[maxn];
int n;
int d[maxn];
int dij(int s,int t)
{
	priority_queue<array<int,2>> que;
	for (int i=1;i<=t;++i)d[i] = inf;
	que.push({0,s});d[s]=0;
	while (!que.empty())
	{
		array<int,2> p = que.top();
		que.pop();
		int u = p[1];
		if (-p[0]>d[u])continue;
		for (array<int,3> ar:G[u])
		{
			int v = ar[0];
			int cost = max(0,ar[2]);
			if (d[v]>d[u]+cost)
			{
				fa[v] = u;
				d[v] = d[u]+cost;
				pa[v] = ar[1];
				que.push({-d[v],v});
			}
		}
	}if (d[t]==inf)return -1;
	return d[t];
}
int tot;
struct stree
{
	array<int,2> rot[maxn];
	void build(int rt,int l,int r)
	{
		if (l==r)
		{
			rot[rt] = {l-b[l],l};
			pa[rt] = l;
			return;
		}
		rot[rt] = {++tot,-1};
		int mid = l+r>>1;
		build(rt<<1,l,mid);
		build(rt<<1|1,mid+1,r);
		G[rot[rt][0]].push_back({rot[rt<<1][0], rot[rt<<1][1], 0});
		G[rot[rt][0]].push_back({rot[rt<<1|1][0], rot[rt<<1|1][1], 0});
	}
	void query(int rt,int l,int r,int ql,int qr,int s)
	{
		if (l>=ql&&r<=qr)
		{
			G[s].push_back({rot[rt][0],rot[rt][1],1});
			return;
		}int  mid = l+r>>1;
		if (mid>=ql)query(rt<<1,l,mid,ql,qr,s);
		if (mid+1<=qr)query(rt<<1|1,mid+1,r,ql,qr,s);
	}
}S1;
int main()
{
	cin>>n;tot=n;
	for (int i=1;i<=n;++i)cin>>a[i];
	for (int i=1;i<=n;++i)cin>>b[i];
	reverse(a+1,a+1+n);reverse(b+1,b+1+n);
	S1.build(1,1,n);
	int t = ++tot;
	for (int i=1;i<=n;++i)
	{
		if (i+a[i]>n)G[i].push_back({t,-1,1});
		else S1.query(1,1,n,i,i+a[i],i);
	}
	int ans = dij(1,t);
	cout<<ans<<endl;
	if (ans==-1)return 0;
	int cur = t;
	vector<int> res;
	while (cur != 1)
	{
//		cout<<cur<<" ";
		if (cur<=n&&cur>=1&&pa[cur]!=-1)res.push_back(pa[cur]);
		if (pa[cur]!=-1)res.push_back(pa[cur]);
		cur = fa[cur];
	}reverse(res.begin(),res.end());
	for (int i=0;i<res.size();i+=2)cout<<n+1-res[i]<<" ";cout<<"0"<<endl;
}

\newpage

E.分治

给你一个长度为nnn的数组aaa和一个长度为mmm的数组bbb

你要保证aaa中的元素相对位置不变,把bbb中元素插入到aaa中,最后得到一个长度为n+mn+mn+m的数组ccc

最小化这个得到的数组的逆序对数。

很明显我们应当把bbb数组从小到大插入进去,这样才能保证bbb数组,自身的逆序对贡献为零

因此,先对bbb数组排序

solve(l1,r1,l2,r2)solve(l_1,r_1,l_2,r_2)solve(l1,r1,l2,r2)表示数组b[l1:r1]b[l_1:r_1]b[l1:r1]与数组a[l2:r2]a[l_2:r_2]a[l2:r2]的答案

假设,我们确定将bib_ibi插入aja_jajaj+1a_{j+1}aj+1之间,那么我们实际上可以递归solve(1,i−1,1,j),solve(i+1,n,j+1,m)solve(1,i-1,1,j),solve(i+1,n,j+1,m)solve(1,i1,1,j),solve(i+1,n,j+1,m)

采用分治法,关键是如何确定bib_ibi插入的位置

为了保证分治法的时间复杂度,我们你可以每次取bmidb_{mid}bmid

这里有一点贪心的思想,我们只用考虑bmidb_{mid}bmidl2,r2l_2,r_2l2,r2中构成逆序对最少的那个位置即可

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6+10;
#define int long long
int msort(int a[],int l,int r)
{
    if(l >= r) return 0;
    int mid = l+r>>1;
    int le = msort(a,l,mid),ri = msort(a,mid+1,r);
    int cnt = 0;
    int now = 0;
    int i=l,j=mid+1;
    int copy[r-l+2];
    while(i<=mid && j <= r)
    {
        if(a[i] <= a[j]) copy[++now] = a[i++];
        else{
            cnt += mid-i+1;
            copy[++now]=a[j++];
        }
    }
    while(i <= mid){
        copy[++now] = a[i++];
    }
    while(j <= r){
        copy[++now] = a[j++];
    }
    now = 0;
    for(int k = l; k <= r; k++) a[k] = copy[++now];
    return le+cnt+ri;
}
void getpos(int a[],int b[],int pos[],int l,int r,int L,int R)
{
    if(l>r) return;
    int mid = l+r>>1;
    int loc = L,now = 0,mn=0;
    for(int i = L+1;i <= R; i++){
        if(a[i-1] > b[mid]) now++;
        if(a[i-1] < b[mid]) now--;
        if(now < mn){
            mn = now;
            loc = i;
        }
    }
    pos[mid] = loc;
    getpos(a,b,pos,l,mid-1,L,loc);
    getpos(a,b,pos,mid+1,r,loc,R);
}
void solve()
{
    int n,m;
    cin>>n>>m;
    int a[n+1],b[m+1],pos[m+1];
    for(int i = 1; i <= n; i++) cin>>a[i];
    for(int i = 1; i <= m; i++) cin>>b[i];
 
    sort(b+1,b+1+m);
    getpos(a,b,pos,1,m,1,n+1);
    int c[n+m+1],cnt=0,now=0;
    for(int i = 1; i <= m; i++){
        while(pos[i] - 1 > now) c[++cnt] = a[++now];
        c[++cnt] = b[i];
    }
    while (now < n) c[++cnt] = a[++now];
    cout << msort(c,1,n+m) << "\n";
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;cin>>t;
    while(t--)
    {
        solve();
    }
}

\newpage

F.贪心

n(1≤5×105)n(1\leq 5\times 10^5)n(15×105) 位登山者和一座初始高度为 d(0≤d≤109)d(0\leq d\leq 10^9)d(0d109) 的山,每位登山者有两个属性 si,ais_i,a_isi,ai,其中 sis_isi 代表登山者 iii 只能攀登高度小于等于 sis_isi 的山,当登山者 iii 成功登山后,山的高度会变为 max⁡(d,ai)\max(d,a_i)max(d,ai)

找到一个最佳的登山顺序,保证有最多的人成功登山,输出最多人数。

首先,排除掉所有 si<ds_i<dsi<d 的人。
max⁡(si,ai)\max(s_i,a_i)max(si,ai) 进行升序排序,如果相等按照 sis_isi 排序。排序后,对每个登山者 iii 进行判断即可。
考虑两个登山者 i,ji,ji,j,进行分类讨论:

1、si<ajs_i<a_jsi<aj,如果把 jjj 放在前面,jjj 能成功登山,iii 一定无法登山,应该先让 iii 进行尝试。

2、ai<sja_i<s_jai<sjjjj 的能力比 iii 强,放在后面一定不劣。

3、si<sjs_i<s_jsi<sj,同情况2,一定不劣。

4、ai<aja_i<a_jai<ajjjj 成功登山后 iii 一定无法登山,应该把 iii 放在前面先进行尝试。

​ 时间复杂度 O(nlog⁡n)O(n\log n)O(nlogn)

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
struct node{
    int s,a;
}A[N];
bool cmp(node A,node B){
    if(max(A.s,A.a)==max(B.s,B.a))
        return A.s<B.s;
    return max(A.s,A.a)<max(B.s,B.a);
}
int main(){
    int n,d,ans=0;
    cin>>n>>d;
    for(int i=1;i<=n;i++)
        scanf("%d %d",&A[i].s,&A[i].a);
    sort(A+1,A+1+n,cmp);
    for(int i=1;i<=n;i++)
        if(d<=A[i].s)
            d=max(d,A[i].a),ans++;
    cout<<ans<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值