ARC
[ARC058E] 和風いろはちゃん
\(\color{#9d3dcf}10.4\)
直接做容易算重 \(\Rightarrow\) 正难则反,求有多少个序列没有俳句。
\(X,Y,Z\) 都非常小,考虑状压,\(f_{n,S}\) 表示前 \(n\) 个数,\(x\in S\) 表示存在一个后缀和为 \(x\)。
俳句代表的状态即为 \(\{z,y+z,x+y+z\}\),不向包含这个状态的 \(S\) 转移即可,复杂度为 \(\mathcal O(nV2^{X+Y+Z})\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int p = 1e9 + 7;
int n, m, x, y, z, e, ans = 1, f[41][1 << 17];
int main() {
cin >> n >> x >> y >> z, m = (1 << (x + y + z)) - 1, e = (1 << (z - 1)) | (1 << (y + z - 1)) | (1 << (x + y + z - 1));
f[0][0] = 1;
for (int i = 1; i <= n; i++, ans = ans * 10ll % p) {
for (int j = 0; j <= m; j++) {
for (int k = 1; k <= 10; k++) {
int s = ((j << k) | (1 << (k - 1))) & m;
if ((s | e) != s) f[i][s] = (f[i][s] + f[i - 1][j]) % p;
}
}
}
for (int i = 0; i <= m; i++) ans = (ans + p - f[n][i]) % p;
cout << ans << '\n';
}
[ARC059E] キャンディーとN人の子供
\(\color{#52c41a}8\)
一开始想成糖果有序了,憋憋。
设 \(f_{i,j}\) 表示前 \(i\) 个人分了 \(j\) 个糖果的答案,有:
前缀和优化即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 405, p = 1e9 + 7;
int n, c, a[N], b[N], s[N][N], f[N][N];
int main() {
scanf("%d%d", &n, &c);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
for (int i = 1; i < N; i++) {
s[i][0] = 1; for (int j = 1; j < N; j++) s[i][j] = (ll)s[i][j - 1] * i % p;
for (int j = 0; j < N; j++) s[i][j] = (s[i][j] + s[i - 1][j]) % p;
}
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= c; j++) {
for (int k = 0; k <= j; k++) f[i][j] = (f[i][j] + (ll)f[i - 1][j - k] * (s[b[i]][k] - s[a[i] - 1][k] + p)) % p;
}
}
printf("%d\n", f[n][c]);
}
[ARC059F] バイナリハック
\(\color{#3498db}9.2\)
首先字符串是什么不重要。设 \(f_{i,j}\) 表示按了 \(i\) 下,敲了长度为 \(j\) 的串的方案数。有:
分别表示按退格和打字符的操作。注意 \(f_{i,0}=2f_{i-1,1}+f_{i-1,0}\),代表没有字符时按了退格。
直接 dp 即可,复杂度为 \(\mathcal O(n^2)\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5005, p = 1e9 + 7;
int n, m, f[N][N]; char s[N];
int main() {
scanf("%d%s", &n, s + 1), m = strlen(s + 1);
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= i; j++) f[i][j] = (f[i - 1][j + 1] * 2ll + f[i - 1][j ? j - 1 : 0]) % p;
}
printf("%d\n", f[n][m]);
}
[ARC060F] 最良表現
\(\color{#9d3dcf}10.4\)
这个题乍看毫无头绪,手玩几个串可以发现,答案只可能是 \(1,2,n\) 之一:
- 如果所有字符均相同,则答案为 \(n\);
- 如果没有循环节,则答案为 \(1\);
- 否则可以选择一个最小循环节,并将其从中间劈开,这样答案是 \(2\)。
前两种直接判掉,第三种枚举断点,判断前后缀是否有循环节,做两次 KMP 即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, ans, nxt[N], txn[N]; char s[N];
void KMP(char s[], int nxt[]) {
for (int i = 2, j = 0; i <= n; i++) {
while (j && s[j + 1] != s[i]) j = nxt[j];
if (s[j + 1] == s[i]) j++; nxt[i] = j;
}
}
int main() {
scanf("%s", s + 1), n = strlen(s + 1);
KMP(s, nxt), reverse(s + 1, s + 1 + n), KMP(s, txn);
if (nxt[n] == n - 1) return printf("%d\n1\n", n), 0;
if (!nxt[n] || n % (n - nxt[n])) return printf("1\n1\n"), 0;
puts("2");
for (int i = 1; i < n; i++) ans += (!nxt[i] || i % (i - nxt[i])) && (!txn[n - i] || (n - i) % (n - i - txn[n - i]));
printf("%d\n", ans);
}
/*
手玩一下发现答案只有 1, 2, n 三种
解释的话,如果所有元素均相同,那么答案只能是 n
如果没有循环节,那么保留整个就可以,答案是 1
否则找到一个最小循环节,在它的中间劈开即可
那从前往后从后往前做两次 KMP 就能求前缀/后缀的循环节长度了
*/
[ARC099E] Independence
\(\color{#9d3dcf}10.6\)
Trick:团转成独立集
分成两部分相当于黑白染色,考虑原图的补图,相当于同一种颜色的点之间不能有连边,即二分图。
容易推出黑白点个数的差越小越好,由于连通二分图的染法只有颜色相反的两种,对每个连通块黑白染色,然后做背包即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 705;
int n, m, cnt, tot, pnt[N], col[N], x;
bool G[N][N], flg;
bitset<N << 1> f;
void dfs(int u) {
pnt[++tot] = u;
for (int v = 1; v <= n; v++) {
if (G[u][v]) {
if (col[u] == col[v]) flg = 0;
else if (col[v] == -1) col[v] = col[u] ^ 1, dfs(v);
}
}
}
int main() {
scanf("%d%d", &n, &m);
memset(G, 1, sizeof(G)), memset(col, -1, sizeof(col));
for (int i = 1; i <= n; i++) G[i][i] = 0;
for (int i = 1, u, v; i <= m; i++) scanf("%d%d", &u, &v), G[u][v] = G[v][u] = 0;
for (int i = 1; i <= n; i++) {
if (col[i] != -1) continue;
cnt++, flg = 1, tot = col[i] = x = 0, dfs(i);
if (!flg) return puts("-1"), 0;
for (int j = 1; j <= tot; j++) col[pnt[j]] == 0 ? x++ : x--;
x = abs(x);
if (cnt == 1) f[n + x] = f[n - x] = 1;
else f = f << x | f >> x;
}
for (int i = 0; i <= n; i++) {
if (f[n + i] || f[n - i]) {
int n1 = (n + i) / 2, n2 = n - n1;
printf("%d\n", n1 * (n1 - 1) / 2 + n2 * (n2 - 1) / 2);
return 0;
}
}
}
[ARC112D] Skate
\(\color{#3498db}9.8\)
首先边界之间都是可以互相到的,另外一块地 \((x,y)\) 的作用是使得第 \(x\) 行和第 \(y\) 列可以互相到,考虑图论建模,对于可以互相到的行列 \(x\) 和 \(y\),在 \((x,y+n)\) 之间连一条无向边,问题变成需要加多少遍使得行能互到或列能互到,并查集维护。
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
int n, m, fa[N << 1], c1, c2;
char s[N];
bool vis[N << 1];
int get(int x) { return x == fa[x] ? x : fa[x] = get(fa[x]); }
void merge(int x, int y) { fa[get(x)] = get(y); }
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n + m; i++) fa[i] = i;
merge(1, n + 1), merge(1, n + m), merge(n, n + 1), merge(n, n + m);
for (int i = 1; i <= n; i++) {
scanf("%s", s + 1);
for (int j = 1; j <= m; j++) if (s[j] == '#') merge(i, n + j);
}
for (int i = 1; i <= n; i++) {
int x = get(i);
if (!vis[x]) vis[x] = 1, c1++;
}
memset(vis, 0, sizeof(vis));
for (int i = n + 1; i <= n + m; i++) {
int x = get(i);
if (!vis[x]) vis[x] = 1, c2++;
}
printf("%d\n", min(c1, c2) - 1);
}
浙公网安备 33010602011771号