2018冬令营模拟测试赛(十二)
[Problem A]snow
试题描述
\(2333\) 年的某一天,临冬突降大雪,主干道已经被雪覆盖不能使用。城主 囧·雪 决定要对主干道进行一次清扫。
临冬城的主干道可以看为一条数轴。囧·雪 一共找来了 \(n\) 个清理工,第 \(i\) 个清理工的工作范围为 \([l_i,r_i]\),也就是说这个清理工会把 \([l_i,r_i]\) 这一段主干道清理干净(当然已经被清理过的部分就被忽略了)。当然有可能主干道不能全部被清理干净,不过这也没啥关系。
虽然 囧·雪 啥都不知道,但是他还是保证了不会出现某一个清理工的工作范围被另一个清理工完全包含的情况(不然就太蠢了)。
作为临冬城主,囧·雪 给出了如下的清扫方案:
在每一天开始的时候,每一个还没有工作过的清理工会观察自己工作范围内的道路,并且记下工作范围内此时还没有被清理的道路的长度(称为这个清理工的工作长度)。然后 囧·雪 会从中选择一个工作长度最小的清理工(如果两个清理工工作长度相同,那么就选择编号小的清理工)。然后被选择的这个清理工会清理自己的工作范围内的道路。为了方便检查工作质量,囧·雪 希望每一天只有一个清理工在工作。
你要注意,清理工的工作长度是可能改变的,甚至有可能变成 \(0\)。尽管如此,这个清理工也还是会在某一天工作。
现在,囧·雪 想要知道每一天都是哪个清理工在工作?
输入
第一行两个整数 \(t,n\)。分别表示主干道的长度(也就是说,主干道是数轴上 \([1,t]\) 的这一段)以及清理工的人数。
接下来 \(n\) 行,每行两个整数 \(l_i,r_i\)。意义如题。
输出
输出 \(n\) 行,第 \(i\) 行表示第 \(i\) 天工作的清理工的编号。
输入示例
15 4
1 6
3 7
6 11
10 14
输出示例
2
1
3
4
数据规模及约定
对于 \(30\%\) 的数据,\(n \le 1000\)
对于 \(60\%\) 的数据,\(n \le 7 \times 10^4\)
对于 \(100\%\) 的数据,\(n \le 3 \times 10^5, 1 \le l_i < r_i \le t \le 10^9\),保证输入的 \(l_i\) 严格递增。
题解
这题其实挺暴力的……
首先离散,那么每小段路都只会被清理最多一次,所以我们可以用个 set 维护一下当前没有被清理的小段路有那些,遇到一个清洁工的时候我们二分一下找到哪些路段需要被清理然后依次暴力清理。
然后有一个需要考虑的问题:一个路段会影响到那些清洁工(使它们的工作长度减小)呢?
由于没有完全包含的区间,且这道题贴心地给你把左端点排好序了,所以左右端点是一同单调的。这样一个路段会影响到的清洁工也是连续的一段,这个左右端点都可以二分得到,于是用个线段树维护清洁工的信息就好咯。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <set>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = getchar(); }
return x * f;
}
#define maxn 300010
#define oo 2147483647
#define pii pair <int, int>
#define x first
#define y second
#define mp(x, y) make_pair(x, y)
int n, num[maxn<<1], cntn, real[maxn];
struct Worker {
int l, r, id;
Worker() {}
Worker(int _1, int _2, int _3): l(_1), r(_2), id(_3) {}
bool operator < (const Worker& t) const { return l < t.l; }
} ns[maxn];
pii mnv[maxn<<2];
int addv[maxn<<2];
void pushdown(int o, int l, int r) {
if(!addv[o] || l == r){ addv[o] = 0; return ; }
int lc = o << 1, rc = lc | 1;
addv[lc] += addv[o]; if(mnv[lc].x < oo) mnv[lc].x += addv[o];
addv[rc] += addv[o]; if(mnv[rc].x < oo) mnv[rc].x += addv[o];
addv[o] = 0;
return ;
}
void build(int o, int l, int r) {
if(l == r) mnv[o] = mp(num[ns[l].r] - num[ns[r].l], ns[l].id);
else {
int mid = l + r >> 1, lc = o << 1, rc = lc | 1;
build(lc, l, mid); build(rc, mid + 1, r);
mnv[o] = min(mnv[lc], mnv[rc]);
}
return ;
}
void finish(int o, int l, int r, int p) {
pushdown(o, l, r);
if(l == r) mnv[o] = mp(oo, -1);
else {
int mid = l + r >> 1, lc = o << 1, rc = lc | 1;
if(p <= mid) finish(lc, l, mid, p);
else finish(rc, mid + 1, r, p);
mnv[o] = min(mnv[lc], mnv[rc]);
}
return ;
}
void decrease(int o, int l, int r, int ql, int qr, int v) {
if(ql > qr) return ;
pushdown(o, l, r);
if(ql <= l && r <= qr) {
addv[o] -= v;
if(mnv[o].x < oo) mnv[o].x -= v;
return ;
}
int mid = l + r >> 1, lc = o << 1, rc = lc | 1;
if(ql <= mid) decrease(lc, l, mid, ql, qr, v);
if(qr > mid) decrease(rc, mid + 1, r, ql, qr, v);
mnv[o] = min(mnv[lc], mnv[rc]);
return ;
}
void clean(int x) {
int ql, qr, l = 1, r = n;
while(l < r) {
int mid = l + r >> 1;
if(ns[mid].r - 1 < x) l = mid + 1; else r = mid;
}
ql = l;
l = 1; r = n + 1;
while(r - l > 1) {
int mid = l + r >> 1;
if(ns[mid].l <= x) l = mid; else r = mid;
}
qr = l;
decrease(1, 1, n, ql, qr, num[x+1] - num[x]);
return ;
}
set <int> road;
int main() {
int t = read(); n = read();
rep(i, 1, n) {
int l = read(), r = read();
ns[i] = Worker(l, r, i);
num[++cntn] = l; num[++cntn] = r;
}
num[++cntn] = t;
sort(num + 1, num + cntn + 1);
cntn = unique(num + 1, num + cntn + 1) - num - 1;
rep(i, 1, n)
ns[i].l = lower_bound(num + 1, num + cntn + 1, ns[i].l) - num,
ns[i].r = lower_bound(num + 1, num + cntn + 1, ns[i].r) - num;
rep(i, 1, cntn - 1) road.insert(i);
sort(ns + 1, ns + n + 1);
rep(i, 1, n) real[ns[i].id] = i;
build(1, 1, n);
rep(kase, 1, n) {
int now = mnv[1].y;
printf("%d\n", now); finish(1, 1, n, real[now]);
Worker w = ns[real[now]];
set <int> :: iterator it = road.lower_bound(w.l), tmp;
while(1) {
if(it == road.end() || *it >= w.r) break;
clean(*it);
tmp = it; it++; road.erase(tmp);
}
}
return 0;
}
[Problem B]hack
试题描述
由于FZYZ教学区禁止使用手机,所以如何在一个课间通知到人就成了一个很大的问题。
所幸,在不知道被信息传递不及时坑了多少次之后,小叶子(@97littleleaf11)完美地解决了这个问题。小叶子组建了一张关系网,每一个人是这张关系网上的一个节点(节点编号为 \([0,n-1]\)),两个人之间的通讯关系就是这张网上的一条有向边(一条 \(u \rightarrow v\) 的边意味着信息可以从 \(u\) 传递到 \(v\))。小叶子是 \(0\) 号节点,也是信息的发出者,n+e是 \(n-1\) 号节点,在这个问题中,他就是信息的接受者。
一条信息从小叶子出发,可以沿着任意的边传递,最终传递给n+e。在这个过程中,一个人(包括小叶子和n+e)可以经过多次,一条边也可以经过多次。
经过多年的观察,小叶子发现这张关系网的每一条边都是有可能被hack的!当然每条边hack的代价是不一样的。所以,小叶子想要评价这个关系网的安全程度。
试想你要入侵这一张关系网,那么你只能事先选择一些边,将这些边hack掉。如果一条边被hack了,就意味着当信息从这条边传递的时候就会被截获。当然n+e也是非常厉害的!如果一条信息在传递过程中被截获两次及以上,那么n+e就能用强大的智商定位出你的位置,那么这一次入侵就必然会失败。当然,如果n+e接收到了消息,但是这条消息没有被截获,那么这次入侵也就是失败的。
更精确地说,一次成功的入侵要满足以下条件:对于任意一种可能的传递信息的方式(对应着一条从 \(0\) 到 \(n-1\) 的路径),必须经过恰好一次被hack的边。
一次入侵的代价就是你选择hack掉的边的代价和。
小叶子想要知道,如果你拥有n+e这样超神的智商,而你又想最小化代价,那么你入侵的代价会是多少呢?
输入
第一行 \(n\),\(m\)。表示点数及边数
接下来 \(m\) 行,每行三个整数 \(u,v,w\),表示一条从 \(u\) 到 \(v\) 的代价为 \(w\) 的边。
输出
输出一行,表示答案。
如果不存在合法的入侵方案,那么输出 \(-1\).
输入示例1
6 7
0 1 5
0 2 5
1 3 1
2 4 1
4 1 1
3 5 5
4 5 5
输出示例1
6
输入示例2
3 3
0 1 1
1 2 1
2 0 1
输出示例2
-1
数据规模及约定
对于 \(30\%\) 的数据, \(m \le 15\)
对于 \(50\%\) 的数据, \(n \le 15\)
对于 \(100\%\) 的数据,\(2 \le n \le 100,m \le 2500,1 \le w \le 10^9,0 \le u,v < n\)
保证存在至少一条从 \(0\) 到 \(n-1\) 的路径。
题解
首先肯定会想到最小割。
但是最小割显然不行,样例就是反例。我们可以试图用最小割跑一下样例,看到底为什么不符合要求。可以看出,最小割跑出来的答案是 \(2\):它割断了 \(1 \rightarrow3\) 和 \(2 \rightarrow 4\) 这两条边;剩下一条从 \(t\) 集合连向 \(s\) 集合的边 \(4 \rightarrow 1\),这样就会产生“某条路径被割断两次或以上”的情况。
不难发现只要避免这种割完之后还有“从 \(t\) 集合连向 \(s\) 集合的边”的情况就得到合法方案了。解决办法是我们给每条边加一条反向的流量为正无穷的边,这样就防止我们会剩下那条边,或是割完之后再把这样“返回”的边又割掉。
注意这题还有一个坑,\(s\) 到达不了的节点需要删掉,否则建图会出现问题。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <set>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = getchar(); }
return x * f;
}
#define maxn 110
#define maxm 5010
#define ool (1ll << 60)
#define LL long long
struct Edge {
int from, to; LL flow;
Edge() {}
Edge(int _1, int _2, LL _3): from(_1), to(_2), flow(_3) {}
};
struct Dinic {
int n, m, s, t, head[maxn], nxt[maxm];
Edge es[maxm];
int vis[maxn], Q[maxn], hd, tl;
int cur[maxn];
void init() {
m = 0; memset(head, -1, sizeof(head));
return ;
}
void setn(int _) {
n = _;
return ;
}
void AddEdge(int a, int b, LL c, LL revc) {
es[m] = Edge(a, b, c); nxt[m] = head[a]; head[a] = m++;
es[m] = Edge(b, a, revc); nxt[m] = head[b]; head[b] = m++;
return ;
}
bool BFS() {
memset(vis, 0, sizeof(vis));
hd = tl = 0; Q[++tl] = t;
vis[t] = 1;
while(hd < tl) {
int u = Q[++hd];
for(int i = head[u]; i != -1; i = nxt[i]) {
Edge& e = es[i^1];
if(!vis[e.from] && e.flow) vis[e.from] = vis[u] + 1, Q[++tl] = e.from;
}
}
return vis[s] > 1;
}
LL DFS(int u, LL a) {
if(u == t || !a) return a;
LL flow = 0, f;
for(int& i = cur[u]; i != -1; i = nxt[i]) {
Edge& e = es[i];
if(vis[e.to] == vis[u] - 1 && (f = DFS(e.to, min(a, e.flow)))) {
flow += f; a -= f;
e.flow -= f; es[i^1].flow += f;
if(!a) return flow;
}
}
return flow;
}
LL MaxFlow(int _s, int _t) {
s = _s; t = _t;
LL flow = 0;
while(BFS()) {
rep(i, 1, n) cur[i] = head[i];
flow += DFS(s, ool);
}
return flow;
}
} sol;
struct Graph {
int n, m, head[maxn], nxt[maxm], to[maxm], dist[maxm];
Graph(): m(0) { memset(head, 0, sizeof(head)); }
void AddEdge(int a, int b, int c) {
to[++m] = b; dist[m] = c; nxt[m] = head[a]; head[a] = m;
return ;
}
} G, D;
int clo, low[maxn], dfn[maxn], scno[maxn], cnts, S[maxn], top;
void dfs(int u, int fa) {
low[u] = dfn[u] = ++clo;
S[++top] = u;
for(int e = G.head[u]; e; e = G.nxt[e]) if(G.to[e] != fa) {
if(scno[G.to[e]]) continue;
if(dfn[G.to[e]]) low[u] = min(low[u], dfn[G.to[e]]);
else dfs(G.to[e], u), low[u] = min(low[u], low[G.to[e]]);
}
if(low[u] == dfn[u]) {
cnts++;
while(S[top] != u) scno[S[top--]] = cnts;
scno[S[top--]] = cnts;
}
return ;
}
int Q[maxn], hd, tl;
bool vis[maxn];
void bfs(int s) {
hd = tl = 0; Q[++tl] = s;
vis[s] = 1;
while(hd < tl) {
int u = Q[++hd];
for(int e = D.head[u]; e; e = D.nxt[e]) if(!vis[D.to[e]]) vis[D.to[e]] = 1, Q[++tl] = D.to[e];
}
return ;
}
int main() {
int n = G.n = read(); int m = read();
rep(i, 1, m) {
int a = read() + 1, b = read() + 1, c = read();
G.AddEdge(a, b, c);
}
dfs(1, 0);
if(scno[1] == scno[n]) return puts("-1"), 0;
rep(i, 1, n) if(scno[i]) {
for(int e = G.head[i]; e; e = G.nxt[e]) if(scno[i] != scno[G.to[e]])
D.AddEdge(scno[i], scno[G.to[e]], G.dist[e]);
}
bfs(scno[1]);
sol.init(); sol.setn(cnts);
rep(i, 1, n) if(scno[i] && vis[scno[i]]) {
for(int e = G.head[i]; e; e = G.nxt[e]) if(scno[i] != scno[G.to[e]] && vis[scno[G.to[e]]])
sol.AddEdge(scno[i], scno[G.to[e]], G.dist[e], ool - G.dist[e]);
}
printf("%lld\n", sol.MaxFlow(scno[1], scno[n]));
return 0;
}
其实这题不用 tarjan 缩点的
[Problem C]count
试题描述
n+e接收到了小叶子的信息,你的hack失败了!
这条信息是这样的:
To n+e:
我的桌子忘记整理了,听说待会检查卫生,求帮忙。
叶子
小叶子的桌面上有n本高度不相同的书,n+e现在需要把这些书按照一定的顺序摆放好。假设第 \(i\) 本书的高度为 \(h[i]\),n+e的摆放用一个 \(1 \sim n\) 的排列 \(p_i\) 来表示。
定义一个摆放的混乱程度:\(|h[p2]-h[p1]|+|h[p3]-h[p2]|+ \cdots +|h[pn]-h[pn-1]|\),即相邻两本书的高度差的绝对值之和。
已知合法的摆放要求其混乱程度不超过 \(L\)。
小叶子想要知道,n+e到底有多少种合法的摆放的方法呢?
作为将要参加NOI的选手,你应该知道,小叶子只关心这个数对 \(10^9+7\) 取模的结果。
输入
第一行两个数 \(n,L\)。
接下来一行 \(n\) 个数 \(h[i]\)
输出
输出一行,表示方案数对 \(10^9+7\) 取模的结果。
输入示例
3 2
2 3 4
输出示例
2
数据规模及约定
对于 \(20\%\) 的数据,\(n \le 8\)
对于 \(40\%\) 的数据,\(n \le 14,L \le 100\)
对于 \(100\%\) 的数据,\(1 \le n \le 100,1 \le L,h[i] \le 1000\)
数据有轻微梯度。
题解
它要你安排一个排列,所以一般的套路想法就是从大到小(或从小到大)依次插入。
这题似乎从大到小插入比较方便?
由于当前插入的一定是全场最矮,那么分情况讨论。我们令 \(f(l, r, k, s)\) 表示左边是否贴边(\(l = 0\) 表示没贴,\(l = 1\) 表示贴边),右边是否贴边(即 \(r\)),当前有 \(k\) 个没填的空缺(不包括左右没贴边的空缺),当前侧边暴露出来的周长为 \(s\)。
那么插入一个全场最矮,有 \(3\) 种情况(下面只讨论插到中间 \(k\) 个空缺中的情况,两边的类似):
贴空缺的左(或右)边,这样 \(k\) 不会减少;
填上一个坑,这样 \(k\) 会减 \(1\);
不贴边,在坑里独自傲娇伫立,这样 \(k\) 会加 \(1\)。
注意以上没有讨论周长 \(s\) 的变化,这是因为如果这样直接做 \(s\) 会增加也会减小,这样有可能加到很大很大然后再一直减减减减回来,最终长度还是不超过 \(L\),但是这样中途的状态就没法记下来了……怎么办呢?
我们每次将“地平线”设置为当前填的书的高度,由于我们按从高到低顺序枚举的书的高度,这样每次地平线会下降一个特定的值,那么转移的时候根据中间的空缺个数和左右是否贴边的情况就可以搞出周长会增加多少了。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <set>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = getchar(); }
return x * f;
}
#define maxn 110
#define maxl 1010
#define MOD 1000000007
#define LL long long
int f[2][2][2][maxn][maxl], h[maxn];
void upd(int& a, int b) {
a += b; if(a >= MOD) a -= MOD;
return ;
}
int main() {
int n = read(), L = read();
rep(i, 1, n) h[i] = read();
sort(h + 1, h + n + 1);
reverse(h + 1, h + n + 1);
if(n == 1) return puts("1"), 0;
int cur = 0;
f[cur][0][1][0][0] = f[cur][1][0][0][0] = f[cur][0][0][0][0] = 1;
rep(i, 1, n - 1) {
memset(f[cur^1], 0, sizeof(f[cur^1]));
rep(l, 0, 1) rep(r, 0, 1) rep(k, 0, n) rep(len, 0, L) if(f[cur][l][r][k][len]) {
int nxtl = len + (h[i] - h[i+1]) * ((k << 1) + !l + !r);
if(nxtl > L) break;
int now = f[cur][l][r][k][len];
if(k) {
upd(f[cur^1][l][r][k-1][nxtl], (LL)now * k % MOD);
upd(f[cur^1][l][r][k][nxtl], ((LL)now * k << 1) % MOD);
upd(f[cur^1][l][r][k+1][nxtl], (LL)now * k % MOD);
}
if(!l) {
upd(f[cur^1][1][r][k][nxtl], now);
upd(f[cur^1][0][r][k][nxtl], now);
upd(f[cur^1][1][r][k+1][nxtl], now);
upd(f[cur^1][0][r][k+1][nxtl], now);
}
if(!r) {
upd(f[cur^1][l][1][k][nxtl], now);
upd(f[cur^1][l][0][k][nxtl], now);
upd(f[cur^1][l][1][k+1][nxtl], now);
upd(f[cur^1][l][0][k+1][nxtl], now);
}
// printf("f[%d][%d][%d][%d][%d] = %d %d\n", i, l, r, k, len, now, nxtl);
}
cur ^= 1;
}
int ans = 0;
rep(len, 0, L) upd(ans, f[cur][1][1][0][len]);
printf("%d\n", ans);
return 0;
}