点击打开链接
点击打开链接
可以发现,任意时刻,玩家均位于以前两次操作路径的中垂线的交点上。
因此,答案即为使得玩家朝向与初始时第一次一致的时刻,即
360 g c d ( X , 360 ) \frac{360}{gcd(X,360)} gcd(X,360)360
时间复杂度 O ( L o g V ) O(LogV) O(LogV) 。
#include
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int main() {
int n; read(n);
cout << 360 / __gcd(n, 360) << endl;
return 0;
}
记 d p i , j ( i ≥ A , j ≥ B ) dp_{i,j}\ (i\geq A, j\geq B) dpi,j (i≥A,j≥B) ,表示 C = i , D = j C=i,D=j C=i,D=j 时,问题的答案。
则显然有
d p A , j = A j − B dp_{A,j}=A^{j-B} dpA,j=Aj−B
对于 d p i , j ( i > A , j ≥ B ) dp_{i,j}\ (i>A, j\geq B) dpi,j (i>A,j≥B) ,考虑枚举第一行第二个黑色的格子的位置,应当有
d p i , j = d p i − 1 , j × j + ∑ k = 1 j − 1 i k − 1 × ( j − k ) × d p i − 1 , j − k dp_{i,j}=dp_{i-1,j}\times j+\sum_{k=1}^{j-1}i^{k-1}\times (j-k)\times dp_{i-1,j-k} dpi,j=dpi−1,j×j+k=1∑j−1ik−1×(j−k)×dpi−1,j−k
用部分和优化转移,可以做到均摊 O ( 1 ) O(1) O(1) 。
时间复杂度 O ( C × D ) O(C\times D) O(C×D) 。
#include
using namespace std;
const int MAXN = 3e3 + 5;
const int P = 998244353;
typedef long long ll;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int dp[MAXN][MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
int a, b, c, d; read(a), read(b), read(c), read(d);
dp[a][b] = 1;
for (int j = b + 1; j <= d; j++)
dp[a][j] = 1ll * dp[a][j - 1] * a % P;
for (int i = a + 1; i <= c; i++) {
int sum = 0;
for (int j = b; j <= d; j++) {
update(dp[i][j], 1ll * dp[i - 1][j] * j % P);
update(dp[i][j], sum);
sum = 1ll * sum * i % P;
update(sum, 1ll * dp[i - 1][j] * j % P);
}
}
cout << dp[c][d] << endl;
return 0;
}
将字符串中的 0 0 0 看做分隔符,记序列 a i a_i ai 表示相邻的两个 0 0 0 之间 1 1 1 的个数。
则一次操作相当于选择 ( i , j ) ( i < j ) (i,j)\ (i
由于合法的序列 a i a_i ai 与合法的字符串一一对应,考虑对序列 a i a_i ai 计数。
则记 d p i , j , k dp_{i,j,k} dpi,j,k 表示决定了 a 1 , a 2 , … , a i a_1,a_2,\dots,a_i a1,a2,…,ai ,其相较于原字符串总的增加量为 j j j ,进行了 k k k 次操作的方案数,显然有 O ( ∣ S ∣ ) O(|S|) O(∣S∣) 转移,则可以得到一个 O ( ∣ S ∣ 4 ) O(|S|^4) O(∣S∣4) 的做法。
用部分和优化转移,时间复杂度 O ( ∣ S ∣ 3 ) O(|S|^3) O(∣S∣3) 。
#include
using namespace std;
const int MAXN = 305;
const int P = 998244353;
typedef long long ll;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
char s[MAXN];
int n, l, m, a[MAXN];
int dp[MAXN][MAXN][MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
scanf("\n%s%d", s + 1, &m);
int last = 0; l = strlen(s + 1), chkmin(m, l);
for (int i = 1; i <= l + 1; i++)
if (s[i] != '1') {
a[++n] = i - last - 1;
last = i;
}
dp[0][0][0] = 1;
for (int i = 1; i <= n; i++) {
static int b[MAXN][MAXN], c[MAXN][MAXN];
memset(b, 0, sizeof(b));
memset(c, 0, sizeof(c));
for (int j = 0; j <= l; j++)
for (int k = 0; k <= m; k++) {
int tmp = dp[i - 1][j][k];
if (tmp == 0) continue;
update(b[j - min(a[i], j)][k], tmp);
update(b[j + 1][k], P - tmp);
update(c[j + 1][k + 1], tmp);
}
for (int j = 0; j <= l; j++)
for (int k = 0; k <= m; k++) {
if (j != 0) update(b[j][k], b[j - 1][k]);
update(dp[i][j][k], b[j][k]);
}
for (int j = 0; j <= l; j++)
for (int k = 0; k <= m; k++) {
if (j != 0 && k != 0) update(c[j][k], c[j - 1][k - 1]);
update(dp[i][j][k], c[j][k]);
}
}
int ans = 0;
for (int i = 0; i <= m; i++)
update(ans, dp[n][0][i]);
cout << ans << endl;
return 0;
}
考虑如何判断一个给定的字符串是否可以被生成。
令所判断的字符串为 T T T ,考虑 S , T S,T S,T 的最后一个字符:
若最后一个字符相同,则 S S S 能够生成 T T T 当且仅当删去一个字符的 S S S 可以生成删去一个字符的 T T T 。
若最后一个字符不同,则必须要通过给定的操作在此处插入一个字符。
对此判定过程设计状态 ( i , j , k ) (i,j,k) (i,j,k) ,表示当前 ∣ T ∣ = i |T|=i ∣T∣=i ,需要通过操作插入 j j j 个 0 0 0 , k k k 个 1 1 1 。
设计动态规划对各个状态的个数进行计数,显然有 O ( 1 ) O(1) O(1) 转移。
考虑如何判断状态 ( i , j , k ) (i,j,k) (i,j,k) 是否对应着可以生成的字符串 T T T 。则我们要求在仅对字符串的前 ∣ S ∣ − ( i − j − k ) |S|-(i-j-k) ∣S∣−(i−j−k) 位进行操作的情况下,可以向后插入 j j j 个 0 0 0 , k k k 个 1 1 1 。
对于 S S S 中的前 2 2 2 个字符,我们可以选择进行一次操作,将其中一个字符插入到后面,也可以选则将任意一个字符插入到前 ∣ S ∣ − ( i − j − k ) |S|-(i-j-k) ∣S∣−(i−j−k) 个字符中,有了这样的字符,我们便可以选择操作 S S S 中的第 1 1 1 个字符和一个被事先插入的字符了。
因此,记 d p i , j , k dp_{i,j,k} dpi,j,k 表示对 S S S 的前 i i i 个字符进行操作,可以向后插入 j j j 个 0 0 0 , k k k 个 1 1 1 的情况下,还可以向前 ∣ S ∣ − ( i − j − k ) |S|-(i-j-k) ∣S∣−(i−j−k) 个字符中至多可以插入的字符数,同样有 O ( 1 ) O(1) O(1) 转移。
时间复杂度 O ( ∣ S ∣ 3 ) O(|S|^3) O(∣S∣3) 。
#include
using namespace std;
const int MAXN = 305;
const int P = 998244353;
typedef long long ll;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
char s[MAXN]; int n;
int dp[MAXN][MAXN][MAXN];
int vis[MAXN][MAXN][MAXN];
bool res[MAXN][MAXN][MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
bool check(int x, int y, int z) {
return res[x][y][z];
}
int main() {
scanf("%s", s + 1), n = strlen(s + 1), dp[n][0][0] = 1;
for (int i = n; i >= 1; i--)
for (int j = 0; j + (n - i) <= n - 1; j++)
for (int k = 0; j + k + (n - i) <= n - 1; k++)
if (s[i] == '0') {
update(dp[i - 1][j][k], dp[i][j][k]);
update(dp[i][j][k + 1], dp[i][j][k]);
} else {
update(dp[i - 1][j][k], dp[i][j][k]);
update(dp[i][j + 1][k], dp[i][j][k]);
}
memset(vis, -1, sizeof(vis));
vis[0][0][0] = 0;
for (int i = 0; i <= n; i++)
for (int j = 0; j <= i / 2; j++)
for (int k = 0; j + k <= i / 2; k++) {
if (vis[i][j][k] == -1) continue;
int tmp = vis[i][j][k];
if (i + 1 <= n && tmp != 0) {
if (s[i + 1] == '0') chkmax(vis[i + 1][j + 1][k], tmp - 1);
if (s[i + 1] == '1') chkmax(vis[i + 1][j][k + 1], tmp - 1);
}
if (i + 2 <= n) {
if (s[i + 1] == '0' || s[i + 2] == '0') chkmax(vis[i + 2][j + 1][k], tmp);
if (s[i + 1] == '1' || s[i + 2] == '1') chkmax(vis[i + 2][j][k + 1], tmp);
chkmax(vis[i + 2][j][k], tmp + 1);
}
}
for (int i = 0; i <= n; i++)
for (int j = n; j >= 0; j--)
for (int k = n; k >= 0; k--) {
res[i][j][k] = vis[i][j][k] != -1;
if (i != 0) res[i][j][k] |= res[i - 1][j][k];
res[i][j][k] |= res[i][j + 1][k];
res[i][j][k] |= res[i][j][k + 1];
}
int ans = 0;
for (int i = 0; i <= n; i++)
for (int j = 0; j + (n - i) <= n; j++)
for (int k = 0; j + k + (n - i) <= n; k++)
if (dp[i][j][k] && (n - i) + j + k != 0) {
if (check(i, j, k)) {
update(ans, dp[i][j][k]);
}
}
cout << ans << endl;
return 0;
}
考虑如何判断答案是否为 − 1 -1 −1 。
则可以发现,答案为 − 1 -1 −1 当且仅当
2 × min { a i } + 1 ≤ max { a i } 2\times \min\{a_i\}+1\leq \max\{a_i\} 2×min{ai}+1≤max{ai}
对不不满足以上条件的情况,考虑出现次数最少的元素,不难构造一种方案。
由于我们需要确定字典序最小的解,我们还需要能够判定以给定排列 { p i } \{p_i\} {pi} 开头的的方案是否存在。那么,令 a i a_i ai 表示剩余各个元素的出现次数,若
2 × min { a i } ≥ max { a i } 2\times \min\{a_i\}\geq \max\{a_i\} 2×min{ai}≥max{ai}
则显然是存在方案的,而相比于刚开始的判定问题,不同的是,若
2 × min { a i } + 1 = max { a i } 2\times \min\{a_i\}+1=\max\{a_i\} 2×min{ai}+1=max{ai}
也是有可能存在方案的。
进一步地,此时,存在方案当且仅当在 { p i } \{p_i\} {pi} 中,使得 a i a_i ai 取到最大的 i i i 均出现在使得 a i a_i ai 取到最小的 i i i 的前面。由此,枚举接在 p i p_i pi 后的新排列的长度,我们可以贪心地找到能够拼接上去的,字典序最小的序列。
时间复杂度 O ( ( ∑ a i ) × K 2 L o g K ) O((\sum a_i)\times K^2LogK) O((∑ai)×K2LogK) 。
#include
using namespace std;
const int MAXN = 1005;
typedef long long ll;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, m, s, a[MAXN];
int x, y, cur[MAXN], res[MAXN], ans[MAXN];
bool cnp(int s, int t) {
if ((a[s] == x) == (a[t] == x)) return s < t;
else return (a[s] == x) < (a[t] == x);
}
void work(int len) {
bool valid = true, found = false;
for (int i = 1; i <= len; i++)
a[cur[i]]--;
int Min = a[1], Max = a[1];
for (int i = 2; i <= n; i++) {
chkmin(Min, a[i]);
chkmax(Max, a[i]);
}
if (Min * 2 >= Max) {
found = true;
for (int i = 1; i <= len; i++)
res[i] = cur[i];
sort(res + 1, res + len + 1);
} else if (Min * 2 + 1 == Max) {
x = Min, y = Max;
int k1 = 0, k2 = 0, k = 0;
static int res1[MAXN], res2[MAXN];
for (int i = 1; i <= len; i++)
if (a[cur[i]] == Min || a[cur[i]] == Max) res1[++k1] = cur[i];
else res2[++k2] = cur[i];
sort(res1 + 1, res1 + k1 + 1, cnp);
sort(res2 + 1, res2 + k2 + 1);
int x1 = 1, x2 = 1;
for (int i = 1; i <= len; i++)
if (x1 <= k1 && x2 <= k2) {
if (res1[x1] < res2[x2]) res[i] = res1[x1++];
else res[i] = res2[x2++];
} else {
if (x1 <= k1) res[i] = res1[x1++];
else res[i] = res2[x2++];
}
found = true;
for (int i = len + 1; i <= n; i++)
if (a[cur[i]] == Min) found = false;
found |= (x1 == 0) || (a[res1[1]] == Max);
}
for (int i = 1; i <= len; i++)
a[cur[i]]++;
if (!valid || !found) res[1] = 0;
}
bool cmp(int *a, int *b) {
int pos = 1;
while (a[pos] == b[pos]) pos++;
return a[pos] < b[pos];
}
bool check() {
int Min = a[1], Max = a[1];
for (int i = 2; i <= n; i++) {
chkmin(Min, a[i]);
chkmax(Max, a[i]);
}
return Min * 2 >= Max;
}
int main() {
read(n);
for (int i = 1; i <= n; i++) {
read(a[i]);
s += a[i];
}
if (!check()) {
puts("-1");
return 0;
}
for (int i = 1; i <= n; i++)
cur[i] = i;
work(n);
for (int i = 1; i <= n; i++) {
m++, a[res[i]]--;
cur[i] = res[i];
ans[m] = res[i];
}
while (m != s) {
static int inc[MAXN];
memset(inc, 0, sizeof(inc));
int len = 0; inc[1] = n + 1;
for (int i = 1; i <= n; i++) {
work(i);
if (res[1] != 0 && cmp(res, inc)) {
len = i;
for (int j = 1; j <= i; j++)
inc[j] = res[j];
}
}
for (int i = 1; i + len <= n; i++)
cur[i] = cur[i + len];
for (int i = 1; i <= len; i++) {
ans[++m] = inc[i];
cur[n - len + i] = inc[i], a[inc[i]]--;
}
}
for (int i = 1; i <= m; i++)
printf("%d ", ans[i]);
printf("\n");
return 0;
}
可以参考问题 Codeforces 1338E JYPnation 。
引理: 重复删去图中入度为 1 1 1 的点,剩余的图是强连通的。并且,对于强连通的合法竞赛图,可以找到唯一的一条哈密尔顿回路,使得每个点的出边均指向其在回路上的一段后继。
关于引理的证明可以参考官方题解。
由此,枚举删去图中入度为 1 1 1 的点的次数,便只需要解决对于强连通的合法竞赛图的计数问题。
固定哈密尔顿回路,并枚举 1 1 1 号节点的出度 d e g deg deg ,记 d p i , j dp_{i,j} dpi,j 表示当前处理到节点 i i i ,其出度为 j − i j-i j−i ,且此时方案仍然合法的方案数,朴素的转移是 O ( N ) O(N) O(N) 的。
时间复杂度 O ( N 5 ) O(N^5) O(N5) 。
#include
using namespace std;
const int MAXN = 205;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, k, P, fac[MAXN], inv[MAXN];
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
int binom(int x, int y) {
if (y > x) return 0;
else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = 1ll * fac[i - 1] * i % P;
inv[n] = power(fac[n], P - 2);
for (int i = n - 1; i >= 0; i--)
inv[i] = inv[i + 1] * (i + 1ll) % P;
}
int solve(int n, int k) {
int ans = 0;
for (int deg = 1; deg <= k && deg <= n - 2; deg++) {
static int dp[MAXN][MAXN];
if ((n - 1) - deg > k) continue;
memset(dp, 0, sizeof(dp)), dp[0][deg] = 1;
for (int i = 0; i <= deg - 1; i++)
for (int j = deg; j <= n - 1; j++) {
int tmp = dp[i][j];
for (int t = max(j, i + 2); t <= n - 1; t++) {
int tnp = t - (i + 1);
if (tnp <= k && (n - 1) - tnp <= k) update(dp[i + 1][t], tmp);
}
}
for (int i = deg; i <= n - 1; i++)
update(ans, dp[deg][i]);
}
return 1ll * ans * fac[n - 1] % P;
}
int main() {
read(n), read(k), read(P);
init(n); int ans = (k == n - 1) ? fac[n] : 0;
for (int i = 3; i <= n; i++)
update(ans, 1ll * fac[n] * inv[i] % P * solve(i, k - (n - i)) % P);
cout << ans << endl;
return 0;
}
用部分和优化转移,时间复杂度 O ( N 4 ) O(N^4) O(N4) 。
#include
using namespace std;
const int MAXN = 205;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template void chkmax(T &x, T y) {x = max(x, y); }
template void chkmin(T &x, T y) {x = min(x, y); }
template void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, k, P, fac[MAXN], inv[MAXN];
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
int binom(int x, int y) {
if (y > x) return 0;
else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = 1ll * fac[i - 1] * i % P;
inv[n] = power(fac[n], P - 2);
for (int i = n - 1; i >= 0; i--)
inv[i] = inv[i + 1] * (i + 1ll) % P;
}
int solve(int n, int k) {
int ans = 0;
for (int deg = 1; deg <= k && deg <= n - 2; deg++) {
static int dp[MAXN][MAXN];
if ((n - 1) - deg > k) continue;
memset(dp, 0, sizeof(dp));
dp[0][deg] = 1, dp[0][deg + 1] = P - 1;
for (int i = 0; i <= deg - 1; i++)
for (int j = deg; j <= n - 1; j++) {
update(dp[i][j], dp[i][j - 1]);
int tmp = dp[i][j], l = max(max(j, i + 2), n + i - k), r = min(n - 1, k + (i + 1));
if (l <= r) update(dp[i + 1][l], tmp), update(dp[i + 1][r + 1], P - tmp);
}
for (int i = deg; i <= n - 1; i++) {
update(dp[deg][i], dp[deg][i - 1]);
update(ans, dp[deg][i]);
}
}
return 1ll * ans * fac[n - 1] % P;
}
int main() {
read(n), read(k), read(P);
init(n); int ans = (k == n - 1) ? fac[n] : 0;
for (int i = 3; i <= n; i++)
update(ans, 1ll * fac[n] * inv[i] % P * solve(i, k - (n - i)) % P);
cout << ans << endl;
return 0;
}