NOI2018游记
一年OI一场空 …
May all the beauty be blessed.
题目地址:洛谷:【P1712】[NOI2016]区间 – 洛谷、BZOJ:Problem 4653. — [Noi2016]区间
在数轴上有 n个闭区间 [l1,r1],[l2,r2],…,[ln,rn]。现在要从中选出 m 个区间,使得这 m个区间共同包含至少一个位置。换句话说,就是使得存在一个 x,使得对于每一个被选中的区间 [li,ri],都有 li≤x≤ri。
对于一个合法的选取方案,它的花费为被选中的最长区间长度减去被选中的最短区间长度。区间 [li,ri] 的长度定义为 ri−li,即等于它的右端点的值减去左端点的值。
求所有合法方案中最小的花费。如果不存在合法的方案,输出 −1。
给你$n$个区间,求选取这些区间的一个子集,使得这个子集中的区间交集不为空,一种选区方案的花费定义为最长区间长度减去最短区间长度,求花费最小的选取方案。
输入格式:
第一行包含两个正整数 n,m用空格隔开,意义如上文所述。保证 1≤m≤n
接下来 n行,每行表示一个区间,包含用空格隔开的两个整数 li 和 ri 为该区间的左右端点。
N<=500000,M<=200000,0≤li≤ri≤10^9
输出格式:
只有一行,包含一个正整数,即最小花费。
输入样例#1:
6 3 3 5 1 2 3 4 2 2 1 5 1 4
输出样例#1:
2
我们对区间按长度排序,贪心地选择尽量短的长度单调的一段,使得这一段中存在一个点被覆盖了至少$m$次,则我们可以用这一段的最长长度减最短长度更新答案。可以证明,这样做一定能找到最优解,相当于在枚举每一个最短长度,贪心地找到最小的最长长度。
判断是否被覆盖$m$次,只需要把区间覆盖转换成线段树上的区间加法,维护全局最大即可。
复杂度$O(n \log n)$。由于左右端点比较大需要离散化。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = res * 10 + c - '0';
return res * neg;
}
inline char readsingle() {
register char c;
while(!isgraph(c = fgc())) {}
return c;
}
const int MAXN = 500005;
int n, m, N;
struct Seg {
int l, r, len;
inline bool operator<(const Seg &rhs) const {
return len < rhs.len;
}
} segs[MAXN];
#define lch o << 1
#define rch o << 1 | 1
#define mid ((l + r) >> 1)
int mx[MAXN << 3], tag[MAXN << 3];
inline void pushdown(int o) {
if(tag[o]) {
tag[lch] += tag[o]; tag[rch] += tag[o];
mx[lch] += tag[o]; mx[rch] += tag[o];
tag[o] = 0;
}
}
void add(int o, int l, int r, int ll, int rr, int v) {
if(l >= ll && r <= rr) {
mx[o] += v; tag[o] += v; return;
}
pushdown(o);
if(ll <= mid) add(lch, l, mid, ll, rr, v);
if(rr > mid) add(rch, mid + 1, r, ll, rr, v);
mx[o] = std::max(mx[lch], mx[rch]);
}
std::vector<int> tmp;
int main() {
n = readint(); m = readint();
tmp.push_back(-1);
for(int i = 1; i <= n; i++) {
segs[i].l = readint(); segs[i].r = readint();
segs[i].len = segs[i].r - segs[i].l;
tmp.push_back(segs[i].l);
tmp.push_back(segs[i].r);
}
std::sort(tmp.begin(), tmp.end());
tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
N = tmp.size() - 1;
for(int i = 1; i <= n; i++) {
segs[i].l = std::lower_bound(tmp.begin(), tmp.end(), segs[i].l) - tmp.begin();
segs[i].r = std::lower_bound(tmp.begin(), tmp.end(), segs[i].r) - tmp.begin();
}
std::sort(segs + 1, segs + n + 1);
int r = 0, ans = 2e9;
for(int l = 1; l <= n; l++) {
while(r < n && mx[1] < m) {
r++; add(1, 1, N, segs[r].l, segs[r].r, 1);
}
if(mx[1] >= m) ans = std::min(ans, segs[r].len - segs[l].len);
add(1, 1, N, segs[l].l, segs[l].r, -1);
}
printf("%d", ans == 2e9 ? -1 : ans);
return 0;
}
题目地址:洛谷:【P3975】[TJOI2015]弦论 – 洛谷、BZOJ:Problem 3998. — [TJOI2015]弦论
为了提高智商,ZJY开始学习弦论。这一天,她在《 String theory》中看到了这样一道问题:对于一个给定的长度为n的字符串,求出它的第k小子串是什么。你能帮帮她吗?
求一个串的本质不同子串中第k小或所有子串中第k小。
输入格式:
第一行是一个仅由小写英文字母构成的字符串s
第二行为两个整数t和k,t为0则表示不同位置的相同子串算作一个,t为1则表示不同位置的相同子串算作多个。k的意义见题目描述。
输出格式:
输出数据仅有一行,该行有一个字符串,为第k小的子串。若子串数目不足k个,则输出-1。
输入样例#1:
aabc 0 3
输出样例#1:
aab
输入样例#2:
aabc 1 3
输出样例#2:
aa
输入样例#3:
aabc 1 11
输出样例#3:
-1
数据范围
对于10%的数据,n ≤ 1000。
对于50%的数据,t = 0。
对于100%的数据,n ≤ 5 × 10^5, t < 2, k ≤ 10^9。
对于SAM来说,这个题是一种经典应用了。
在DAWG上的任何一条从起点出发的路径都是原串的一个子串,因此,如果我们能求出经过一个点的路径数(即DAWG上它能到达的点的数量)就可以知道某一个点的“子树”(由于DAWG并不是严格的树,因此采用这种称呼)中可以表示多少串,从起始点开始往深走就可以求出答案。
对于本质不同的情况,只需把起点到这个点的路径数设置为1,求这个值的“子树和”即可,对于考虑重复的情况,上述值设置为$\mathrm{endpos}$集合大小即可。求和可以用拓扑序($\max$从大到小的顺序)处理。
这里用了std::sort
因此复杂度$O(n \log n)$。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
#include <set>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = res * 10 + c - '0';
return res * neg;
}
const int MAXN = 1000005;
struct SuffixAutomaton {
int ch[MAXN][26], len[MAXN], nxt[MAXN], siz[MAXN], lst, tot;
SuffixAutomaton() {
lst = tot = 1;
}
inline void extend(int c) {
int u = ++tot, v = lst;
len[u] = len[v] + 1;
for(; v && !ch[v][c]; v = nxt[v]) ch[v][c] = u;
if(!v) {
nxt[u] = 1;
} else if(len[ch[v][c]] == len[v] + 1) {
nxt[u] = ch[v][c];
} else {
int o = ch[v][c], n = ++tot;
memcpy(ch[n], ch[o], sizeof(ch[o]));
len[n] = len[v] + 1;
for(; v && ch[v][c] == o; v = nxt[v]) ch[v][c] = n;
nxt[n] = nxt[o]; nxt[o] = nxt[u] = n;
}
lst = u; siz[lst] = 1;
}
} sam;
inline bool cmp(int a, int b) {
return sam.len[a] > sam.len[b];
}
int n, t, k, a[MAXN], sum[MAXN];
char s[MAXN];
int main() {
scanf("%s%d%d", s + 1, &t, &k);
n = strlen(s + 1);
for(int i = 1; i <= n; i++) {
sam.extend(s[i] - 'a');
}
for(int i = 1; i <= sam.tot; i++) {
a[i] = i;
}
std::sort(a + 1, a + sam.tot + 1, cmp);
for(int i = 1; i <= sam.tot; i++) {
if(t) sam.siz[sam.nxt[a[i]]] += sam.siz[a[i]];
else sam.siz[a[i]] = 1;
}
sam.siz[1] = 0;
for(int i = 1; i <= sam.tot; i++) {
sum[a[i]] = sam.siz[a[i]];
for(int j = 0; j < 26; j++) {
if(sam.ch[a[i]][j]) sum[a[i]] += sum[sam.ch[a[i]][j]];
}
}
if(k > sum[1]) {
puts("-1"); return 0;
}
int p = 1;
while(k - sam.siz[p] > 0) {
k -= sam.siz[p];
int q = 0;
while(k > sum[sam.ch[p][q]]) k -= sum[sam.ch[p][q++]];
p = sam.ch[p][q];
putchar('a' + q);
}
return 0;
}
题目地址:洛谷:【P4700】[CEOI2011]Traffic – 洛谷、BZOJ:Problem 2387. — [Ceoi2011]Traffic
格丁尼亚的中心位于Kacza河中的一座岛屿。每天清晨,成千上万辆汽车通过岛屿从西岸的住宅区(由桥连接岛的西部)到东岸的工业区(由桥连接岛的东部)。该岛类似于矩形,它的边平行于主方向。故可将它看作是笛卡尔坐标系中的一个A*B的矩形,它的对角分别为(0, 0)和(A, B)。岛上有n个交通节点,编号为1…n(junction,此处可理解为广义的路口),第i个节点坐标为(xi, yi)。如果一个节点的坐标为(0, y),它就位于岛的西岸。类似的,坐标为(A, y)的节点位于岛的东岸。各个节点由街道连接,每条街道用线段连接两个节点。街道有单向行驶或双向行驶之分。除端点外任意两条街道都没有公共点。也没有桥梁或者隧道。你不能对道路网络形状做任何其他假设。沿河岸的街道或节点可能没有入口或者出口街道。由于交通堵塞日趋严重,市长聘请你测试岛上当前的道路网是否足够。要求你写一个程序确定从岛的西岸的每个节点能够到达东岸的多少个节点。
在平面直角坐标系上有 n 个点,其中第 i 个点的坐标是 (xi,yi) ,所有点在一个以 (0,0) 和 (A,B) 为相对顶点的矩形内。
如果 xi=0 ,那么我们称这个点在西侧。如果 xi=A ,那么我们称这个点在东侧。
这些点之间有 m 条边,每条边可能是有向边也可能是无向边,保证边在交点以外的任何地方不相交。
现在请你求出,对于每一个西侧的点,能够沿着边到达多少东侧的点。
输入格式:
第1行包含4个整数n, m, A, B(1≤n≤300000, 0≤m≤900000,1≤A,B≤109),
分别表示格丁尼亚市中心的节点数,街道数和岛屿的尺寸。
接下来的n行,每行包含两个整数xi,yi (0≤xi≤A,0≤yi≤B),表示第i个节点的坐标。任意两个节点的坐标都不相同。
再往下的m行表示街道,每条街道用3个整数ci, di, ki(1≤ci, di≤n, ci≠di, ki∈{1,2}),
表示节点ci、di有街道连接
如果ki=1,表示从ci到di的街道是单向的,否则,这条街道可以双向行驶。每个无序对{ci, di}最多出现1次。
你可以假设西岸节点中至少有1个能够到达东岸的一些节点。
输出格式:
为每个西岸节点输出1行,包括从这个节点出发能够到达东岸的节点数目
输入样例#1:
5 3 1 3 0 0 0 1 0 2 1 0 1 1 1 4 1 1 5 2 3 5 2
输出样例#1:
2 0 2
输入样例#2:
12 13 7 9 0 1 0 3 2 2 5 2 7 1 7 4 7 6 7 7 3 5 0 5 0 9 3 9 1 3 2 3 2 1 3 4 1 4 5 1 5 6 1 9 3 1 9 4 1 9 7 1 9 12 2 10 9 1 11 12 1 12 8 1 12 10 1
输出样例#2:
4 4 0 2
$1 \leq n \leq 300000, 0 \leq m \leq 900000, 1 \leq A, B \leq 10^9$
首先显然可以对这个图缩一波强连通分量。注意到题目隐含了一个平面图的条件,对于一个平面图的左侧点,它能到达的右侧点应该在右侧是相邻的一段。因此,我们可以先扔掉那些不可达的右侧点,然后按照$y$坐标的顺序编号,用DFS来计算出缩点后的图中,每一个点对应的右侧区间左右端点,这样就可以计算出答案了。
复杂度$O(n \log n)$。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
const int MAXN = 300005;
int n, m, A, B;
std::vector<int> gra[MAXN], gran[MAXN];
typedef std::pair<int, int> PII;
std::vector<int> lft, rgt;
PII node[MAXN];
int id[MAXN];
bool vis[MAXN];
int dfn[MAXN], low[MAXN], clk, sno[MAXN], scc, mx[MAXN], mn[MAXN];
bool insta[MAXN];
std::stack<int> sta;
std::set<PII> edges;
void tarjan(int u) {
dfn[u] = low[u] = ++clk;
insta[u] = true; sta.push(u);
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(!dfn[v]) {
tarjan(v);
low[u] = std::min(low[u], low[v]);
} else if(insta[v]) {
low[u] = std::min(low[u], dfn[v]);
}
}
if(low[u] == dfn[u]) {
scc++;
int p;
do {
p = sta.top(); sta.pop();
insta[p] = false;
sno[p] = scc;
if(node[p].first == A) {
mx[scc] = std::max(mx[scc], id[p]);
mn[scc] = std::min(mn[scc], id[p]);
}
} while(p != u);
}
}
void dfs(int u) {
if(vis[u]) return;
vis[u] = true;
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
dfs(v);
}
}
void dfs_m(int u) {
if(vis[u]) return;
vis[u] = true;
for(int i = 0; i < gran[u].size(); i++) {
int v = gran[u][i];
dfs_m(v);
mx[u] = std::max(mx[u], mx[v]);
mn[u] = std::min(mn[u], mn[v]);
}
}
inline bool cmp(int a, int b) {
return node[a].second > node[b].second;
}
int main() {
memset(mn, 0x3f, sizeof(mn));
n = readint(); m = readint(); A = readint(); B = readint();
for(int i = 1; i <= n; i++) {
int x = readint(), y = readint();
if(x == 0) lft.push_back(i);
if(x == A) rgt.push_back(i);
node[i] = PII(x, y);
}
for(int i = 1, u, v, k; i <= m; i++) {
u = readint(); v = readint(); k = readint();
gra[u].push_back(v);
if(k == 2) gra[v].push_back(u);
}
for(int i = 0; i < lft.size(); i++) {
int u = lft[i];
dfs(u);
}
std::sort(rgt.begin(), rgt.end(), cmp);
for(int i = 0; i < rgt.size(); i++) {
int u = rgt[i];
if(!vis[u]) rgt.erase(rgt.begin() + i), i--;
else id[u] = i + 1;
}
for(int i = 1; i <= n; i++) {
if(!dfn[i]) tarjan(i);
}
for(int u = 1; u <= n; u++) {
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(sno[u] != sno[v] && !edges.count(PII(sno[u], sno[v]))) {
gran[sno[u]].push_back(sno[v]);
edges.insert(PII(sno[u], sno[v]));
}
}
}
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= scc; i++) dfs_m(i);
std::sort(lft.begin(), lft.end(), cmp);
for(int i = 0; i < lft.size(); i++) {
int u = lft[i];
printf("%d\n", std::max(0, mx[sno[u]] - mn[sno[u]] + 1));
}
return 0;
}