Codeforces Round #638 (Div. 2) May/01/2020 22:35UTC+8

比赛链接 https://codeforces.com/contest/1348
比赛记录 https://blog.csdn.net/cheng__yu_/article/details/105395197

A. Phoenix and Balance(水)

在这里插入图片描述

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;

int t,n;

int main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		ll ans1=0,ans2=0;
		for(int i=1;i<=n/2-1;++i)
			ans1+=(1<<i);
		ans1+=(1<<n);
		for(int i=n/2;i<=n-1;++i)
			ans2+=(1<<i);
		cout<<ans1-ans2<<"\n";	
	}
	return 0;
}

B. Phoenix and Beauty(周期)

在这里插入图片描述
题意:如果一个数组所有长度为k的子数组的和相同,就称为是美丽数组。
给你一个长度为n的数组,里面的元素由[1,n]组成。你可以向数组任意位置填充[1,n]的数。请你输出填充完后的美丽数组。否则输出 -1
比赛体验:一开始想的是拿出 给定数组里面前k个元素,然后按照这k个元素填充。那么后面的数,一定要出现在前面的k个数中。
wa了一发后,想明白了,如果前k个数是这样的:4,4,4,4。那么绝对是不可能的。
思路:因此应该想到,这k个数字不变的,且向右一步步递推过去。要把整个数组扩展成美丽数组,那么数组里面的数就必须 被包含在 这k个数字中。
所以正确的思路是,子数组长度为k,最多只有k个不同的数字。如果不足k个数字,可以补充成k个数字。然后拿这k个数字作为一个循环来填充

  • 大于k个不同数字,输出 -1
  • 小于k个不同数字,补充为k个数字
  • 等于k个不同数字,直接把这个循环输出n遍也行,不过我自己是根据这k个数字,对给定数组做补充
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;

int t,n,k;
int a[maxn];

int main()
{
    cin>>t;
    while(t--)
    {
        cin>>n>>k;
        set<int> s;
        for(int i=1;i<=n;++i)
            cin>>a[i],s.insert(a[i]);
        if(s.size()>k)
        {
            puts("-1");
            continue;
        }
        else if(s.size()<k)
        {
            for(int i=1;i<=n;++i)
            {
                s.insert(i);
                if(s.size()==k)
                    break;
            }
        }
        vector<int> ans;
        for(auto i : s)
            ans.push_back(i);

        for(int i=2;i<=n;++i)
        {
            int p=(int)ans.size()-k;
            if(a[i]==ans[p])
                ans.push_back(a[i]);
            else
                ans.push_back(ans[p]),--i;
        }
        cout<<(int)ans.size()<<"\n";
        for(auto i : ans)
            cout<<i<<" ";
        cout<<"\n";
    }
	return 0;
}

C. Phoenix and Distribution(分类讨论)

在这里插入图片描述
题意:给定一个长度为n的字符串,分成k个分空的子集,使得字典序最大的子集最小。输出这个最大的子集

思路

  • 判断前k个是不是相等,如果相等,在判断后面的所有是不是相等。相等才能均分,不相等全加到a的后面就好了。
  • 如果不相等,输出s[k],即第k大的字母
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;

int t,n,k;
string s;

int main()
{
	cin>>t;
	while(t--)
	{
		cin>>n>>k>>s;
		sort(s.begin(),s.end());
		s="0"+s;
		string ans="";
		if(s[1]==s[k])
		{
			ans=s[1];
			if(s[k+1]==s[n])
			{
				int cnt=n/k+(n%k?1:0)-1;
				for(int i=1;i<=cnt;++i)
					ans+=s[k+1];	
			}
			else
			{
				for(int i=k+1;i<=n;++i)
					ans+=s[i];
			}
		}
		else
			ans=s[k];
		cout<<ans<<"\n";	
	}
	return 0;
}

D. Phoenix and Science(构造)

在这里插入图片描述
题意:原题意就不分析了。翻译过来的题意是: a 1 + a 2 + a i + ⋯ + a k = n a_1+a_2+a_i+\dots+a_k=n a1+a2+ai++ak=n
其中, a i − 1 < = a i < = 2 a i − 1 a_{i-1}<=a_i<=2a_{i-1} ai1<=ai<=2ai1,使得 k k k 值最小,最后输出 a a a 的差分数组。

