Atcoder abc214
Packing Under Range Regulations
\(10^9\) 个盒子,有 \(n\) 个球,每个球只能放在 \([L_i,R_i]\) 中的某一个盒子中,问能否将 \(n\) 个球都放进盒子中
\(n\le 2\times 10^5\)
sol & code
按左端点排序,对于一些可以放的球,优先放右端点小的,用小根堆维护。
\(now\) 表示当前放球的位置,枚举排序后的区间 \([L_i,R_i]\)
若 \(L_i=now\) 说明可以考虑这个球,将 \(R_i\) 加入堆中
否则 \(now
若出现 \(R_i
\(O(n\log n)\)
#include
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 4e5 + 5;
int n, Ti;
struct L {
int l, r;
bool operator < (L o) const {
if (l ^ o.l) return l < o.l;
return r < o.r;
}
} a[N];
priority_queue< int, vector, greater > Q;
int main() {
scanf("%d", &Ti);
while (Ti--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &a[i].l, &a[i].r);
}
a[n + 1].l = a[n + 1].r = 2e9;
sort(a + 1, a + n + 1);
int flg = 1;
int now = 0;
while (!Q.empty()) Q.pop();
for (int i = 1; i <= n + 1; i++) {
if (now == a[i].l) Q.push(a[i].r);
else {
while (now < a[i].l && !Q.empty()) {
int u = Q.top(); Q.pop();
if (now <= u) ++now;
else { flg = 0; break; }
}
now = a[i].l, Q.push(a[i].r);
}
if (!flg) break;
}
if (flg) puts("Yes");
else puts("No");
}
}
Substrings
给一个字符串,求由不相邻的字符组成的子串个数 \(mod\;10^9+7\) ,\(|s|\le 2\times 10^5\)
sol & code
考虑弱化版:可以相邻。
设 \(f_{i}\) 为从 \(1\) 到 \(i\) 选且必须选 \(s_i\) 的方案。
naive 的想法是 \(f_i=\sum_{j=0}^{i-1}f_j\) ,显然容易重复。
正确的:记 \(k\) 为最大的使得 \(s_k=s_i\) 相等的的位置,此时转移为 \(f_{i}=\sum_{j=k}^{i-1}f_j\)
即如果 \([1,k-1]\) 中的子串要选 \(s_i\) ,可以用 \(s_k\) 代替,保证不重复
转移像是 \(O(n)\) 的,实际上最多枚举 26
个就会找到 \(k\) 。所以转移总共 \(O(26n)\)
回到本题:不能相邻
多判断是否 \(j
最后 \(ans=\sum f_i\)
#include
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 2e5 + 5;
const LL P = 1e9 + 7;
int n;
LL f[N], ans;
char a[N];
int main() {
scanf("%s", a + 1);
n = strlen(a + 1);
f[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = i - 1; ~j; j--) {
if (!j || j < i - 1)
(f[i] += f[j]) %= P;
if (a[j] == a[i]) {
if (j > 1)
(f[i] += f[j - 1]);
break;
}
}
(ans += f[i]) %= P;
}
printf("%lld", ans);
}
Three Permutations
给两个 \((1,\cdots n)\) 的排列 \(a,b\) ,求排列 \(r\) 的个数, \(r\) 满足 \(\forall i,r_i\ne a_i\and r_i\ne b_i\) ,\(n\le 3000\)
sol
容斥,设 \(h(i)\) 表示有 \(i\) 为 \(r_i=a_i\) 或 \(r_i=b_i\) ,则
需要求 \(h\) ,如果 \((a_i,b_i)\) 连边,则图由多个环构成,因为每个点的度都是 2
其实就是往边里面填数,每一个点可以选择与他连接的边匹配。
每一个环都是一个子问题,设 \(g(i,j)\) 表示长度为 \(i\) 的环里 \(j\) 对点、边匹配的方案
设共有 \(T\) 条边,第 \(i\) 个环长度为 \(c_i\) ,则枚举 \(i=1,\cdots,T\)
可以滚掉一维,则最后 \(h(i)\) 等于 \(h(T,i)\)
求解 \(g_{i,j}\) ,设 \(f_{i,j,0/1,0/1}\) 表示长度为 \(i\) 的环,\(j\) 对点边匹配。
将环看成序列, \(i\) 个点 \(i-1\) 条边,末端到开始一条边 \((i,1)\)
第一个 \(0/1\) 表示点 \(i\) 是否和 \((i,i-1)\) 匹配,第二个 \(0/1\) 表示点 \(i\) 是否和 \((i,1)\) 匹配
每次向 \(i+1\) 扩展相当于新加入一个点、一条边,对于 \(f_{i,j,k,l}\) 可以转移到:
- \(f_{i+1,j,0,l}\) ,第 \(i+1\) 个点不与新加入的边匹配
- \(f_{i+1,j+1,1,l}\) 第 \(i+1\) 个点和新加入的边匹配
- \(f_{i+1,j+1,0,l}\) 仅当 \(k=0\) 时可行,让第 \(i\) 个点和新加的边匹配
显然 \(f_{i,j,k,l}\) 的 \(k,l\) 不能同时为 \(1\) ,因为末端点不能同时匹配两条边,所以
解决问题,\(O(n^2)\)
#include
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 3005;
const LL P = 1e9 + 7;
int n, a[N], b[N], to[N], vis[N];
LL f[N][N][2][2], g[N][N], fac[N], h[N], h2[N], ans;
inline void Ad(LL &x, LL y) { (x += y) %= P; }
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) scanf("%d", &b[i]), to[a[i]] = b[i];
f[1][0][0][0] = 1;
f[1][1][1][0] = 1;
f[1][1][0][1] = 1;
for (int i = 1; i < n; i++)
for (int j = 0; j <= i; j++)
for (int k = 0; k < 2; k++)
for (int l = 0, v; l < 2; l++)
if (f[i][j][k][l]) {
LL v = f[i][j][k][l];
Ad(f[i + 1][j][0][l], v);
Ad(f[i + 1][j + 1][1][l], v);
if (!k) Ad(f[i + 1][j + 1][0][l], v);
}
g[1][0] = g[1][1] = 1;
for (int i = 2; i <= n; i++)
for (int j = 0; j <= i; j++)
for (int k = 0; k < 2; k++)
for (int l = 0, v; l < 2; l++)
if (k + l < 2) Ad(g[i][j], f[i][j][k][l]);
h[0] = 1;
for (int i = 1, le; i <= n; i++) {
if (vis[i]) continue;
le = 0;
for (int j = i; !vis[j]; j = to[j]) vis[j] = 1, ++le;
memset(h2, 0, sizeof(h2));
for (int j = 0; j <= n; j++)
for (int k = 0; k <= le; k++)
if (j + k <= n) Ad(h2[j + k], h[j] * g[le][k] % P);
for (int j = 0; j <= n; j++) h[j] = h2[j];
}
fac[0] = 1;
for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % P;
for (int i = 0, op = 1; i <= n; i++, op *= -1)
Ad(ans, op * fac[n - i] * h[i] % P);
printf("%lld", (ans + P) % P);
}
Collecting
\(n\) 个点 \(m\) 条边的有向图,点 \(i\) 有 \(a_i\) 个物品,\(K\) 个人依次从 1 开始遍历这个图。
所到之处收集所有物品,问 \(K\) 个人最多收集的物品。
\(n,m\le 2\times 10^5,K\le 10\)
sol
显然在一个强联通分量的点可以一次收集完。先缩点,得到一个 DAG
设 \(X_i\) 为编号为 \(i\) 的 scc 的点权和,\(id_i\) 为原图点 \(i\) 所在的 scc 编号, \(cnt\) 为 scc 总个数
关键(没想到): \(K\) 很小,可以用网络流。
一个点只能收集 1 次,拆点 \(u\) 为 \(in_u,out_u\) ,注意 \(u\) 为缩点后的点
连边方式易得:
- \(in_u\rightarrow out_u\) ,一条边为 \((1,-X_u)\) ,另一条 \((\infin,0)\)
- 对于原来的边 \((u,v)\) ,连一条 \(out_u\rightarrow in_v\) ,\((\infin,0)\)
- \(out_u\rightarrow T\) ,\((\infin,0)\)
- \(S\rightarrow in_{id_1}\) ,\((K,0)\)
跑一个最小费用最大流,但是存在负边权,考虑转为正边权
(细节)按照拓扑序对这个 DAG
重新编号。
实现时由于 tarjan
缩点是 dfs
,其实可以直接反转编号
加边修改如下:
- \(in_u\rightarrow out_u\) ,一条边为 \((1,0)\) ,另一条 \((\infin,X_u)\)
- 对于原来的边 \((u,v)\) ,连一条 \(out_u\rightarrow in_v\) ,\((\infin,X_{u+1}+\cdots+X_{v-1})\)
- \(out_u\rightarrow T\) ,\((\infin,X_{u+1}+\cdots+X_{cnt})\)
- \(S\rightarrow in_{id_1}\) ,\((K,X_1+\cdots+X_{id_1})\)
最后 \(ans=K\sum_{u}X_u-mincost\)
理解:假设每一个人都可以获得全图的物品。
重复回到一个点就损失了 \(X_u\) ,由于按照拓扑序排好,从 \(u\) 到 \(v\) 就跳过了 \(u+1,\cdots,v-1\) 这些点
最后求最小损失。
最多增广 \(K\) 次,用 dijkstra
实现可以做到 \(O(Kn\log n)\)
注意 long long
code
3K 代码,大嘘
#include
#define MP make_pair
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 4e5 + 5;
int n, m, K, a[N], lst[N], Ecnt, dfn[N], low[N], st[N], top, clk, cl[N], cnt, ins[N];
LL X[N], sm[N];
struct Edg { int to, nxt; } e[N];
inline void Ae(int fr, int go) {
e[++Ecnt] = (Edg){ go, lst[fr] }, lst[fr] = Ecnt;
}
void tarjan(int u) {
dfn[u] = low[u] = ++clk, st[++top] = u, ins[u] = 1;
for (int i = lst[u], v; i; i = e[i].nxt) {
if (!dfn[v = e[i].to])
tarjan(v), low[u] = min(low[u], low[v]);
else if (ins[v]) low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
register int o; ++cnt;
do o = st[top--], ins[o] = 0, X[cnt] += a[o], cl[o] = cnt; while (o ^ u);
}
}
struct net {
int lst[N], Ecnt, St, Ed, eg[N], pre[N];
LL dis[N], Cost;
priority_queue< pair > Q;
net() { Ecnt = 1, memset(lst, 0, sizeof(lst)); }
struct Edge { int to, nxt, qz; LL cs; } e[N << 4];
inline void Ae(int fr, int go, int vl, LL o) {
e[++Ecnt] = (Edge){ go, lst[fr], vl, o }, lst[fr] = Ecnt;
}
inline void lk(int u, int v, int w, LL q) {
Ae(u, v, w, q), Ae(v, u, 0, -q);
}
bool dijk() {
for (int i = 1; i <= Ed; i++) dis[i] = 1e18;
Q.push(MP(0, St)), dis[St] = 0;
for (int u; !Q.empty(); ) {
pair now = Q.top(); Q.pop();
u = now.second;
if (-now.first > dis[u]) continue;
for (int i = lst[u], v; i; i = e[i].nxt)
if (e[i].qz > 0 && dis[v = e[i].to] > dis[u] + e[i].cs) {
dis[v] = dis[u] + e[i].cs, pre[v] = u, eg[v] = i;
Q.push(MP(-dis[v], v));
}
}
return dis[Ed] < 1e18;
}
LL sol() {
LL res = 0;
while (dijk()) {
int fl = 10;
for (int u = Ed; u ^ 1; u = pre[u]) fl = min(fl, e[eg[u]].qz);
res += dis[Ed] * fl;
for (int u = Ed; u ^ 1; u = pre[u]) e[eg[u]].qz -= fl, e[eg[u] ^ 1].qz += fl;
}
return res;
}
} T;
int main() {
scanf("%d%d%d", &n, &m, &K);
for (int i = 1, u, v; i <= m; i++) {
scanf("%d%d", &u, &v);
Ae(u, v);
}
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
T.St = 1, T.Ed = cnt * 2 + 2;
reverse(X + 1, X + cnt + 1);
for (int i = 1; i <= n; i++) cl[i] = cnt - cl[i] + 1;
for (int i = 1; i <= cnt; i++) sm[i] = sm[i - 1] + X[i];
for (int i = 1; i <= n; i++)
for (int j = lst[i], v; j; j = e[j].nxt)
if (cl[i] ^ cl[v = e[j].to]) {
T.lk(cl[i] << 1 | 1, cl[v] << 1, K, sm[cl[v] - 1] - sm[cl[i]]);
}
for (int i = 1; i <= cnt; i++) {
T.lk(i << 1, i << 1 | 1, 1, 0);
T.lk(i << 1, i << 1 | 1, K, X[i]);
T.lk(i << 1 | 1, T.Ed, K, sm[cnt] - sm[i]);
}
T.lk(T.St, cl[1] << 1, K, sm[cl[1] - 1]);
printf("%lld", sm[cnt] * K - T.sol());
}