diff --git a/graph/chromatic_polynomial/checker.cpp b/graph/chromatic_polynomial/checker.cpp new file mode 100644 index 00000000..98391ba5 --- /dev/null +++ b/graph/chromatic_polynomial/checker.cpp @@ -0,0 +1,62 @@ +// https://github.com/MikeMirzayanov/testlib/blob/master/checkers/wcmp.cpp + +// The MIT License (MIT) + +// Copyright (c) 2015 Mike Mirzayanov + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "testlib.h" + +using namespace std; + +int main(int argc, char * argv[]) +{ + setName("compare sequences of tokens"); + registerTestlibCmd(argc, argv); + + int n = 0; + string j, p; + + while (!ans.seekEof() && !ouf.seekEof()) + { + n++; + + ans.readWordTo(j); + ouf.readWordTo(p); + + if (j != p) + quitf(_wa, "%d%s words differ - expected: '%s', found: '%s'", n, englishEnding(n).c_str(), compress(j).c_str(), compress(p).c_str()); + } + + if (ans.seekEof() && ouf.seekEof()) + { + if (n == 1) + quitf(_ok, "\"%s\"", compress(j).c_str()); + else + quitf(_ok, "%d tokens", n); + } + else + { + if (ans.seekEof()) + quitf(_wa, "Participant output contains extra tokens"); + else + quitf(_wa, "Unexpected EOF in the participants output"); + } +} diff --git a/graph/chromatic_polynomial/gen/big.cpp b/graph/chromatic_polynomial/gen/big.cpp new file mode 100644 index 00000000..e2a3a7f5 --- /dev/null +++ b/graph/chromatic_polynomial/gen/big.cpp @@ -0,0 +1,35 @@ +#include +#include +#include "random.h" +#include "../params.h" + +using namespace std; + +int main(int, char* argv[]) { + + long long seed = atoll(argv[1]); + auto gen = Random(seed); + + int n = gen.uniform(MAX_N - 3, MAX_N); + int m = gen.uniform(0, n * (n - 1) / 2); + + using P = pair; + vector

edges; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (gen.uniform_bool()) { + edges.push_back({i, j}); + } else { + edges.push_back({j, i}); + } + } + } + gen.shuffle(edges.begin(), edges.end()); + edges.resize(m); + + printf("%d %d\n", n, m); + for (int i = 0; i < m; i++) { + printf("%d %d\n", edges[i].first, edges[i].second); + } + return 0; +} \ No newline at end of file diff --git a/graph/chromatic_polynomial/gen/clique_cycle.cpp b/graph/chromatic_polynomial/gen/clique_cycle.cpp new file mode 100644 index 00000000..1647f649 --- /dev/null +++ b/graph/chromatic_polynomial/gen/clique_cycle.cpp @@ -0,0 +1,102 @@ +#include +#include +#include "random.h" +#include "../params.h" + +/* + +4 cases +case 0,1 : n = 4*5 +case 2,3 : n = 3*7 ( random 1 removed ) + +*/ + +using namespace std; + +int main(int, char* argv[]) { + + long long seed = atoll(argv[1]); + auto gen = Random(seed); + + int n1 = 4; + int n2 = 5; // size of a cycle : an odd number + if(seed / 2 == 1){ + n1 = 3; + n2 = 7; + } + + int n = n1 * n2; + + vector> edges; + + if(seed % 2 == 0){ + for(int c=0; c perm(n); + for(int i=0; i> edges_filtered; + for(auto e : edges){ + if(e.first < n && e.second < n){ + edges_filtered.push_back(e); + } + } + swap(edges, edges_filtered); + m = (int)edges.size(); + } + + printf("%d %d\n", n, m); + for (int i = 0; i < m; i++) { + printf("%d %d\n", edges[i].first, edges[i].second); + } + return 0; +} \ No newline at end of file diff --git a/graph/chromatic_polynomial/gen/example_00.in b/graph/chromatic_polynomial/gen/example_00.in new file mode 100644 index 00000000..3e8bd730 --- /dev/null +++ b/graph/chromatic_polynomial/gen/example_00.in @@ -0,0 +1,8 @@ +5 7 +0 1 +0 2 +0 4 +1 3 +2 3 +2 4 +3 4 diff --git a/graph/chromatic_polynomial/gen/example_01.in b/graph/chromatic_polynomial/gen/example_01.in new file mode 100644 index 00000000..ade6bbae --- /dev/null +++ b/graph/chromatic_polynomial/gen/example_01.in @@ -0,0 +1 @@ +20 0 diff --git a/graph/chromatic_polynomial/gen/example_02.in b/graph/chromatic_polynomial/gen/example_02.in new file mode 100644 index 00000000..a722fbed --- /dev/null +++ b/graph/chromatic_polynomial/gen/example_02.in @@ -0,0 +1,2 @@ +3 1 +2 2 diff --git a/graph/chromatic_polynomial/gen/example_03.in b/graph/chromatic_polynomial/gen/example_03.in new file mode 100644 index 00000000..d7616c3a --- /dev/null +++ b/graph/chromatic_polynomial/gen/example_03.in @@ -0,0 +1,6 @@ +2 5 +0 1 +1 0 +0 1 +0 1 +1 0 diff --git a/graph/chromatic_polynomial/gen/loop.cpp b/graph/chromatic_polynomial/gen/loop.cpp new file mode 100644 index 00000000..64f6db86 --- /dev/null +++ b/graph/chromatic_polynomial/gen/loop.cpp @@ -0,0 +1,38 @@ +#include +#include +#include "random.h" +#include "../params.h" + +using namespace std; + +int main(int, char* argv[]) { + long long seed = atoll(argv[1]); + auto gen = Random(seed); + + int n = gen.uniform(MIN_N, MAX_N); + int m = gen.uniform(0, n * (n - 1) / 2); + + using P = pair; + vector