思路

  • 最强的构造方法:直接构造 1+2+4+8+16+x=n。最后将这些数排个序,然后输出差分数组
  • 第二种构造方法:按常规思维递增构造,维护一个ans数组,每次加入数组的数是 m i n ( n / 2 , a n s . b a c k ( ) ∗ 2 ) min(n/2,ans.back()*2) min(n/2,ans.back()2),这样贪心可以构造一个递增的数列

1、直接构造 1+2+4+8+16+x=n。最后将这些数排个序,然后输出差分数组

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;

int t,n;

int main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		vector<int> ans;
		for(int i=1;i<=n;i*=2)
		{
			ans.push_back(i);
			n-=i;	
		}
		if(n>0)
		{
			ans.push_back(n);
			sort(ans.begin(),ans.end());
		}
		int n=(int)ans.size()-1;
		cout<<n<<"\n";
		for(int i=1;i<=n;++i)
			cout<<ans[i]-ans[i-1]<<" ";
		cout<<"\n";
	}
	return 0;
}

2、每次加入数组的数是 m i n ( n / 2 , a n s . b a c k ( ) ∗ 2 ) min(n/2,ans.back()∗2) min(n/2,ans.back()2)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;

int t,n;

int main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		vector<int> ans;
		ans.push_back(1);
		n--;
		while(1)
		{
			if(2*ans.back()>=n)
			{
				ans.push_back(n);
				break;
			}
			ans.push_back(min(n/2,ans.back()*2));
			n-=ans.back();
		}
		
		int n=(int)ans.size()-1;
		cout<<n<<"\n";
		for(int i=1;i<=n;++i)
			cout<<ans[i]-ans[i-1]<<" ";
		cout<<"\n";
	}
	return 0;
}
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;

int t,n;

int main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		vector<int> ans;
		ans.push_back(1);
		int cur=1;
		n--;
		while(cur*4<=n)
		{
			cur*=2;
			n-=cur;
			ans.push_back(cur);
		}
		
		if(cur*2>=n)
			ans.push_back(n);
		else
			ans.push_back(n/2),ans.push_back(n-n/2);
		int cnt=ans.size()-1;
		cout<<cnt<<"\n";
		for(int i=1;i<=cnt;++i)
			cout<<ans[i]-ans[i-1]<<" ";
		cout<<"\n";		
	}
	return 0;
}

E. Phoenix and Berries(DP)

在这里插入图片描述
题意:有 n 棵果树,每棵树上有a_i个红莓和 b_i 个蓝莓。有容量为 k 的篮子,不同树同颜色的可以放一起,同一棵树的红莓和蓝莓可以放一起。问最多能装满几个篮子

思路
状态表示: d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i 颗树剩下 j j j 个红莓的方案的集合
属性:凑成最多的篮子数
状态转移:因为是模 k k k 意义下的,所以从 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j] 来转移会更方便。

  • 1、取相同的颜色放篮子里: d p [ i ] [ ( j + a i ) % k ] = d p [ i − 1 ] [ j ] + ( j + a i ) % k + ( s u m [ i − 1 ] − k × d p [ i − 1 ] [ j ] − j + b i ) dp[ i] [(j+a_i)\%k]=dp[i-1][j]+(j+a_i)\%k+(sum[i-1]-k\times dp[i-1][j]-j+b_i) dp[i][(j+ai)%k]=dp[i1][j]+(j+ai)%k+(sum[i1]k×dp[i1][j]j+bi)
  • 2、取同一棵树的不同颜色: d p [ i ] [ ( j + a i + s ) % k ] = d p [ i − 1 ] [ j ] + ( j + a i + s ) % k + ( s u m [ i − 1 ] − k × d p [ i − 1 ] [ j ] − j + b i − k + s ) dp[i][(j+a_i+s)\%k]=dp[i-1][j]+(j+a_i+s)\%k+(sum[i-1]-k\times dp[i-1][j]-j+b_i-k+s) dp[i][(j+ai+s)%k]=dp[i1][j]+(j+ai+s)%k+(sum[i1]k×dp[i1][j]j+bik+s)
    枚举取 s s s 个红莓,至少可以取 m a x ( 1 , k − b [ i ] ) max(1,k-b[i]) max(1,kb[i]) ,至多可以取 m i n ( k − 1 , a [ i ] ) min(k-1,a[i]) min(k1,a[i])
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=500+5,maxm=1e5+5;
const int mod=998244353,inf=0x7f7f7f7f;

