結果
| 問題 | No.3450 Permutation of Even Scores |
| コンテスト | |
| ユーザー |
37zigen
|
| 提出日時 | 2026-02-21 05:53:39 |
| 言語 | Java (openjdk 25.0.2) |
| 結果 |
TLE
|
| 実行時間 | - |
| コード長 | 21,045 bytes |
| 記録 | |
| コンパイル時間 | 3,842 ms |
| コンパイル使用メモリ | 104,392 KB |
| 実行使用メモリ | 86,756 KB |
| 最終ジャッジ日時 | 2026-02-21 05:54:50 |
| 合計ジャッジ時間 | 70,282 ms |
|
ジャッジサーバーID (参考情報) |
judge1 / judge2 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| sample | AC * 4 |
| other | AC * 45 TLE * 1 |
ソースコード
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Array;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.DoubleUnaryOperator;
import java.util.function.IntBinaryOperator;
import java.util.function.IntFunction;
import java.util.function.IntToDoubleFunction;
import java.util.function.IntToLongFunction;
import java.util.function.IntUnaryOperator;
import java.util.function.LongBinaryOperator;
import java.util.function.LongToDoubleFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
import java.util.random.RandomGenerator;
import java.util.stream.IntStream;
import java.util.stream.Stream;
class FastScanner {
private static FastScanner instance = null;
private final InputStream in = System.in;
private final byte[] buffer = new byte[1024];
private int ptr = 0;
private int buflen = 0;
private FastScanner() {
}
public static FastScanner getInstance() {
if (instance == null) {
instance = new FastScanner();
}
return instance;
}
private boolean hasNextByte() {
if (ptr < buflen) {
return true;
}
ptr = 0;
try {
buflen = in.read(buffer);
} catch (IOException e) {
e.printStackTrace();
}
return buflen > 0;
}
private int readByte() {
if (hasNextByte()) {
return buffer[ptr++];
} else {
return -1;
}
}
private boolean isPrintableChar(int c) {
return (33 <= c) && (c <= 126);
}
public boolean hasNext() {
while (hasNextByte() && (!isPrintableChar(buffer[ptr]))) {
ptr++;
}
return hasNextByte();
}
public long nextLong() {
if (!hasNext()) {
throw new NoSuchElementException();
}
long n = 0;
boolean minus = false;
int b = readByte();
if (b == '-') {
minus = true;
b = readByte();
}
while ((b >= '0') && (b <= '9')) {
// n = n * 10 + (b - '0');
n = ((n << 1) + (n << 3)) + (b - '0');
b = readByte();
}
return minus ? -n : n;
}
public int nextInt() {
return ((int) (nextLong()));
}
public int[] nextInts(int n) {
int[] a = new int[n];
for (int i = 0; i < n; ++i) {
a[i] = nextInt();
}
return a;
}
}
class MergeFiles {}
class PolynomialFp {
public static final long mod = 998244353;// 119×2^{23}+1
static long[][] bitreversedRoots = new long[30][];
static long[][] bitreversedInvRoots = new long[30][];
static long ADD(long a, long b) {
long sum = a + b;
return sum >= mod ? sum - mod : sum;
}
static long SUB(long a, long b) {
return ADD(a, mod - b);
}
static void prepareRoots(int n) {
int sz = Integer.numberOfTrailingZeros(n);
if (bitreversedRoots[sz] != null) {
return;
}
long g = 3;
long root = MathUtils.modPow(g, (mod - 1) / n, mod);
long iroot = MathUtils.modInv(root, mod);
bitreversedRoots[sz] = new long[n];
bitreversedInvRoots[sz] = new long[n];
for (int n_ = n / 2; n_ >= 1; n_ /= 2 , root = (root * root) % mod , iroot = (iroot * iroot) % mod) {
long w = 1;
long iw = 1;
for (int j = 0; j < n_; ++j) {
bitreversedRoots[sz][n_ + j] = w;
bitreversedInvRoots[sz][n_ + j] = iw;
w = (w * root) % mod;
iw = (iw * iroot) % mod;
}
int cur = 0;
for (int j = 0; j < n_; ++j) {
if (cur < j) {
ArrayUtils.swap(n_ + cur, n_ + j, bitreversedRoots[sz]);
ArrayUtils.swap(n_ + cur, n_ + j, bitreversedInvRoots[sz]);
}
for (int k = n_ / 2; k > (cur ^= k); k /= 2);
}
}
}
/**
* fftをbitreversedした順で返す。
* Scott, Michael. "A note on the implementation of the number theoretic transform." IMA International Conference on Cryptography and Coding. Cham: Springer International Publishing, 2017.
*
* @param a
*/
public static void fftTobitReversed(long[] a) {
int n = a.length;
int sz = Integer.numberOfTrailingZeros(a.length);
if (bitreversedRoots[sz] == null) {
prepareRoots(a.length);
}
for (int m = 1, t = n / 2; m <= (n / 2); m *= 2 , t /= 2) {
for (int i = 0, k = 0; i < m; ++i , k += 2 * t) {
long S = bitreversedRoots[sz][m + i];
for (int j = k; j < (k + t); ++j) {
long u = a[j];
long v = (a[j + t] * S) % mod;
a[j] = ADD(u, v);
a[j + t] = SUB(u, v);
}
}
}
}
/**
* Scott, Michael. "A note on the implementation of the number theoretic transform." IMA International Conference on Cryptography and Coding. Cham: Springer International Publishing, 2017.
*
* @param a
*/
public static void ifftFromBitreversed(long[] a) {
long invN = MathUtils.modInv(a.length, mod);
int n = a.length;
int sz = Integer.numberOfTrailingZeros(n);
if (bitreversedInvRoots[sz] == null) {
prepareRoots(a.length);
}
for (int m = n / 2, t = 1; m >= 1; m /= 2 , t *= 2) {
for (int i = 0, k = 0; i < m; ++i , k += 2 * t) {
long S = bitreversedInvRoots[sz][m + i];
if (m == 1) {
S = (S * invN) % mod;
}
for (int j = k; j < (k + t); ++j) {
long u = a[j];
long v = a[j + t];
if (m == 1) {
a[j] = ((u + v) * invN) % mod;
} else {
a[j] = ADD(u, v);
}
a[j + t] = (((u + mod) - v) * S) % mod;
}
}
}
}
static long[] mulFFT(long[] a, long[] b) {
int n = 1;
int len = (a.length + b.length) - 1;
while (n < ((a.length + b.length) - 1)) {
n *= 2;
}
a = Arrays.copyOf(a, n);
b = Arrays.copyOf(b, n);
prepareRoots(n);
fftTobitReversed(a);
fftTobitReversed(b);
for (int i = 0; i < a.length; ++i) {
a[i] = (a[i] * b[i]) % mod;
}
ifftFromBitreversed(a);
return resize(a, len);
}
public static long[] mulNaive(long[] a, long[] b) {
long[] ret = new long[(a.length + b.length) - 1];
for (int i = 0; i < a.length; ++i) {
for (int j = 0; j < b.length; ++j) {
ret[i + j] += a[i] * b[j];
ret[i + j] %= mod;
}
}
return ret;
}
/**
* [-mod+1, mod-1]の範囲外の要素があると、ADD/SUBでバグる。
*
* @param a
* @param b
* @return */
public static long[] mul(long[] a, long[] b) {
for (int i = 0; i < a.length; i++) {
if (a[i] < 0) {
a[i] += mod;
}
}
for (int i = 0; i < b.length; i++) {
if (b[i] < 0) {
b[i] += mod;
}
}
if ((((a.length + b.length) - 1) <= 512) || (Math.min(a.length, b.length) <= 10)) {
return mulNaive(a, b);
} else {
return mulFFT(a, b);
}
}
static long[] resize(long[] a, int len) {
return Arrays.copyOf(a, len);
}
}
class OnlineConvolution {
long[] f;
long[] g;
long[] h;
int n = 0;
static final long mod = 998244353;
public OnlineConvolution(int n) {
f = new long[n];
g = new long[n];
h = new long[n];
}
public long append(long a, long b) {
/* appendされた列を
[0][1 2][3 4 5 6][7 8 9 a b c d e]
というように分解して、短い方の区間の長さに合わせて、区間同士の積を逐次計算する。
例えば [1 2], [3 4 5 6] の積は [3 4] [5 6] に分割して計算する。
[2^i-1 .. 2^(i+1)-2]という区間は
位置2(2^i-1)の値がappendされた直後には2乗を計算しないといけないが、
区間の末尾が2(2^i-1)なので良い。
長さ2^i同士の区間の積は N/2^i回起きるので全体で O(N log(N)^2)
*/
f[n] = a;
g[n] = b;
++n;
int v = n - (Integer.highestOneBit(n) - 1);
for (int len = Integer.lowestOneBit(v); len >= 1; len /= 2) {
// n-1で終わる区間の長さlenを列挙
for (int swap = 0; swap < 2; ++swap) {
if (((n - len) != (len - 1)) || (swap == 0)) {
long[] x = Arrays.copyOfRange(f, n - len, n);
long[] y = Arrays.copyOfRange(g, len - 1, (2 * len) - 1);
long[] z = PolynomialFp.mul(x, y);
for (int i = 0; (i < z.length) && (((i + n) - 1) < h.length); i++) {
h[(i + n) - 1] += z[i];
h[(i + n) - 1] %= mod;
}
}
{
var tmp = f;
f = g;
g = tmp;
}
}
}
return h[n - 1];
}
}
class ArrayUtils {
public static void swap(int i, int j, long[] A) {
if (i == j) {
return;
}
long tmp = A[i];
A[i] = A[j];
A[j] = tmp;
}
public static void swap(long[] A, long[] B) {
if (A.length != B.length) {
throw new AssertionError();
}
for (int i = 0; i < A.length; i++) {
long tmp = A[i];
A[i] = B[i];
B[i] = tmp;
}
}
}
class MyPrintWriter extends PrintWriter {
private static MyPrintWriter instance = null;
private MyPrintWriter() {
super(System.out);
}
public static MyPrintWriter getInstance() {
if (instance == null) {
instance = new MyPrintWriter();
}
return instance;
}
public void println(boolean[][] a) {
for (int i = 0; i < a.length; i++) {
println(a[i], " ");
}
}
public void println(boolean[] a, String separator) {
for (int i = 0; i < a.length; ++i) {
super.print((a[i] ? 1 : 0) + (i == (a.length - 1) ? "\n" : separator));
}
}
}
class Fp {
final long mod;
long[] fac = new long[0];
long[] ifac = new long[0];
long[] inv = new long[0];
public Fp(long mod) {
this.mod = mod;
}
public void expand(int n) {
fac = new long[n];
ifac = new long[n];
inv = new long[n];
Arrays.fill(fac, 1);
Arrays.fill(ifac, 1);
Arrays.fill(inv, 1);
for (int i = 2; i < n; ++i) {
fac[i] = (i * fac[i - 1]) % mod;
inv[i] = mod - (((mod / i) * inv[((int) (mod % i))]) % mod);
ifac[i] = (inv[i] * ifac[i - 1]) % mod;
}
}
public long fac(int n) {
if (fac.length <= n) {
expand(Math.max(2 * fac.length, n + 1));
}
return fac[n];
}
public long inv(long n) {
if (n < 0) {
n = reduce(n);
}
return n < inv.length ? inv[((int) (n))] : MathUtils.modInv(n, mod);
}
/**
* *
* 剰余を取り、0以上mod未満の値を返す。
*
* @param a
* @return */
public long reduce(long a) {
a %= mod;
if (a < 0) {
a += mod;
}
return a;
}
}
class MathUtils {
public static long modPow(long a, long n, long mod) {
if (n < 0) {
long inv = MathUtils.modInv(a, mod);
return MathUtils.modPow(inv, -n, mod);
}
if (n == 0) {
return 1;
}
return (MathUtils.modPow((a * a) % mod, n / 2, mod) * ((n % 2) == 1 ? a : 1)) % mod;
}
/**
* 拡張ユークリッドの互除法で逆元を求める。
*
* @param a
* @param mod
* @return */
public static long modInv(long a, long mod) {
a = ((a % mod) + mod) % mod;
long[] f0 = new long[]{ 1, 0, mod };
long[] f1 = new long[]{ 0, 1, a };
while (f1[2] != 0) {
long q = f0[2] / f1[2];
for (int i = 0; i < 3; i++) {
f0[i] -= q * f1[i];
}
ArrayUtils.swap(f0, f1);
}
return f0[1] < 0 ? mod + f0[1] : f0[1];
}
}
public class Main implements Runnable {
public static void main(String[] args) throws IOException {
Thread.setDefaultUncaughtExceptionHandler((t, e) -> System.exit(1));
// Runtime runtime = Runtime.getRuntime();
// new Thread(null, new Main(), "MainThreadWithLargeStack", (1024 * 1024) * 1024).start();
// new Main().test();
// new Main().gen();
new Main().run();
// long usedMemory = runtime.totalMemory() - runtime.freeMemory();
// System.err.printf("使用メモリ: %.2f MB%n", usedMemory / 1024.0 / 1024.0);
MyPrintWriter.getInstance().flush();
}
@Override
public void run() {
Random rnd = new Random();
FastScanner sc = FastScanner.getInstance();
MyPrintWriter pw = MyPrintWriter.getInstance();
long mod = 998244353;
Fp fp = new Fp(mod);
int N = sc.nextInt();
int M = sc.nextInt();
int[] A = sc.nextInts(M);
Arrays.sort(A);
long[] dp2 = new long[N + 1];
dp2[0] = 1;
boolean[] contains = new boolean[N + 1];
for (int a : A) {
contains[a] = true;
}
// for (int i = 0; i < A.length; i++) {
// for (int j = 0; j < A[i]; j++) {
// dp2[A[i]]+=dp2[j]*fp.fac(A[i]-Math.max(j-1, 0))%mod*2%mod*(mod-1)%mod;
// dp2[A[i]]%=mod;
// }
// }
long[] dp = new long[N + 1];
dp[0] = 1;
OnlineConvolution cnv = new OnlineConvolution(N + 10);
cnv.append(0, fp.reduce((-2) * 2));
for (int i = 1; i < contains.length; i++) {
long v = cnv.append(i == 1 ? 0 : dp[i - 1], fp.reduce((-2) * fp.fac(i + 2)));
if (!contains[i]) {
continue;
}
// j = 0 の
// dp[i] += dp[0] * fp.fac(i)(-2)
// を別処理
v += (fp.fac(i) % mod) * (-2);
v = fp.reduce(v);
dp[i] = v;
}
long all = fp.fac(N);
long alt = 0;
for (int i = 0; i < dp.length; i++) {
alt += (dp[i] * fp.fac(N - Math.max(i - 1, 0))) % mod;
alt %= mod;
}
long even = (((alt + all) % mod) * fp.inv(2)) % mod;
pw.println(even);
}
}
// --- Original Code ---
// package template;
//
// import java.io.IOException;
// import java.util.Arrays;
// import java.util.Random;
//
// import lib.tools.FastScanner;
// import lib.tools.MergeFiles;
// import lib.tools.MyPrintWriter;
// import lib.util.Fp;
// import lib.util.polynomial.PolynomialFp;
//
// public class Main implements Runnable {
//
// public static void main(String[] args) throws IOException {
// // Runtime runtime = Runtime.getRuntime();
// // new Thread(null, new Main(), "MainThreadWithLargeStack", (1024 * 1024) * 1024).start();
// // new Main().test();
// // new Main().gen();
// new Main().run();
// // long usedMemory = runtime.totalMemory() - runtime.freeMemory();
// // System.err.printf("使用メモリ: %.2f MB%n", usedMemory / 1024.0 / 1024.0);
// MyPrintWriter.getInstance().flush();
// MergeFiles.export();
// }
//
// @Override
// public void run() {
// Random rnd = new Random();
// FastScanner sc = FastScanner.getInstance();
// MyPrintWriter pw = MyPrintWriter.getInstance();
//
// long mod = 998244353;
// Fp fp = new Fp(mod);
// int N = sc.nextInt();
// int M = sc.nextInt();
// int[] A = sc.nextInts(M);
// Arrays.sort(A);
// long[] dp2 = new long[N + 1];
// dp2[0] = 1;
// boolean[] contains = new boolean[N + 1];
// for (int a : A) {
// contains[a] = true;
// }
// // for (int i = 0; i < A.length; i++) {
// // for (int j = 0; j < A[i]; j++) {
// // dp2[A[i]]+=dp2[j]*fp.fac(A[i]-Math.max(j-1, 0))%mod*2%mod*(mod-1)%mod;
// // dp2[A[i]]%=mod;
// // }
// // }
// long[] dp = new long[N + 1];
// dp[0] = 1;
// OnlineConvolution cnv = new OnlineConvolution(N + 10);
// cnv.append(0, fp.reduce((-2) * 2));
// for (int i = 1; i < contains.length; i++) {
// long v = cnv.append(i == 1 ? 0 : dp[i - 1], fp.reduce((-2) * fp.fac(i + 2)));
// if (!contains[i]) {
// continue;
// }
// // j = 0 の
// // dp[i] += dp[0] * fp.fac(i)(-2)
// // を別処理
// v += (fp.fac(i) % mod) * (-2);
// v = fp.reduce(v);
// dp[i] = v;
// }
// long all = fp.fac(N);
// long alt = 0;
// for (int i = 0; i < dp.length; i++) {
// alt += (dp[i] * fp.fac(N - Math.max(i - 1, 0))) % mod;
// alt %= mod;
// }
// long even = (((alt + all) % mod) * fp.inv(2)) % mod;
// pw.println(even);
//
//
//
//
// }
//
//
//
//
//
//
// void gen() {
// Random rnd = new Random();
// MyPrintWriter pw = MyPrintWriter.getInstance();
// }
//
// void test() {
// Random rnd = new Random();
// for (int TEST = 0; TEST < 10000; TEST++) {
// }
// }
//
// void abc() {
// Random rnd = new Random();
// int a = rnd.nextInt(212, 445);
// System.out.println(a);
// }
//
// void tr(Object... objects) {
// System.out.println(Arrays.deepToString(objects));
// }
//
//
//
//
// }
//
// class OnlineConvolution {
// long[] f, g, h;
// int n = 0;
// static final long mod=998244353;
//
// public OnlineConvolution(int n) {
// f = new long[n];
// g = new long[n];
// h = new long[n];
// }
//
// public long append(long a, long b) {
// /* appendされた列を
// * [0][1 2][3 4 5 6][7 8 9 a b c d e]
// * というように分解して、短い方の区間の長さに合わせて、区間同士の積を逐次計算する。
// * 例えば [1 2], [3 4 5 6] の積は [3 4] [5 6] に分割して計算する。
// * [2^i-1 .. 2^(i+1)-2]という区間は
// * 位置2(2^i-1)の値がappendされた直後には2乗を計算しないといけないが、
// * 区間の末尾が2(2^i-1)なので良い。
// * 長さ2^i同士の区間の積は N/2^i回起きるので全体で O(N log(N)^2)
// */
// f[n] = a;
// g[n] = b;
// ++n;
//
// int v = n - (Integer.highestOneBit(n) - 1);
// for (int len = Integer.lowestOneBit(v); len >= 1; len /= 2) {
// //n-1で終わる区間の長さlenを列挙
// for (int swap = 0; swap < 2; ++swap) {
// if (n - len != len - 1 || swap == 0) {
// long[] x = Arrays.copyOfRange(f, n - len, n);
// long[] y = Arrays.copyOfRange(g, len - 1, 2 * len - 1);
// long[] z = PolynomialFp.mul(x, y);
// for (int i = 0; i < z.length && i + n - 1 < h.length; i++) {
// h[i + n - 1] += z[i];
// h[i + n - 1] %= mod;
// }
// }
// {
// var tmp = f;
// f = g;
// g = tmp;
// }
// }
//
// }
// return h[n - 1];
//
// }
//
// void tr(Object...objects) {System.out.println(Arrays.deepToString(objects));}
// }
//
37zigen