火车进出栈序列问题
火车进出栈序列问题
一、枚举进出栈序列方案
1到n的数字按照顺序入栈,请你按照字典序从小到大输出前20种可能的出栈方案
\(1<=n<=20\)
题解:\(dfs\)搜索
看到数据范围很容易想到\(dfs\)爆搜枚举
我们首先要维护3个状态:
- 栈内的状态
- 准备入栈的数字是什么
- 已经出栈的序列
对于每一次操作来说,我们有两种选择:
- 如果栈内存在元素,我们可以选择让栈顶元素出栈
- 如果准备入栈的数字不超过n,我们可以选择让该数字入栈
如果已经出栈的序列中序列长度已经为n了,说明已经找到一种出栈方案,我们回溯时不要忘记还原现场
我们如何按字典序从大到小枚举出可能的出栈方案呢?
显然我们可以知道栈内的元素一定小于尚未入栈的元素,所以我们每次贪心的先选择弹出栈顶元素,再选择将为入栈的元素入栈
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;
int n;
int stk[N], tt;
vector<int> ans;
int cnt = 20;
void dfs(int cur) // cur维护的是当前准备入栈的元素
{
if (!cnt)
return;
if (ans.size() == n)
{
cnt--;
for (int i = 0; i < ans.size(); ++i)
cout << ans[i];
cout << endl;
return;
}
if (tt) // 先选择弹出栈顶元素
{
ans.push_back(stk[tt]);
tt--;
dfs(cur);
stk[++tt] = ans.back();
ans.pop_back();
}
if (cur <= n)
{
stk[++tt] = cur;
cur++;
dfs(cur);
cur--;
tt--;
}
}
void solve()
{
cin >> n;
dfs(1);
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
二、进出栈序列方案数
一列火车 \(n\) 节车厢,依次编号为 \(1,2,3,…,n\)
每节车厢有两种运动方式,进栈与出栈,问 \(n\) 节车厢出栈的可能排列方式有多少种。
\(1<=n<=60000\)
题解:数论:卡特兰数 + 不取模求组合数 + 高精度 + 阶乘分解质因数 + 欧拉筛 \(O(n^2)\)
首先我们可以将出栈看成\(0\),入栈看成\(1\),那么很明显进出站序列中一定会有\(n\)个\(0\),\(n\)个\(1\),即长度为\(2n\)的\(01\)序列,
那么我们发现如果某一时刻我们选择将栈顶元素出栈,那么栈内必须存在元素,或者换句话说我们01序列中任意时刻1的前缀和一定大于等于0的前缀和,那么我们就发现方案数一定是卡特兰数
但是题目给定的n很大且我们不能取模,所以我们必须将\(C_a^b\)进行质因子分解后再利用高精度乘法计算答案
但是我们如何对\(C_a^b\)分解质因子呢?
首先\(C_a^b = \frac{a!}{b!(a-b)!}\),我们可以将\(a!,b!,(a-b)!\)分别分解质因子,然后分子的质因子数减去分母相对应的质因子数即可得到\(C_a^b\)的质因子的幂次,那么求出\([1,2n]\)中的质因子我们可以用欧拉筛\(O(n)\)求出,最后我们对其质因子利用高精度乘法相乘即可得到答案
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;
int n;
int p[N], vis[N], idx;
int cnt[N];
unordered_map<int, int> mp;
int qpow(int a, int b)
{
int res = 1;
while (b)
{
if (b & 1)
res = res * a;
b >>= 1;
a = a * a;
}
return res;
}
vector<int> mul(vector<int> &a, int b)
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size() || t; i++)
{
if (i < a.size())
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (c.size() > 1 && c.back() == 0)
c.pop_back();
return c;
}
void get_primes(int n)
{
for (int i = 2; i <= n; ++i)
{
if (!vis[i])
p[++idx] = i;
for (int j = 1; j <= idx && p[j] * i <= n; ++j)
{
vis[p[j] * i] = 1;
if (i % p[j] == 0)
break;
}
}
}
int get(int n, int p)
{
int res = 0;
while (n)
{
res += n / p;
n /= p;
}
return res;
}
void solve()
{
cin >> n;
get_primes(2 * n);
int x = n + 1;
for (int i = 2; i <= x / i; ++i)
{
while (x % i == 0)
{
mp[i]++;
x /= i;
}
}
if (x > 1)
mp[x]++;
for (int i = 1; i <= idx; ++i)
{
cnt[p[i]] = get(2 * n, p[i]) - 2 * get(n, p[i]) - mp[p[i]];
}
vector<int> res;
res.push_back(1);
for (int i = 1; i <= idx; ++i)
{
if (!cnt[p[i]])
continue;
res = mul(res, qpow(p[i], cnt[p[i]]));
}
for (int i = res.size() - 1; i >= 0; i--)
cout << res[i];
cout << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
三、判断合法出栈序列
给定一个最大容量为 M 的堆栈,将 N 个数字按 1, 2, 3, ..., N 的顺序入栈,允许按任何顺序出栈 ,给出\(q\)次询问,每次回答给定的出栈序列是否合法
题解:模拟栈 \(O(n)\)
首先出栈的顺序存在三种情况:
- 一入栈就出栈,例如:1 2 3
- 入栈几次后再出栈,例如:3 2 1
- 前面两种情况的复合情况
对于本题我们只需模拟栈即可,因为一个数字如果要出栈,说明这个数字之前的所有数字都已经入栈过一次了,利用该性质我们利用栈模拟该过程即可;我们发现每个数字只会被入栈和出栈\(1\)次,所以查询一次的复杂度为\(O(n)\)
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;
int m, n, q;
int stk[N], tt;
int a[N];
void solve()
{
cin >> m >> n >> q;
while (q--)
{
tt = 0;
for (int i = 1; i <= n; ++i)
cin >> a[i];
int j = 1;
for (int i = 1; i <= n; ++i)
{
stk[++tt] = i;
if (tt > m)
break;
while (tt && stk[tt] == a[j])
{
tt--;
j++;
}
}
if (tt > m || tt)
cout << "NO" << endl;
else
cout << "YES" << endl;
}
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}