Codeforces Round #740 (Div. 2) CF1561

A.模拟

看一下,数据范围很小,我们按照题意模拟暴力就好了

#include<bits/stdc++.h>
using namespace std;
int a[950];
int n;
inline bool check()
{
	for (int i=1;i<=n;++i)if (a[i]!=i)return false;
	return true;
}
int main()
{
	ios::sync_with_stdio(0);
	int t;cin>>t;
	while (t--)
	{
		cin>>n;
		for (int i=1;i<=n;++i)cin>>a[i];
		int ans=0;
		while (!check())
		{
			++ans;
			if (ans&1)
			{
				for (int i=1;i<=n-2;i+=2)if (a[i]>a[i+1])swap(a[i],a[i+1]);
			}
			else
			{
				for (int i=2;i<=n-1;i+=2)if (a[i]>a[i+1])swap(a[i],a[i+1]);
			}
		}cout<<ans<<"\n";
	}
}

B.耐心推公式

这题不难,关键是要理清逻辑关系

我们先固定出谁先手谁后手,那么就可以知道每个人作为先手发球了几轮

默认,发球的都胜利,那么一共 n n n

先手发球了 ⌈ n 2 ⌉ \lceil \frac{n}{2} \rceil 2n 也赢了 ⌈ n 2 ⌉ \lceil \frac{n}{2} \rceil 2n 题目中所给条件赢了 a a a

后手发球了 ⌊ n 2 ⌋ \lfloor \frac{n}{2} \rfloor 2n 也赢了 ⌊ n 2 ⌋ \lfloor \frac{n}{2} \rfloor 2n 题目中所给条件赢了 b b b

假设先手发球输了 x x x场,后手发球输了 y y y

那么 ⌈ n 2 ⌉ − x + y = a \lceil \frac{n}{2} \rceil-x+y = a 2nx+y=a

⌊ n 2 ⌋ − y + x = b \lfloor \frac{n}{2} \rfloor -y +x = b 2ny+x=b

其中 0 ≤ x ≤ ⌈ n 2 ⌉ 0 \le x \le \lceil \frac{n}{2} \rceil 0x2n 0 ≤ y ≤ ⌊ n 2 ⌋ 0\le y\le \lfloor \frac{n}{2} \rfloor 0y2n

消元法,消去 y y y,然后求出 x x x的取值范围

最后交换一下先后手再统计一次答案即可

#include<bits/stdc++.h>
using namespace std;
set<int> se;

//na-x+y=a,nb-y+x=b;
//x-y=na-a=b-nb
//y=x-na+a
//0<=x<=na,0<=y<=nb
//0<=x<=na,na-a<=x<=nb+na-a
void solve(int a,int b)
{
	int sum = a+b;
	int na = (sum+1)/2;
	int nb = sum-na;
	for (int i=max(0,na-a);i<=min(na,b);++i)se.insert(i+i-na+a);
	
}
int main()
{
	ios::sync_with_stdio(0);
	int t;cin>>t;
	while (t--)
	{
		se.clear();
		int a,b;
		cin>>a>>b;
		solve(a,b);
		solve(b,a);
		cout<<(int)se.size()<<endl;
		for (int res:se)cout<<res<<" ";
		cout<<endl;
	}
}

C.贪心

简单的贪心问题

首先单看每一组,我们可以统计出攻略这个组中所有的怪兽所需要最小力量

m a x ( a i − i ) + 1 max(a_i-i)+1 max(aii)+1 其中 0 ≤ i < l e n 0\le i< len 0i<len记本组长度为 l e n len len

那么我们现在知道了每一组的最小攻略力量和每一组的怪兽数量

要处理的是将那些组放在前面攻略,将那些组放在后面攻略 理所当然被放在后面的组在攻略时更占优势

策略是贪心,我们按照攻略的难易程度,由易到难进行排序,然后统计出答案即可。

为什么贪心是对的呢?

我们不妨设想,假设最终我们是在攻略第 i i i组时导致需要使用的力量最高

那么,如果将他和一个攻略难度更高的进行交换,事情只会变得更糟糕!

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+100;
int n;
pair<int,int> res[maxn];
int main()
{
	ios::sync_with_stdio(0);
	int t;cin>>t;
	while (t--)
	{
		cin>>n;
		for (int i=1;i<=n;++i)
		{
			cin>>res[i].second;
			res[i].first=0;
			for (int j=0;j<res[i].second;++j)
			{
				int num;cin>>num;
				res[i].first = max(res[i].first,num-j);
			}
		}
		sort(res+1,res+1+n);
		int pre=0;
		int ans=0;
		for (int i=1;i<=n;i++)
		{
			res[i].first -= pre;
			ans = max(ans,res[i].first);
			pre += res[i].second;
		}
		cout<<ans+1<<endl;
	}
}

D1.整除分块

总体策略是 d p dp dp

d p [ i ] dp[i] dp[i]表示 n = i n=i n=i时的答案

