T1
Topcoder SRM 632 div2 Hard - GoodSubset
直接記搜,不整除就不取當前數。由於給定數的因數個數是很少的,所以記搜完全跑得過去。
程式碼
#include <iostream>
#include <map>
using namespace std;
const int P = 1000000007;
map<pair<int, int>, int> vis;
int n;
int a[105];
int V;
void Madd(int& x, int y) { (x += y) >= P ? (x -= P) : 0; }
int dfs(int p, int v) {
if (p == n + 1)
return v == 1;
pair<int, int> pr = make_pair(p, v);
if (vis.count(pr))
return vis[pr];
int ret = 0;
if (v % a[p] == 0)
ret = dfs(p + 1, v / a[p]);
Madd(ret, dfs(p + 1, v));
return vis[pr] = ret;
}
int main() {
cin >> V >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
cout << dfs(1, V) - (V == 1) << "\n";
return 0;
}
T2
NFLSOJ P2845 數位和同餘
考慮類似於根號分治的東西。對於 \(P\) 比較小的情況,直接數位 dp,記錄前面位的數位和和原數對 \(P\) 的餘數。我寫的可以跑到 \(P = 10^4\)。對於剩下的情況,首先注意到數位和不大,在 \([0, 90]\),所以餘數也只能是這些。我們列舉數位和,每次給當前數加上 \(P\) 直到超過 \(M\) 為止,檢查每個數對 \(P\) 取模是否等於其數位和。複雜度 \(O(能過)\)。
程式碼
#include <iostream>
#include <string.h>
#define int long long
using namespace std;
int P, M;
int dp[15][95][10005];
int d[15], dcnt;
int dfs(int pos, int x, int y, bool lim) {
if (pos == 0)
return x == y;
if (!lim && dp[pos][x][y] != -1)
return dp[pos][x][y];
int ret = 0;
int up = (lim ? d[pos] : 9);
for (int i = 0; i <= up; i++)
ret += dfs(pos - 1, (x + i) % P, (y * 10 + i) % P, lim & (i == up));
if (!lim)
dp[pos][x][y] = ret;
return ret;
}
int calc(int x) {
int ret = 0;
while (x) ret += x % 10, x /= 10;
return ret;
}
signed main() {
memset(dp, -1, sizeof dp);
cin >> P >> M;
int tmp = M;
while (M) d[++dcnt] = M % 10, M /= 10;
if (P <= 10000)
cout << dfs(dcnt, 0, 0, 1) - 1 << "\n";
else {
int a1 = -1;
for (int i = 0; i <= dcnt * 9; i++) {
for (int j = i; j <= tmp; j += P)
a1 += (i == calc(j) % P);
}
cout << a1 << "\n";
}
return 0;
}
T3
Topcoder SRM 555 div1 Medium - XorBoard
先把所有操作中對同一行 / 列的兩次操作的去掉,設有 \(x\) 個行在最後被操作了,\(y\) 個列在最後被操作了。可以列出白色格子數量 \(S = xW + yH - 2xy\)。列舉 \(x\),可以解出 \(y\),然後對解出的 \(y\) 有一些合法性的要求。特別地,可能會解出無窮多解的情況,這是合法的。確定了行列各有多少在最後是被操作的,就可以計算方案數。先把這些行列操作掉,則還要求行和列的剩餘操作次數都是 \(2\) 的倍數。相當於把 \(x\) 個被操作的行分配到 \(H\) 個行裡,把剩下的很多對操作分配到每一行上。兩部分都是組合數,把行和列的方案乘起來最後求和即為答案。
程式碼
#include <iostream>
#define int long long
using namespace std;
const int P = 555555555;
int H, W, r, c, S;
int C[4005][4005];
void Madd(int& x, int y) { (x += y % P) >= P ? (x -= P) : 0; }
signed main() {
cin >> H >> W >> r >> c >> S;
C[0][0] = 1;
for (int i = 1; i <= 4000; i++) {
for (int j = 0; j <= 4000; j++)
j == 0 ? (C[i][j] = 1) : (C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P);
}
int ans = 0;
for (int x = 0; x <= H; x++) {
int p = S - x * W;
int q = H - 2 * x;
if (q != 0 && p % q != 0)
continue;
else if (q == 0 && p != 0)
continue;
if (p == 0 && q == 0) {
for (int y = 0; y <= W; y++) {
if ((r - x < 0 || ((r - x) & 1)) || (c - y < 0 || ((c - y) & 1)))
continue;
int n1 = (r - x) >> 1;
int n2 = (c - y) >> 1;
Madd(ans, (C[H][x] * C[W][y] % P) * (C[n1 + H - 1][H - 1] * C[n2 + W - 1][W - 1] % P));
}
} else {
int y = p / q;
if (y < 0 || y > W)
continue;
if ((r - x < 0 || ((r - x) & 1)) || (c - y < 0 || ((c - y) & 1)))
continue;
int n1 = (r - x) >> 1;
int n2 = (c - y) >> 1;
Madd(ans, (C[H][x] * C[W][y] % P) * (C[n1 + H - 1][H - 1] * C[n2 + W - 1][W - 1] % P));
}
}
cout << ans << "\n";
return 0;
}
T4
Topcoder SRM 550 div1 Hard - ConversionMachine
考慮所有合法操作一定規約成先把原串變成合法,然後三個一組地對某個字母操作。考慮一個 \(dp[a][i][j][k]\) 表示操作 \(a\) 次後有 \(i\) 個字母剩 \(1\) 次操作正確,\(j\) 個剩 \(2\) 次,\(k\) 個已經正確的方案數。轉移考慮改哪種字元。由於 \(i + j + k = n\),最後一維可以去掉。注意到這裡沒有限制操作代價,是因為對代價的限制可以轉化為對操作次數的限制。根據前面的規約,我們可以算出合法的最小代價。然後三個一組的操作相當於每次給代價加上特定值。當代價超過限制時就不能操作了。所以對代價的限制本質上相當於對操作次數的限制。所以可以直接矩乘快速冪最佳化這個 dp。注意這個矩乘中還有一維要記錄字首和。
程式碼
#include <iostream>
#include <string.h>
#include <map>
#define int long long
using namespace std;
const int P = 1000000007;
const int N = 100;
struct Matrix {
int a[105][105];
int* operator[](int x) { return a[x]; }
Matrix(int x = 0) {
memset(a, 0, sizeof a);
for (int i = 0; i <= N; i++) a[i][i] = x;
}
} I, T;
Matrix operator*(Matrix a, Matrix b) {
Matrix c;
for (int i = 0; i <= N; i++) {
for (int j = 0; j <= N; j++) {
for (int k = 0; k <= N; k++)
c[i][j] = (c[i][j] + a[i][k] * b[k][j] % P) % P;
}
}
return c;
}
Matrix operator^(Matrix x, int y) {
Matrix ret(1);
while (y) {
if (y & 1)
ret = ret * x;
y >>= 1;
x = x * x;
}
return ret;
}
map<pair<int, int>, int> mp;
int f(int a, int b) { return mp[make_pair(a, b)]; }
int c[3], mxc, n;
signed main() {
string a, b;
cin >> a >> b;
n = a.size();
cin >> mxc;
cin >> c[0] >> c[1] >> c[2] >> mxc;
int Cost0 = 0, c1, c2;
c1 = c2 = 0;
for (int i = 0; i < n; i++) {
if (a[i] == 'a' && b[i] == 'b')
Cost0 += c[0], ++c1;
if (a[i] == 'a' && b[i] == 'c')
Cost0 += c[0] + c[1], ++c2;
if (a[i] == 'b' && b[i] == 'a')
Cost0 += c[1] + c[2], ++c2;
if (a[i] == 'b' && b[i] == 'c')
Cost0 += c[1], ++c1;
if (a[i] == 'c' && b[i] == 'a')
Cost0 += c[2], ++c1;
if (a[i] == 'c' && b[i] == 'b')
Cost0 += c[2] + c[0], ++c2;
}
int S = c[0] + c[1] + c[2];
if (mxc < Cost0) {
cout << 0 << "\n";
return 0;
}
int MaxStep = (mxc - Cost0) / S;
int ncnt = 0;
for (int a1 = 0; a1 <= n; a1++) {
for (int a2 = 0; a1 + a2 <= n; a2++) {
mp[make_pair(a1, a2)] = ++ncnt;
}
}
mp[make_pair(n, 1)] = ++ncnt;
I[0][f(c1, c2)] = 1;
for (int a1 = 0; a1 <= n; a1++) {
for (int a2 = 0; a1 + a2 <= n; a2++) {
int cur = f(a1, a2);
if (a1)
T[cur][f(a1 - 1, a2)] = a1;
if (a2)
T[cur][f(a1 + 1, a2 - 1)] = a2;
if (a1 + a2 != n)
T[cur][f(a1, a2 + 1)] = n - a1 - a2;
}
}
MaxStep = MaxStep * 3 + c1 + c2 * 2 + 1;
T[f(0, 0)][f(n, 1)] = 1;
T[f(n, 1)][f(n, 1)] = 1;
I = (I * (T ^ MaxStep));
cout << I[0][f(n, 1)] << "\n";
return 0;
}
T5
Topcoder SRM 549 div1 Hard - CosmicBlocks
先列舉所有層各有哪些顏色,然後列舉相鄰層間所有顏色之間的覆蓋關係,然後由於要滿足這個覆蓋關係,我們從上往下建邊,由覆蓋的連向被覆蓋的,然後為了限制每個顏色的數量,我們對每個顏色拆點,拆出的點間邊容量為該顏色數量,所有邊有流量下界 \(1\),跑一個上下界網路流,若有解就再接一個 DAG 拓撲序計數,如果方案數合法就加入答案。
程式碼
#include <iostream>
#include <algorithm>
#include <time.h>
#include <random>
#include <queue>
using namespace std;
const int inf = 0x3f3f3f3f;
int n;
int LL, RR;
struct Edge {
int u, v, l, r;
} e[1005];
int clr[10][10];
pair<int, int> es[15];
int a[10];
int in[105], out[105];
int head[1005], nxt[1005], to[1005], res[1005], ecnt;
int cur[1005];
inline void add(int u, int v, int ww) {
to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, res[ecnt] = ww;
to[++ecnt] = u, nxt[ecnt] = head[v], head[v] = ecnt, res[ecnt] = 0;
}
int S, T;
int dep[1005];
queue<int> q;
bool bfs() {
for (int i = 1; i <= T; i++) dep[i] = -1;
dep[S] = 1;
q.push(S);
while (!q.empty()) {
int x = q.front();
q.pop();
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (dep[v] == -1 && res[i]) {
dep[v] = dep[x] + 1;
q.push(v);
}
}
}
return (dep[T] > 0);
}
int dfs(int x, int flow) {
if (x == T)
return flow;
int ret = 0;
for (int& i = cur[x]; i; i = nxt[i]) {
int v = to[i];
if (dep[v] == dep[x] + 1 && res[i]) {
int tmp = dfs(v, min(flow, res[i]));
res[i] -= tmp;
res[i ^ 1] += tmp;
flow -= tmp;
ret += tmp;
}
}
if (!ret)
dep[x] = -1;
return ret;
}
bool check(int m) {
ecnt = 1;
for (int i = 1; i <= T; i++) head[i] = 0, in[i] = out[i] = 0;
for (int i = 1; i <= m; i++) {
in[e[i].v] += e[i].l;
out[e[i].u] += e[i].l;
add(e[i].u, e[i].v, e[i].r - e[i].l);
}
S = n * 2 + 3, T = S + 1;
for (int i = 1; i <= n * 2 + 1; i++) {
if (in[i] < out[i])
add(i, T, out[i] - in[i]);
else if (in[i] > out[i])
add(S, i, in[i] - out[i]);
}
while (bfs()) {
for (int i = 1; i <= T; i++) cur[i] = head[i];
dfs(S, inf);
}
bool flag = 1;
for (int i = head[S]; i; i = nxt[i]) flag &= (res[i] == 0);
return flag;
}
int dp[1005];
int count(int m, int S) {
for (int i = 1; i <= n; i++) nxt[i] = 0;
for (int i = 1; i <= m; i++) nxt[es[i].first] |= (((S >> (i - 1)) & 1) << (es[i].second - 1));
for (int i = 1; i < (1 << n); i++) dp[i] = 0;
for (int i = 1; i < (1 << n); i++) {
for (int j = 1; j <= n; j++) {
if (((i >> (j - 1)) & 1) & ((nxt[j] & i) == nxt[j]))
dp[i] += dp[i ^ (1 << (j - 1))];
}
}
return dp[(1 << n) - 1];
}
int ans;
void get(int x, int S) {
if (S == 0) {
int e = 0;
for (int i = 1; i < x - 1; i++) {
for (int j = 1; j <= clr[i][0]; j++) {
for (int k = 1; k <= clr[i + 1][0]; k++)
es[++e] = make_pair(clr[i][j], clr[i + 1][k]);
}
}
int c = 2 * n + 1;
for (int i = 1; i <= clr[1][0]; i++) ::e[++c] = (Edge) { n << 1 | 1, clr[1][i] * 2 - 1, 0, inf };
int rec = c;
for (int i = 0; i < (1 << e); i++) {
c = rec;
for (int j = 0; j < e; j++) {
if ((i >> j) & 1)
::e[++c] = (Edge) { es[j + 1].first * 2, es[j + 1].second * 2 - 1, 1, inf };
}
if (check(c)) {
int cnt = count(e, i);
ans += (LL <= cnt && cnt <= RR);
}
}
return;
}
for (int i = S; i; i = (i - 1) & S) {
clr[x][0] = 0;
for (int j = 0; j < n; j++) {
if ((i >> j) & 1)
clr[x][++clr[x][0]] = j + 1;
}
get(x + 1, S ^ i);
}
}
int main() {
*dp = 1;
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + n + 1);
int t = clock();
int c = 0;
for (int i = 1; i <= n; i++) e[++c] = (Edge) { i << 1, n * 2 + 2, 0, inf }, e[++c] = (Edge) { i * 2 - 1, i << 1, a[i], a[i] };
e[++c] = (Edge) { n * 2 + 2, n << 1 | 1, 0, inf };
cin >> LL >> RR;
get(1, (1 << n) - 1);
cout << ans << "\n";
cerr << (clock() * 1.0 - t) / CLOCKS_PER_SEC << "\n";
return 0;
}
T6
Topcoder SRM 548 div1 Hard KingdomAndCities
先考慮 \(k = 0\) 的情況,即為 \(n\) 個點 \(m\) 條邊的無向連通圖計數。考慮容斥一下,數一數有多少不連通的。總共的方案數是 \(\binom{\binom{n}{2}}{m}\),列舉根所在的連通塊有幾個點、幾條邊,有轉移 \(f[n][m] = \sum_{i = 1}^{n - 1}\sum_{j = i - 1}^{\binom{i}{2}} f[i][j]\times \binom{\binom{n - i}{2}}{m - j}\),其中 \(f[i][j]\) 是根所在連通塊的方案數,後面是該連通塊之外隨便連的總方案數。這樣就解決了 \(k = 0\) 的情況。
考慮 \(k = 1\),相當於把一個點插到一條邊中間或者併到一條邊旁邊形成三元環。注意,如果形成非三元環的環,則該方案可能會被與插進邊的情況算重。兩種分別是多了一個點一條邊和一個點兩條邊,所以把 \(f\) 乘上各自選邊的方案數加起來即為答案。
考慮 \(k = 2\),首先考慮兩個插入的點之間有邊的情況。
-
此時如果把兩個點共同連到一個點上形成三元環,則多了 \(2\) 個點 \(3\) 條邊,方案數即為原點數。此時是否交換兩個點的位置是一樣的。
-
如果把兩個點共同插到一條邊裡去,則多了 \(2\) 個點 \(2\) 條邊,方案數為原邊數。此時交換兩個點的位置形成的方案不同,方案數要乘以 \(2\)。
-
如果把兩個點併到一條邊旁邊,則多了 \(2\) 個點 \(3\) 條邊,方案數為原邊數。此時交換兩個點形成的方案不同,方案數要乘以 \(2\)。
考慮插入的兩個點之間沒有邊的情況。分別考慮兩個點以什麼形式插入。
-
兩個點都形成三元環,多了 \(2\) 個點 \(4\) 條邊,選到相同邊不影響,方案數為原邊數的平方。
-
一個點成三元環,一個點插入邊,此時兩個點不能選到相同邊,理由看下面的情況。相當於多了 \(2\) 個點 \(3\) 條邊,方案數為原邊數 乘以 原邊數減一。交換兩個點的方案不同,方案數要乘以 \(2\)。
-
兩個點都插入邊。
-
- 插入相同邊,這種情況下有一個點的邊是原邊,一個點的邊要新開。這種情況實際上相當於前面那種情況選到了相同邊,但是這種情況下交換兩個點不影響,所以不能乘以 \(2\)。方案數為原邊數。
-
- 插入不同邊,多了 \(2\) 個點 \(2\) 條邊,方案數為原邊數 乘以 原邊數減一。
將上面幾種的方案數全加起來即為答案。
注意特判一堆奇奇怪怪的邊界情況,記得開夠陣列。
程式碼
#include <iostream>
#define int long long
using namespace std;
const int P = 1000000007;
int n, m, k;
int f[3005][3005];
int C[3005][3005];
void Madd(int& x, int y) { (x += y) >= P ? (x -= P) : 0; }
signed main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
cin >> n >> k >> m;
if (m < n - 1) {
cout << 0 << "\n";
return 0;
}
C[0][0] = 1;
for (int i = 1; i <= 3000; i++) {
for (int j = 0; j <= 3000; j++)
j == 0 ? (C[i][j] = 1) : (C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P);
}
f[1][0] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 0; j <= i * (i - 1) / 2; j++) {
for (int a = 1; a < i; a++) {
for (int b = a - 1; b <= j; b++)
Madd(f[i][j], f[a][b] * C[i - 1][a - 1] % P * C[C[i - a][2]][j - b] % P);
}
f[i][j] = (C[C[i][2]][j] + P - f[i][j]) % P;
}
}
if (n == 1 && m >= 1) {
cout << "0\n";
return 0;
} else if (n == 1) {
cout << (k == 0) << "\n";
return 0;
}
if (n == 2 && m > 1) {
cout << "0\n";
return 0;
} else if (n == 2) {
cout << (k == 0) << "\n";
return 0;
}
if (n == 3 && m > 3) {
cout << "0\n";
return 0;
} else if (n == 3 && m == 3) {
cout << "1\n";
return 0;
} else if (n == 3 && m == 2) {
cout << (k == 0 ? 3 : (k == 1)) << "\n";
return 0;
} else if (n == 3) {
cout << "0\n";
return 0;
}
if (k == 0)
cout << f[n][m] << "\n";
else if (k == 1)
cout << ((m - 2) * f[n - 1][m - 2] % P + (m - 1) * f[n - 1][m - 1] % P) % P << "\n";
else {
int a1 = ((n - 2) * f[n - 2][m - 3] % P + 2 * (m - 2) * f[n - 2][m - 2] % P + 2 * (m - 3) * f[n - 2][m - 3] % P) % P;
int a2 = ((m - 4) * (m - 4) * f[n - 2][m - 4] % P + 2 * (m - 3) * (m - 4) * f[n - 2][m - 3] % P
+ (m - 3) * f[n - 2][m - 3] % P + (m - 2) * (m - 3) * f[n - 2][m - 2] % P) % P;
cout << (a1 + a2) % P << "\n";
}
return 0;
}