Codeforces Round 857 (Div. 2) 题解

文章介绍了几道编程竞赛中的题目,包括构造满足特定条件的好矩阵、选择物品以最小化价值差以及音乐节表演最优排列,涉及贪心算法、动态规划和树状数组等数据结构和算法。同时,文章展示了如何通过这些策略解决实际问题。
摘要由CSDN通过智能技术生成

前言:感觉这场的题目质量还是很高的

C. The Very Beautiful Blanket

大意:

定义一个矩阵是好的,如果对于其中每一个4*4的子矩阵都满足如下条件

A11⊕A12⊕A21⊕A22=A33⊕A34⊕A43⊕A44,
A13⊕A14⊕A23⊕A24=A31⊕A32⊕A41⊕A42

现在要求构造一个n*m的好矩阵,并使得其中不同的数字尽可能多

思路:
盲猜可以做到所有数字都不同,然后就往这个方向去想。

会发现想要满足好矩阵是很容易的,难的是做到所有数字都不同。那么其实可以按位考虑,第i杭,如果第k位为1,我们就让第i行的值都加上(1<<k),那么第j列的话,我们就加上(1<<(k+11)),这样就能做到数字不会重复了。

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=1e5+10;
ll n,m;
vector<ll> a,b;
void init()
{
	for(int i=0;i<10;++i) a.push_back((1<<i));
	for(int i=0;i<10;++i) b.push_back((1<<(i+11)));
} 
void solve()
{
	cin>>n>>m;
	cout<<n*m<<endl;
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		{
			ll ans=0;
			for(int k=0;k<10;++k) if(i&(1<<k)) ans|=a[k];
			for(int k=0;k<10;++k) if(j&(1<<k)) ans|=b[k];
			cout<<ans<<' ';
		}
		cout<<endl;
	}
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	init();
	ll t;cin>>t;while(t--)
	solve();
	return 0;
}

D. Buying gifts

大意:
n组物品,每组物品有两个价值,选第一个就放在第一堆里,选第二个就放在第二堆里,问两堆最大值的最小差值

n<=5e5

思路:

赛时没什么思路,感觉像dp,但是死活不会设状态,随便试了一发贪心碰运气(日常做题碰运气),wa了就摆烂

但是其实贪心是可以做的

我们不妨将所有物品按第一个价值升序排序(很常规的操作)

然后我们枚举放在第一堆的最大值,枚举到第i个物品的话,假设当前第一堆的最大值是ai,i+1~n的位置都只能选第二个价值,1~i-1的位置就可以选第一个价值,也可以选第二个价值。如果i!=n的话,我们就把i+1~n的第二个位置的最大值拿出来,以及1~i-1的第二个位置的离ai最近的两个数,比一比就可以了。i=n的话,就只有1~i-1的位置能拿出来比了

所以维护两个set,分别记录大于i和小于i的第二个位置的值就可以了

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=5e5+10;
ll n;
pair<ll,ll> mas[N];
multiset<ll> s1,s2;
void solve()
{
	cin>>n;
	s2.clear();
	s1.clear();
	for(int i=1;i<=n;++i)
	{
		cin>>mas[i].first>>mas[i].second;
		s2.insert(mas[i].second);
	}
	sort(mas+1,mas+1+n);
	ll ans=1e9;
	for(int i=1;i<=n;++i)
	{
		s2.erase(s2.lower_bound(mas[i].second));//一次只删一个 
		if(!s2.empty())
		{
			ll mx=*prev(s2.end());
			if(mx>=mas[i].first)
			{
				ans=min(ans,mx-mas[i].first);
				//continue;	
			}
			else
			{
				ans=min(ans,mas[i].first-mx);
				auto it=s1.lower_bound(mas[i].first);
				if(it!=s1.end()) ans=min(ans,*it-mas[i].first);
				if(it!=s1.begin()) ans=min(ans,mas[i].first-*prev(it));
			}
		}
		else
		{
			auto it=s1.lower_bound(mas[i].first);
			if(it!=s1.end()) ans=min(ans,*it-mas[i].first);
			if(it!=s1.begin()) ans=min(ans,mas[i].first-*prev(it));
		} 
		s1.insert(mas[i].second);
	}
	cout<<ans<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	ll t;cin>>t;while(t--)
	solve();
	return 0;
}

