Codeforces Round #672 (Div. 2)补题

鸽了好久……也颓了很多。

这场的题目A、B、C1、D是不难的。

A逆序数,B位运算计数、C1贪心orDP、C2线段树or贪心、D排序组合数学+树状数组or直接扫一遍、E斜率优化DP。

这场用Pokémon和Ori做题面,双厨狂喜(嘿我现在就在听Fleeing Kuro (alternate))。

A.Cubes Sorting

思路

冒泡排序最坏情况为严格单调递减,除了这种情况一定比 n × ( n − 1 ) 2 \frac{n\times(n-1)}{2} 2n×(n1)小。

代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
signed main(signed argc, char const *argv[])
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	int t,n,pre,nex;
	cin>>t;
	while(t--)
	{
		cin>>n>>pre;
		bool ok=0;
		for(int i=2;i<=n;i++)
		{
			cin>>nex;
			if(nex>=pre)
				ok=1;
			pre=nex;
		}
		cout<<(ok?"YES":"NO")<<endl;
	}
	return 0;
}

B.Rock and Lever

思路

开个桶计录每个位作为最高位的数量,然后遍历32个桶, C ( x , 2 ) C(x,2) C(x,2)即可。

代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
#define int ll
#define ff first
#define ss second
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
signed main(signed argc, char const *argv[])
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	int t,n,tmp;
	cin>>t;
	while(t--)
	{
		cin>>n;//计算a&b > a^b的个数
		vector<int>mp(33,0);
		for(int i=1;i<=n;i++)
		{
			cin>>tmp;
			for(int k=32;k>=0;k--)
			{
				if(tmp&(1ll<<k))
				{
					mp[k]++;
					break;
				}
			}
		}
		ll ans=0;
		for(int i=0;i<=32;i++)
			ans+=mp[i]*(mp[i]-1)/2;
		cout<<ans<<endl;
	}
	return 0;
}

C1 & C2.Pokémon Army (easy & hard version)

题意

给你 n n n个正整数 a 1 … a i a_1 \dots a_i a1ai,你从中选出一个序列,使得序列中奇数项元素减去偶数项元素的和最大。

C2多出了 q q q次交换,每次给定 l l l r r r,交换 a a a中的 a l a_l al a r a_r ar

输出 q + 1 q+1 q+1行答案。

思路

先不考虑修改,可以发现为了使得答案最大,每次一定选取奇数个。

我们要让每个奇数项尽量大、每个偶数项尽量小。

所以第一个数一定会挑选第一个出现的峰,而之后都是两个两个加入进来,因为偶数项做负贡献,其后必定加入一个大于它的数来增加贡献。这个奇数项一定为一个谷,而偶数项一定为峰。
在这里插入图片描述
可以发现一个规律,即所有的峰和谷都必定会计入答案,其中峰对答案有正贡献,谷对答案有负贡献。
C1就可以A掉了。
因此,在C2中,考虑如何维护好每个峰和谷的贡献。
可以发现,每个数是不是峰或者谷只与它相邻数以及它本身的大小关系有关。因此,修改下标为 l l l的数的时候,受到影响的峰和谷只有 l − 1 , l , l + 1 l-1,l,l+1 l1,l,l+1这三个数。
所以交换操作可以看作首先消除 [ l − 1 , l + 1 ] [l-1,l+1] [l1,l+1] [ r − 1 , r + 1 ] [r-1,r+1] [r1,r+1]区间的峰和谷的贡献,再计入数值更改后该区间峰和谷的贡献.
l l l r r r区间有交集时,讨论会变得非常复杂,可以使用set去重来避免讨论。

