Codeforces 590E Birthday
首先,可以用 AC 自动机计算出字符串的包含关系。
注意到包含关系构成了一个偏序集,剩余的问题本质上是要求出最长反链。
由 Dilworth 定理,可以得到,最长反链的大小在数值上等于最小链划分的大小。
这里,最小链划分指的是将偏序集划分为尽可能少的元素两两可比的子集,这样的子集称为链。
传递闭包后,最小链划分可以转化为 DAG 上的最小路径覆盖问题,可以用网络流解决。
考虑如何输出方案:
首先,我们需要能够从二分图最大匹配的方案得到对应的二分图最大独立集的方案。
也就是优先选取不在最大匹配上的点,然后贪心求出一组方案。
这里,令最大匹配为 M M M ,则最大独立集为 2 N − M 2N-M 2N−M ,最小链划分,也即最小路径覆盖为 N − M N-M N−M 。
注意到两侧都在最大独立集中的点数应当 ≥ N − M \geq N-M ≥N−M ,取这些点作为最大反链即可。
时间复杂度 O ( ∑ ∣ S ∣ + N 3 w + N 2 N ) O(\sum|S|+\frac{N^3}{w}+N^2\sqrt{N}) O(∑∣S∣+wN3+N2N) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1505;
const int MAXM = 1e7 + 5;
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;
}
namespace NetworkFlow {
const int INF = 1e9 + 7;
const int MAXP = 2e3 + 5;
struct edge {
int dest, flow;
unsigned pos;
}; vector <edge> a[MAXP];
int tot, s, t, dist[MAXP];
unsigned curr[MAXP];
void addedge(int x, int y, int z) {
a[x].push_back((edge) {y, z, a[y].size()});
a[y].push_back((edge) {x, 0, a[x].size() - 1});
}
int dinic(int pos, int limit) {
if (pos == t) return limit;
int used = 0, tmp;
for (unsigned &i = curr[pos]; i < a[pos].size(); i++)
if (a[pos][i].flow != 0 && dist[pos] + 1 == dist[a[pos][i].dest] && (tmp = dinic(a[pos][i].dest, min(limit - used, a[pos][i].flow)))) {
used += tmp;
a[pos][i].flow -= tmp;
a[a[pos][i].dest][a[pos][i].pos].flow += tmp;
if (used == limit) return used;
}
return used;
}
bool bfs() {
static int q[MAXP];
int l = 0, r = 0;
memset(dist, 0, sizeof(dist));
dist[s] = 1, q[0] = s;
while (l <= r) {
int tmp = q[l];
for (unsigned i = 0; i < a[tmp].size(); i++)
if (dist[a[tmp][i].dest] == 0 && a[tmp][i].flow != 0) {
q[++r] = a[tmp][i].dest;
dist[q[r]] = dist[tmp] + 1;
}
l++;
}
return dist[t] != 0;
}
int flow() {
int ans = 0;
while (bfs()) {
memset(curr, 0, sizeof(curr));
ans += dinic(s, INF);
}
return ans;
}
}
bitset <MAXN> mp[MAXN];
namespace ACAutomaton {
struct Node {
int child[2];
int fail, father, home, last;
} a[MAXM];
int root, size;
void init() {
root = size = 0;
}
void insert(char *s, int x) {
int now = root, len = strlen(s + 1);
for (int i = 1; i <= len; i++) {
int tmp = s[i] - 'a';
if (a[now].child[tmp] == 0) {
a[now].child[tmp] = ++size;
a[size].father = now;
}
now = a[now].child[tmp];
}
a[now].home = x;
}
void build() {
static int q[MAXM];
int l = 0, r = -1;
for (int i = 0; i <= 1; i++)
if (a[root].child[i]) q[++r] = a[root].child[i];
while (l <= r) {
int tmp = q[l++];
if (a[tmp].home) a[tmp].last = a[tmp].home;
else a[tmp].last = a[a[tmp].fail].last;
for (int i = 0; i <= 1; i++)
if (a[tmp].child[i]) {
a[a[tmp].child[i]].fail = a[a[tmp].fail].child[i];
q[++r] = a[tmp].child[i];
} else a[tmp].child[i] = a[a[tmp].fail].child[i];
}
for (int i = 1; i <= size; i++)
if (a[i].home) {
int tmp = a[i].father;
if (a[a[i].fail].last) mp[a[i].home][a[a[i].fail].last] = 1;
while (tmp != root) {
if (a[tmp].last) mp[a[i].home][a[tmp].last] = 1;
tmp = a[tmp].father;
}
}
}
}
char s[MAXM]; bool vis[MAXN];
int n, len[MAXN], pos[MAXN], match[MAXN];
void debug() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
printf("%d", mp[i][j] == 1);
puts("");
}
}
void work(int pos) {
vis[pos] = true;
if (pos <= n) {
for (int i = 1; i <= n; i++)
if (mp[pos][i] && !vis[i + n]) work(i + n);
} else if (!vis[match[pos]]) work(match[pos]);
}
int main() {
read(n), ACAutomaton :: init();
for (int i = 1; i <= n; i++) {
scanf("\n%s", s + 1);
pos[i] = i, len[i] = strlen(s + 1);
ACAutomaton :: insert(s, i);
}
ACAutomaton :: build();
sort(pos + 1, pos + n + 1, [&] (int x, int y) {return len[x] < len[y];} );
for (int i = 1; i <= n; i++) {
int now = pos[i];
for (int j = 1; j <= n; j++)
if (mp[now][j]) mp[now] |= mp[j];
}
using namespace NetworkFlow;
NetworkFlow :: s = 2 * n + 1;
NetworkFlow :: t = 2 * n + 2;
for (int i = 1; i <= n; i++) {
addedge(2 * n + 1, i, 1);
addedge(i + n, 2 * n + 2, 1);
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (mp[i][j]) addedge(i, j + n, 1);
cout << n - flow() << endl;
for (int i = n + 1; i <= 2 * n; i++)
for (auto x : a[i])
if (x.dest <= n && x.flow == 1) {
match[i] = x.dest;
match[x.dest] = i;
}
for (int i = 1; i <= n; i++)
if (match[i] == 0 && !vis[i]) work(i);
for (int i = 1; i <= n; i++)
if (vis[i] && !vis[i + n]) printf("%d ", i);
return 0;
}
Codeforces 594E Cutting the Line
I prefer to solve problems about world history rather than such string theories.
Codeforces 603E Pastoral Oddities
我们称一张图合法,当且仅当可以通过选择一个边的子集使得其满足条件。
首先考虑如何判断一张图是否合法。
引理: 一张图合法,当且仅当其各个连通块的大小为偶数。
证明: 不妨分别考虑图的每一个联通块。
对于大小为奇数的连通块,由于总度数为偶数,因此总有点的度数是偶数,显然是不合法的。
对于大小为偶数的连通块,取出其任意一棵生成树,按照 DFS 序考虑每个节点,若其度数为偶数,则割去其父边,即可构造一组解。
考虑离线操作,然后从后往前处理各个询问的答案,则答案将不断变大。
需要进行的修改就是对于一个区间,加上一条边,用线段树分治解决即可。
时间复杂度 O ( M L o g M L o g N ) O(MLogMLogN) O(MLogMLogN) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 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, x[MAXN], y[MAXN], z[MAXN], ans[MAXN];
int cnt, res, f[MAXN], s[MAXN], rk[MAXN], sa[MAXN];
int find(int x) {
if (f[x] == x) return x;
else return find(f[x]);
}
struct SegmentTree {
struct Node {
int lc, rc;
vector <pair <int, int>> mod;
} a[MAXN * 2];
int n, root, size;
void build(int &root, int l, int r) {
root = ++size;
if (l == r) return;
int mid = (l + r) / 2;
build(a[root].lc, l, mid);
build(a[root].rc, mid + 1, r);
}
void init(int x) {
n = x, root = size = 0;
build(root, 1, n);
}
void modify(int root, int l, int r, int ql, int qr, pair <int, int> x) {
if (l == ql && r == qr) {
a[root].mod.push_back(x);
return;
}
int mid = (l + r) / 2;
if (mid >= ql) modify(a[root].lc, l, mid, ql, min(mid, qr), x);
if (mid + 1 <= qr) modify(a[root].rc, mid + 1, r, max(mid + 1, ql), qr, x);
}
void modify(int l, int r, pair <int, int> x) {
modify(root, 1, n, l, r, x);
}
void dfs(int root, int l, int r) {
int bakcnt = cnt;
vector <pair <int, pair <int, int>>> bak;
for (auto x : a[root].mod) {
int tx = find(x.first), ty = find(x.second);
if (tx != ty) {
cnt -= s[tx] & 1;
cnt -= s[ty] & 1;
bak.emplace_back(tx, make_pair(f[tx], s[tx]));
bak.emplace_back(ty, make_pair(f[ty], s[ty]));
if (s[tx] > s[ty]) swap(tx, ty);
f[tx] = ty, s[ty] += s[tx];
cnt += s[ty] & 1;
}
}
if (l == r) {
while (cnt != 0 && res != 0) {
if (sa[res] > l) {
res--;
continue;
}
int tx = find(x[sa[res]]), ty = find(y[sa[res]]);
if (sa[res] < l) modify(sa[res], l - 1, make_pair(x[sa[res]], y[sa[res]]));
if (tx != ty) {
cnt -= s[tx] & 1;
cnt -= s[ty] & 1;
bak.emplace_back(tx, make_pair(f[tx], s[tx]));
bak.emplace_back(ty, make_pair(f[ty], s[ty]));
if (s[tx] > s[ty]) swap(tx, ty);
f[tx] = ty, s[ty] += s[tx];
cnt += s[ty] & 1;
}
res--;
}
if (cnt != 0) ans[l] = -1;
else ans[l] = z[sa[res + 1]];
} else {
int mid = (l + r) / 2;
dfs(a[root].rc, mid + 1, r);
dfs(a[root].lc, l, mid);
}
cnt = bakcnt;
reverse(bak.begin(), bak.end());
for (auto x : bak) {
f[x.first] = x.second.first;
s[x.first] = x.second.second;
}
}
} ST;
bool cmp(int x, int y) {
return z[x] > z[y];
}
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++)
f[i] = i, s[i] = 1;
for (int i = 1; i <= m; i++)
read(x[i]), read(y[i]), read(z[i]), sa[i] = i;
sort(sa + 1, sa + m + 1, cmp);
for (int i = 1; i <= m; i++)
rk[sa[i]] = i;
res = m, cnt = n;
ST.init(m), ST.dfs(ST.root, 1, m);
for (int i = 1; i <= m; i++)
printf("%d\n", ans[i]);
return 0;
}
Codeforces 605E Intergalaxy Trips
若我们已经知道 f i f_i fi 表示从点 i i i 出发的最优期望步数,我们的策略一定是:若当天开启的最优的虫洞到达的点 j j j 的 f j f_j fj 小于某个阈值,则行动,否则等待。并且,很显然我们不会走向 f j ≥ f i f_j\geq f_i fj≥fi 的点。
因此,按照 f i f_i fi 小到大确定各个值,则每一个新确定的 f i f_i fi 只受已经确定的值的影响。
那么,使用类似 Dijkstra 最短路的算法解题即可。
时间复杂度 O ( N 2 ) O(N^2) O(N2) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
const double eps = 1e-15;
const double INF = 1e100;
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;
}
int n, m; bool vis[MAXN];
double p[MAXN][MAXN], lft[MAXN], sum[MAXN], ans[MAXN];
int main() {
read(n);
for (int i = 1; i <= n - 1; i++)
ans[i] = INF, sum[i] = 1, lft[i] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
int x; read(x);
p[i][j] = 0.01 * x;
}
for (int t = 1; t <= n; t++) {
int pos = 0;
for (int i = 1; i <= n; i++)
if (!vis[i] && (pos == 0 || ans[i] < ans[pos])) pos = i;
vis[pos] = true;
for (int i = 1; i <= n; i++)
if (!vis[i]) {
sum[i] += lft[i] * p[i][pos] * ans[pos];
lft[i] *= (1 - p[i][pos]);
double tres = INF;
if (1 - lft[i] > eps) tres = sum[i] / (1 - lft[i]);
chkmin(ans[i], tres);
}
}
printf("%.10lf\n", ans[1]);
return 0;
}
Codeforces 607E Cross Sum
首先考虑求出第 M M M 小的距离。
可以先二分答案 A n s Ans Ans ,然后统计 A n s Ans Ans 为半径的圆内有多少个交点。
注意到两条直线在圆内相交当且仅当这两条直线在圆周上的两个交点形成的区间不存在包含关系,可以由此将问题转化为一个在环上的统计问题,从而用树状数组解决。
求出第 M M M 小的距离后,只需要考虑如何 O ( 1 ) O(1) O(1) 找到一个圆内的交点即可。
可以在任意处将环断开,然后将所有区间按照长度排序,从短到长处理各个区间,找到区间内的所有出现的直线计算贡献,然后删除区间的两个端点,则可以每次 O ( 1 ) O(1) O(1) 找到一个圆内的交点。
时间复杂度 O ( N L o g N L o g V + M ) O(NLogNLogV+M) O(NLogNLogV+M) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const double rate = 0.001;
const double eps = 1e-9;
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;
}
struct BinaryIndexTree {
int n, a[MAXN];
void init(int x) {
n = x;
memset(a, 0, sizeof(a));
}
void modify(int x, int d) {
for (int i = x; i <= n; i += i & -i)
a[i] += d;
}
int query(int x) {
int ans = 0;
for (int i = x; i >= 1; i -= i & -i)
ans += a[i];
return ans;
}
int query(int l, int r) {
int ans = 0;
for (int i = r; i >= 1; i -= i & -i)
ans += a[i];
for (int i = l - 1; i >= 1; i -= i & -i)
ans -= a[i];
return ans;
}
} BIT;
struct point {double x, y; };
int n, m, tot, fa[MAXN]; point p;
pair <double, int> v[MAXN];
double a[MAXN], b[MAXN];
int find(int x) {
if (fa[x] == x) return x;
else return fa[x] = find(fa[x]);
}
int check(double mid) {
tot = 0;
for (int i = 1; i <= n; i++) {
double dist = abs(a[i] * p.x - p.y + b[i]) / sqrt(a[i] * a[i] + 1);
if (dist < mid) {
double ta = a[i] * a[i] + 1, tb = 2 * a[i] * (b[i] - p.y) - 2 * p.x, tc = p.x * p.x + (b[i] - p.y) * (b[i] - p.y) - mid * mid;
double delta = sqrt(tb * tb - 4 * ta * tc);
double tx = (-tb + delta) / (2 * ta), ty = tx * a[i] + b[i];
double sx = (-tb - delta) / (2 * ta), sy = sx * a[i] + b[i];
v[++tot] = make_pair(atan2(ty - p.y, tx - p.x), i);
v[++tot] = make_pair(atan2(sy - p.y, sx - p.x), i);
}
}
sort(v + 1, v + tot + 1);
BIT.init(tot);
static int f[MAXN], s[MAXN];
memset(f, 0, sizeof(f));
memset(s, 0, sizeof(s));
for (int i = 1; i <= tot; i++)
if (f[v[i].second]) BIT.modify(i, -1), s[v[i].second] = i;
else BIT.modify(i, 1), f[v[i].second] = i;
ll ans = 0;
for (int i = 1; i <= tot; i++)
if (f[v[i].second] == i) {
ans += BIT.query(s[v[i].second]);
BIT.modify(s[v[i].second], 2);
BIT.modify(f[v[i].second], -1);
} else BIT.modify(s[v[i].second], -1);
assert(ans % 2 == 0);
return ans / 2;
}
double getdist(int x, int y) {
double tx = (b[x] - b[y]) / (a[y] - a[x]), ty = tx * a[x] + b[x];
return sqrt((tx - p.x) * (tx - p.x) + (ty - p.y) * (ty - p.y));
}
double getans(double mid) {
double ans = mid * m;
tot = 0;
for (int i = 1; i <= n; i++) {
double dist = abs(a[i] * p.x - p.y + b[i]) / sqrt(a[i] * a[i] + 1);
if (dist < mid) {
double ta = a[i] * a[i] + 1, tb = 2 * a[i] * (b[i] - p.y) - 2 * p.x, tc = p.x * p.x + (b[i] - p.y) * (b[i] - p.y) - mid * mid;
double delta = sqrt(tb * tb - 4 * ta * tc);
double tx = (-tb + delta) / (2 * ta), ty = tx * a[i] + b[i];
double sx = (-tb - delta) / (2 * ta), sy = sx * a[i] + b[i];
v[++tot] = make_pair(atan2(ty - p.y, tx - p.x), i);
v[++tot] = make_pair(atan2(sy - p.y, sx - p.x), i);
}
}
sort(v + 1, v + tot + 1);
static int f[MAXN], s[MAXN];
memset(f, 0, sizeof(f));
memset(s, 0, sizeof(s));
for (int i = 1; i <= tot; i++)
if (f[v[i].second]) BIT.modify(i, -1), s[v[i].second] = i;
else BIT.modify(i, 1), f[v[i].second] = i;
vector <pair <int, int>> tmp;
for (int i = 1; i <= n; i++)
if (f[i]) tmp.emplace_back(s[i] - f[i], i);
sort(tmp.begin(), tmp.end());
for (int i = 1; i <= tot + 1; i++)
fa[i] = i;
for (auto x : tmp) {
int l = f[x.second], r = s[x.second];
fa[l] = l + 1, fa[r] = r + 1;
while ((l = find(l)) < r) {
ans -= mid;
ans += getdist(x.second, v[l].second);
l += 1;
}
}
return ans;
}
int main() {
read(n), read(p.x), read(p.y), read(m);
p.x *= rate, p.y *= rate;
for (int i = 1; i <= n; i++) {
read(a[i]), a[i] *= rate;
read(b[i]), b[i] *= rate;
}
double l = 0, r = 1e10;
while (l + 1e-9 < r && (r - l) / r > 1e-15) {
double mid = (l + r) / 2;
if (check(mid) < m) l = mid;
else r = mid;
}
double ans = getans(l);
printf("%.10lf\n", ans);
return 0;
}
Codeforces 611G New Year and Cake
枚举对角线的一端,显然面积是关于另一端单调的。
那么,最大的使得面积不超过 S 2 \frac{S}{2} 2S 的对角线的另一端应当是单调的,双指针找到之。
然后用差分 + + + 前缀和拆一拆每条对角线的贡献即可。
时间复杂度 O ( N ) O(N) O(N) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
const int P = 1e9 + 7;
typedef long long ll;
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;
}
struct point {int x, y; };
point operator + (point a, point b) {return (point) {a.x + b.x, a.y + b.y}; }
point operator - (point a, point b) {return (point) {a.x - b.x, a.y - b.y}; }
point operator * (point a, int b) {return (point) {a.x * b, a.y * b}; }
long long operator * (point a, point b) {return 1ll * a.x * b.y - 1ll * a.y * b.x; }
point a[MAXN]; int prex[MAXN], prey[MAXN], uses[MAXN], f[MAXN], g[MAXN]; ull pres[MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
ull area(int l, int r) {
return pres[r - 1] - pres[l - 1] + a[r] * a[l];
}
int main() {
int n; read(n);
for (int i = 1, j = n; i <= n; i++, j--) {
read(a[j].x), read(a[j].y);
a[j + n] = a[j];
}
ull sum = 0;
for (int i = 1; i <= n; i++)
sum += a[i] * a[i + 1];
a[2 * n + 1] = a[1];
for (int i = 1; i <= 2 * n; i++) {
pres[i] = pres[i - 1] + a[i] * a[i + 1];
prex[i] = prex[i - 1] + a[i].x;
if (prex[i] < 0) prex[i] += P;
if (prex[i] >= P) prex[i] -= P;
prey[i] = prey[i - 1] + a[i].y;
if (prey[i] < 0) prey[i] += P;
if (prey[i] >= P) prey[i] -= P;
}
int ans = 0, pos = 1;
for (int i = 1; i <= n; i++) {
while (area(i, pos + 1) * 2 < sum) pos++;
int cnt = pos - i - 1, res = 0;
update(res, 1ll * a[i].y * (prex[pos] - prex[i] + P) % P);
update(res, 1ll * a[i].x * (prey[i] - prey[pos] + P) % P);
update(f[i], cnt + 1), update(g[i + 1], P - 1), update(g[pos + 1], 1);
update(ans, sum % P * cnt % P), update(ans, (P - 2ll * res % P) % P);
}
for (int i = 1; i <= 2 * n; i++) {
update(g[i], g[i - 1]);
update(f[i], f[i - 1]);
update(f[i], g[i]);
update(ans, (P - a[i] * a[i + 1] % P * 2ll * f[i] % P) % P);
}
cout << ans << endl;
return 0;
}
Codeforces 611H New Year and Forgotten Tree
首先,同类型的点之间的连边显然是任意的,问题可以转述为如下形式:
现有
M
M
M 类点,每类
a
i
a_i
ai 个,第
i
,
j
(
i
<
j
)
i,j\ (i<j)
i,j (i<j) 类点之间应当连
e
i
,
j
e_{i,j}
ei,j 条边,问是否能够连成一棵树。
可以用调整法证明,如下构造方式是充分必要的:
在每类点中选择一个关键点,在关键点间连成一棵树。
默认剩余的边一定是由非关键点连向关键点的。
因此,可以枚举关键点间树的形态,剩余问题可以看做一个边到点的二分图匹配问题,用网络流解决即可。
时间复杂度 O ( N + M M − 2 × D i n i c ( M 2 , M 2 ) ) = O ( N + M M + 1 ) O(N+M^{M-2}\times Dinic(M^2,M^2))=O(N+M^{M+1}) O(N+MM−2×Dinic(M2,M2))=O(N+MM+1) ,其中 M = 6 M=6 M=6 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int MAXM = 15;
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 NetworkFlow {
const int INF = 1e9 + 7;
const int MAXP = 1e2 + 5;
struct edge {
int dest, flow;
unsigned pos;
}; vector <edge> a[MAXP];
int cntp, s, t, dist[MAXP];
unsigned curr[MAXP];
void addedge(int x, int y, int z) {
a[x].push_back((edge) {y, z, a[y].size()});
a[y].push_back((edge) {x, 0, a[x].size() - 1});
}
int dinic(int pos, int limit) {
if (pos == t) return limit;
int used = 0, tmp;
for (unsigned &i = curr[pos]; i < a[pos].size(); i++)
if (a[pos][i].flow != 0 && dist[pos] + 1 == dist[a[pos][i].dest] && (tmp = dinic(a[pos][i].dest, min(limit - used, a[pos][i].flow)))) {
used += tmp;
a[pos][i].flow -= tmp;
a[a[pos][i].dest][a[pos][i].pos].flow += tmp;
if (used == limit) return used;
}
return used;
}
bool bfs() {
static int q[MAXP];
int l = 0, r = 0;
memset(dist, 0, sizeof(dist));
dist[s] = 1, q[0] = s;
while (l <= r) {
int tmp = q[l];
for (unsigned i = 0; i < a[tmp].size(); i++)
if (dist[a[tmp][i].dest] == 0 && a[tmp][i].flow != 0) {
q[++r] = a[tmp][i].dest;
dist[q[r]] = dist[tmp] + 1;
}
l++;
}
return dist[t] != 0;
}
int flow() {
int ans = 0;
while (bfs()) {
memset(curr, 0, sizeof(curr));
ans += dinic(s, INF);
}
return ans;
}
}
bool vis[MAXM]; int tot, a[MAXM];
int n, m, bits[MAXN], x[MAXM], y[MAXM];
int Min[MAXM], Max[MAXM], cnt[MAXM], cnte[MAXM][MAXM], num[MAXM][MAXM];
vector <pair <int, int>> ans;
void dfs(int pos, int nxt, int cur) {
if (cur == m) {
for (int i = 1; i <= m - 1; i++) {
if (x[i] > y[i]) swap(x[i], y[i]);
if (cnte[x[i]][y[i]] == 0) return;
}
for (int i = 1; i <= m - 1; i++)
cnte[x[i]][y[i]]--;
using namespace NetworkFlow;
s = 0, cntp = m; int goal = 0;
for (int i = 0; i <= m; i++)
NetworkFlow :: a[i].clear();
for (int i = 1; i <= m; i++) {
addedge(s, i, cnt[i] - 1);
goal += cnt[i] - 1;
}
for (int i = 1; i <= m; i++)
for (int j = i + 1; j <= m; j++) {
num[i][j] = ++cntp;
NetworkFlow :: a[cntp].clear();
addedge(i, cntp, INF);
addedge(j, cntp, INF);
}
t = ++cntp, NetworkFlow :: a[t].clear();
for (int i = 1; i <= m; i++)
for (int j = i + 1; j <= m; j++)
addedge(num[i][j], t, cnte[i][j]);
if (flow() == goal) {
for (int i = 1; i <= m - 1; i++)
ans.emplace_back(Min[x[i]], Min[y[i]]);
for (int i = 1; i <= m; i++)
for (int j = i + 1; j <= m; j++) {
int p = num[i][j];
for (auto x : NetworkFlow :: a[p])
if (x.dest <= m) {
int used = x.flow, k = (x.dest == i) ? j : i;
while (used--) ans.emplace_back(Min[k], Max[x.dest]--);
}
}
for (auto x : ans)
printf("%d %d\n", x.first, x.second);
exit(0);
}
for (int i = 1; i <= m - 1; i++)
cnte[x[i]][y[i]]++;
} else {
while (pos <= cur) {
while (nxt <= m) {
if (!vis[nxt]) {
vis[nxt] = true;
a[++tot] = nxt;
x[cur] = a[pos];
y[cur] = nxt;
dfs(pos, nxt, cur + 1);
a[tot--] = 0;
vis[nxt] = false;
}
nxt++;
}
nxt = 1, pos++;
}
}
}
int main() {
read(n), bits[1] = Min[1] = Max[1] = cnt[1] = 1;
for (int i = 2; i <= n; i++) {
bits[i] = bits[i - 1];
if (bits[i] == bits[i / 10]) {
bits[i]++;
Min[bits[i]] = i;
}
cnt[bits[i]]++;
Max[bits[i]] = i;
} m = bits[n];
for (int i = 1; i <= n - 1; i++) {
static char s[MAXM], t[MAXM];
scanf("\n%s %s", s, t);
int x = strlen(s);
int y = strlen(t);
if (x > y) swap(x, y);
cnte[x][y]++;
}
for (int i = 1; i <= m; i++) {
while (cnte[i][i]--) {
if (cnt[i] <= 1) {
puts("-1");
return 0;
}
cnt[i]--, Max[i]--;
ans.emplace_back(Max[i], Max[i] + 1);
}
}
vis[1] = true;
a[tot = 1] = 1;
dfs(1, 1, 1);
puts("-1");
return 0;
}
Codeforces 613E Puzzle Lover
注意到由于行数只有 2 2 2 ,任意一条路径只有可能在头尾处向内侧凹陷。
由此,将一条路径唯一地表达为凹陷 - 路径 - 凹陷的形式,可以设计一个简单 DP 完成计数。
需要用后缀数组支持询问 LCP 。
时间复杂度 O ( N × M ) O(N\times M) O(N×M) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2005;
const int MAXM = 1e5 + 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;
}
namespace SuffixArray {
const int MAXN = 100005;
const int MAXLOG = 20;
const int MAXC = 256;
int sa[MAXN], rnk[MAXN], height[MAXN];
int Min[MAXN][MAXLOG], bit[MAXN], N;
void init(char *a, int n) {
N = n;
static int x[MAXN], y[MAXN], cnt[MAXN], rk[MAXN];
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; i++)
cnt[(int) a[i]]++;
for (int i = 1; i <= MAXC; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
sa[cnt[(int) a[i]]--] = i;
rnk[sa[1]] = 1;
for (int i = 2; i <= n; i++)
rnk[sa[i]] = rnk[sa[i - 1]] + (a[sa[i]] != a[sa[i - 1]]);
for (int k = 1; rnk[sa[n]] != n; k <<= 1) {
for (int i = 1; i <= n; i++) {
x[i] = rnk[i];
y[i] = (i + k <= n) ? rnk[i + k] : 0;
}
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; i++)
cnt[y[i]]++;
for (int i = 1; i <= n; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
rk[cnt[y[i]]--] = i;
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; i++)
cnt[x[i]]++;
for (int i = 1; i <= n; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
sa[cnt[x[rk[i]]]--] = rk[i];
rnk[sa[1]] = 1;
for (int i = 2; i <= n; i++)
rnk[sa[i]] = rnk[sa[i - 1]] + (x[sa[i]] != x[sa[i - 1]] || y[sa[i]] != y[sa[i - 1]]);
}
int now = 0;
for (int i = 1; i <= n; i++) {
if (now) now--;
while (a[i + now] == a[sa[rnk[i] + 1] + now]) now++;
height[rnk[i]] = now;
}
for (int i = 1; i <= n; i++)
Min[i][0] = height[i];
for (int p = 1; p < MAXLOG; p++) {
int tmp = 1 << (p - 1);
for (int i = 1, j = tmp + 1; j <= n; i++, j++)
Min[i][p] = min(Min[i][p - 1], Min[i + tmp][p - 1]);
}
for (int i = 1; i <= n; i++) {
bit[i] = bit[i - 1];
if (i >= 1 << (bit[i] + 1)) bit[i]++;
}
}
int lcp(int x, int y) {
if (x == y) return N - x + 1;
x = rnk[x], y = rnk[y];
if (x > y) swap(x, y);
int tmp = bit[y - x];
return min(Min[x][tmp], Min[y - (1 << tmp)][tmp]);
}
}
int n, m, tot, dp[MAXN][MAXN][2], f[2][MAXN], g[2][MAXN];
char s[2][MAXN], t[MAXN], all[MAXM];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int solve() {
tot = 0;
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++)
all[++tot] = s[0][i], f[0][i] = tot;
for (int i = 1; i <= n; i++)
all[++tot] = s[1][i], f[1][i] = tot;
for (int i = 1; i <= m; i++)
all[++tot] = t[i], g[0][i] = tot;
for (int i = m; i >= 1; i--)
all[++tot] = t[i], g[1][i] = tot;
SuffixArray :: init(all, tot);
int ans = 0;
for (int i = 1; i <= n + 1; i++) {
update(dp[i][0][0], 1);
update(dp[i][0][1], 1);
for (int j = 2; j <= i - 1 && j * 2 <= m; j++) {
using namespace SuffixArray;
if (lcp(g[0][j + 1], f[0][i - j]) >= j && lcp(g[1][j], f[1][i - j]) >= j) update(dp[i][j * 2][0], 1);
if (lcp(g[0][j + 1], f[1][i - j]) >= j && lcp(g[1][j], f[0][i - j]) >= j) update(dp[i][j * 2][1], 1);
}
for (int j = 0; j <= m - 1; j++) {
if (t[j + 1] == s[0][i]) {
update(dp[i + 1][j + 1][0], dp[i][j][0]);
if (j <= m - 2 && t[j + 2] == s[1][i]) update(dp[i + 1][j + 2][1], dp[i][j][0]);
}
if (t[j + 1] == s[1][i]) {
update(dp[i + 1][j + 1][1], dp[i][j][1]);
if (j <= m - 2 && t[j + 2] == s[0][i]) update(dp[i + 1][j + 2][0], dp[i][j][1]);
}
}
update(ans, dp[i][m][0]);
update(ans, dp[i][m][1]);
for (int j = 2; j <= n - i + 1 && j * 2 <= m; j++) {
using namespace SuffixArray;
if (lcp(g[0][m - 2 * j + 1], f[0][i]) >= j && lcp(g[1][m], f[1][i]) >= j) update(ans, dp[i][m - j * 2][0]);
if (lcp(g[0][m - 2 * j + 1], f[1][i]) >= j && lcp(g[1][m], f[0][i]) >= j) update(ans, dp[i][m - j * 2][1]);
}
}
return ans;
}
int main() {
scanf("\n%s", s[0] + 1);
scanf("\n%s", s[1] + 1);
scanf("\n%s", t + 1);
n = strlen(s[0] + 1), m = strlen(t + 1);
int ans = solve();
reverse(t + 1, t + m + 1);
update(ans, solve());
if (m == 1) {
for (int i = 1; i <= n; i++) {
update(ans, P - (t[1] == s[0][i]));
update(ans, P - (t[1] == s[1][i]));
}
}
if (m == 2) {
for (int i = 1; i <= n; i++) {
update(ans, P - (t[1] == s[0][i] && t[2] == s[1][i]));
update(ans, P - (t[1] == s[1][i] && t[2] == s[0][i]));
}
} else if (m % 2 == 0) {
using namespace SuffixArray;
for (int i = m / 2; i <= n; i++) {
if (lcp(g[0][m / 2 + 1], f[0][i - m / 2 + 1]) >= m / 2 && lcp(g[1][m / 2], f[1][i - m / 2 + 1]) >= m / 2) update(ans, P - 1);
if (lcp(g[0][m / 2 + 1], f[1][i - m / 2 + 1]) >= m / 2 && lcp(g[1][m / 2], f[0][i - m / 2 + 1]) >= m / 2) update(ans, P - 1);
}
for (int i = 1; i + m / 2 - 1 <= n; i++) {
if (lcp(g[0][1], f[0][i]) >= m / 2 && lcp(g[1][m], f[1][i]) >= m / 2) update(ans, P - 1);
if (lcp(g[0][1], f[1][i]) >= m / 2 && lcp(g[1][m], f[0][i]) >= m / 2) update(ans, P - 1);
}
}
cout << ans << endl;
return 0;
}
Codeforces 626G Raffles
有一个显然正确的贪心,注意到对于每一个奖池,投入一张彩票带来的贡献是递减的,可以直接每次选择带来贡献最大的奖池投入彩票,直到彩票用尽,或是没有可以投入彩票的奖池。
对于一次修改,一个自然的想法是在已有的方案上进行调整。
若用线段树维护各个奖池投入和获得彩票的贡献,单次调整的复杂度可以做到 O ( L o g N ) O(LogN) O(LogN) 。
考虑如何分析调整的次数,对于一个原本有
x
x
x 张彩票的奖池,投入第
i
i
i 张彩票的贡献为
i
x
+
i
−
i
−
1
x
+
i
−
1
=
x
(
x
+
i
)
(
x
+
i
−
1
)
\frac{i}{x+i}-\frac{i-1}{x+i-1}=\frac{x}{(x+i)(x+i-1)}
x+ii−x+i−1i−1=(x+i)(x+i−1)x
若在原有方案中,该奖池选择了 c c c 张彩票,那么 x ( x + c ) ( x + c − 1 ) \frac{x}{(x+c)(x+c-1)} (x+c)(x+c−1)x 应当不小于其余所有可选的彩票的贡献。那么,当 x x x 增加 1 1 1 ,第 c − 1 c-1 c−1 张彩票的贡献为 x + 1 ( x + c ) ( x + c − 1 ) \frac{x+1}{(x+c)(x+c-1)} (x+c)(x+c−1)x+1 ,因而不会被换出方案。
类似地考虑 x x x 减少 1 1 1 的情况,可以得出,调整的次数是 O ( 1 ) O(1) O(1) 的。
时间复杂度 O ( N + ( T + Q ) L o g N ) O(N+(T+Q)LogN) O(N+(T+Q)LogN) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const double eps = 1e-12;
const double inf = 1e100;
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 SegmentTree {
struct Node {
int lc, rc;
pair <double, int> Max;
pair <double, int> Min;
} a[MAXN * 2];
int n, root, size;
void build(int &root, int l, int r) {
root = ++size;
if (l == r) {
a[root].Max.second = l;
a[root].Min.second = l;
return;
}
int mid = (l + r) / 2;
build(a[root].lc, l, mid);
build(a[root].rc, mid + 1, r);
}
void init(int x) {
n = x, root = size = 0;
build(root, 1, n);
}
void update(int root) {
a[root].Max = max(a[a[root].lc].Max, a[a[root].rc].Max);
a[root].Min = min(a[a[root].lc].Min, a[a[root].rc].Min);
}
void modifyMax(int root, int l, int r, int pos, double d) {
if (l == r) {
a[root].Max.first = d;
return;
}
int mid = (l + r) / 2;
if (mid >= pos) modifyMax(a[root].lc, l, mid, pos, d);
else modifyMax(a[root].rc, mid + 1, r, pos, d);
update(root);
}
void modifyMax(int pos, double d) {
modifyMax(root, 1, n, pos, d);
}
void modifyMin(int root, int l, int r, int pos, double d) {
if (l == r) {
a[root].Min.first = d;
return;
}
int mid = (l + r) / 2;
if (mid >= pos) modifyMin(a[root].lc, l, mid, pos, d);
else modifyMin(a[root].rc, mid + 1, r, pos, d);
update(root);
}
void modifyMin(int pos, double d) {
modifyMin(root, 1, n, pos, d);
}
} ST;
double p[MAXN], ans;
int n, t, s, q, a[MAXN], c[MAXN];
void update(int pos) {
if (c[pos] == a[pos]) ST.modifyMax(pos, 0);
else ST.modifyMax(pos, p[pos] * a[pos] / (a[pos] + c[pos]) / (a[pos] + c[pos] + 1));
if (c[pos] == 0) ST.modifyMin(pos, inf);
else ST.modifyMin(pos, p[pos] * a[pos] / (a[pos] + c[pos]) / (a[pos] + c[pos] - 1));
}
int main() {
read(n), read(t), read(q), ST.init(n);
for (int i = 1; i <= n; i++)
read(p[i]);
for (int i = 1; i <= n; i++)
read(a[i]);
for (int i = 1; i <= n; i++) {
ST.modifyMax(i, p[i] / (a[i] + 1));
ST.modifyMin(i, inf);
}
while (t > 0 && ST.a[ST.root].Max.first > eps) {
ans += ST.a[ST.root].Max.first;
int pos = ST.a[ST.root].Max.second;
c[pos]++, t--, update(pos);
}
for (int i = 1; i <= q; i++) {
int opt, x; read(opt), read(x);
if (opt == 1) {
ans -= p[x] * c[x] / (a[x] + c[x]), a[x]++;
ans += p[x] * c[x] / (a[x] + c[x]);
update(x);
} else {
ans -= p[x] * c[x] / (a[x] + c[x]), a[x]--;
if (c[x] > a[x]) c[x]--, t++;
ans += p[x] * c[x] / (a[x] + c[x]);
update(x);
}
while (t > 0 && ST.a[ST.root].Max.first > eps) {
ans += ST.a[ST.root].Max.first;
int pos = ST.a[ST.root].Max.second;
c[pos]++, t--, update(pos);
}
while (ST.a[ST.root].Max.first > ST.a[ST.root].Min.first + eps) {
ans += ST.a[ST.root].Max.first - ST.a[ST.root].Min.first;
int p = ST.a[ST.root].Max.second, q = ST.a[ST.root].Min.second;
c[p]++, c[q]--, update(p), update(q);
}
printf("%.10lf\n", ans);
}
return 0;
}
Codeforces 627F Island Puzzle
有如下显然的观察:
(
1
)
(1)
(1) 、操作是可逆的,问题同样可以看做在始末状态上同时操作,使得两个状态相同
(
2
)
(2)
(2) 、如果一个叶子节点上始末状态的两个数相同,且非零,可以删除这个叶子节点
(
3
)
(3)
(3) 、如果一个叶子节点上存在恰好一个零,且将这个零交换给相邻的节点后,该叶子节点上始末状态的两个数相同,可以交换后删除这个叶子节点
由此,我们可以进行一个类似于拓扑排序的过程,不断地从叶子入手考虑问题。
最终,我们将得到一棵叶子节点均不满足 ( 2 ) , ( 3 ) (2),(3) (2),(3) 的树。
若这棵树仅包含一个节点,则可以不加边解决问题。
否则,必须加入环边,那么,若存在始末状态的两个数均为 0 0 0 ,且相邻的位置始末状态两个数相同的节点,应当进行两次交换,将两个零均交换给相邻的节点后,删除这个叶子。
若所剩下的树是一条路径,则加入一条边,将其连成环;考虑环上的问题,否则,问题应当无解。
环上的问题可以简单分类解决。
时间复杂度 O ( N ) O(N) O(N) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const long long INF = 1e18;
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, ans, tot, x[MAXN], y[MAXN];
int l, r, q[MAXN * 2], d[MAXN]; bool vis[MAXN];
vector <int> a[MAXN], rem; pair <int, int> v[MAXN];
void work(int pos) {
vis[pos] = true;
v[++tot] = make_pair(x[pos], y[pos]);
if (pos == rem[1]) return;
for (auto x : a[pos])
if (!vis[x]) work(x);
}
ll calc(int pos) {
static pair <int, int> home[MAXN]; int cnt = 0;
for (int i = 1; i <= tot; i++)
if (i != pos) {
home[v[i].first].first = ++cnt;
home[v[i].second].second = cnt;
}
int x = 0, y = 0;
for (int i = 1; i <= n; i++)
if (home[i].first) {
int tx = abs(home[i].first - home[i].second), ty = cnt - tx;
if (x + y == 0) x = tx, y = ty;
else if (x != tx && y != tx) return INF;
}
return 1ll * min(x, y) * tot;
}
int main() {
read(n);
for (int i = 1; i <= n; i++)
read(x[i]);
for (int i = 1; i <= n; i++)
read(y[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);
}
l = 1, r = 0;
for (int i = 1; i <= n; i++) {
d[i] = a[i].size();
if (d[i] == 1) q[++r] = i;
}
while (l <= r) {
int pos = q[l++];
if (x[pos] == y[pos] && x[pos] != 0) {
vis[pos] = true;
for (auto v : a[pos])
if (!vis[v] && --d[v] == 1) q[++r] = v;
} else {
int fa = 0;
for (auto v : a[pos])
if (!vis[v]) fa = v;
if (fa == 0) {
assert(l > r);
printf("%d %d\n", 0, ans);
return 0;
} else if (x[pos] == 0 && y[pos] == x[fa]) {
swap(x[pos], x[fa]), ans++;
vis[pos] = true; if (--d[fa] == 1) q[++r] = fa;
} else if (y[pos] == 0 && x[pos] == y[fa]) {
swap(y[pos], y[fa]), ans++;
vis[pos] = true; if (--d[fa] == 1) q[++r] = fa;
} else if (x[pos] == y[pos] && x[pos] == 0 && x[fa] == y[fa]) {
if (rem.size() >= 2 || l > r) {
swap(x[pos], x[fa]), ans++;
swap(y[pos], y[fa]), ans++;
vis[pos] = true; if (--d[fa] == 1) q[++r] = fa;
} else q[++r] = pos;
} else rem.push_back(pos);
}
}
if (rem.size() >= 3) {
puts("-1");
return 0;
}
assert(rem.size() == 2);
if (rem[0] > rem[1]) swap(rem[0], rem[1]);
ll res = INF; work(rem[0]);
int x = 0, y = 0;
for (int i = 1; i <= tot; i++) {
if (v[i].first == 0) x = i;
if (v[i].second == 0) y = i;
}
if (x > y) {
swap(x, y);
for (int i = 1; i <= tot; i++)
swap(v[i].first, v[i].second);
}
for (int i = x + 1; i <= y; i++)
swap(v[i].first, v[i - 1].first);
chkmin(res, y - x + calc(y));
for (int i = y; i >= x + 1; i--)
swap(v[i].first, v[i - 1].first);
for (int i = x; i >= 2; i--)
swap(v[i].first, v[i - 1].first);
swap(v[1].first, v[tot].first);
for (int i = tot; i >= y + 1; i--)
swap(v[i].first, v[i - 1].first);
chkmin(res, tot - (y - x) + calc(y));
if (res < INF) printf("%d %d %lld\n", rem[0], rem[1], res + ans);
else puts("-1");
return 0;
}