Codeforces Round #751 (Div. 2)

A.暴力

题意:

给一个字符串 s s s,要求从该字符串中抽取出一个子字符串序列 a a a,剩下的为 b b b

要求 a a a的字典序最小

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

#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.思维+找规律

题意:

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

c n t [ n u m ] cnt[num] cnt[num],为 n u m num num在数组 a a a中出现的次数。

每一轮变换 a [ i ] a[i] a[i]变为 c n t [ a [ i ] ] cnt[a[i]] cnt[a[i]]

每一轮变换结束后, c n t cnt cnt更新

给出 q q q次询问,格式为

x x x k k k

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

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

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

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

#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.思维

题意:

给一个长为 n n n的数组 a a a,要求选择一个数 k k k

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

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

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

要求求出所有能做到的 k k k

结论一: k k k一定是 n n n的因数

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

因此,一定是 k k k k k k个变成零

所以, k k k一定是 n n n的因数

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

k k k一定是第 i i i位中为 1 1 1的个数的因数

基本想法和结论一一样

因此,我们统计每一位的 1 1 1的个数,求他们的 g c d gcd gcd

然后输出该 g c d gcd gcd所有的因数即可

特别的,如果所有的数都为 0 0 0,那么答案应该为 1 1 1 n n n

#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.图论

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

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

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

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

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

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

但是条件一中我们要从 i i i i , 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.分治

给你一个长度为 n n n的数组 a a a和一个长度为 m m m的数组 b b b

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

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

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

因此,先对 b b b数组排序

s o l v e ( l 1 , r 1 , l 2 , r 2 ) solve(l_1,r_1,l_2,r_2) solve(l1,r1,l2,r2)表示数组 b [ l 1 : r 1 ] b[l_1:r_1] b[l1:r1]与数组 a [ l 2 : r 2 ] a[l_2:r_2] a[l2:r2]的答案

假设,我们确定将 b i b_i bi插入 a j a_j aj a j + 1 a_{j+1} aj+1之间,那么我们实际上可以递归 s o l v e ( 1 , i − 1 , 1 , j ) , s o l v e ( 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)

采用分治法,关键是如何确定 b i b_i bi插入的位置

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

这里有一点贪心的思想,我们只用考虑 b m i d b_{mid} bmid l 2 , r 2 l_2,r_2 l2,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 × 1 0 5 ) n(1\leq 5\times 10^5) n(15×105) 位登山者和一座初始高度为 d ( 0 ≤ d ≤ 1 0 9 ) d(0\leq d\leq 10^9) d(0d109) 的山,每位登山者有两个属性 s i , a i s_i,a_i si,ai,其中 s i s_i si 代表登山者 i i i 只能攀登高度小于等于 s i s_i si 的山,当登山者 i i i 成功登山后,山的高度会变为 max ⁡ ( d , a i ) \max(d,a_i) max(d,ai)

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

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

1、 s i < a j s_i<a_j si<aj,如果把 j j j 放在前面, j j j 能成功登山, i i i 一定无法登山,应该先让 i i i 进行尝试。

2、 a i < s j a_i<s_j ai<sj j j j 的能力比 i i i 强,放在后面一定不劣。

3、 s i < s j s_i<s_j si<sj,同情况2,一定不劣。

4、 a i < a j a_i<a_j ai<aj j j j 成功登山后 i i i 一定无法登山,应该把 i i i 放在前面先进行尝试。

​ 时间复杂度 O ( n log ⁡ 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值