loj 2719 「NOI2018」冒泡排序 - 組合數學

題目傳送門

  傳送門

題目大意

  (相信大家都知道)

  顯然要考慮一個排列$p$合法的充要條件。

  考慮這樣一個構造$p$的過程。設排列$p^{-1}_{i}$滿足$p_{p^{-1}_i} = i$。

  • 初始令$q = (1, 2, \cdots, n)$。
  • 依次考慮$i = 1, 2, \cdots, n$。
    • 設$x = p_i$,如果$q^{-1}_x > i$,那麼交換$q_x, q_{x - 1}$。

  上述算法每次交換的時候會使逆序對增加1。

  考慮給出的下界,假設交換的是$i$和$i + 1$。

  不難用歸納法證明$p_i \leqslant i$。

  那麼考慮$ \Delta = (i + 1 - p_i + |p_{i + 1} - i|) - (i - p_i + |p_{i + 1} - i - 1|)$。

  • 如果$p_{i + 1} \geqslant i + 1$,那麼有$ \Delta = (i + 1 - p_i + p_{i + 1} - i) - (i - p_i + p_{i + 1} - i - 1) =2$
  • 如果$p_{i + 1} \leqslant i$,那麼有$\Delta = (i + 1 - p_i + i - p_{i + 1}) - (i - p_i + i + 1 - p_{i + 1}) = 0$

  每次改變量要麼爲0,要麼爲2,如果某一次爲0,那麼將永遠達不到下界。

  因此序列合法當僅當上述算法中,每次交換滿足$q_x \geqslant x$。

  上述算法中,未確定的數並且可以向前移動的是一段後綴,並且滿足$q_x = x$。

  假如某次將$y$向前移動,那麼如果一個$z < y$,並且$z$未確定,那麼你不能將$z$向前移動。

  然後考慮一下沒有字典序限制怎麼做,顯然這個問題不會更難。

  設$f_{i, j}$表示考慮到排列的前$i$個數,其中最大值爲$j$。

  轉移考慮最大值有沒有發生改變。

  $(i, j)$是平面上的一個點,考慮把這個問題轉化到平面上。

  最大值改變等於可以向上走若干步,不變相當於向右走一步。

  另外還需要滿足$i \geqslant j$。

  用折線法可以輕鬆計算出方案數。

  然後我們來考慮原問題。

  字典序嚴格大於似乎有點煩?考慮小於等於。(其實是我今天想的時候把題意記錯了,寫完發現過不了樣例)

  仍然考慮枚舉一個長度爲$i - 1$的前綴,然後計算在$i$脫離限制後的方案數。

  下面只考慮長度爲$i - 1$的前綴是合法的情況。

  • 如果$a_{i}$是一個前綴最大值,那麼考慮$i - 1$的前綴最大值是$mx$,答案加上從$(i, mx), (i, mx + 1), \cdots, (i, a_i - 1)$開始的方案數。
  • 如果$a_i$不是前綴最大值
    • 如果比不是前綴最大值的最小值還大,那麼此時前綴$i$不合法,答案加上從$(i, mx)$開始的方案書。
    • 否則對答案沒有貢獻。

Code

/**
 * loj
 * Problem#2719
 * Accepted
 * Time: 652ms
 * Memory: 10236k
 */
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;
#define ll long long

void exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
	} else {
		exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
}

int inv(int a, int n) {
	int x, y;
	exgcd(a, n, x, y);
	return (x < 0) ? (x + n) : (x);
}

const int Mod = 998244353;

template <const int Mod = :: Mod>
class Z {
	public:
		int v;

		Z() : v(0) {	}
		Z(int x) : v(x){	}
		Z(ll x) : v(x % Mod) {	}

		friend Z operator + (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
		}
		friend Z operator - (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
		}
		friend Z operator * (const Z& a, const Z& b) {
			return Z(a.v * 1ll * b.v);
		}
		friend Z operator ~(const Z& a) {
			return inv(a.v, Mod);
		}
		friend Z operator - (const Z& a) {
			return Z(0) - a;
		}
		Z& operator += (Z b) {
			return *this = *this + b;
		}
		Z& operator -= (Z b) {
			return *this = *this - b;
		}
		Z& operator *= (Z b) {
			return *this = *this * b;
		}
		friend boolean operator == (const Z& a, const Z& b) {
			return a.v == b.v;
		} 
};

