【2019杭电多校训练赛】1007-permutation 2 题解

原题链接:1007-permutation 2
在这里插入图片描述在这里插入图片描述
其实这题很有技巧性,而且我看了很多题解都没有这道题的详细解答(= = 我好心累啊),想了一下干脆我来写一些自己的看法吧:

这题题意很好理解,就是在就是将1~N的数字按照头是x,尾是y的方法排列,然后要求每两个数相差的绝对值要小于等于2,问排列的个数

这题其实暴力你能看出规律来,就是一个i等于前i-1, i-3相加,我主要讲一下怎么稍微不那么暴力写出来。

我们先建一个坐标系假设x , y
在这里插入图片描述
那么假如x=1,y=n最理想的情况肯定的解法就是从x到y
在这里插入图片描述
所以肯定存在这样的情况(可能不会很好理解,举个例子,如果你本来有一个序列1,2,3,4,5,像这样变换就变成了1,3,2,4,5,就是将两个数交换,这样才能满足绝对值小于等于二的要求)
在这里插入图片描述
然后就是所有解的解法(意会一下吧,可能图画的不是很准)
在这里插入图片描述
也就是说,我们就是求两个数交换的可能性加上原上升序列,就是答案。
如果要求到的交换序列符合题意,就必须要满足以下两个条件:

  1. 相邻两个数才能交换
  2. 每交换两个数必须至少间隔一个数才能交换

这样就很简单了,列个表格:

n 3 4 5 6 7 8
n-2(删去左右固定点) 1 2 3 4 5 6
ans 1 2 3 4 6 9

其实用这个规律你就已经可以推导出来dp[i]=dp[i-1]+dp[i-3]了,甚至你可以直接写个暴力算法找规律
不过我还是讲一下具体答案是怎么构成的

n 3 4 5 6 7 8
n-2(删去左右固定点) 1 2 3 4 5 6
ans 1=1 2=1+1 3=2+1 4=3+1 6=4+2 9=5+2+1

我就仅仅讲解一下n=8的情况9=5+3怎么来的:
有序列1 2 3 4 5 6 7 8 ,1和8是固定的
5就是每单单两个交换比如说1 3-2 4 5 6 7 8,总共5种情况
然后2+1就是同时交换两个的情况
间隔一个的话就是 1 3-2 4 6-5 7 8 还有 1 2 3-4 5 7-6 8 两种情况
间隔两个就是1 3-2 4 5 7-6 8
所以一共8种,以此类推。

解决了x ~ y = 1 ~ N 的情况之后,不在 1 ~ N 范围内的也非常好解,为什么呢?
因为这就相当于将求解的部分分成三段1-x,x-y,y-N,相当于直接平移,因为1-x和y-N是唯一确定的
在这里插入图片描述
为啥是唯一确定的呢,这样子说明吧,因为x之后的数字一定都比x大,所以想参与到数列中只能自己组成数列并且最终回归点要在x±2范围内,所以起点为x,终点为x+1,然后这种数列只能组成一种,比如说7 5 3 1 2 4 6 8递增递减数为2的情况(1为顶点不包含)合起来才能变成一个1 ~ 8的完整数组。

附上学长的暴力求全情况的算法(太厉害了),用了dfs,简单粗暴,不想看分析的朋友可以根据这个推出来:

#include <bits/stdc++.h>
#define ll long long
#pragma comment(linker, “/STACK:1024000000,1024000000”)
#define mem(a, b) memset(a, b, sizeof(a))
#define line printf("----------------------\n")
using namespace std;

const int maxn = 1e5 + 10;
int num[maxn], used[maxn];
int n, x, y;
ll ans;
vector<int> tmp;

void dfs(int x, int cnt) {
	if(cnt == n-1) {
		if(abs(tmp[tmp.size() - 1] - y) <= 2) {
			ans++;
			for(int i = 0; i < tmp.size(); i++) {
				cout << tmp[i] << " ";
			}
			cout << y << endl;
		}
		return;
	}
	for(int i = max(1, x - 2); i <= min(x + 2, n); i++) {
		if(!used[i] && abs(tmp[tmp.size() - 1] - i) <= 2) {
			used[i] = 1;
			tmp.push_back(i);
			dfs(i, cnt + 1);
			used[i] = 0;
			tmp.erase(--tmp.end());
		}
	}
}

int main() {
	int t;
	scanf("%d", &t);
	while(t--) {
		ll ans = 0;
		mem(used, 0);
		tmp.clear();
		scanf("%d %d %d", &n, &x, &y);
		tmp.push_back(x);
		used[x] = used[y] = 1;
		dfs(x, 1);
		line;
	}
}

然后是这题推导后的代码(费时间的主要是推导过程):

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

#define ll long long 
const ll Max = 1e6+5;
const ll Mod = 998244353;

ll a[Max];

int main(){
	a[0]=1;
	a[1]=1;
	a[2]=1;
	a[3]=2;
	for(int i=4;i<Max;i++)
		a[i]=(a[i-1]+a[i-3])%Mod;
	ll t,n,l,r;
	while(cin>>t)
		while(t--){
			cin>>n>>l>>r;
			if(l!=1) l++;
			if(r!=n) r--;
			cout<<a[r-l]<<endl;
		}
}

猜你喜欢

转载自blog.csdn.net/weixin_43164778/article/details/98595887