很明显根据第一个操作, d p [ i ] dp[i] dp[i]可以转移到 d p [ 1 ] , d p [ 2 ] , d p [ 3 ] , d p [ 4 ] … d p [ i − 1 ] dp[1],dp[2],dp[3],dp[4]\ldots dp[i-1] dp[1],dp[2],dp[3],dp[4]dp[i1]

这个我们维护一个前缀和 p r e [ i ] pre[i] pre[i]即可 d p [ i ] + = p r e [ i − 1 ] dp[i]+=pre[i-1] dp[i]+=pre[i1]

关键是第二个条件,我们无法利用前缀和加快状态的转移

但是我们注意到了,他所转移的状态好像不是很多

这便是经典的整除分快了

对于 l ≤ n l\leq n ln 整除结果为 ⌊ n l ⌋ \lfloor \frac{n}{l} \rfloor ln

那么对于 r = ⌊ n ⌊ n l ⌋ ⌋ r = \lfloor \frac{n}{\lfloor \frac{n}{l} \rfloor} \rfloor r=lnn

⌊ n r ⌋ = ⌊ n l ⌋ \lfloor \frac{n}{r} \rfloor = \lfloor \frac{n}{l} \rfloor rn=ln ⌊ n r + 1 ⌋ ≠ ⌊ n l ⌋ \lfloor \frac{n}{r+1} \rfloor \ne \lfloor \frac{n}{l} \rfloor r+1n=ln

依据这个结论我们可以快速地跳跃找到所有的状态

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+100;
ll mod,n;
ll dp[maxn];
ll pre[maxn];

int main()
{
	ios::sync_with_stdio(0);
	cin>>n>>mod;
	dp[1]=pre[1]=1;
	for (int i=2;i<=n;++i)
	{
		dp[i]=pre[i-1];
		int l=2;
		while (l<=i)
		{
			int r = i/(i/l);
			dp[i] = (dp[i]+(r-l+1)*dp[i/l]%mod)%mod;
			l = r+1;
		}
		pre[i]=(pre[i-1]+dp[i])%mod;
//		cout<<i<<":"<<dp[i]<<","<<pre[i]<<endl;
	}
	cout<<dp[n]<<endl;
}

D2.思维

O K OK OK n n n的范围变成了 4 e 6 4e6 4e6很明显, O ( n n ) O(n\sqrt{n}) O(nn )的算法不再起作用

我们需要更快的算法!!

关键还是在于如何提高操作 2 2 2的转移速度!!!

这次我们反过来思考,似乎从 d p [ i ] dp[i] dp[i] d p [ j ] , j < i dp[j],j<i dp[j],j<i 的转移 O ( n n ) O(n\sqrt n) O(nn )已经到极限了

我们考虑,在 d p [ i ] dp[i] dp[i]已经求出的情况下,向 d p [ j ] , j > i dp[j],j>i dp[j],j>i更新 这也是 d p dp dp的一个常见的想法

为此我们需要仔细地思考操作二的意义

对于 j j j, 2 ≤ z ≤ j 2 \le z \le j 2zj ⌊ j z ⌋ = i \lfloor \frac{j}{z} \rfloor = i zj=i

那么对于 [ z i , z i + z − 1 ] [zi,zi+z-1] [zi,zi+z1] 中的所有数 j j j ⌊ j z ⌋ = i \lfloor \frac{j}{z} \rfloor = i zj=i

因此,当我们求出 d p [ i ] dp[i] dp[i]的情况下,我们可以枚举 2 ≤ z ≤ n 2 \le z\le n 2zn

对每一个 [ z i , z i + z − 1 ] [zi,zi+z-1] [zi,zi+z1] 区间加上 d p [ i ] dp[i] dp[i]

从而实现操作二!!!

暂时不考虑区间加的代价,我们发现这是一个调和级数的复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

有了区间加,我们如果使用线段树或者树状数组复杂度上升至 n l o g n 2 nlogn^2 nlogn2

这里我们选择前缀和维护区间加操作即可 总复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

速度还是很可观的

#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e6+100;
int n,mod;
int pre[maxn];//与D1不同,这里维护的是操作二的前缀和
int main()
{
	ios::sync_with_stdio(0);
	cin>>n>>mod;
	int tmp = 0,ans = 0;//tmp 为D1中的pre
	for (int i=1;i<=n;++i)
	{
		pre[i] = (pre[i]+pre[i-1])%mod;
		ans = (pre[i]+tmp)%mod;
		if (i==1)ans=1;
		tmp = (tmp+ans)%mod;
		
		for (int j=2;j<=n;++j)
		{
			int l = i*j;
			int r = (i+1)*j-1;
			r = min(r,n);
			if (l>n)break;
			pre[l]=(pre[l]+ans)%mod;
			pre[r+1]=(pre[r+1]-ans+mod)%mod;
		}
	}
	cout<<ans<<endl;
}

E.构造

一步步思考

一开始看感觉很束手无策

我们不妨先减少一些条件,假如没有长度为奇数的限制的话,我们该如何构造呢?

这种构造有一种比较套路的构造方法:一个一个放位置

即,我们先把 n n n放在 a [ n ] a[n] a[n]上,再把 n − 1 n-1 n1放在 a [ n − 1 ] a[n-1] a[n1] … \ldots