Z<> qpow(Z<> a, int p) {
	Z<> rt = Z<>(1), pa = a;
	for ( ; p; p >>= 1, pa = pa * pa) {
		if (p & 1) {
			rt = rt * pa;
		}
	}
	return rt;
}

typedef Z<> Zi;

typedef class Input {
	protected:
		const static int limit = 65536;
		FILE* file; 

		int ss, st;
		char buf[limit];
	public:
		
		Input():file(NULL)	{	};
		Input(FILE* file):file(file) {	}

		void open(FILE *file) {
			this->file = file;
		}

		void open(const char* filename) {
			file = fopen(filename, "r");
		}

		char pick() {
			if (ss == st)
				st = fread(buf, 1, limit, file), ss = 0;//, cerr << "str: " << buf << "ed " << st << endl;
			return buf[ss++];
		}
} Input;

#define digit(_x) ((_x) >= '0' && (_x) <= '9')

Input& operator >> (Input& in, unsigned& u) {
	char x;
	while (~(x = in.pick()) && !digit(x));
	for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
	return in;
}

Input& operator >> (Input& in, unsigned long long& u) {
	char x;
	while (~(x = in.pick()) && !digit(x));
	for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
	return in;
}

Input& operator >> (Input& in, int& u) {
	char x;
	while (~(x = in.pick()) && !digit(x) && x != '-');
	int aflag = ((x == '-') ? (x = in.pick(), -1) : (1));
	for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
	u *= aflag;
	return in;
}

Input& operator >> (Input& in, long long& u) {
	char x;
	while (~(x = in.pick()) && !digit(x) && x != '-');
	int aflag = ((x == '-') ? (x = in.pick(), -1) : (1));
	for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
	u *= aflag;
	return in;
}

Input in (stdin);

const int N = 6e5 + 5;
const int N2 = N << 1;

int T, n;
Zi fac[N2], _fac[N2];

void init_fac(int l, int r) {
	for (int i = l; i <= r; i++) {
		fac[i] = fac[i - 1] * i;
	}
	_fac[r] = ~fac[r];
	for (int i = r; i > l; i--) {
		_fac[i - 1] = _fac[i] * i;
	}
}
void init_fac(int n) {
	static int old = 0;
	fac[0] = 1, _fac[0] = 1;
	if (n > old) {
		init_fac(old + 1, n);
		old = n;
	}
}
Zi comb(int n, int m) {
	return (n < m) ? (0) : (fac[n] * _fac[m] * _fac[n - m]);
}

Zi C(int x, int y) {
	return comb(x + y, x);
}
Zi S(int x, int y) {
	if (y + 1 <= x)
		return 0;
	return (y == n) ? (1) : (C(n - x, n - y) - C(n + 1 - x, n - 1 - y));
}

boolean vis[N];
int main() {
	freopen("inverse.in", "r", stdin);
	freopen("inverse.out", "w", stdout);
	in >> T;
	while (T--) {
		in >> n;
		if (!n) {
			puts("0");
			continue;
		}
		init_fac(n << 1);
		memset(vis, false, n + 2);
		int mx = 0, sc = 1, i = 1, a;
		Zi ans = 0;
		for (i = 1; i < n; i++) {
			in >> a;
			if (a > mx) {
				for (int j = mx; j < a; j++) {
					ans += S(i, j);
				}
				mx = a;
			} else {
				while (vis[sc]) sc++;
				if (sc ^ a) {
					ans += S(i, mx);
					break;
				}
			}
			vis[a] = true;
		}
		if (i == n) {
			in >> a;
			ans += 1;
		} else {
			while (++i <= n)	
				in >> a;
		} 
		ans = S(0, 0) - ans;
		printf("%d\n", ans.v);
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章