int n,k;
ll dp[maxn][maxn];
int a[maxn],b[maxn];
ll sum[maxn];

int main()
{
    memset(dp,-1,sizeof(dp));
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d",&a[i],&b[i]);
        sum[i]=sum[i-1]+a[i]+b[i];
    }

    dp[0][0]=0;
    for(int i=1;i<=n;++i)
    {
        for(int j=0;j<k;++j)
        {
            if(dp[i-1][j]==-1)
                continue;
            int l=max(1,k-b[i]),r=min(k-1,a[i]);
            for(int s=l;s<=r;++s)
            {
                int x=j+a[i]-s;
                int y=sum[i-1]-k*dp[i-1][j]-j+b[i]-k+s;
                dp[i][x%k]=max(dp[i][x%k],dp[i-1][j]+1+x/k+y/k);
            }
            int x=j+a[i];
            int y=sum[i-1]-k*dp[i-1][j]-j+b[i];
            dp[i][x%k]=max(dp[i][x%k],dp[i-1][j]+x/k+y/k);
        }
    }
    ll ans=0;
    for(int j=0;j<k;++j)
        ans=max(ans,dp[n][j]);
    printf("%lld\n",ans);
    return 0;
}

F. Phoenix and Memory(贪心 + 线段树)

在这里插入图片描述

链接https://codeforces.com/contest/1348/problem/F
题意:给定每个人的区间,还原出每个人的原来的位置(排列)。如果答案唯一,输出YES,打印一组排列。否则输出NO,并打印2组排列
思路:先按右区间从小到大排列,贪心还原出一个排列。然后判断是否可以有其他排列。

  • 对于第 i 个人和第 j 个人,如果存在 l j ≤ p i < p j ≤ r i l_j\le p_i<p_j\le r_i ljpi<pjri,那么这两个人是可交换的
  • 只需要在 [ p i + 1 , r i ] [ p_i+1,r_i ] [pi+1ri]范围内找到一个最小的 l j l_j lj,判断 p i p_i pi l j l_j lj 的大小即可
  • 因此需要用线段树维护 l j l_j lj j j j 两个值。按贪心出来的排列更新,维护 l j l_j lj 的最小值
#include <bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define ls (rt<<1)
#define rs ((rt<<1)|1)
using namespace std;
const int maxn=2e5+5,maxm=1e5+5;
const int mod=998244353,inf=0x7f7f7f7f;

int n;

struct People
{
    int l,r,id;
    bool operator<(const People & b) const
    {
        return r<b.r;
    }
}p[maxn];

pair<int,int> ST[maxn<<2];
pair<int,int> pp[maxn];
set<int> s;
int pos[maxn];

void print()
{
    for(int i=1;i<=n;++i)
        cout<<pos[i]<<" ";
    puts("");
}

void Push_up(int rt)
{
    ST[rt]=min(ST[ls],ST[rs]);
}

void Build(int rt,int L,int R)
{
    if(L==R)
    {
        ST[rt]=pp[L];
        return;
    }
    int mid=(L+R)>>1;
    Build(ls,L,mid);
    Build(rs,mid+1,R);
    Push_up(rt);
}

pii Query(int rt,int l,int r,int L,int R)
{
    if(l<=L&&R<=r)
        return ST[rt];
    int mid=(L+R)>>1;
    pii ans1={inf,0},ans2={inf,0};
    if(l<=mid)
        ans1=Query(ls,l,r,L,mid);
    if(r>mid)
        ans2=Query(rs,l,r,mid+1,R);
    return min(ans1,ans2);
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d",&p[i].l,&p[i].r);
        p[i].id=i;
        s.insert(i);
    }

    sort(p+1,p+1+n);
    for(int i=1;i<=n;++i)
    {
        auto it=s.lower_bound(p[i].l);
        pos[p[i].id]=*it;
        pp[*it]={p[i].l,p[i].id};
        s.erase(it);
    }
    Build(1,1,n);
    for(int i=1;i<=n;++i)
    {
        int pi=pos[p[i].id],Ri=p[i].r;
        if(pi+1>Ri)
            continue;
        auto ans=Query(1,pi+1,Ri,1,n);
        if(ans.first<=pi)
        {
            puts("NO");
            print();
            swap(pos[p[i].id],pos[ans.second]);
            print();
            return 0;
        }
    }
    puts("YES");
    print();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值