原题链接: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,就是将两个数交换,这样才能满足绝对值小于等于二的要求)
然后就是所有解的解法(意会一下吧,可能图画的不是很准)
也就是说,我们就是求两个数交换的可能性加上原上升序列,就是答案。
如果要求到的交换序列符合题意,就必须要满足以下两个条件:
- 相邻两个数才能交换
- 每交换两个数必须至少间隔一个数才能交换
这样就很简单了,列个表格:
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;
}
}