这种构造非常简单,我们先找到 n n n在原数组中的位置 i d n id_n idn

然后 r e v e r s e ( i d n ) reverse(id_n) reverse(idn) 然后 r e v e r s e ( n ) reverse(n) reverse(n)

即,我们先找到 i i i在原数组中的位置 i d i id_i idi

然后 r e v e r s e ( i d i ) , r e v e r s e ( i ) reverse(id_i),reverse(i) reverse(idi),reverse(i)

加上奇数的限制的话该怎么办呢?

奇数限制改变了什么呢?

我们猛然发现对于一个数 a a a她在奇数位置上,那么无论 r e v e r s e reverse reverse多少次,她最终还是还会在奇数位置上

如果 a a a在偶数位置上,最终她还是在偶数位置上

换而言之,奇数位置的数是绝对无法和偶数位置上的数交换的!

也就是说,奇数位置上必须要有 1 , 3 , 5 , 7 … n 1,3,5,7\ldots n 1,3,5,7n

偶数位置上必须要有 2 , 4 , 6 , … n − 1 2,4,6,\dots n-1 2,4,6,n1

然后

我们可以按照简化版的方案将奇数位置上的数排序

我们也可以模仿按照简化版的方案将偶数位置上的数排序

但是现实是骨感的,我们对奇数位置上的数排序后,再对偶数位置上的数排序时

会打乱奇数位置上的有序状态

很糟糕!

因此,我们决定将奇数和偶数绑定在一起排序

即,我们第一次将 n , n − 1 n,n-1 n,n1归位,再将 n − 2 , n − 3 n-2,n-3 n2,n3归位 … \ldots

即,我们将 i , i − 1 ( i   i s   o d d ) i,i-1 (i\ is\ odd) i,i1(i is odd)绑定在一起,使 i d i = i d i − 1 + 1 id_i=id_{i-1}+1 idi=idi1+1

我们 r e v e r s e ( i d i ) , r e v e r s e ( i ) reverse(id_i),reverse(i) reverse(idi),reverse(i)即可

这样的话,关键就是绑定操作了!

如何绑定呢?

我们可以先找到 i d i id_i idi 然后 r e v e r s e ( i d i ) reverse(id_i) reverse(idi)

此时 i d i = 1 id_i=1 idi=1我们再 r e v e r s e ( i d i − 1 − 1 ) reverse(id_{i-1}-1) reverse(idi11)

此时 i d i = i d i − 1 − 1 id_i=id_{i-1}-1 idi=idi11

我们再 r e v e r s e ( i d i − 1 + 1 ) reverse(id_{i-1}+1) reverse(idi1+1)

此时 i d i = 3 , i d i − 1 = 2 id_i=3,id_{i-1}=2 idi=3,idi1=2

r e v e r s e ( 3 ) reverse(3) reverse(3) i d i = 1 , i d i − 1 = 2 id_i=1,id_{i-1}=2 idi=1,idi1=2 成功绑定了

最后再 r e v e r s e ( i ) reverse(i) reverse(i)即可

也就是说,我们想要归位 i − 1 , i i-1,i i1,i i i i为奇数) 需要的操作为

r e v e r s e ( i d i ) reverse(id_i) reverse(idi)

r e v e r s e ( i d i − 1 − 1 ) reverse(id_{i-1}-1) reverse(idi11)

r e v e r s e ( i d i − 1 + 1 ) reverse(id_{i-1}+1) reverse(idi1+1)

r e v e r s e ( 3 ) reverse(3) reverse(3)

r e v e r s e ( i ) reverse(i) reverse(i)

正好 5 5 5步!!!

问题解决!!

#include<bits/stdc++.h>
using namespace std;
int a[2077];
int n;
inline int find_id(int tar)
{
	for (int i=1;i<=n;++i)if (a[i]==tar)return i;
}
vector<int> res;
int main()
{
	ios::sync_with_stdio(0);
	int t;cin>>t;
	while (t--)
	{

		cin>>n;res.clear();
		for (int i=1;i<=n;++i)cin>>a[i];
		if (n==1)
		{
			cout<<"0\n";
			continue;
		}
		bool f = true;
		for (int i=n;i>1;i-=2)
		{
			int id1 = find_id(i);
			int id2 = find_id(i-1);
			if (id1%2==0||id2%2==1)
			{
				f=false;
				cout<<"-1\n";
				break;
			}
			
			res.push_back(id1);
			reverse(a+1,a+1+id1);
			id1 = 1;
			id2 = find_id(i-1);
			
			res.push_back(id2-1);
			reverse(a+1,a+1+id2-1);
			id1 = id2-1;
			
			res.push_back(id2+1);
			reverse(a+1,a+1+id2+1);
			id1=3;id2=2;
			
			res.push_back(3);
			reverse(a+1,a+1+3);
			id1=1;id2=2;
			
			res.push_back(i);
			reverse(a+1,a+1+i);
			
		}if (!f)continue;
		
		cout<<res.size()<<"\n";
		for (int num:res)cout<<num<<" ";
		cout<<"\n";
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值