C1代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
//#define int ll
#define ff first
#define ss second
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
//#define DEBUG//#define lowbit(x) ((x) & -(x))//<<setiosflags(ios::fixed)<<setprecision(9)
signed main(signed argc, char const *argv[])
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie
	(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	int t,n,q;
	cin>>t;
	while(t--)
	{
		cin>>n>>q;//q次交换l与r
		ll ans=0,pre=0,low=0,tmp;
		for(int i=1;i<=n;i++)
		{
			cin>>tmp;
			if(tmp>pre)
			{
				ans+=tmp-low;
				low=tmp;
			}
			else
				low=min(low,tmp);
			pre=tmp;
		}
		cout<<ans<<endl;
	}
	return 0;
}
C2代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
//#define int ll
#define ff first
#define ss second
const int maxn=3e5+10,inf=0x3f3f3f3f,mod=1000000007;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
int a[maxn];
signed main(signed argc, char const *argv[])
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	int t,n,q,l,r;
	cin>>t;
	while(t--)
	{
		cin>>n>>q;
		for(int i=1;i<=n;i++)
			cin>>a[i];
		a[0]=a[n+1]=-inf;
		ll ans=0;
		for(int i=1;i<=n;i++)
		{
			if(a[i-1]<a[i]&&a[i]>a[i+1])
				ans+=a[i];
			if(a[i-1]>a[i]&&a[i]<a[i+1])
				ans-=a[i];
		}
		cout<<ans<<endl;
		auto del=[&](int pos){
			if(pos<1||pos>n)
				return;
			if(a[pos-1]<a[pos]&&a[pos]>a[pos+1])
				ans-=a[pos];
			if(a[pos-1]>a[pos]&&a[pos]<a[pos+1])
				ans+=a[pos];
		}
		auto add=[&](int pos){
			if(pos<1||pos>n)
				return;
			if(a[pos-1]<a[pos]&&a[pos]>a[pos+1])
				ans+=a[pos];
			if(a[pos-1]>a[pos]&&a[pos]<a[pos+1])
				ans-=a[pos];
		};
		while(q--)
		{
			cin>>l>>r;
			set<int>now{l-1,l,l+1,r-1,r,r+1};
			for(auto &x:now)
				del(x);
			swap(a[l],a[r]);
			for(auto &x:now)
				add(x);
			cout<<ans<<endl;
		}
	}
	return 0;
}

D.Rescue Nibel!

题意

n n n盏灯,每盏灯都有一个点亮时间 [ l i , r i ] [l_i,r_i] [li,ri],你要从中选出 k k k盏,使得这 k k k盏存在一个瞬间全部灯都是点亮的,输出符合条件的组合数。

思路

这题比C2简单,很快就口胡出来了……

代码也很清晰,将所有开灯关灯时间点做好标记按时间点排序。

开一个变量维护此时已经亮着的灯的数目,按时间遍历。

每当一个灯点亮的时候,假如此时数目 ≥ k \ge k k,则计入答案,为了避免重复,新加入的这一个是一定选入的,剩下的 k − 1 k-1 k1盏在之前维护的值里选,即 C ( 之 前 点 亮 的 数 目 , k − 1 ) C(之前点亮的数目,k-1) C(,k1)

代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
//#define int ll
#define ff first
#define ss second
const int maxn=1e6+10,inf=0x3f3f3f3f,mod=998244353;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
ll fac[maxn],a[maxn];
ll quick(ll a,ll b){//快速幂
	ll ans=1;
	while(b){
		if(b&1)ans=ans*a%mod;
		a=a*a%mod;
		b/=2;
	}
	return ans%mod;
}
ll ccc(ll n,ll m){//求组合数
	return (fac[n] * quick(fac[m], mod - 2) % mod * quick(fac[n - m], mod - 2) % mod)%mod;
}
void initccc()
{
	fac[0] = 1;
	for (int i = 1; i <maxn ;i++){
		fac[i] = fac[i - 1] * i % mod;
	}
}
signed main(signed argc, char const *argv[])
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int n,k,l,r;
	initccc();
	vector<pii>vec;
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>l>>r;
		vec.push_back(pii(l,1));
		vec.push_back(pii(r+1,-1));
	}
	sort(vec.begin(),vec.end());
	ll ans=0,now=0;
	for(pii &x:vec)
	{
		now+=x.ss;
		if(x.ss==1)
			ans=(ans+ccc(now-1,k-1))%mod;
	}
	cout<<ans<<endl;
	return 0;
}
/*
 *选k个灯,存在同时亮
 *求种类总数
 */

E.Battle Lemmings

题意

n n n个守卫排成一行,编号为 1 1 1 n n n。这些守卫里有的拿着盾牌,有的没有,一个守卫只能同时拿一个盾牌。

称一对守卫是被保护的,当且仅当这两个守卫都没拿盾牌,但他们之间有守卫拿了盾牌。