edges; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (gen.uniform_bool()) { + edges.push_back({i, j}); + } else { + edges.push_back({j, i}); + } + } + } + gen.shuffle(edges.begin(), edges.end()); + edges.resize(m); + + int k = gen.uniform(0, n - 1); + edges.push_back({k, k}); + ++m; + + printf("%d %d\n", n, m); + for (int i = 0; i < m; i++) { + printf("%d %d\n", edges[i].first, edges[i].second); + } + return 0; +} \ No newline at end of file diff --git a/graph/chromatic_polynomial/gen/multi_edge.cpp b/graph/chromatic_polynomial/gen/multi_edge.cpp new file mode 100644 index 00000000..aa769c9c --- /dev/null +++ b/graph/chromatic_polynomial/gen/multi_edge.cpp @@ -0,0 +1,34 @@ +#include +#include +#include "random.h" +#include "../params.h" + +using namespace std; + +int main(int, char* argv[]) { + long long seed = atoll(argv[1]); + auto gen = Random(seed); + + int n = gen.uniform(MIN_N, MAX_N); + int m = gen.uniform(0, MAX_M); + + using P = pair; + vector

edges; + for (int i = 0; i < m; ++i) { + while (1) { + int a = gen.uniform(0, n - 1); + int b = gen.uniform(0, n - 1); + + if (seed % 2 == 0 && a == b) continue; + + edges.push_back({a, b}); + break; + } + } + + printf("%d %d\n", n, m); + for (int i = 0; i < m; i++) { + printf("%d %d\n", edges[i].first, edges[i].second); + } + return 0; +} \ No newline at end of file diff --git a/graph/chromatic_polynomial/gen/random.cpp b/graph/chromatic_polynomial/gen/random.cpp new file mode 100644 index 00000000..2a5d7d67 --- /dev/null +++ b/graph/chromatic_polynomial/gen/random.cpp @@ -0,0 +1,35 @@ +#include +#include +#include "random.h" +#include "../params.h" + +using namespace std; + +int main(int, char* argv[]) { + + long long seed = atoll(argv[1]); + auto gen = Random(seed); + + int n = gen.uniform(MIN_N, MAX_N); + int m = gen.uniform(0, n * (n - 1) / 2); + + using P = pair; + vector

