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个相等的数,然后将他们一起变成零
因此,一定是kkk个kkk个变成零
所以,kkk一定是nnn的因数
扩展结论一,我们按照每一位来看。
kkk一定是第iii位中为111的个数的因数
基本想法和结论一一样
因此,我们统计每一位的111的个数,求他们的gcdgcdgcd
然后输出该gcdgcdgcd所有的因数即可
特别的,如果所有的数都为000,那么答案应该为111到nnn
#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,跳出数组。
每次你可以跳000到a[i]a[i]a[i]米,换而言之,身处索引iii你可以跳到[i−a[i],a[i]][i-a[i],a[i]][i−a[i],a[i]]
但是,到从iii跳到jjj时,你会后退b[j]b[j]b[j]步,即最终落脚在j−b[j]j-b[j]j−b[j]处
要求你求出最少需要多少步才能跳出
很容易看出,这似乎是一个最短路模型
对于向下划b[j]b[j]b[j]步这个条件,我们直接将i→ji\rightarrow ji→j改成i→j−b[j]i\rightarrow j-b[j]i→j−b[j]即可
但是条件一中我们要从iii到i,i−a[i]i,i-a[i]i,i−a[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_jaj与aj+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,i−1,1,j),solve(i+1,n,j+1,m)
采用分治法,关键是如何确定bib_ibi插入的位置
为了保证分治法的时间复杂度,我们你可以每次取bmidb_{mid}bmid
这里有一点贪心的思想,我们只用考虑bmidb_{mid}bmid在l2,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(1≤5×105) 位登山者和一座初始高度为 d(0≤d≤109)d(0\leq d\leq 10^9)d(0≤d≤109) 的山,每位登山者有两个属性 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<sj,jjj 的能力比 iii 强,放在后面一定不劣。
3、si<sjs_i<s_jsi<sj,同情况2,一定不劣。
4、ai<aja_i<a_jai<aj,jjj 成功登山后 iii 一定无法登山,应该把 iii 放在前面先进行尝试。
时间复杂度 O(nlogn)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;
}
本文介绍了六种算法问题的解决方案,涉及暴力遍历、找规律、图论模型、分治策略和贪心思想。题目涵盖字符串操作、数组变换、数组归零条件、跳跃游戏、逆序对优化和登山者路径规划,展示了不同算法在解决实际问题中的应用。
478

被折叠的 条评论
为什么被折叠?