E. Music Festival

大意:

n个数组,定义每一个数字有价值,如果它是当前出现的最大值(不含等号),现在要求重排n个数组,使得总的有价值的数字最多

思路:

首先每一个数组内部我们显然可以处理成单调增的形式,然后我们记录每一个数组的最大值。

如果数组i的最大值小于数组j的最大值,那么i放在j后面的话,i不会产生任何贡献,所以我们可以按照每一个数组的最大值来升序排序

这样就好了吗?

当然没有(拜托这是e题欸。。。

 此时按照道理a放在b的前面,但是这样一来b前面一段就没有任何贡献了。如果b放在前面的话,我们完全可以得到更多贡献(也就是a被放到最后面,被丢弃了)。

那么现在仔细考虑一下。枚举到第i个位置的时候,对于第i个数组的每一个元素j,我们看一下前面i-1个数组丢弃掉一些之后,在最大值不大于j的情况下,最大贡献是多少,然后我们再加上j到该数组结束能产生的贡献,就可以更新到第i个数组位置的最大值了。至于如何丢弃数组,其实直接维护值域的话,自然就能做到这一步。所以我们用树状数组维护一下前缀最大值对应的最大价值就可以了。

code

#include<bits/stdc++.h>
using namespace std;
#define IL inline
#define ll int
#define endl '\n'
#define low(x) x&(-x)
const ll N=2e5+10;
ll n;
struct ty
{
	ll ma;
	vector<ll> vt;
}mas[N];
ll ma;
inline bool cmp(const ty& a,const ty& b)
{
	return a.ma<b.ma;
}
struct tree
{
	ll tr[N];
	inline void init(ll x)
	{
		while(x<=ma)
		{
			tr[x]=0;
			x+=low(x);
		}
	}
	inline void add(ll x,ll y)
	{
		while(x<=ma)
		{
			tr[x]=max(tr[x],y);
			x+=low(x);	
		}
	}
	inline ll maxn(ll x)
	{
		ll ans=0;
		while(x)
		{
			ans=max(ans,tr[x]);
			x-=low(x);
		}
		return ans;
	}
}T;
ll dp[N];
ll num,a;
void solve()
{
	scanf("%d",&n);
	ma=0;
	for(register int i=1;i<=n;++i) dp[i]=0;
	for(register int i=1;i<=n;++i)
	{
		mas[i].vt.clear();
		mas[i].ma=0;
		
		scanf("%d",&num);
		for(int j=1;j<=num;++j)
		{
			scanf("%d",&a);
			if(a<=mas[i].ma) continue;
			ma=max(ma,a);
			mas[i].vt.push_back(a);
			mas[i].ma=a;
		}
	}
	sort(mas+1,mas+1+n,cmp);
	ll ans=0;
	for(register int i=1;i<=n;++i)
	{
		ll len=mas[i].vt.size();
		for(register int j=len-1;j>=0;--j)
		{
			ll gt=T.maxn(mas[i].vt[j]-1);
			dp[i]=max(dp[i],gt+len-j); 
		}
		T.add(mas[i].vt.back(),dp[i]);
		ans=max(ans,dp[i]);
	}
	printf("%d\n",ans);
	for(register int i=1;i<=n;++i) T.init(mas[i].vt.back());
}
int main()
{
//	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	ll t;scanf("%d",&t);while(t--)
	solve();
	return 0;
	 
}

//5
//8
//8 8 8 6 10 4 8 4
//3
//2 1 8
//8
//6 8 9 5 3 4 10 6
//10
//8 3 9 7 1 7 7 4 6 10
//2
//3 1

F. The way home

大意:
n个点,m条边,有一个初始金额p。在第i个点可以不断卖艺来获得金额ai,每经过一条边要消耗对应的边权的金额,现在问能不能从1走到n,并求最少卖艺次数

思路:
还是第一次做到这种题

首先是有一个贪心的思路在里面的。因为每一个点可以卖艺无数次,所以如果我们在后面某个时刻缺钱的话,完全可以在之前某一个卖艺收入最多的点把该卖的艺都卖了。那么如何知道该卖多少艺呢?我们就需要维护从1走到n的过程中的卖艺收入最多的点,中间更新的时候就直接拿该点的权值除一下就好了。我们还需要维护最小卖艺次数(题目所求),在此基础上,想要转移的话,肯定还需要最小卖艺次数下能获得的最多金额。

这是所有需要维护的状态,然后我们直接跑dij就好了。

其实跟普通的维护距离相比,这里只不过是变成了维护两个有不同优先级的值,我们用pair来进行大小比较就可以了

总结一下,dis[i][j]表示从1走到i,中间经过的最佳卖艺点是j时,最小的卖艺次数以及在此基础上的最大收入。我们在路上更新一下最佳卖艺点,缺钱的时候补上卖艺次数,钱也加上,就好了。

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pll pair<ll,ll>
#define mk make_pair
#define endl '\n'
const ll N=1010;
struct ty
{
	ll t,l,next;
}edge[6010];
ll cn=0;
ll head[6010];
void add(ll a,ll b,ll c)
{
	edge[++cn].t=b;
	edge[cn].l=c;
	edge[cn].next=head[a];
	head[a]=cn;
}
ll n,m,p;
ll val[N];//每次演出的收入 
pll dis[N][N];
priority_queue<pair<pll,pll>> q;
void solve()
{
	memset(head,-1,sizeof head);
	cn=0;
	cin>>n>>m>>p;
	for(int i=1;i<=n;++i) cin>>val[i];
	for(int i=1;i<=m;++i)
	{
		ll a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
	//	add(b,a,c);
	}
	for(int i=0;i<=n+10;++i)
	{
		for(int j=0;j<=n+10;++j) dis[i][j]=make_pair(-1e18,-1e18);
	}
	dis[1][0]=mk(0,p);//最少表演0次,此时最大钱数为p
	q.push(mk(dis[1][0],mk(1,0)));
	while(!q.empty())
	{
		auto fr=q.top().first;
		ll pos=q.top().second.first;
		ll tar=q.top().second.second; //中间最大值 
		q.pop();
		if(val[pos]>val[tar]) tar=pos;
		
		for(int i=head[pos];i!=-1;i=edge[i].next)
		{
			ll y=edge[i].t;
			ll va=edge[i].l;
			
			pll new_fr=fr;
			if(new_fr.second<va)
			{
				ll det=(va-new_fr.second)/val[tar]+((va-new_fr.second)%val[tar]!=0);
				
				new_fr.first-=det;//负优化
				new_fr.second+=det*val[tar];//加钱 
			} 
			new_fr.second-=va;
			//cout<<new_fr.second<<" "<<"va "<<va<<endl;
			if(dis[y][tar]<new_fr)
			{
				dis[y][tar]=new_fr;
				q.push(mk(new_fr,mk(y,tar)));
			}
		}
	} 
//	for(int i=1;i<=n;++i)
//    {
//    	for(int j=1;j<=n;++j)
//    	{
//    		cout<<dis[i][j].first<<' ';
//		}
//		cout<<endl;
//	}
	pll ans=mk(-1e18,-1e18);
	for(int i=1;i<=n;++i) ans=max(ans,dis[n][i]);
	if(ans.first<-1e17)
	{
		cout<<-1<<endl;
	}
	else cout<<-ans.first<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    ll t;cin>>t;while(t--)
	solve(); 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值