edges; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (gen.uniform_bool()) { + edges.push_back({i, j}); + } else { + edges.push_back({j, i}); + } + } + } + gen.shuffle(edges.begin(), edges.end()); + edges.resize(m); + + printf("%d %d\n", n, m); + for (int i = 0; i < m; i++) { + printf("%d %d\n", edges[i].first, edges[i].second); + } + return 0; +} \ No newline at end of file diff --git a/graph/chromatic_polynomial/hash.json b/graph/chromatic_polynomial/hash.json new file mode 100644 index 00000000..5dea8b6a --- /dev/null +++ b/graph/chromatic_polynomial/hash.json @@ -0,0 +1,70 @@ +{ + "big_00.in": "eca36bce63e2474ec08a58afc9239f456c4517f5b0940be5b7b516964404c175", + "big_00.out": "f91927758dde8fcca1c353d7978da05cf2ab39bfe7c8fce7dd63c5309fa563a4", + "big_01.in": "8cd39f1f16a172b2edae14457b867ad7671daf42124cfecab474aabaf3200c97", + "big_01.out": "b5c94846a99fb598fbabcb6aa2a645c75fe51b3eeee2feb58dcbb1b8fc6ba2d7", + "big_02.in": "f6c2004c2e000012d1b0dde73f0020356b6d55ea12780e1eccae642276d2c66f", + "big_02.out": "5ff6d8eb123a599ee943b2d524cac45220526e2fc6183f464f7ef855e709af37", + "big_03.in": "218ef8cb7bf76df2ab14c409f4f926a613b8132a139889c53cc3b7108fd17b73", + "big_03.out": "518bb25694f6d24d8a72518420e09f7226ded94cecad57642fcc5ccb5d799204", + "big_04.in": "1ecf2d7e7abaee216b8d050e9cab45e5692222310b846bf24251d3e469c29d25", + "big_04.out": "9bddb0e912c61d0c2af56348db9bd85067a4564707b86aaefa57cd7cb7196c35", + "big_05.in": "d8903774683d06abb3248ca4f0fa342da210adfeab66fbfbe378d93f998259bd", + "big_05.out": "30a86961a66bf351a37f972280a704dcf2675ccd7ae228840c80bb7df0378a49", + "big_06.in": "abeb844e8bc148f58df01cea49d0eef2474b1dfd0d0ef849fb49c9d3936c7298", + "big_06.out": "eeb630d1ad9d0cfc4af45e944f1a1e8132326e6950eb1281879417727ccb6854", + "big_07.in": "0395186b58c4b164a91849aca8fbaebdbbcd9fdec18afa2d0ce51c3d0db0cdfe", + "big_07.out": "e8e620af6f3f5ed1dfdf2d5fe3b5904c59d0626c887839dcb93c2455651b1d6e", + "big_08.in": "06b4f8711110f19a48d44ab6b4f4dad02044bd62b17fd148154079e2d9f8defc", + "big_08.out": "715f3730a0aed9df94237407c51ec6acf427aa7747640ae3d35d6974978bc515", + "big_09.in": "6e0e9c98516c8f41f5bc0b2b0de2173c55802b619a998a6f9e32d9ad46504d7f", + "big_09.out": "4349fdc0e7a44b0c2f3d91642dc48b146a9fd8100860f55a728a2b91336eba1c", + "clique_cycle_00.in": "1eba3cc95e92c5f0c9f0b3e993744b48b3690a36e2f55728725b812d18ffb241", + "clique_cycle_00.out": "941afed2bf3bcce0923a3a0d904a39b43b945730549dc3faf3b78b8fd2abf367", + "clique_cycle_01.in": "7390f9fa99faf3c9be51684936591fa04d7098ceb37a4fc63b057a53aad1dace", + "clique_cycle_01.out": "fe415a816df8841bfe90b4de142ff58b8b67b4d6ffb4fb69ea19e06ebb43e9a1", + "clique_cycle_02.in": "516a43ceee39df55546adb525e6f29c27fc54f0c8511f728f292d5849002826b", + "clique_cycle_02.out": "03de692519b3b8b3bc0451e822311337e1ff423fcab97d910e81c94818ccdf47", + "clique_cycle_03.in": "2779d8f020a8bf2c9a24983a133b46906eab3015061f01668496e8d4ddee5dfc", + "clique_cycle_03.out": "b056622bb268bc5113fa10348950e6155a37ed04b2cf82de9d400f584e159a38", + "example_00.in": "dc2aa6a49ec29cc14d4e3beafb3edcd35467b62d1613abd0d5caa5d02567f270", + "example_00.out": "edc52b303972a12ded79cf0847eabaf52ff33e6767b84a7673340875095afbe7", + "example_01.in": "37898b3db856790aa3d175dc27ded6e95abb1e3fcb5363b00176c18ce68ce703", + "example_01.out": "c74a988930b935843ebffc02b91ee9fbe7af4930577f57e4fe0449964e102241", + "example_02.in": "b51532892e9ab7ee94d039f5513bafe7c72b52b57dc00ec41739f22d7bddf124", + "example_02.out": "c5bea6d5172950ed3fc3f0433afd51fc1913e545f5dd34c849c65dc166209a00", + "example_03.in": "0722a2b17e80e1b1cf73c976905edb06c7735b34eb780571353e65a331706c20", + "example_03.out": "6dbaece45216c0a241f062733d3f048717bfb49ebc0d5d23938e4582f6a82155", + "loop_00.in": "e7c0dc8f5b32d86c33761b16fc38319d804a951c505409e4e6230733d58ec963", + "loop_00.out": "060edb33dbed7f5d786b8ca6dcc02dcbde7ec2d6781f7a9e1208abe9ad17e56c", + "loop_01.in": "0dab29861074eb3abd7ba99ebc439594cbc4afdcc4beb6a4603208ed79cc9749", + "loop_01.out": "2746d8486443c0620601af45735b57713a2639346594473fff944397c58354f5", + "multi_edge_00.in": "625bde3e8123481f3a36f4d82fd10c95d24c773b2d2972c36ba746c2d24721d2", + "multi_edge_00.out": "b9d23c83f53614c09051230936fa378caaf190522b2b2a5a2686a2c8809e740a", + "multi_edge_01.in": "2221a6e26b3aa6f2c6fb6422e3f56b0ea6c2a627c0f712b05c91d10898f22a1e", + "multi_edge_01.out": "2746d8486443c0620601af45735b57713a2639346594473fff944397c58354f5", + "multi_edge_02.in": "3c6cd8db196a4b9f18ffda1dd9468a1d5bc450752a8dadcd90e95048081bf1d1", + "multi_edge_02.out": "b609d7d6e3055ac575d3e2e676d05625ca5e838dbb1d96bbfe0bee07c988e2f3", + "multi_edge_03.in": "4262eeb1d1ed9d1ce1d3ef0400e756ddfe5e30beea667bd26fe965588df5fa84", + "multi_edge_03.out": "0ccdb5a77ba5bf7687f2565a8ed97dfb9c1af45503c496fb646312239fab5101", + "random_00.in": "bcd7e13978fcbc494bb43a0e3cdaea0ded3ec95e8904f38e29a7df00f1eac56b", + "random_00.out": "b12d985e36ebbb8eb8536774dd4e3b37b1f79779532c4102e2fd06d3eacfa23f", + "random_01.in": "015ae823b1b86989843b1619f771fc1e9b527e510040fe1f84e6a14a74548cd7", + "random_01.out": "e5f868fadb49aed7ce95f388ce846ecb839b921207974da2c4b9e18c3164d738", + "random_02.in": "81ace98a88f78085c4ed6da86b4ad8222683cc377135817e38bdf1b0c5b0fbb3", + "random_02.out": "b609d7d6e3055ac575d3e2e676d05625ca5e838dbb1d96bbfe0bee07c988e2f3", + "random_03.in": "f4a8ae8e74ddfb896a256de4e3099911dcaa6a9302591713898069b0bcd6e3d7", + "random_03.out": "a79122992d53d358e6bbbbb98883d64fa0c15df3bcb08ff7b65a0580870af424", + "random_04.in": "1ecf2d7e7abaee216b8d050e9cab45e5692222310b846bf24251d3e469c29d25", + "random_04.out": "9bddb0e912c61d0c2af56348db9bd85067a4564707b86aaefa57cd7cb7196c35", + "random_05.in": "ccc47407f7d754a1fa44688a887d5532f78ab8e7f316e8bd9dcb20361b996b4f", + "random_05.out": "eb386a3ce159523f4e87999814f87553de120ed79deb367dadb2c7d9d9feb213", + "random_06.in": "af622085299d845070e3d41572cec699cba714e3e2b294557c2aae1d9bad831f", + "random_06.out": "ff2acbc3fb778dc236019d6c221b98d5a9c88bb85bf25d07ec3375a37ef6970c", + "random_07.in": "0395186b58c4b164a91849aca8fbaebdbbcd9fdec18afa2d0ce51c3d0db0cdfe", + "random_07.out": "e8e620af6f3f5ed1dfdf2d5fe3b5904c59d0626c887839dcb93c2455651b1d6e", + "random_08.in": "fabfde9db2b09a3d62d8c0f296f2b9edba75aad6f05a2842e574a61949ed5578", + "random_08.out": "b9f6de755703ae8f4818d87591257bf7294ed3bafcb8b1343a7e75e295b2a168", + "random_09.in": "f4a8ae8e74ddfb896a256de4e3099911dcaa6a9302591713898069b0bcd6e3d7", + "random_09.out": "a79122992d53d358e6bbbbb98883d64fa0c15df3bcb08ff7b65a0580870af424" +} \ No newline at end of file diff --git a/graph/chromatic_polynomial/info.toml b/graph/chromatic_polynomial/info.toml new file mode 100644 index 00000000..c33a8bf9 --- /dev/null +++ b/graph/chromatic_polynomial/info.toml @@ -0,0 +1,28 @@ +title = 'Chromatic Polynomial' +timelimit = 10.0 +forum = "https://github.com/yosupo06/library-checker-problems/issues/984" + +[[tests]] + name = "example.in" + number = 4 +[[tests]] + name = "random.cpp" + number = 10 +[[tests]] + name = "big.cpp" + number = 10 +[[tests]] + name = "clique_cycle.cpp" + number = 4 +[[tests]] + name = "loop.cpp" + number = 2 +[[tests]] + name = "multi_edge.cpp" + number = 4 + +[params] + MIN_N = 1 + MAX_N = 20 + MAX_M = 500 + MOD = 998244353 diff --git a/graph/chromatic_polynomial/sol/correct.cpp b/graph/chromatic_polynomial/sol/correct.cpp new file mode 100644 index 00000000..85059536 --- /dev/null +++ b/graph/chromatic_polynomial/sol/correct.cpp @@ -0,0 +1,662 @@ +#include +#include +#include +#include +#include +#include + +using namespace std; + +using ll = long long; +using u32 = unsigned int; +using u64 = unsigned long long; + +using pi = pair; +using vi = vector; +template +using vc = vector; +template +using vvc = vector>; + +// https://trap.jp/post/1224/ +#define FOR1(a) for (ll _ = 0; _ < ll(a); ++_) +#define FOR2(i, a) for (ll i = 0; i < ll(a); ++i) +#define FOR3(i, a, b) for (ll i = a; i < ll(b); ++i) +#define FOR4(i, a, b, c) for (ll i = a; i < ll(b); i += (c)) +#define FOR1_R(a) for (ll i = (a)-1; i >= ll(0); --i) +#define FOR2_R(i, a) for (ll i = (a)-1; i >= ll(0); --i) +#define FOR3_R(i, a, b) for (ll i = (b)-1; i >= ll(a); --i) +#define overload4(a, b, c, d, e, ...) e +#define overload3(a, b, c, d, ...) d +#define FOR(...) overload4(__VA_ARGS__, FOR4, FOR3, FOR2, FOR1)(__VA_ARGS__) +#define FOR_R(...) overload3(__VA_ARGS__, FOR3_R, FOR2_R, FOR1_R)(__VA_ARGS__) + +#define FOR_subset(t, s) \ + for (ll t = (s); t >= 0; t = (t == 0 ? -1 : (t - 1) & (s))) +#define all(x) x.begin(), x.end() +#define len(x) ll(x.size()) +#define elif else if + +#define eb emplace_back +#define mp make_pair +#define mt make_tuple +#define fi first +#define se second + +#define stoi stoll + +int popcnt(int x) { return __builtin_popcount(x); } +int popcnt(u32 x) { return __builtin_popcount(x); } +int popcnt(ll x) { return __builtin_popcountll(x); } +int popcnt(u64 x) { return __builtin_popcountll(x); } +// (0, 1, 2, 3, 4) -> (-1, 0, 1, 1, 2) +int topbit(int x) { return (x == 0 ? -1 : 31 - __builtin_clz(x)); } +int topbit(u32 x) { return (x == 0 ? -1 : 31 - __builtin_clz(x)); } +int topbit(ll x) { return (x == 0 ? -1 : 63 - __builtin_clzll(x)); } +int topbit(u64 x) { return (x == 0 ? -1 : 63 - __builtin_clzll(x)); } +// (0, 1, 2, 3, 4) -> (-1, 0, 1, 0, 2) +int lowbit(int x) { return (x == 0 ? -1 : __builtin_ctz(x)); } +int lowbit(u32 x) { return (x == 0 ? -1 : __builtin_ctz(x)); } +int lowbit(ll x) { return (x == 0 ? -1 : __builtin_ctzll(x)); } +int lowbit(u64 x) { return (x == 0 ? -1 : __builtin_ctzll(x)); } + +template +T floor(T a, T b) { + return a / b - (a % b && (a ^ b) < 0); +} +template +T ceil(T x, T y) { + return floor(x + y - 1, y); +} +template +T bmod(T x, T y) { + return x - y * floor(x, y); +} +template +pair divmod(T x, T y) { + T q = floor(x, y); + return {q, x - q * y}; +} + +template +T SUM(const vector& A) { + T sm = 0; + for (auto&& a: A) sm += a; + return sm; +} + +#define MIN(v) *min_element(all(v)) +#define MAX(v) *max_element(all(v)) +#define LB(c, x) distance((c).begin(), lower_bound(all(c), (x))) +#define UB(c, x) distance((c).begin(), upper_bound(all(c), (x))) +#define UNIQUE(x) \ + sort(all(x)), x.erase(unique(all(x)), x.end()), x.shrink_to_fit() + +template +mint inv(int n) { + static const int mod = mint::get_mod(); + static vector dat = {0, 1}; + assert(0 <= n); + if (n >= mod) n %= mod; + while (len(dat) <= n) { + int k = len(dat); + int q = (mod + k - 1) / k; + dat.eb(dat[k * q - mod] * mint::raw(q)); + } + return dat[n]; +} + +template +mint fact(int n) { + static const int mod = mint::get_mod(); + assert(0 <= n && n < mod); + static vector dat = {1, 1}; + while (len(dat) <= n) dat.eb(dat[len(dat) - 1] * mint::raw(len(dat))); + return dat[n]; +} + +template +mint fact_inv(int n) { + static vector dat = {1, 1}; + if (n < 0) return mint(0); + while (len(dat) <= n) dat.eb(dat[len(dat) - 1] * inv(len(dat))); + return dat[n]; +} + +template +mint fact_invs(Ts... xs) { + return (mint(1) * ... * fact_inv(xs)); +} + +template +mint multinomial(Head&& head, Tail&&... tail) { + return fact(head) * fact_invs(std::forward(tail)...); +} + +template +mint C_dense(int n, int k) { + static vvc C; + static int H = 0, W = 0; + auto calc = [&](int i, int j) -> mint { + if (i == 0) return (j == 0 ? mint(1) : mint(0)); + return C[i - 1][j] + (j ? C[i - 1][j - 1] : 0); + }; + if (W <= k) { + FOR(i, H) { + C[i].resize(k + 1); + FOR(j, W, k + 1) { C[i][j] = calc(i, j); } + } + W = k + 1; + } + if (H <= n) { + C.resize(n + 1); + FOR(i, H, n + 1) { + C[i].resize(W); + FOR(j, W) { C[i][j] = calc(i, j); } + } + H = n + 1; + } + return C[n][k]; +} + +template +mint C(ll n, ll k) { + assert(n >= 0); + if (k < 0 || n < k) return 0; + if constexpr (dense) return C_dense(n, k); + if constexpr (!large) return multinomial(n, k, n - k); + k = min(k, n - k); + mint x(1); + FOR(i, k) x *= mint(n - i); + return x * fact_inv(k); +} + +template +mint C_inv(ll n, ll k) { + assert(n >= 0); + assert(0 <= k && k <= n); + if (!large) return fact_inv(n) * fact(k) * fact(n - k); + return mint(1) / C(n, k); +} + +// [x^d](1-x)^{-n} +template +mint C_negative(ll n, ll d) { + assert(n >= 0); + if (d < 0) return mint(0); + if (n == 0) { return (d == 0 ? mint(1) : mint(0)); } + return C(n + d - 1, d); +} + +template +struct modint { + static constexpr u32 umod = u32(mod); + static_assert(umod < u32(1) << 31); + u32 val; + + static modint raw(u32 v) { + modint x; + x.val = v; + return x; + } + constexpr modint() : val(0) {} + constexpr modint(u32 x) : val(x % umod) {} + constexpr modint(u64 x) : val(x % umod) {} + constexpr modint(int x) : val((x %= mod) < 0 ? x + mod : x){}; + constexpr modint(ll x) : val((x %= mod) < 0 ? x + mod : x){}; + bool operator<(const modint& other) const { return val < other.val; } + modint& operator+=(const modint& p) { + if ((val += p.val) >= umod) val -= umod; + return *this; + } + modint& operator-=(const modint& p) { + if ((val += umod - p.val) >= umod) val -= umod; + return *this; + } + modint& operator*=(const modint& p) { + val = u64(val) * p.val % umod; + return *this; + } + modint& operator/=(const modint& p) { + *this *= p.inverse(); + return *this; + } + modint operator-() const { return modint::raw(val ? mod - val : u32(0)); } + modint operator+(const modint& p) const { return modint(*this) += p; } + modint operator-(const modint& p) const { return modint(*this) -= p; } + modint operator*(const modint& p) const { return modint(*this) *= p; } + modint operator/(const modint& p) const { return modint(*this) /= p; } + bool operator==(const modint& p) const { return val == p.val; } + bool operator!=(const modint& p) const { return val != p.val; } + modint inverse() const { + int a = val, b = mod, u = 1, v = 0, t; + while (b > 0) { + t = a / b; + swap(a -= t * b, b), swap(u -= t * v, v); + } + return modint(u); + } + modint pow(ll n) const { + assert(n >= 0); + modint ret(1), mul(val); + while (n > 0) { + if (n & 1) ret *= mul; + mul *= mul; + n >>= 1; + } + return ret; + } + static constexpr int get_mod() { return mod; } +}; + +using modint998 = modint<998244353>; + +template +struct Edge { + int frm, to; + T cost; + int id; +}; + +template +struct Graph { + static constexpr bool is_directed = directed; + int N, M; + using cost_type = T; + using edge_type = Edge; + vector edges; + vector indptr; + vector csr_edges; + bool prepared; + + class OutgoingEdges { + public: + OutgoingEdges(const Graph* G, int l, int r) : G(G), l(l), r(r) {} + + const edge_type* begin() const { + if (l == r) { return 0; } + return &G->csr_edges[l]; + } + + const edge_type* end() const { + if (l == r) { return 0; } + return &G->csr_edges[r]; + } + + private: + const Graph* G; + int l, r; + }; + + bool is_prepared() { return prepared; } + + Graph() : N(0), M(0), prepared(0) {} + Graph(int N) : N(N), M(0), prepared(0) {} + + void build(int n) { + N = n, M = 0; + prepared = 0; + edges.clear(); + indptr.clear(); + csr_edges.clear(); + } + + void add(int frm, int to, T cost = 1, int i = -1) { + assert(!prepared); + assert(0 <= frm && 0 <= to && to < N); + if (i == -1) i = M; + auto e = edge_type({frm, to, cost, i}); + edges.eb(e); + ++M; + } + + void build() { + assert(!prepared); + prepared = true; + indptr.assign(N + 1, 0); + for (auto&& e: edges) { + indptr[e.frm + 1]++; + if (!directed) indptr[e.to + 1]++; + } + for (int v = 0; v < N; ++v) { indptr[v + 1] += indptr[v]; } + auto counter = indptr; + csr_edges.resize(indptr.back() + 1); + for (auto&& e: edges) { + csr_edges[counter[e.frm]++] = e; + if (!directed) + csr_edges[counter[e.to]++] = edge_type({e.to, e.frm, e.cost, e.id}); + } + } + + OutgoingEdges operator[](int v) const { + assert(prepared); + return {this, indptr[v], indptr[v + 1]}; + } +}; + +template +vc convolution_naive(const vc& a, const vc& b) { + int n = int(a.size()), m = int(b.size()); + if (n > m) return convolution_naive(b, a); + if (n == 0) return {}; + vector ans(n + m - 1); + FOR(i, n) FOR(j, m) ans[i + j] += a[i] * b[j]; + return ans; +} + +// 任意の環でできる +template +vc convolution_karatsuba(const vc& f, const vc& g) { + const int thresh = 30; + if (min(len(f), len(g)) <= thresh) return convolution_naive(f, g); + int n = max(len(f), len(g)); + int m = ceil(n, 2); + vc f1, f2, g1, g2; + if (len(f) < m) f1 = f; + if (len(f) >= m) f1 = {f.begin(), f.begin() + m}; + if (len(f) >= m) f2 = {f.begin() + m, f.end()}; + if (len(g) < m) g1 = g; + if (len(g) >= m) g1 = {g.begin(), g.begin() + m}; + if (len(g) >= m) g2 = {g.begin() + m, g.end()}; + vc a = convolution_karatsuba(f1, g1); + vc b = convolution_karatsuba(f2, g2); + FOR(i, len(f2)) f1[i] += f2[i]; + FOR(i, len(g2)) g1[i] += g2[i]; + vc c = convolution_karatsuba(f1, g1); + vc F(len(f) + len(g) - 1); + assert(2 * m + len(b) <= len(F)); + FOR(i, len(a)) F[i] += a[i], c[i] -= a[i]; + FOR(i, len(b)) F[2 * m + i] += b[i], c[i] -= b[i]; + if (c.back() == T(0)) c.pop_back(); + FOR(i, len(c)) if (c[i] != T(0)) F[m + i] += c[i]; + return F; +} + +template +vc convolution(const vc& a, const vc& b) { + return convolution_karatsuba(a, b); +} + +template +vc fps_inv_sparse(const vc& f) { + int N = len(f); + vc> dat; + FOR(i, 1, N) if (f[i] != mint(0)) dat.eb(i, f[i]); + vc g(N); + mint g0 = mint(1) / f[0]; + g[0] = g0; + FOR(n, 1, N) { + mint rhs = 0; + for (auto&& [k, fk]: dat) { + if (k > n) break; + rhs -= fk * g[n - k]; + } + g[n] = rhs * g0; + } + return g; +} + +template +vc fps_inv(const vc& f) { + assert(f[0] != mint(0)); + return fps_inv_sparse(f); +} + +// n, m 次多項式 (n>=m) a, b → n-m 次多項式 c +// c[i] = sum_j b[j]a[i+j] +template +vc middle_product(vc& a, vc& b) { + return middle_product_naive(a, b); +} + +template +vc middle_product_naive(vc& a, vc& b) { + vc res(len(a) - len(b) + 1); + FOR(i, len(res)) FOR(j, len(b)) res[i] += b[j] * a[i + j]; + return res; +} + +template +vc all_inverse(vc& X) { + for (auto&& x: X) assert(x != mint(0)); + int N = len(X); + vc res(N + 1); + res[0] = mint(1); + FOR(i, N) res[i + 1] = res[i] * X[i]; + mint t = res.back().inverse(); + res.pop_back(); + FOR_R(i, N) { + res[i] *= t; + t *= X[i]; + } + return res; +} + +template +struct SubproductTree { + int m; + int sz; + vc> T; + SubproductTree(const vc& x) { + m = len(x); + sz = 1; + while (sz < m) sz *= 2; + T.resize(2 * sz); + FOR(i, sz) T[sz + i] = {1, (i < m ? -x[i] : 0)}; + FOR3_R(i, 1, sz) T[i] = convolution(T[2 * i], T[2 * i + 1]); + } + + vc evaluation(vc f) { + int n = len(f); + if (n == 0) return vc(m, mint(0)); + f.resize(2 * n - 1); + vc> g(2 * sz); + g[1] = T[1]; + g[1].resize(n); + g[1] = fps_inv(g[1]); + g[1] = middle_product(f, g[1]); + g[1].resize(sz); + + FOR3(i, 1, sz) { + g[2 * i] = middle_product(g[i], T[2 * i + 1]); + g[2 * i + 1] = middle_product(g[i], T[2 * i]); + } + vc vals(m); + FOR(i, m) vals[i] = g[sz + i][0]; + return vals; + } + + vc interpolation(vc& y) { + assert(len(y) == m); + vc a(m); + FOR(i, m) a[i] = T[1][m - i - 1] * (i + 1); + + a = evaluation(a); + vc> t(2 * sz); + FOR(i, sz) t[sz + i] = {(i < m ? y[i] / a[i] : 0)}; + FOR3_R(i, 1, sz) { + t[i] = convolution(t[2 * i], T[2 * i + 1]); + auto tt = convolution(t[2 * i + 1], T[2 * i]); + FOR(k, len(t[i])) t[i][k] += tt[k]; + } + t[1].resize(m); + reverse(all(t[1])); + return t[1]; + } +}; + +template +vc multipoint_eval(vc& f, vc& x) { + if (x.empty()) return {}; + SubproductTree F(x); + return F.evaluation(f); +} + +template +vc multipoint_interpolate(vc& x, vc& y) { + if (x.empty()) return {}; + SubproductTree F(x); + return F.interpolation(y); +} + +template +vc> ranked_zeta(const vc& f) { + int n = topbit(len(f)); + assert(n <= LIM); + assert(len(f) == 1 << n); + vc> Rf(1 << n); + for (int s = 0; s < (1 << n); ++s) Rf[s][popcnt(s)] = f[s]; + for (int i = 0; i < n; ++i) { + int w = 1 << i; + for (int p = 0; p < (1 << n); p += 2 * w) { + for (int s = p; s < p + w; ++s) { + int t = s | 1 << i; + for (int d = 0; d <= n; ++d) Rf[t][d] += Rf[s][d]; + } + } + } + return Rf; +} + +template +vc ranked_mobius(vc>& Rf) { + int n = topbit(len(Rf)); + assert(len(Rf) == 1 << n); + for (int i = 0; i < n; ++i) { + int w = 1 << i; + for (int p = 0; p < (1 << n); p += 2 * w) { + for (int s = p; s < p + w; ++s) { + int t = s | 1 << i; + for (int d = 0; d <= n; ++d) Rf[t][d] -= Rf[s][d]; + } + } + } + vc f(1 << n); + for (int s = 0; s < (1 << n); ++s) f[s] = Rf[s][popcnt(s)]; + return f; +} + +template +vc subset_convolution(const vc& A, const vc& B) { + auto RA = ranked_zeta(A); + auto RB = ranked_zeta(B); + int n = topbit(len(RA)); + FOR(s, len(RA)) { + auto &f = RA[s], &g = RB[s]; + FOR_R(d, n + 1) { + T x = 0; + FOR(i, d + 1) x += f[i] * g[d - i]; + f[d] = x; + } + } + return ranked_mobius(RA); +} + +// for fixed sps s, consider linear map F:a->b = subset-conv(a,s) +// given x, calculate transpose(F)(x) +template +vc transposed_subset_convolution(vc s, vc x) { + /* + sum_{j}x_jb_j = sum_{i subset j}x_ja_is_{j-i} = sum_{i}y_ia_i. + y_i = sum_{j supset i}x_js_{j-i} + (rev y)_i = sum_{j subset i}(rev x)_js_{i-j} + y = rev(conv(rev x), s) + */ + reverse(all(x)); + x = subset_convolution(x, s); + reverse(all(x)); + return x; +} + +// assume s[0]==0 +// calculate sum_i wt_i(s^k/k!)_i for k=0,1,...,N +template +vc power_projection_of_sps_egf(vc wt, vc& s) { + const int N = topbit(len(s)); + assert(len(s) == (1 << N) && len(wt) == (1 << N) && s[0] == mint(0)); + vc y(N + 1); + y[0] = wt[0]; + auto& dp = wt; + FOR(i, N) { + vc newdp(1 << (N - 1 - i)); + FOR(j, N - i) { + vc a = {s.begin() + (1 << j), s.begin() + (2 << j)}; + vc b = {dp.begin() + (1 << j), dp.begin() + (2 << j)}; + b = transposed_subset_convolution(a, b); + FOR(k, len(b)) newdp[k] += b[k]; + } + swap(dp, newdp); + y[1 + i] = dp[0]; + } + return y; +} + +// calculate sum_i x_i(s^k)_i for k=0,1,...,M-1 +template +vc power_projection_of_sps(vc wt, vc s, int M) { + const int N = topbit(len(s)); + assert(len(s) == (1 << N) && len(wt) == (1 << N)); + mint c = s[0]; + s[0] -= c; + vc x = power_projection_of_sps_egf(wt, s); + vc g(M); + mint pow = 1; + FOR(i, M) { g[i] = pow * fact_inv(i), pow *= c; } + x = convolution(x, g); + x.resize(M); + FOR(i, M) x[i] *= fact(i); + return x; +} + +// O(N^22^N) +template +vc chromatic_polynomial(Graph G) { + int N = G.N; + assert(N <= MAX_N); + vc ng(1 << N); + for (auto& e: G.edges) { + int i = e.frm, j = e.to; + ng[(1 << i) | (1 << j)] = 1; + } + FOR(s, 1 << N) { + if (ng[s]) { + FOR(i, N) { ng[s | 1 << i] = 1; } + } + } + vc f(1 << N); + FOR(s, 1 << N) { + if (!ng[s]) f[s] = 1; + } + vc wt(1 << N); + wt.back() = 1; + vc Y = power_projection_of_sps(wt, f, N + 1); + vc X(N + 1); + FOR(i, N + 1) X[i] = i; + return multipoint_interpolate(X, Y); +} + +using mint = modint998; + +void solve() { + int N, M; + scanf("%d %d", &N, &M); + Graph G(N); + for (int i = 0; i < M; ++i) { + int a, b; + scanf("%d %d", &a, &b); + G.add(a, b); + } + G.build(); + vc f = chromatic_polynomial(G); + f.resize(N + 1); + for (int i = 0; i <= N; ++i) { + if (i) printf(" "); + printf("%d", f[i].val); + } + printf("\n"); +} + +signed main() { + solve(); + return 0; +} diff --git a/graph/chromatic_polynomial/task.md b/graph/chromatic_polynomial/task.md new file mode 100644 index 00000000..8aba2224 --- /dev/null +++ b/graph/chromatic_polynomial/task.md @@ -0,0 +1,45 @@ +## @{keyword.statement} + +@{lang.en} +Given an undirected graph $G$ with $N$ vertices and $M$ edges. The $i$-th edge is $\lbrace u_i, v_i\rbrace$. + + +Calculate the chromatic polynomial $P(G,x)=p_0+p_1+\cdots+p_Nx^N$ modulo $@{param.MOD}$. + +@{lang.ja} +$N$ 頂点 $M$ 辺の無向グラフ $G$ が与えられる。 $i$ 番目の辺は $\lbrace u_i, v_i\rbrace$ である。 + +$G$ の彩色多項式 $P(G,x)=p_0+p_1+\cdots+p_Nx^N$ を mod $@{param.MOD}$ で求めてください。 +@{lang.end} + +## @{keyword.constraints} + +- $@{param.MIN_N} \leq N \leq @{param.MAX_N}$ +- $0 \leq M \leq @{param.MAX_M}$ +- $0 \leq u_i, v_i < N$ + +## @{keyword.input} + +~~~ +$N$ $M$ +$u_0$ $v_0$ +$u_1$ $v_1$ +: +$u_{M - 1}$ $v_{M - 1}$ +~~~ + +## @{keyword.output} + +~~~ +$p_0$ $p_1$ $\cdots$ $p_N$ +~~~ + +## @{keyword.sample} + +@{example.example_00} + +@{example.example_01} + +@{example.example_02} + +@{example.example_03} diff --git a/graph/chromatic_polynomial/verifier.cpp b/graph/chromatic_polynomial/verifier.cpp new file mode 100644 index 00000000..2169a42c --- /dev/null +++ b/graph/chromatic_polynomial/verifier.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +#include "testlib.h" +#include "params.h" + +int main() { + registerValidation(); + + int n = inf.readInt(MIN_N, MAX_N, "n"); + inf.readSpace(); + int m = inf.readInt(0, MAX_M, "m"); + inf.readChar('\n'); + + using P = std::pair; + std::set

edges; + for (int i = 0; i < m; i++) { + int u = inf.readInt(0, n - 1, "u_i"); + inf.readSpace(); + int v = inf.readInt(0, n - 1, "v_i"); + inf.readChar('\n'); + edges.insert({u, v}); + } + inf.readEof(); + return 0; +}