贪心 + 对顶堆,AC 自动机 + 树状数组,Tarjan 缩点 + 拓扑排序
# A. 「APIO2015」巴邻旁之桥 Palembang Bridges
刚开始的时候没啥思路,然后看到数据范围 或 ?!
首先把不需要过河的人都排除掉,然后考虑 的情况,那么每一组点对都要经过这个桥,所以就是所有点到这个桥的距离之和,显然中位数是最优的,于是把所有点都存下来求个中位数即可。
再来看 的情况,我们可以枚举一个断点,强制令左边的点都从左边的桥走,右边的点都从右边的桥走,所以整个对顶堆快速维护中位数即可。
关于对顶堆维护中位数 P1168 中位数
#include <bits/stdc++.h> | |
#define ll long long | |
using namespace std; | |
namespace IO{ | |
inline ll read(){ | |
ll x = 0; | |
char ch = getchar(); | |
while(!isdigit(ch)) ch = getchar(); | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const ll N = 3e5 + 10; | |
const ll INF = 1e18; | |
ll m, n; | |
char op1[5], op2[5]; | |
struct node{ | |
ll x, y; | |
inline bool operator < (const node &b) const{ | |
return x + y < b.x + b.y; | |
} | |
}per[N]; | |
ll p[N]; | |
ll tot, cnt, ans; | |
priority_queue <ll> q1, q2; | |
ll sum1, sum2, s[N]; | |
inline void Push(ll x){ | |
if(q1.empty() || x <= q1.top()) q1.push(x), sum1 += x; | |
else q2.push(-x), sum2 += x; | |
if(q2.size() + 1 < q1.size()){ | |
ll now = q1.top(); | |
q1.pop(), q2.push(-now); | |
sum1 -= now, sum2 += now; | |
} | |
if(q1.size() + 1 < q2.size()){ | |
ll now = q2.top(); | |
q2.pop(), q1.push(-now); | |
sum1 -= now, sum2 += now; | |
} | |
} | |
inline void solve1(){ | |
sort(p + 1, p + 1 + tot); | |
for(ll i = 1; i <= tot; ++i) ans += abs(p[i] - p[cnt]); | |
write(ans), puts(""); | |
} | |
inline void solve2(){ | |
sort(per + 1, per + 1 + cnt); | |
for(ll i = 1; i <= cnt; ++i){ | |
Push(per[i].x), Push(per[i].y); | |
s[i] = sum2 - sum1; | |
} | |
while(!q1.empty()) q1.pop(); | |
while(!q2.empty()) q2.pop(); | |
sum1 = sum2 = 0; | |
for(ll i = cnt; i > 0; --i){ | |
s[i] += sum2 - sum1; | |
Push(per[i].x), Push(per[i].y); | |
} | |
ll mins = INF; | |
for(ll i = 1; i <= cnt; ++i) mins = min(mins, s[i]); | |
write(ans + mins), puts(""); | |
} | |
signed main(){ | |
// freopen("A.in", "r", stdin); | |
// freopen("A.out", "w", stdout); | |
m = read(), n = read(); | |
for(ll i = 1, x, y; i <= n; ++i){ | |
scanf("%s%lld%s%lld", op1, &x, op2, &y); | |
if(op1[0] == op2[0]) {ans += abs(x - y); continue;} | |
per[++cnt] = (node){x, y}; | |
p[++tot] = x, p[++tot] = y, ans++; | |
} | |
if(!cnt) return write(ans), puts(""), 0; | |
if(m == 1) solve1(); | |
else solve2(); | |
return 0; | |
} |
# B. 「NOI2011」阿狸的打字机
虽然之前做过一遍,但是不会了 QwQ
# 40pts 暴力
暴力建出 自动机,对于每组询问暴力在自动机上跑。
# 60pts 暴力
观察到有许多重复的询问,所以把询问离线下来,按 排序,开个桶一块询问。
建出 自动机后,把 指针倒过来,此时问题就变成了查询 在从根到 这条链中出现了多少次,所以找出 序,把根到 这条链全都标记为 1(这个操作可以用树状数组解决),然后 暴力往下跳。
# 100pts
感觉上面这种做法已经很优了,为什么还是过不了呢?
很简单,因为有很多串多次插入树状数组,所以同样把询问离线下来,按 排个序,统一处理即可。
#include <bits/stdc++.h> | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0; | |
char ch = getchar(); | |
while(!isdigit(ch)) ch = getchar(); | |
while(isdigit(ch)) x =(x << 3) +(x << 1) + ch - '0', ch = getchar(); | |
return x; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 2e5 + 10; | |
struct node{ | |
int v, nxt; | |
} edge[N]; | |
int head[N], cnt; | |
struct Query{ | |
int x, y, id, ans; | |
inline bool operator < (const Query &b) const{ | |
return y < b.y; | |
} | |
} q[N << 1]; | |
char s[N]; | |
int n, m; | |
int trie[N][27], tot; | |
int fa[N], fail[N], End[N], num[N]; | |
int vis[N][27]; | |
int in[N], out[N], tim; | |
int c[N], ans[N]; | |
int ql[N], qr[N]; | |
void add(int x, int y){ | |
edge[++cnt] =(node){y, head[x]}; | |
head[x] = cnt; | |
} | |
void init(){ | |
scanf("%s", s + 1); | |
int now = 0; | |
int len = strlen(s + 1); | |
for(int i = 1; i <= len; i++){ | |
if(s[i] >= 'a' && s[i] <= 'z'){ | |
if(!trie[now][s[i] - 'a']) trie[now][s[i] - 'a'] = ++tot, fa[tot] = now; | |
now = trie[now][s[i] - 'a']; | |
} | |
if(s[i] == 'B') now = fa[now]; | |
if(s[i] == 'P') End[++n] = now, num[now] = n; | |
} | |
} | |
void build(){ | |
queue<int> q; | |
for(int i = 0; i < 26; i++) | |
if(trie[0][i]) q.push(trie[0][i]); | |
while(!q.empty()){ | |
int now = q.front(); | |
q.pop(); | |
for(int i = 0; i < 26; i++){ | |
if(trie[now][i]) | |
fail[trie[now][i]] = trie[fail[now]][i], q.push(trie[now][i]); | |
else trie[now][i] = trie[fail[now]][i]; | |
} | |
} | |
} | |
void dfs(int x){ | |
in[x] = ++tim; | |
for(int i = head[x]; i; i = edge[i].nxt) dfs(edge[i].v); | |
out[x] = tim; | |
} | |
void update(int x, int y){ | |
for(; x <= tim; x +=(x &(-x))) c[x] += y; | |
} | |
int query(int x){ | |
int res = 0; | |
for(; x > 0; x -=(x &(-x))) res += c[x]; | |
return res; | |
} | |
void solve(int x){ | |
update(in[x], 1); | |
if(num[x]) | |
for(int i = ql[num[x]]; i <= qr[num[x]]; i++) | |
q[i].ans = query(out[End[q[i].x]]) - query(in[End[q[i].x]] - 1); | |
for(int i = 0; i < 26; i++) | |
if(vis[x][i]) solve(vis[x][i]); | |
update(in[x], -1); | |
} | |
int main(){ | |
init(); | |
for(int i = 0; i <= tot; i++) | |
for(int j = 0; j < 26; j++) | |
vis[i][j] = trie[i][j]; | |
build(); | |
for(int i = 1; i <= tot; i++) add(fail[i], i); | |
dfs(0); | |
scanf("%d", &m); | |
for(int i = 1; i <= m; i++) q[i] = (Query){read(), read(), i, 0}; | |
sort(q + 1, q + 1 + m); | |
for(int i = 1, pos = 1; i <= m; i = pos){ | |
ql[q[i].y] = i; | |
while(q[i].y == q[pos].y) pos++; | |
qr[q[i].y] = pos - 1; | |
} | |
solve(0); | |
for(int i = 1; i <= m; i++) ans[q[i].id] = q[i].ans; | |
for(int i = 1; i <= m; i++) printf("%d\n", ans[i]); | |
return 0; | |
} |
# C. 「CEOI2011」Traffic
观察到这么一句话:保证边在交点以外的任何地方不相交。
(图源自:ppp204 巨佬的题解)
然后把中间到不了的点都删掉,剩下的点就是右边一段连续的区间。
因此我们只需要 一遍跑出它能到达的最高点与最低点,就计算出了答案。
由于会有环,所以 缩点一下。
#include <bits/stdc++.h> | |
using namespace std; | |
namespace IO{ | |
inline int read(){ | |
int x = 0; | |
char ch = getchar(); | |
while(!isdigit(ch)) ch = getchar(); | |
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar(); | |
return x; | |
} | |
template <typename T> inline void write(T x){ | |
if(x > 9) write(x / 10); | |
putchar(x % 10 + '0'); | |
} | |
} | |
using namespace IO; | |
const int N = 3e5 + 10; | |
const int M = 9e5 + 10; | |
const int INF = 2e9; | |
int n, m, A, B; | |
struct node{ | |
int x, y, id; | |
inline bool operator < (const node &b) const{ | |
return y > b.y; | |
} | |
}p[N]; | |
vector <int> g[N], G[N]; | |
vector <node> L, R; | |
int ans[N]; | |
int mx[N], mn[N]; | |
int dfn[N], low[N], tim; | |
int stk[N], vis[N], top; | |
int scc[N], cnt; | |
inline void tarjan(int x){ | |
dfn[x] = low[x] = ++tim; | |
stk[++top] = x, vis[x] = 1; | |
if(p[x].x == A) R.push_back(p[x]); | |
for(auto y : g[x]){ | |
if(!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]); | |
else if(vis[y]) low[x] = min(low[x], dfn[y]); | |
} | |
if(dfn[x] == low[x]){ | |
int k; cnt++; | |
do{ | |
k = stk[top--]; | |
vis[k] = 0; | |
scc[k] = cnt; | |
}while(x != k); | |
} | |
} | |
inline void dfs(int x){ | |
if(vis[x]) return; | |
vis[x] = 1; | |
for(auto y : G[x]){ | |
dfs(y); | |
mx[x] = max(mx[x], mx[y]), mn[x] = min(mn[x], mn[y]); | |
} | |
} | |
int main(){ | |
n = read(), m = read(), A = read(), B = read(); | |
for(int i = 1; i <= n; ++i) p[i] = (node){read(), read(), i}; | |
for(int i = 1; i <= m; ++i){ | |
int x = read(), y = read(), k = read(); | |
g[x].push_back(y); | |
if(k == 2) g[y].push_back(x); | |
} | |
for(int i = 1; i <= n; ++i){ | |
mx[i] = 0, mn[i] = INF; | |
if(!p[i].x){ | |
L.push_back(p[i]); | |
if(!dfn[i]) tarjan(i); | |
} | |
} | |
for(int x = 1; x <= n; ++x) | |
for(auto y : g[x]) | |
if(scc[x] != scc[y]) G[scc[x]].push_back(scc[y]); | |
sort(L.begin(), L.end()); | |
sort(R.begin(), R.end()); | |
for(auto x : R){ | |
int now = scc[x.id], res = lower_bound(R.begin(), R.end(), x) - R.begin() + 1; | |
mx[now] = max(mx[now], res); | |
mn[now] = min(mn[now], res); | |
} | |
memset(vis, 0, sizeof(vis)); | |
for(auto u : L){ | |
dfs(scc[u.id]); | |
write(max(mx[scc[u.id]] - mn[scc[u.id]] + 1, 0)), puts(""); | |
} | |
return 0; | |
} |