每一秒,指挥官可以下达两种命令中的一种,分别是:

  • 选一个带盾牌的守卫,把它的盾牌给他左边的守卫。
  • 选一个带盾牌的守卫,把它的盾牌给他右边的守卫。

请求出时刻 0 0 0 n × ( n − 1 ) 2 \frac{n \times (n - 1)}{2} 2n×(n1)中每个时刻最多有多少对守卫是被保护的。

思路

照着这位大佬的思路补的。

一对不带盾的守卫只有在他们中间存在持盾守卫才有贡献,因此正难则反。总贡献可以通过所有无盾守卫的总对数减无贡献对数

设无盾守卫数目为 c 0 c_0 c0,持盾守卫数目为 c 1 c_1 c1,一共有 k k k段连续的 0 0 0,第 i i i段数目为 l i l_i li,则 a n s = c 0 × ( c 0 − 1 ) 2 − ∑ i = 1 k l i × ( l i − 1 ) 2 ans=\frac{c_0 \times (c_0 -1)}{2}-\sum\limits^{k}_{i=1}{\frac{l_i\times(l_i -1)}{2}} ans=2c0×(c01)i=1k2li×(li1)

对于两个状态间的最小转化步数,从左扫到右,累加两个状态按次序的每个 1 1 1的下标差的绝对值。

d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]的含义为:计算了前 i i i位,第 i i i位为 1 1 1,(对于前 k k k 1 1 1)已经进行了 j j j次操作,前 i i i位有 k k k 1 1 1 [ 1 , i ] [1,i] [1,i]区间的最小不受保护对数。转移的时候枚举下一个 1 1 1的位置来进行更新。

比较令人困惑的地方在于,第一个 1 1 1的操作次数为什么是 d p [ i ] [ a b s ( p 1 − i ) ] [ 1 ] = ( i − 1 ) ∗ ( i − 2 ) / 2 dp[i][abs(p_1-i)][1]=(i-1)*(i-2)/2 dp[i][abs(p1i)][1]=(i1)(i2)/2,可能原本在 i i i的左面有不止一个 1 1 1,移动次数也不止 a b s ( p 1 − i ) abs(p_1-i) abs(p1i)

因为此时这个 d p dp dp状态只考虑了第一个 1 1 1对于操作次数的贡献,之后再进行转移的时候才会逐步累计其后 1 1 1对操作次数的贡献。

普通转移都整不明白,斜率优化以后再说吧。
在这里插入图片描述

代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//#define int ll
const int maxn=85,inf=0x3f3f3f3f,mod=1000000007;
int a[maxn],pos[maxn];
int dp[maxn][maxn*maxn][maxn];
signed main(signed argc, char const *argv[])
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int n,c0=0,c1=0;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		if(a[i])
			pos[++c1]=i;
		else
			c0++;
	}
	memset(dp,inf,sizeof(dp));
	//dp[i][j][k]表示第i位为1,使用j次操作,前i个含有k个1
	//状态下[1,i]的最小未受保护对数
	int lim=n*(n-1)/2;
	for(int i=1;i<=n;i++)
	{//将第一个1移到i处
		dp[i][abs(pos[1]-i)][1]=(i-1)*(i-2)/2;//[1,i-1]全部为0
		for(int j=0;j<=lim;j++)
		{//枚举操作步数j
			for(int k=1;k<=i&&k<=c1;k++)
			{//枚举[1,i]中1的数量k
				if(dp[i][j][k]==inf)
					continue;
				int rest=c1-k;//剩下的1数目
				for(int l=i+1;l<=n-rest+1;l++)//枚举下一个1来进行转移
				{//将第k+1个1移到l处
					int nex=j+abs(pos[k+1]-l);//计算步数
					if(nex<=lim)//
						dp[l][nex][k+1]=min(dp[l][nex][k+1],dp[i][j][k]+(l-i-1)*(l-i-2)/2);
				}
			}
		}
	}
	int res=c0*(c0-1)/2;
	for(int j=0;j<=lim;j++)
	{
		for(int i=c1;i<=n;i++)//第i位放最后一个1,后面补充剩下的0的贡献
			res=min(res,dp[i][j][c1]+(n-i)*(n-i-1)/2);
		cout<<c0*(c0-1)/2-res<<' ';
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值