Atcoder Grand Contest 30
Problem C. Coloring Torus
考虑
K
K
K 是
4
4
4 的倍数的情况,取偶数
N
=
K
2
N=\frac{K}{2}
N=2K ,令
a
i
,
j
=
(
i
+
j
)
%
N
+
1
+
i
%
2
×
N
a_{i,j}=(i+j)\%N+1+i\%2\times N
ai,j=(i+j)%N+1+i%2×N
不难发现这是一组可行解,并且将所有 i + N i+N i+N 变为 i i i 后,依然是一个可行解。
由此,我们可以处理 K K K 不是 4 4 4 的倍数的情况。
时间复杂度 O ( K 2 ) O(K^2) O(K2) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 505;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
int main() {
read(k), n = (k + 1) / 2;
if (k == 1) {
puts("1");
puts("1");
return 0;
}
if (n & 1) n += 1;
cout << n << endl;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
if (i & 1) printf("%d ", (i + j) % n + 1);
else if ((i + j) % n + 1 + n > k) printf("%d ", (i + j) % n + 1);
else printf("%d ", (i + j) % n + 1 + n);
printf("\n");
}
return 0;
}
Problem D. Inversion Sum
考虑倒序 DP ,记 d p i , j , k dp_{i,j,k} dpi,j,k 表示考虑最后 i i i 次交换,当前 a a a 在 j j j 处, b b b 在 k k k 处,使得最终 a a a 排在 b b b 之后的操作序列个数。分开考虑每一对数的贡献,则由 d p Q , ∗ , ∗ dp_{Q,*,*} dpQ,∗,∗ ,可以直接计算出答案。
直接进行 DP 是 O ( Q N 2 ) O(QN^2) O(QN2) 的,但注意到一次交换操作时,除了 O ( N ) O(N) O(N) 个位置,剩余位置的 DP 值均为原有的 DP 值 × 2 \times 2 ×2 ,从而可以维护全局乘标记来优化转移。
时间复杂度 O ( N 2 + N Q ) O(N^2+NQ) O(N2+NQ) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3005;
const int P = 1e9 + 7;
const int inv2 = (P + 1) / 2;
typedef long long ll;
template <typename T> 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 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 n, q, a[MAXN], x[MAXN], y[MAXN];
int mul, inv, dp[MAXN][MAXN];
int main() {
read(n), read(q);
for (int i = 1; i <= n; i++)
read(a[i]);
for (int i = 1; i <= q; i++)
read(x[i]), read(y[i]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
dp[i][j] = i > j;
mul = inv = 1;
for (int i = q; i >= 1; i--) {
int tmp = 1ll * inv2 * inv % P;
for (int j = 1; j <= n; j++)
if (j != x[i] && j != y[i]) {
dp[x[i]][j] = dp[y[i]][j] = 1ll * mul * (dp[x[i]][j] + dp[y[i]][j]) % P * tmp % P;
dp[j][x[i]] = dp[j][y[i]] = 1ll * mul * (dp[j][x[i]] + dp[j][y[i]]) % P * tmp % P;
}
dp[x[i]][y[i]] = dp[y[i]][x[i]] = 1ll * mul * (dp[x[i]][y[i]] + dp[y[i]][x[i]]) % P * tmp % P;
mul = 2ll * mul % P, inv = tmp;
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
dp[i][j] = 1ll * dp[i][j] * mul % P;
int ans = 0;
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
if (a[i] < a[j]) ans = (ans + dp[i][j]) % P;
else if (a[i] > a[j]) ans = (ans + dp[j][i]) % P;
cout << ans << endl;
return 0;
}
Problem E. Less than 3
考虑用每一段连续的颜色的长度以及开头颜色来描述数组。
对于一次不在开头处的合法操作,相当于交换了连续的一对 1 , 2 1,2 1,2 。
对于一次在开头或结尾处的操作,相当于一次 1 , 1 1,1 1,1 和 2 2 2 和互换,同时会改变所操作端的颜色。
考虑不对两头进行操作的情况,则答案显然为对应 2 2 2 出现位置差的和。
那么,枚举在开头处的操作次数,首先进行有关开头的操作,可以类似地计算对应代价。
这样的做法需要将字符串反向再做一次,这是因为最优策略可能是先操作头,也可能是先操作尾。
时间复杂度 O ( N 2 ) O(N^2) O(N2) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e3 + 5;
const int INF = 1e9;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
vector <int> a, b;
char s[MAXN], t[MAXN];
bool fail(vector <int> &a, int cnt, int &ans) {
vector <int> tmp = a;
if (cnt == 0) return false;
if (cnt >= 0) {
int now = 0;
while (cnt > 0) {
if (a.empty()) return true;
if (a.back() == 1) now++;
else {
cnt--;
ans += now + 1;
now += 2;
}
a.pop_back();
}
while (now--) a.push_back(1);
} else {
cnt = -cnt;
int now = 0, state = 0;
while (cnt > 0) {
if (a.empty()) return true;
if (a.back() == 2) now++;
else if (state == 0) state = 1, ans += now;
else {
ans += now + 1;
cnt--, now++;
state = 0;
}
a.pop_back();
}
while (now--) a.push_back(2);
}
return false;
}
void debug(vector <int> &a) {
for (auto x : a)
cerr << x << ' ';
cerr << endl;
}
int func(int x) {
int y = b.size() - x - a.size();
vector <int> c = a; int ans = 0;
reverse(c.begin(), c.end());
if (fail(c, x, ans)) return INF;
reverse(c.begin(), c.end());
if (fail(c, y, ans)) return INF;
vector <int> e, d;
for (unsigned i = 0; i < b.size(); i++) {
if (b[i] == 2) d.push_back(i);
if (c[i] == 2) e.push_back(i);
}
assert(d.size() == e.size());
for (unsigned i = 0; i < d.size(); i++)
ans += abs(d[i] - e[i]);
return ans;
}
int main() {
read(n);
scanf("\n%s", s + 1);
scanf("\n%s", t + 1);
if (n == 1) {
if (s[1] != t[1]) cout << 1 << endl;
else cout << 0 << endl;
return 0;
}
for (int i = 1; i <= n; i++) {
if (s[i] == s[i - 1]) {
a.pop_back();
a.push_back(2);
} else a.push_back(1);
if (t[i] == t[i - 1]) {
b.pop_back();
b.push_back(2);
} else b.push_back(1);
}
int ans = INF;
for (int i = -n; i <= n; i++)
if ((s[1] == t[1]) ^ (i % 2 != 0)) chkmin(ans, func(i));
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());
for (int i = -n; i <= n; i++)
if ((s[n] == t[n]) ^ (i % 2 != 0)) chkmin(ans, func(i));
cout << ans << endl;
return 0;
}
Problem F. Permutation and Minimum
首先,我们可以将已经确定 B i B_i Bi 对应的 A i A_i Ai 从数组中删去,此后,一个 B i B_i Bi 只可能对应一对 − 1 -1 −1 ,或者一个 − 1 -1 −1 ,一个数值。
令 C C C 表示对应一对 − 1 -1 −1 的 B i B_i Bi 的总数,我们可以令这些 B i B_i Bi 是无序的,再将最终答案 × C ! \times C! ×C! 。
不难发现此时我们只关心某个数值是否在 A i A_i Ai 中出现过,记为 v i s i vis_i visi 。
考虑从小到大向 A A A 中填入数字,进行动态规划。
将数字 x x x 填入一个尚未填入的组表示使其成为 B i B_i Bi ,我们需要决策与 x x x 成为一组的数值的 v i s vis vis , v i s vis vis 同为 t r u e true true 的两个数字不能出现在同一组。
将数字 x x x 填入一个已经填入的组不会使其成为 B i B_i Bi ,但若 v i s x = t r u e vis_x=true visx=true ,则需要决策与 x x x 配对的数字。
记 d p i , j , k dp_{i,j,k} dpi,j,k 表示已经填入 1 ∼ i 1\sim i 1∼i ,存在 j + k j+k j+k 个已经填入一个数字的组,且有 j j j 组组内的另一个数字的 v i s vis vis 为 t r u e true true , k k k 组组内的另一个数字的 v i s vis vis 为 f a l s e false false 。
那么,对于
v
i
s
=
t
r
u
e
vis=true
vis=true 的位置,有
d
p
i
,
j
,
k
⇒
d
p
i
+
1
,
j
,
k
+
1
j
×
d
p
i
,
j
,
k
⇒
d
p
i
+
1
,
j
−
1
,
k
dp_{i,j,k}\Rightarrow dp_{i+1,j,k+1}\\j\times dp_{i,j,k}\Rightarrow dp_{i+1,j-1,k}
dpi,j,k⇒dpi+1,j,k+1j×dpi,j,k⇒dpi+1,j−1,k
对于
v
i
s
=
f
a
l
s
e
vis=false
vis=false 的位置,有
d
p
i
,
j
,
k
⇒
d
p
i
+
1
,
j
,
k
+
1
d
p
i
,
j
,
k
⇒
d
p
i
+
1
,
j
+
1
,
k
d
p
i
,
j
,
k
⇒
d
p
i
+
1
,
j
,
k
−
1
dp_{i,j,k}\Rightarrow dp_{i+1,j,k+1}\\dp_{i,j,k}\Rightarrow dp_{i+1,j+1,k}\\dp_{i,j,k}\Rightarrow dp_{i+1,j,k-1}
dpi,j,k⇒dpi+1,j,k+1dpi,j,k⇒dpi+1,j+1,kdpi,j,k⇒dpi+1,j,k−1
时间复杂度 O ( N 3 ) O(N^3) O(N3) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 305;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
bool vis[MAXN * 2];
int n, a[MAXN * 2], dp[MAXN * 2][MAXN][MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
read(n);
for (int i = 1; i <= n * 2; i++) {
read(a[i]);
if (a[i] == -1) a[i] = 0;
}
for (int i = 1; i <= n * 2; i += 2) {
if (a[i] != 0 && a[i + 1] != 0) {
int x = a[i], y = a[i + 1];
for (int j = 1; j <= n * 2; j++)
a[j] -= (a[j] >= x) + (a[j] >= y);
for (int j = i; j <= n * 2; j++)
a[j] = a[j + 2];
n--, i -= 2;
}
}
int now = 0, ans = 1;
for (int i = 1; i <= n * 2; i += 2) {
vis[a[i]] = vis[a[i + 1]] = true;
if (a[i] == 0 && a[i + 1] == 0) ans = 1ll * ans * ++now % P;
}
dp[1][0][0] = 1;
for (int i = 1; i <= n * 2; i++) {
for (int j = 0; j <= n; j++)
for (int k = 0; j + k <= n; k++) {
int tmp = dp[i][j][k];
if (tmp == 0) continue;
if (vis[i]) {
update(dp[i + 1][j][k + 1], tmp);
if (j) update(dp[i + 1][j - 1][k], 1ll * j * tmp % P);
} else {
update(dp[i + 1][j][k + 1], tmp);
update(dp[i + 1][j + 1][k], tmp);
if (k) update(dp[i + 1][j][k - 1], tmp);
}
}
}
cout << 1ll * ans * dp[n * 2 + 1][0][0] % P << endl;
return 0;
}
Atcoder Grand Contest 31
Problem D. A Sequence of Permutations
记置换 i → p i i\rightarrow p_i i→pi 为置换 p p p ,定义 p − 1 p^{-1} p−1 表示 p i → i p_i\rightarrow i pi→i , p × q p\times q p×q 表示 i → q p i i\rightarrow q_{p_i} i→qpi 。
那么
a
1
=
p
a
2
=
q
a
3
=
p
−
1
q
a
4
=
q
−
1
p
−
1
q
a
5
=
q
−
1
p
q
−
1
p
−
1
q
a
6
=
q
−
1
p
2
q
−
1
p
−
1
q
a
7
=
q
−
1
p
q
p
q
−
1
p
−
1
q
a
8
=
q
−
1
p
q
p
−
1
q
p
q
−
1
p
−
1
q
a_1=p\\a_2=q\\a_3=p^{-1}q\\a_4=q^{-1}p^{-1}q\\a_5=q^{-1}pq^{-1}p^{-1}q\\a_6=q^{-1}p^2q^{-1}p^{-1}q\\a_7=q^{-1}pqpq^{-1}p^{-1}q\\a_8=q^{-1}pqp^{-1}qpq^{-1}p^{-1}q
a1=pa2=qa3=p−1qa4=q−1p−1qa5=q−1pq−1p−1qa6=q−1p2q−1p−1qa7=q−1pqpq−1p−1qa8=q−1pqp−1qpq−1p−1q
注意到令
r
=
q
−
1
p
q
r=q^{-1}pq
r=q−1pq ,有
a
7
=
r
p
r
−
1
,
a
8
=
r
p
−
1
q
p
r
−
1
a_7=rpr^{-1},a_8=rp^{-1}qpr^{-1}
a7=rpr−1,a8=rp−1qpr−1
并且
f
(
r
p
r
−
1
,
r
q
r
−
1
)
=
r
f
(
p
,
q
)
r
−
1
f(rpr^{-1},rqr^{-1})=rf(p,q)r^{-1}
f(rpr−1,rqr−1)=rf(p,q)r−1
可将
6
6
6 个操作为一周期考虑,令
K
=
6
m
+
1
K=6m+1
K=6m+1 ,则答案为
r
p
r
−
1
rpr^{-1}
rpr−1 ,其中
r
=
(
q
−
1
p
q
p
−
1
)
m
p
m
+
1
r=(q^{-1}pqp^{-1})^{m}p^{m+1}
r=(q−1pqp−1)mpm+1
时间复杂度 O ( N L o g K ) O(NLogK) O(NLogK) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
struct info {int a[MAXN]; };
int n; ll m;
info inv(info a) {
info res;
for (int i = 1; i <= n; i++)
res.a[a.a[i]] = i;
return res;
}
info unit() {
info res;
for (int i = 1; i <= n; i++)
res.a[i] = i;
return res;
}
info operator * (info a, info b) {
info res;
for (int i = 1; i <= n; i++)
res.a[i] = b.a[a.a[i]];
return res;
}
info p, q, r;
info power(info a, ll x) {
if (x == 0) return unit();
if (x < 0) return power(inv(a), -x);
info tmp = power(a, x / 2);
if (x % 2 == 0) return tmp * tmp;
else return tmp * tmp * a;
}
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++)
read(p.a[i]);
for (int i = 1; i <= n; i++)
read(q.a[i]);
while (m % 6 != 1) {
info r = inv(p) * q;
p = q, q = r, m--;
}
r = power(inv(q) * p * q * inv(p), m / 6) * power(p, m / 6 + 1);
info res = r * p * inv(r);
for (int i = 1; i <= n; i++)
printf("%d ", res.a[i]);
return 0;
}
Problem E. Snuke the Phantom Thief
若只存在 L 和 U 两种限制,则问题可以通过最大费用流解决。
在源汇之间建立 2 N 2N 2N 个点,两两连边,表示是否选取各个珠宝,由于两种限制的各个集合存在包含关系,显然可以建立辅助点限流。在源点与中间点之间对于 L 限制限流,汇点与中间点之间对于 U 限制限流,可以在 O ( M i n C o s t F l o w ( N + V , N + V ) ) O(MinCostFlow(N+V,N+V)) O(MinCostFlow(N+V,N+V)) 内解决问题。
对于原本的问题,我们需要在中间点的一侧对两种限制同时限流。
问题的关键在于枚举总共偷走的珠宝数,一旦枚举了总共偷走的珠宝数 K K K ,对于一个 R 型限制,我们可以将其看做一个 L 型的下界限制,从而用有上下界的费用流解题。
时间复杂度 O ( N × M i n C o s t F l o w ( N + V , N + V ) ) O(N\times MinCostFlow(N+V,N+V)) O(N×MinCostFlow(N+V,N+V)) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 505;
const int MAXV = 100;
const int INF = 1e9;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
namespace MincostFlow {
const int MAXP = 12005;
const int MAXQ = 2e5 + 5;
const long long INF = 1e18;
struct edge {int dest, flow, pos; ll cost; };
vector <edge> a[MAXP];
int n, m, s, t, tot, flow;
ll dist[MAXP], cost; int path[MAXP], home[MAXP];
void FlowPath() {
int p = t, ans = 1e9;
while (p != s) {
ans = min(ans, a[path[p]][home[p]].flow);
p = path[p];
}
flow += ans;
cost += ans * dist[t];
p = t;
while (p != s) {
a[path[p]][home[p]].flow -= ans;
a[p][a[path[p]][home[p]].pos].flow += ans;
p = path[p];
}
}
bool spfa() {
static int q[MAXQ];
static bool inq[MAXP];
static int l = 0, r = 0;
for (int i = 0; i <= r; i++)
dist[q[i]] = -INF;
q[l = r = 0] = s, dist[s] = 0, inq[s] = true;
while (l <= r) {
int tmp = q[l];
for (unsigned i = 0; i < a[tmp].size(); i++)
if (a[tmp][i].flow != 0 && dist[tmp] + a[tmp][i].cost > dist[a[tmp][i].dest]) {
dist[a[tmp][i].dest] = dist[tmp] + a[tmp][i].cost;
path[a[tmp][i].dest] = tmp;
home[a[tmp][i].dest] = i;
if (!inq[a[tmp][i].dest]) {
q[++r] = a[tmp][i].dest;
inq[q[r]] = true;
}
}
l++, inq[tmp] = false;
}
return dist[t] != -INF;
}
void addedge(int x, int y, int z, ll c) {
a[x].push_back((edge){y, z, a[y].size(), c});
a[y].push_back((edge){x, 0, a[x].size() - 1, -c});
}
ll work(int n, int ns, int nt, int intend) {
flow = cost = 0;
for (int i = 1; i <= n; i++)
dist[i] = -INF;
while (spfa()) FlowPath();
if (flow < intend) return 0;
s = ns, t = nt;
while (spfa() && dist[t] > 0) FlowPath();
return cost;
}
void init() {
for (int i = 1; i <= tot; i++)
a[i].clear();
s = 1; t = tot = 2;
}
}
int l[MAXN], r[MAXN], u[MAXN], d[MAXN];
int s, t, n, m, tot, x[MAXN], y[MAXN]; ll v[MAXN];
int main() {
read(n);
for (int i = 1; i <= n; i++)
read(x[i]), read(y[i]), read(v[i]);
read(m);
for (int i = 0; i <= MAXV + 1; i++)
l[i] = r[i] = u[i] = d[i] = n;
l[0] = d[0] = 0, r[MAXV + 1] = u[MAXV + 1] = 0;
for (int i = 1; i <= m; i++) {
char c; int x, y;
scanf("\n%c%d%d", &c, &x, &y);
if (c == 'L') l[x] = y;
if (c == 'R') r[x] = y;
if (c == 'U') u[x] = y;
if (c == 'D') d[x] = y;
}
for (int i = 1, j = MAXV; i <= MAXV; i++, j--) {
chkmin(r[i], r[i - 1]), chkmin(u[i], u[i - 1]);
chkmin(l[j], l[j + 1]), chkmin(d[j], d[j + 1]);
}
ll ans = 0;
for (int k = 1; k <= n; k++) {
MincostFlow :: init();
s = ++MincostFlow :: tot;
t = ++MincostFlow :: tot;
bool fail = false; int intend = 0;
static int xp[MAXN], yp[MAXN], inp[MAXN], outp[MAXN];
for (int i = 1; i <= n; i++) {
inp[i] = ++MincostFlow :: tot;
outp[i] = ++MincostFlow :: tot;
}
for (int i = 1; i <= MAXV; i++) {
xp[i] = ++MincostFlow :: tot;
yp[i] = ++MincostFlow :: tot;
}
MincostFlow :: addedge(t, s, INF, -1e15);
for (int i = 1; i <= n; i++) {
MincostFlow :: addedge(inp[i], outp[i], 1, v[i]);
MincostFlow :: addedge(xp[x[i]], inp[i], 1, 0);
MincostFlow :: addedge(outp[i], yp[y[i]], 1, 0);
}
xp[MAXV + 1] = s, yp[MAXV + 1] = t;
for (int i = 1; i <= MAXV; i++) {
int Min, Max;
Max = min(l[i], k), Min = max(k - r[i + 1], 0), intend += Min;
if (Min > Max) {fail = true; break; }
MincostFlow :: addedge(xp[i + 1], xp[i], Max - Min, 0);
MincostFlow :: addedge(MincostFlow :: s, xp[i], Min, 0);
MincostFlow :: addedge(xp[i + 1], MincostFlow :: t, Min, 0);
Max = min(d[i], k), Min = max(k - u[i + 1], 0), intend += Min;
if (Min > Max) {fail = true; break; }
MincostFlow :: addedge(yp[i], yp[i + 1], Max - Min, 0);
MincostFlow :: addedge(MincostFlow :: s, yp[i + 1], Min, 0);
MincostFlow :: addedge(yp[i], MincostFlow :: t, Min, 0);
}
if (fail) continue;
else chkmax(ans, MincostFlow :: work(MincostFlow :: tot, s, t, intend));
}
cout << ans << endl;
return 0;
}
Problem F. Walk on Graph
首先考虑从 T T T 走到 S S S ,每经过一条边 c c c 就令 x = 2 x + c x=2x+c x=2x+c 。
令 [ v , x ] [v,x] [v,x] 表示一个在 v v v 处数值为 x x x 的状态,考虑问题的以下性质:
( 1 ) (1) (1) 、考虑在边 ( a , b , c ) (a,b,c) (a,b,c) 上往返,则状态变化形如 [ a , x ] → [ b , 2 x + c ] → [ a , 4 x + 3 c ] [a,x]\rightarrow [b,2x+c]\rightarrow [a,4x+3c] [a,x]→[b,2x+c]→[a,4x+3c] ,可以发现其一定能够回到 [ a , x ] [a,x] [a,x] ,因此可以认为到达关系是双向的,能够相互到达的状态相互等价,因此,我们只需要判断始末状态是否等价。
( 2 ) (2) (2) 、考虑在两条交于点 v v v ,长度为 a , b a,b a,b 的边上往返,则有 [ v , x ] = [ v , 4 x + 3 a ] = [ v , 4 x + 3 b ] [v,x]=[v,4x+3a]=[v,4x+3b] [v,x]=[v,4x+3a]=[v,4x+3b] ,又因为 4 4 4 存在逆元,状态 [ v , x ] [v,x] [v,x] 一定存在,因此,我们可以在 v v v 处对 x x x 任意增加 3 ( a − b ) 3(a-b) 3(a−b) 。
( 3 ) (3) (3) 、综上,令 g g g 是最大的使得存在 z z z 使得所有边长 c ≡ z ( m o d g ) c\equiv z\ (mod\ g) c≡z (mod g) 的 M o d Mod Mod 的因数,则可以令 M o d = g c d ( 3 g , M o d ) Mod=gcd(3g,Mod) Mod=gcd(3g,Mod) ,而不对结果产生影响。
若将所有边长 − z -z −z ,则在新图中 z z z 可以到达 z + r z+r z+r 等价于在原图中 0 0 0 可以到达 r r r 。
在新图中,各条边长均为 g g g 的倍数,从某处权值 r r r 出发到达的权值可以表示为 p z + q s pz+qs pz+qs 的形式,其中 q ∈ { 0 , 1 , 2 } , p = 2 i q\in\{0,1,2\},p=2^i q∈{0,1,2},p=2i 。又由于 [ v , x ] → [ v , 4 x + 3 g ] = [ v , 4 x ] [v,x]\rightarrow[v,4x+3g]=[v,4x] [v,x]→[v,4x+3g]=[v,4x] ,可以认为 [ v , x ] = [ v , 4 x ] [v,x]=[v,4x] [v,x]=[v,4x] ,从而 p ∈ { 1 , 2 } p\in\{1,2\} p∈{1,2} ,状态总数减少至了 O ( N ) O(N) O(N) ,可以直接通过并查集维护连通性。
最后,回答询问时,需要判断 [ t , z ] [t,z] [t,z] 能否到达 [ s , z + r ] [s,z+r] [s,z+r] ,枚举 p , q p,q p,q ,判断是否 [ t , z ] = [ s , p z + q s ] [t,z]=[s,pz+qs] [t,z]=[s,pz+qs] ,并且存在 i i i 使得 x = 4 i , p x + q s = z + r x=4^i,px+qs=z+r x=4i,px+qs=z+r 即可。
时间复杂度 O ( M o d + ( N + M + Q ) α ( N ) ) O(Mod+(N+M+Q)\alpha(N)) O(Mod+(N+M+Q)α(N)) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
const int MAXP = 1e6 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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, q, P, f[MAXN];
int tot, point[MAXN][2][3];
int x[MAXN], y[MAXN], w[MAXN];
int find(int x) {
if (f[x] == x) return x;
else return f[x] = find(f[x]);
}
void merge(int x, int y) {
f[find(x)] = find(y);
}
bool same(int x, int y) {
return find(x) == find(y);
}
bool vis[2][MAXP];
int main() {
read(n), read(m), read(q), read(P); int g = P;
for (int i = 1; i <= m; i++) {
read(x[i]), read(y[i]), read(w[i]);
g = __gcd(g, abs(w[i] - w[1]));
}
P = __gcd(3 * g, P);
for (int i = 1; i <= n; i++)
for (int p = 0; p <= 1; p++)
for (int q = 0; q <= 2; q++) {
point[i][p][q] = ++tot;
f[tot] = tot;
}
int z = w[1] % g;
for (int x = z, i = 0; i <= P; i++, x = 2 * x % P)
vis[i & 1][x] = true;
for (int i = 1; i <= m; i++) {
int tx = x[i], ty = y[i];
int tw = (w[i] - z) % P / g;
for (int p = 0; p <= 1; p++)
for (int q = 0; q <= 2; q++)
merge(point[tx][p][q], point[ty][p ^ 1][(2 * q + tw) % 3]);
}
for (int i = 1; i <= q; i++) {
bool res = false;
int s, t, r; read(s), read(t), read(r);
for (int p = 0; p <= 1; p++)
for (int q = 0; q <= 2; q++)
res |= same(point[t][0][0], point[s][p][q]) && vis[p][(r + z + (3 - q) * g) % P];
if (res) puts("YES");
else puts("NO");
}
return 0;
}
Atcoder Grand Contest 32
Problem C. Three Circuits
首先,若这张图不是一张欧拉图,答案为 N o No No ,下令各点度数为偶数。
若存在一个点度数 ≥ 6 \geq 6 ≥6 ,该点将在欧拉回路上出现至少 3 3 3 次,我们可以将该欧拉回路分成 3 3 3 部分,答案为 Y e s Yes Yes ,下令各点度数 ∈ { 2 , 4 } \in \{2,4\} ∈{2,4} ,记度为 4 4 4 的点数为 c n t 4 cnt_4 cnt4 。
若 c n t 4 ≥ 3 cnt_4\geq3 cnt4≥3 ,取三个度为 4 4 4 的点 A , B , C A,B,C A,B,C ,由于 A A A 点将在欧拉回路上出现 2 2 2 次,我们可以将该欧拉回路分成 2 2 2 部分, B , C B,C B,C 都会出现在某一条上 2 2 2 次 ( 1 ) (1) (1) ,或在两条上各出现 1 1 1 次 ( 2 ) (2) (2) 。对于情况 ( 1 ) (1) (1) ,直接将对应部分的欧拉回路断开即可将原图的欧拉回路分成 3 3 3 部分;对于情况 ( 2 ) (2) (2) , ( A , B ) , ( B , C ) , ( A , C ) (A,B),(B,C),(A,C) (A,B),(B,C),(A,C) 间的边可分别组成 3 3 3 条欧拉回路,因此答案为 Y e s Yes Yes ,下令 c n t 4 ≤ 2 cnt_4\leq 2 cnt4≤2 。
由于形成 3 3 3 条欧拉回路至少需要 M ≥ N + 2 M\geq N+2 M≥N+2 ,因此 M ≤ N + 1 M\leq N+1 M≤N+1 时答案为 N o No No ,剩余的情况即 c n t 4 = 2 cnt_4=2 cnt4=2 ,令这两个度为 4 4 4 的点为 X , Y X,Y X,Y 。
若 X , Y X,Y X,Y 之间有 4 4 4 条路径,答案为 N o No No ,否则,即 X , Y X,Y X,Y 之间有 2 2 2 条路径,答案为 Y e s Yes Yes 。
时间复杂度 O ( N + M ) O(N+M) O(N+M) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, m, cnt, x, y, d[MAXN];
vector <int> a[MAXN];
void dfs(int pos, int from) {
if (pos == x && from) return;
if (pos == y) {
cnt++;
return;
}
for (auto x : a[pos])
if (x != from) dfs(x, pos);
}
int main() {
read(n), read(m);
if (m < n + 2) {
puts("No");
return 0;
}
for (int i = 1; i <= m; i++) {
int x, y; read(x), read(y);
d[x]++, d[y]++;
a[x].push_back(y);
a[y].push_back(x);
}
for (int i = 1; i <= n; i++)
if (d[i] % 2) {
puts("No");
return 0;
}
x = 0, y = 0;
for (int i = 1; i <= n; i++)
if (d[i] > 2) {
if (d[i] > 4) {
puts("Yes");
return 0;
}
if (x == 0) x = i;
else if (y == 0) y = i;
else {
puts("Yes");
return 0;
}
}
dfs(x, 0);
if (cnt == 4) puts("No");
else {
assert(cnt == 2);
puts("Yes");
}
return 0;
}
Problem D. Rotation Sort
考虑旋转操作的本质,即选择一个数,花费一定代价将其插入至左侧任一位置,或花费一定代价将其插入至右侧任一位置。
考虑对于最终没有移动的数进行动态规划,记 d p i , j dp_{i,j} dpi,j 表示考虑前 i i i 个数,最后一个没有移动的数为 j j j 的情况下最小的代价,分 a i > j a_i>j ai>j 和 a i < j a_i<j ai<j 转移即可。
时间复杂度 O ( N 2 ) O(N^2) O(N2) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
const long long INF = 1e18;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
ll dp[MAXN][MAXN];
int n, f, b, a[MAXN];
int main() {
read(n), read(f), read(b);
for (int i = 1; i <= n; i++)
read(a[i]);
for (int i = 0; i <= n; i++)
for (int j = 0; j <= n; j++)
dp[i][j] = INF;
dp[0][0] = 0;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= n; j++) {
ll tmp = dp[i - 1][j];
if (tmp == INF) continue;
if (a[i] > j) {
chkmin(dp[i][j], tmp + f);
chkmin(dp[i][a[i]], tmp);
} else chkmin(dp[i][j], tmp + b);
}
ll ans = INF;
for (int i = 0; i <= n; i++)
chkmin(ans, dp[n][i]);
writeln(ans);
return 0;
}
Problem E. Modulo Pairing
首先,排序整个数列。
可以证明,所有解都可以调整至如下形式:存在一个分界点 M i d Mid Mid , [ 1 , M i d ] [1,Mid] [1,Mid] 中的元素按照 1 − M i d , 2 − ( M i d − 1 ) , . . . 1-Mid,2-(Mid-1),... 1−Mid,2−(Mid−1),... 的形式匹配,且各组的和小于 M M M ; [ M i d + 1 , 2 ∗ N ] [Mid+1,2*N] [Mid+1,2∗N] 中的元素按照 ( M i d + 1 ) − ( 2 ∗ N ) , ( M i d + 2 ) − ( 2 ∗ N − 1 ) , . . . (Mid+1)-(2*N),(Mid+2)-(2*N-1),... (Mid+1)−(2∗N),(Mid+2)−(2∗N−1),... 的形式匹配,且各组的和不小于 M M M 。
若用蓝线表示一组和小于 M M M 的匹配,红线表示一组和不小于 M M M 的匹配,按照下图的方式调整可以保证方案不会变劣。
同时,当分界点 M i d Mid Mid 向左移动的时候,两部分的最大和都会减少,因此,我们之需要找到使得右侧的最小和不小于 M M M 的最小的 M i d Mid Mid ,构造答案。二分即可。
时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int INF = 2e9;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, m, a[MAXN];
bool check(int pos) {
for (int i = 2 * pos + 1, j = 2 * n; i <= j; i++, j--)
if (a[i] + a[j] < m) return false;
return true;
}
int main() {
read(n), read(m);
for (int i = 1; i <= 2 * n; i++)
read(a[i]);
sort(a + 1, a + 2 * n + 1);
int l = 0, r = n;
while (l < r) {
int mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid + 1;
}
int ans = 0;
for (int i = 1, j = 2 * l; i <= j; i++, j--)
chkmax(ans, a[i] + a[j]);
for (int i = 2 * l + 1, j = 2 * n; i <= j; i++, j--)
chkmax(ans, a[i] + a[j] - m);
writeln(ans);
return 0;
}
Problem F. One Third
考虑将一次在 x x x 处的划分描述为三条线,红线 x x x ,蓝线 x + 120 ∘ x+120{}^{\circ} x+120∘ ,绿线 x − 120 ∘ x-120{}^{\circ} x−120∘ 。
最接近 1 3 \frac{1}{3} 31 的值与 1 3 \frac{1}{3} 31 的差值即为不同颜色的两条线形成的最小非负夹角。
令第一次划分有 x = 0 x=0 x=0 ,在数轴上标红 0 0 0 ,标蓝 1 3 \frac{1}{3} 31 ,剩余的每一次划分在 [ 0 , 1 3 ) [0,\frac{1}{3}) [0,31) 内都存在恰好一个随机分布,随机颜色的点,距离最近的两个异色点的距离的期望即为答案。
这
N
−
1
N-1
N−1 个点将
[
0
,
1
3
)
[0,\frac{1}{3})
[0,31) 分成了
N
N
N 份,考虑最小的一部分的期望长度
E
1
E_1
E1 ,有
E
1
=
1
3
∫
0
1
N
P
(
L
e
n
≥
x
)
d
x
=
1
3
∫
0
1
N
(
1
−
n
x
)
n
−
1
d
x
=
1
3
N
2
E_1=\frac{1}{3}\int_{0}^{\frac{1}{N}}P(Len\geq x)dx=\frac{1}{3}\int_{0}^{\frac{1}{N}}(1-nx)^{n-1}dx=\frac{1}{3N^2}
E1=31∫0N1P(Len≥x)dx=31∫0N1(1−nx)n−1dx=3N21
考虑第
i
i
i 小的一部分的期望长度与第
i
−
1
i-1
i−1 小的一部分的期望长度的差
E
i
−
E
i
−
1
E_i-E_{i-1}
Ei−Ei−1 ,考虑将所有长度第
j
(
j
>
i
−
1
)
j\ (j>i-1)
j (j>i−1) 小的部分的长度减去第
i
−
1
i-1
i−1 小的长度,计算剩余线段的期望最短长度,有
E
i
−
E
i
−
1
=
1
3
×
1
−
∑
j
=
1
i
−
1
(
N
−
j
+
1
)
(
E
j
−
E
j
−
1
)
(
N
−
i
+
1
)
2
=
1
3
N
(
N
−
i
+
1
)
E_i-E_{i-1}=\frac{1}{3}\times \frac{1-\sum_{j=1}^{i-1}(N-j+1)(E_j-E_{j-1})}{(N-i+1)^2}=\frac{1}{3N(N-i+1)}
Ei−Ei−1=31×(N−i+1)21−∑j=1i−1(N−j+1)(Ej−Ej−1)=3N(N−i+1)1
每一部分都有
1
3
\frac{1}{3}
31 的概率两端同色,因此答案
E
a
n
s
Eans
Eans 有
E
a
n
s
=
∑
i
=
1
N
1
3
i
−
1
×
(
E
i
−
E
i
−
1
)
=
∑
i
=
1
N
1
3
i
N
(
N
−
i
+
1
)
Eans=\sum_{i=1}^{N}\frac{1}{3^{i-1}}\times(E_i-E_{i-1})=\sum_{i=1}^{N}\frac{1}{3^{i}N(N-i+1)}
Eans=i=1∑N3i−11×(Ei−Ei−1)=i=1∑N3iN(N−i+1)1
时间复杂度 O ( N ) O(N) O(N) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int INF = 2e9;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, m, a[MAXN];
bool check(int pos) {
for (int i = 2 * pos + 1, j = 2 * n; i <= j; i++, j--)
if (a[i] + a[j] < m) return false;
return true;
}
int main() {
read(n), read(m);
for (int i = 1; i <= 2 * n; i++)
read(a[i]);
sort(a + 1, a + 2 * n + 1);
int l = 0, r = n;
while (l < r) {
int mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid + 1;
}
int ans = 0;
for (int i = 1, j = 2 * l; i <= j; i++, j--)
chkmax(ans, a[i] + a[j]);
for (int i = 2 * l + 1, j = 2 * n; i <= j; i++, j--)
chkmax(ans, a[i] + a[j] - m);
writeln(ans);
return 0;
}
Atcoder Grand Contest 33
Problem D. Complexity
朴素的 DP 是 O ( H 2 W 2 ) O(H^2W^2) O(H2W2) 的,但注意到 Complexity 的范围应当在 O ( L o g N ) O(LogN) O(LogN) 级别,可以考虑将一维 DP 数组的下标变为 Complexity ,再进行 DP 。
时间复杂度 O ( H W ( H + W ) L o g ( H + W ) ) O(HW(H+W)Log(H+W)) O(HW(H+W)Log(H+W)) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 190;
const int MAXM = 18;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
char s[MAXN][MAXN]; int n, m;
int dp[MAXN][MAXN][MAXN][MAXM];
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++)
scanf("\n%s", s[i] + 1);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
bool same = true; char col = s[i][j];
for (int k = j; k <= m; k++) {
if (s[i][k] != col) same = false;
if (same) {
if (s[i - 1][j] == s[i][j]) dp[i][j][k][0] = dp[i - 1][j][k][0];
else dp[i][j][k][0] = i;
} else dp[i][j][k][0] = i + 1;
}
}
for (int i = 1; i <= n; i++)
for (int len = 1; len <= m; len++) {
for (int j = 1, k = len; k <= m; j++, k++) {
for (int v = 1; v < MAXM; v++) {
int tmp = dp[i][j][k][v - 1];
if (tmp == 1) dp[i][j][k][v] = 1;
else dp[i][j][k][v] = dp[tmp - 1][j][k][v - 1];
int l = j, r = k - 1;
while (l <= r) {
int mid = (l + r) / 2;
chkmin(dp[i][j][k][v], max(dp[i][j][mid][v - 1], dp[i][mid + 1][k][v - 1]));
if (dp[i][j][mid][v - 1] > dp[i][mid + 1][k][v - 1]) r = mid - 1;
else l = mid + 1;
}
}
}
}
int ans = 0;
while (dp[n][1][m][ans] != 1) ans++;
printf("%d\n", ans);
return 0;
}
Problem E. Go around a Circle
细节比较多的 DP 题,首先特判 S i S_i Si 都相同的情况,此时要求每一个位置都与 S 1 S_1 S1 对应的颜色相邻。
否则,不妨设
S
1
=
R
S_1=R
S1=R ,则要求如下:
(
1
)
(1)
(1) 、存在蓝色的边,但连续段长度至多为
1
1
1
(
2
)
(2)
(2) 、每一段红色的边连续段长度均为奇数,且在某个数
M
i
n
Min
Min 以内
可以用前缀和优化朴素的 DP 直接计数。
时间复杂度 O ( N + M ) O(N+M) O(N+M) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, m;
char s[MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
read(n), read(m);
scanf("%s", s + 1);
bool same = true;
for (int i = 1; i <= m; i++)
if (s[i] != s[1]) same = false;
if (same) {
static int dp[MAXN], sum[MAXN];
dp[1] = sum[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = sum[i - 2];
sum[i] = (sum[i - 1] + dp[i]) % P;
}
int ans = (sum[n - 1] + 1) % P;
for (int i = 2, j = n - 1; i <= n; i++, j--)
update(ans, sum[j]);
printf("%d\n", ans);
return 0;
}
if (n & 1) {
puts("0");
return 0;
}
int Min = n - (n - 1) % 2;
int last = 0;
for (int i = 1; i <= m; i++)
if (s[i] != s[1]) {
int len = i - last - 1;
if (last == 0) chkmin(Min, len + 1);
if (len % 2 == 1) chkmin(Min, len);
last = i;
}
static int dp[MAXN], sum[MAXN];
dp[1] = sum[1] = 1;
for (int i = 2; i <= n; i++) {
if (i % 2 == 1) {
dp[i] = sum[i - 2];
update(dp[i], P - sum[max(i - Min - 3, 0)]);
}
sum[i] = (sum[i - 1] + dp[i]) % P;
}
int ans = (sum[n - 1] - sum[max(n - Min - 2, 0)] + P) % P;
for (int i = 2, j = n - 1; i <= n && j >= n - Min - 2; i++, j--)
update(ans, (sum[j] - sum[max(n - Min - 2, 0)] + P) % P);
printf("%d\n", ans);
return 0;
}
Problem F. Adding Edges
直接用队列模拟加边的过程,即依次考虑每一条已有的边,找到所有尚未加入队列,且与其存在一个公共点的,可以被加入的边加入队列。
朴素的实现时 O ( N 3 ) O(N^3) O(N3) 的,但可以简单地用 bitset 进行优化。
时间复杂度 O ( N 3 w + M ) O(\frac{N^3}{w}+M) O(wN3+M) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2005;
typedef long long ll;
typedef bitset <MAXN> bits;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
bits all, sub[MAXN], fat[MAXN], path[MAXN], g[MAXN];
int n, m, lca[MAXN][MAXN], step[MAXN][MAXN], father[MAXN];
vector <int> a[MAXN], tmp;
void efs(int pos, int fa, int from, int val) {
step[from][pos] = val;
tmp.push_back(pos);
for (auto x : a[pos])
if (x != fa) efs(x, pos, from, val);
}
void dfs(int pos, int fa) {
father[pos] = fa;
path[pos] = path[fa];
path[pos].set(pos);
sub[pos].set(pos);
for (auto x : a[pos])
if (x != fa) {
dfs(x, pos);
tmp.clear();
efs(x, pos, pos, x);
for (auto y : tmp)
for (int i = sub[pos]._Find_first(); i <= n; i = sub[pos]._Find_next(i))
lca[y][i] = lca[i][y] = pos;
sub[pos] |= sub[x];
}
fat[pos] = all ^ sub[pos];
fat[pos].set(pos);
}
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++)
all.set(i);
for (int i = 1; i <= n - 1; i++) {
int x, y; read(x), read(y);
a[x].push_back(y);
a[y].push_back(x);
}
dfs(1, 0);
int l = 1, r = 0;
static pair <int, int> q[MAXN * MAXN];
for (int i = 1; i <= n; i++)
g[i].set(i);
for (int i = 1; i <= m; i++) {
int x, y; read(x), read(y);
g[x].set(y), g[y].set(x);
q[++r] = make_pair(x, y);
}
while (l <= r) {
int x = q[l].first, y = q[l++].second;
if (sub[y][x]) swap(x, y);
bits ex, ey;
if (sub[x][y]) ex = ey = sub[y] | path[y] | fat[step[x][y]];
else {
ex = ey = sub[y] | (path[y] ^ path[x]) | sub[x];
ex.set(lca[x][y]), ey.set(lca[x][y]);
}
ex &= ~g[x] & g[y], ey &= ~g[y] & g[x];
for (int i = ex._Find_first(); i <= n; i = ex._Find_next(i)) {
g[i].set(x), g[x].set(i);
q[++r] = make_pair(i, x);
}
for (int i = ey._Find_first(); i <= n; i = ey._Find_next(i)) {
g[i].set(y), g[y].set(i);
q[++r] = make_pair(i, y);
}
}
cout << r << endl;
return 0;
}
Atcoder Grand Contest 34
Problem D. Manhattan Max Matching
由于我们需要最大化答案,因此将绝对值去掉不会影响答案。
枚举两维坐标的符号,利用中间点优化建图,直接用费用流解题即可。
若将 s p f a spfa spfa 的时间复杂度看做 O ( N ) O(N) O(N) ,时间复杂度为 O ( N S ) O(NS) O(NS) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXQ = 1e7 + 5;
const int MAXN = 1e3 + 5;
const int MAXP = 5e3 + 5;
const int INF = 1e9;
const long long INFLL = 1e18;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct edge {int dest, flow, pos, cost; };
vector <edge> a[MAXP];
int s, t, tot, flow; ll cost;
ll dist[MAXP]; int path[MAXP], home[MAXP];
void FlowPath() {
int p = t, ans = INF;
while (p != s) {
ans = min(ans, a[path[p]][home[p]].flow);
p = path[p];
}
flow += ans;
cost += ans * dist[t];
p = t;
while (p != s) {
a[path[p]][home[p]].flow -= ans;
a[p][a[path[p]][home[p]].pos].flow += ans;
p = path[p];
}
}
bool spfa() {
static int q[MAXQ];
static bool inq[MAXP];
static int l = 0, r = 0;
for (int i = 0; i <= r; i++)
dist[q[i]] = -INFLL;
q[l = r = 0] = s, dist[s] = 0, inq[s] = true;
while (l <= r) {
int tmp = q[l];
for (unsigned i = 0; i < a[tmp].size(); i++)
if (a[tmp][i].flow != 0 && dist[tmp] + a[tmp][i].cost > dist[a[tmp][i].dest]) {
dist[a[tmp][i].dest] = dist[tmp] + a[tmp][i].cost;
path[a[tmp][i].dest] = tmp;
home[a[tmp][i].dest] = i;
if (!inq[a[tmp][i].dest]) {
q[++r] = a[tmp][i].dest;
inq[q[r]] = true;
}
}
l++, inq[tmp] = false;
}
return dist[t] != -INFLL;
}
void addedge(int x, int y, int z, int c) {
a[x].push_back((edge){y, z, a[y].size(), c});
a[y].push_back((edge){x, 0, a[x].size() - 1, -c});
}
int n, mid[4], cntx[MAXN], cnty[MAXN];
pair <int, int> posx[MAXN], posy[MAXN];
int main() {
read(n), tot = 2 * n;
s = ++tot, t = ++tot;
for (int i = 0; i <= 3; i++)
mid[i] = ++tot;
for (int i = 1; i <= n; i++) {
read(posx[i].first), read(posx[i].second), read(cntx[i]);
addedge(s, i, cntx[i], 0);
addedge(i, mid[0], INF, -posx[i].first - posx[i].second);
addedge(i, mid[1], INF, posx[i].first - posx[i].second);
addedge(i, mid[2], INF, -posx[i].first + posx[i].second);
addedge(i, mid[3], INF, posx[i].first + posx[i].second);
}
for (int i = 1; i <= n; i++) {
read(posy[i].first), read(posy[i].second), read(cnty[i]);
addedge(n + i, t, cnty[i], 0);
addedge(mid[0], n + i, INF, posy[i].first + posy[i].second);
addedge(mid[1], n + i, INF, -posy[i].first + posy[i].second);
addedge(mid[2], n + i, INF, posy[i].first - posy[i].second);
addedge(mid[3], n + i, INF, -posy[i].first - posy[i].second);
}
for (int i = 1; i <= tot; i++)
dist[i] = -INFLL;
while (spfa()) FlowPath();
writeln(cost);
return 0;
}
Problem E. Complete Compress
考虑判断一个位置 r o o t root root 是否能够成为终点。
**引理 1 1 1 :**最优方案可以避免对存在祖孙关系的两点进行操作。
**证明:**找到最后一次这样的操作,我们用以下方式调整,使得最终方案中不存在这样的操作。令该操作设计两个棋子
A
,
B
A,B
A,B,所在节点为
x
,
y
x,y
x,y,其中
x
x
x 为
y
y
y 的祖先。
(
1
)
(1)
(1) 、若下一次操作不含
A
,
B
A,B
A,B,那么交换这两次操作。
(
2
)
(2)
(2) 、若下一次操作为
(
A
,
C
)
(A,C)
(A,C),将
(
A
,
B
)
,
(
A
,
C
)
(A,B),(A,C)
(A,B),(A,C) 直接替换为
(
B
,
C
)
(B,C)
(B,C)。
(
3
)
(3)
(3) 、若下一次操作为
(
B
,
C
)
(B,C)
(B,C),且
x
,
y
x,y
x,y 间距离大于
2
2
2,那么那么交换这两次操作;否则
x
,
y
x,y
x,y 间距离为
2
2
2,即将
(
A
,
B
)
,
(
B
,
C
)
(A,B),(B,C)
(A,B),(B,C) 直接替换为
(
B
,
C
)
(B,C)
(B,C) 不会使得棋子位置的可重集发生变化。
**引理 2 2 2 :**令 x i x_i xi 为第 i i i 次操作的两个位置的 L c a Lca Lca ,最优方案可以避免存在 i , j ( i < j ) i,j\ (i<j) i,j (i<j) ,满足 x i x_i xi 为 y i y_i yi 的祖先。
**证明:**若 x i x_i xi 为 x i + 1 x_{i+1} xi+1 的祖先,交换两次操作,操作序列依然合法。
由此,我们可以用树形 d p dp dp 解决该问题,记 s i z e i size_i sizei 表示子树中的棋子数, M a x i Max_i Maxi 表示子树内所有棋子的深度和, M i n i Min_i Mini 表示满足引理 1 1 1 的情况下,在子树内进行若干操作,可以使得子树内所有棋子的深度和达到的最小值。
r o o t root root 能够成为终点当且仅当 M i n r o o t = 0 Min_{root}=0 Minroot=0 。
时间复杂度 O ( N 2 ) O(N^2) O(N2) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e3 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct info {
int size, Max, Min;
} dp[MAXN];
int ans, now;
char s[MAXN];
vector <int> a[MAXN];
info operator + (info a, int one) {
a.Max += a.size;
a.Min += a.size;
return a;
}
info operator + (info a, info b) {
info ans;
ans.size = a.size + b.size;
ans.Max = a.Max + b.Max;
if (a.Min > b.Min) swap(a, b);
if (a.Max > b.Min) ans.Min = ans.Max & 1;
else ans.Min = b.Min - a.Max;
return ans;
}
void work(int pos, int fa, int depth) {
dp[pos] = (info) {0, 0, 0};
for (auto x : a[pos])
if (x != fa) {
work(x, pos, depth + 1);
dp[pos] = dp[pos] + (dp[x] + 1);
}
if (s[pos] == '1') {
dp[pos].size++;
now += depth;
}
}
int main() {
int n; read(n), ans = 1e9;
scanf("%s", s + 1);
for (int i = 1; i <= n - 1; i++) {
int x, y; read(x), read(y);
a[x].push_back(y);
a[y].push_back(x);
}
for (int i = 1; i <= n; i++) {
now = 0, work(i, 0, 0);
if (dp[i].Min == 0) chkmin(ans, now / 2);
}
if (ans == 1e9) puts("-1");
else writeln(ans);
return 0;
}
Problem F. RNG and XOR
由题意列出方程,有
x
0
=
0
x
i
=
∑
j
p
j
x
i
⊕
j
+
1
(
i
≥
1
)
x_0=0\\ x_i=\sum_{j}p_jx^{i\oplus j}+1\ (i\geq1)
x0=0xi=j∑pjxi⊕j+1 (i≥1)
其中,第二种方程可以写作异或卷积的形式
(
x
0
,
x
1
,
…
,
x
2
N
−
1
)
⊕
(
p
0
,
p
1
,
…
,
p
2
N
−
1
)
=
(
?
,
x
1
−
1
,
…
,
x
2
N
−
1
−
1
)
(x_0,x_1,\dots,x_{2^N-1})\oplus(p_0,p_1,\dots,p_{2^N-1})=(?,x_1-1,\dots,x_{2^N-1}-1)
(x0,x1,…,x2N−1)⊕(p0,p1,…,p2N−1)=(?,x1−1,…,x2N−1−1)
注意到该卷积若成立, ( ∑ x i ) ( ∑ p i ) = ? + ∑ ( x i − 1 ) (\sum x_i)(\sum p_i)=?+\sum(x_i-1) (∑xi)(∑pi)=?+∑(xi−1) ,因此 ? = x 0 + 2 N − 1 ?=x_0+2^N-1 ?=x0+2N−1 。
因此
(
x
0
,
x
1
,
…
,
x
2
N
−
1
)
⊕
(
p
0
,
p
1
,
…
,
p
2
N
−
1
)
=
(
x
0
+
2
N
−
1
,
x
1
−
1
,
…
,
x
2
N
−
1
−
1
)
(x_0,x_1,\dots,x_{2^N-1})\oplus(p_0,p_1,\dots,p_{2^N-1})=(x_0+2^N-1,x_1-1,\dots,x_{2^N-1}-1)
(x0,x1,…,x2N−1)⊕(p0,p1,…,p2N−1)=(x0+2N−1,x1−1,…,x2N−1−1)
(
x
0
,
x
1
,
…
,
x
2
N
−
1
)
⊕
(
p
0
−
1
,
p
1
,
…
,
p
2
N
−
1
)
=
(
2
N
−
1
,
−
1
,
…
,
−
1
)
(x_0,x_1,\dots,x_{2^N-1})\oplus(p_0-1,p_1,\dots,p_{2^N-1})=(2^N-1,-1,\dots,-1)
(x0,x1,…,x2N−1)⊕(p0−1,p1,…,p2N−1)=(2N−1,−1,…,−1)
使用异或卷积除法即可,注意 ( 2 N − 1 , − 1 , … , − 1 ) (2^N-1,-1,\dots,-1) (2N−1,−1,…,−1) 在 F W T FWT FWT 后有且只有第 0 0 0 项为 0 0 0 ,剩余位置的值都可以通过直接除法得到,第 0 0 0 项处的值可取任意,这可能导致 x i x_i xi 被整体加上一个值,最后利用 x 0 = 0 x_0=0 x0=0 还原即可。
时间复杂度 O ( N × 2 N ) O(N\times2^N) O(N×2N) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1 << 18;
const int P = 998244353;
const int inv2 = (P + 1) / 2;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> 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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
void FWT(int *a, int N) {
for (int len = 2; len <= N; len <<= 1)
for (int i = 0; i < N; i += len)
for (int j = i, k = i + len / 2; k < i + len; j++, k++) {
int tmp = (a[j] + a[k]) % P, tnp = (a[j] - a[k] + P) % P;
a[j] = tmp, a[k] = tnp;
}
}
void UFWT(int *a, int N) {
for (int len = 2; len <= N; len <<= 1)
for (int i = 0; i < N; i += len)
for (int j = i, k = i + len / 2; k < i + len; j++, k++) {
int tmp = (a[j] + a[k]) % P, tnp = (a[j] - a[k] + P) % P;
a[j] = 1ll * tmp * inv2 % P, a[k] = 1ll * tnp * inv2 % P;
}
}
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 n, goal, p[MAXN], q[MAXN], res[MAXN];
int main() {
read(n), goal = 1 << n;
int sum = 0;
for (int i = 0; i < goal; i++)
read(p[i]), sum += p[i];
sum = power(sum, P - 2);
for (int i = 0; i < goal; i++) {
p[i] = 1ll * p[i] * sum % P;
q[i] = P - 1;
}
p[0] -= 1;
q[0] = goal - 1;
FWT(p, goal);
FWT(q, goal);
for (int i = 1; i < goal; i++)
res[i] = 1ll * q[i] * power(p[i], P - 2) % P;
UFWT(res, goal);
for (int i = 0; i < goal; i++)
writeln((res[i] - res[0] + P) % P);
return 0;
}