[SDOI2011]计算器 题解
题目地址:洛谷:【P2485】[SDOI2011]计算器 – 洛谷、BZOJ: …
May all the beauty be blessed.
题目地址:洛谷:【P2414】[NOI2011]阿狸的打字机 – 洛谷、BZOJ:Problem 2434. — [Noi2011]阿狸的打字机
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。
打字机上只有28个按键,分别印有26个小写英文字母和’B’、’P’两个字母。经阿狸研究发现,这个打字机是这样工作的:
例如,阿狸输入aPaPBbP,纸上被打印的字符如下:
a aa ab
我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。
阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?
你有一个打字机,打字机支持三种操作
纸上会被印上若干行文字,现在你有$m$组询问,每组询问求第$x$行的字符串在第$y$行的字符串中出现了几次。
输入格式:
输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。
第二行包含一个整数m,表示询问个数。
接下来m行描述所有由小键盘输入的询问。其中第i行包含两个整数x, y,表示第i个询问为(x, y)。
输出格式:
输出m行,其中第i行包含一个整数,表示第i个询问的答案。
输入样例#1:
aPaPBbP 3 1 2 1 3 2 3
输出样例#1:
2 1 0
数据范围:
对于100%的数据,n<=100000,m<=100000,第一行总长度<=100000。
首先我们可以对所有串建一个AC自动机,由于上面的操作的一些特殊性质,其实可以一位一位地边操作边插入到Trie树中。
处理每个询问的时候都在AC自动机上跑匹配不现实,会TLE。我们需要一种优化到$O(\log n)$回答单次询问的方法。我们想到了fail树,问$y$中有几个$x$等价于问fail树中$x$的末结点子树中有几个属于$y$路径上的节点。
我们考虑把fail树建出来,处理DFS序,对DFS序建一个树状数组。我们还是按照读入操作的那个顺序去遍历这个Trie树,每到一个节点对树状数组中这个节点DFS序的位置+1,退出一个节点就-1,这样,当到达每个串的结尾节点时,它的Trie树上的树链节点都被+1了,此时对于这个串作为$y$的询问就可以回答了,直接在树状数组上查$x$的子树和就好。
因此,思路是,离线处理这些询问,把询问按$y$分类,每遍历到一个字符串结尾的时候回答该串作为$y$的询问,这样复杂度就是很靠谱的$O(n \log n)$了。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
const int MAXN = 100005;
char op[MAXN];
int n, m, ans[MAXN];
typedef std::pair<int, int> PII;
std::vector<PII> vec[MAXN];
int ch[MAXN][26], fa[MAXN], fail[MAXN], end[MAXN], tot, stot;
std::vector<int> gra[MAXN];
inline void insert() {
int p = 0;
for(int i = 1; i <= n; i++) {
if(op[i] == 'P') {
end[++stot] = p;
} else if(op[i] == 'B') {
p = fa[p];
} else {
int c = op[i] - 'a';
if(!ch[p][c]) {
ch[p][c] = ++tot;
fa[tot] = p;
}
p = ch[p][c];
}
}
}
std::queue<int> que;
inline void calfail() {
for(int i = 0; i < 26; i++) {
if(ch[0][i]) {
gra[0].push_back(ch[0][i]);
que.push(ch[0][i]);
}
}
while(!que.empty()) {
int u = que.front(); que.pop();
for(int i = 0; i < 26; i++) {
if(ch[u][i]) {
int p = fail[u];
while(p && !ch[p][i]) p = fail[p];
fail[ch[u][i]] = ch[p][i];
gra[ch[p][i]].push_back(ch[u][i]);
que.push(ch[u][i]);
}
}
}
}
int dfn[MAXN], siz[MAXN], clk;
void dfs(int u) {
dfn[u] = ++clk; siz[u] = 1;
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
dfs(v);
siz[u] += siz[v];
}
}
int tr[MAXN];
inline int lowbit(int x) {
return x & -x;
}
inline void add(int x, int v) {
for(int i = x; i <= clk; i += lowbit(i)) {
tr[i] += v;
}
}
inline int query(int x) {
int res = 0;
for(int i = x; i; i -= lowbit(i)) {
res += tr[i];
}
return res;
}
inline void solve() {
int p = 0, cnt = 0;
for(int i = 1; i <= n; i++) {
if(op[i] == 'P') {
cnt++;
for(int j = 0; j < vec[cnt].size(); j++) {
int x = vec[cnt][j].first;
ans[vec[cnt][j].second] += query(dfn[end[x]] + siz[end[x]] - 1) - query(dfn[end[x]] - 1);
}
} else if(op[i] == 'B') {
add(dfn[p], -1);
p = fa[p];
} else {
int c = op[i] - 'a';
p = ch[p][c];
add(dfn[p], 1);
}
}
}
int main() {
scanf("%s%d", op + 1, &m); n = strlen(op + 1);
for(int i = 1, x, y; i <= m; i++) {
scanf("%d%d", &x, &y);
vec[y].push_back(PII(x, i));
}
insert();
calfail();
dfs(0);
solve();
for(int i = 1; i <= m; i++) {
printf("%d\n", ans[i]);
}
return 0;
}
题目地址:洛谷:【P2501】[HAOI2006]数字序列 – 洛谷、BZOJ:Problem 1049. — [HAOI2006]数字序列
现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。
输入格式:
第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。
输出格式:
第一行一个整数表示最少需要改变多少个数。
第二行一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。
输入样例#1:
4 5 2 3 5
输出样例#1:
1 4
【数据范围】
90%的数据n<=6000。
100%的数据n<=35000。
保证所有数列是随机的。
参考资料:
如果对于任意的$i, j \ (i < j)$都有$a_j – a_i \geq j – i$,那么这个序列就是单增的了,这是因为保证了元素之间至少有1的差值。既然最少需要改变的个数不好求,那么我们求最多不用改变的个数就好了,我们定义$b_i = a_i – i$,那么最多不用改变的个数就是$b$数组的最长不下降子序列长度了。
后面的问题是如何改变使变化最小,我们观察$b$数组的LIS,设$dp[i]$是以$a_i$结尾的LIS长度,那么如果有$j < i, dp[i] = dp[j] + 1$,那么$[i+1, j-1]$这一段元素就需要修改。我们考虑,这一段元素中,离$j$较近的一段$[j+1, k]$的$b$数组值全修改成$b_j$,而$[k+1, i-1]$全修改成$b_i$是一个最优解(形式化的证明参见参考资料2),那么我们枚举$k$去判断就好了。设$dp2[i]$是前面$i$个元素变成增序列的最小变化,那么可以通过上面枚举$k$的方式转移。
后面这个DP是$O(n^3)$的,由于数据随机,跑不满上限,因此跑的飞快。
// 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 << 1) + (res << 3) + c - '0';
return res * neg;
}
const int MAXN = 35005;
int n, a[MAXN], dp[MAXN];
LL dp2[MAXN], sum1[MAXN], sum2[MAXN];
int tmp[MAXN], tot;
std::vector<int> vec[MAXN];
int main() {
n = readint();
for(int i = 1; i <= n; i++) {
a[i] = readint() - i;
}
a[0] = -1e9; a[++n] = 1e9;
memset(tmp, 0x3f, sizeof(tmp));
tmp[0] = -1e9; tmp[1] = a[1]; tot = 1; dp[1] = 1;
for(int i = 2; i <= n; i++) {
int p = std::upper_bound(tmp, tmp + tot + 1, a[i]) - tmp;
tot = std::max(tot, p);
tmp[p] = a[i];
dp[i] = p;
}
printf("%d\n", n - dp[n]);
for(int i = 0; i <= n; i++) vec[dp[i]].push_back(i);
memset(dp2, 0x3f, sizeof(dp2));
dp2[0] = 0;
for(int i = 1; i <= n; i++) {
for(int jj = 0; jj < vec[dp[i] - 1].size(); jj++) {
int j = vec[dp[i] - 1][jj];
if(j > i) break;
if(a[j] > a[i]) continue;
for(int k = j; k <= i; k++) {
sum1[k] = abs(a[k] - a[j]); sum2[k] = abs(a[k] - a[i]);
}
for(int k = j + 1; k <= i; k++) {
sum1[k] += sum1[k - 1]; sum2[k] += sum2[k - 1];
}
for(int k = j; k < i; k++) {
dp2[i] = std::min(dp2[i], dp2[j] + sum1[k] - sum1[j] + sum2[i] - sum2[k]);
}
}
}
printf("%lld", dp2[n]);
return 0;
}
题目地址:洛谷:【P2607】[ZJOI2008]骑士 – 洛谷、BZOJ:Problem 1040. — [ZJOI2008]骑士
Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。
最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的Z国又怎能抵挡的住Y国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。
骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。
战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。
为了描述战斗力,我们将骑士按照1至N编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。
有$n$个人,每个人有一个讨厌的人和一个权值,求集合内不存在讨厌的人的最大权集合。
输入格式:
输入文件knight.in第一行包含一个正整数N,描述骑士团的人数。
接下来N行,每行两个正整数,按顺序描述每一名骑士的战斗力和他最痛恨的骑士。
输出格式:
输出文件knight.out应包含一行,包含一个整数,表示你所选出的骑士军团的战斗力。
输入样例#1:
3 10 2 20 3 30 1
输出样例#1:
30
对于30%的测试数据,满足N ≤ 10;
对于60%的测试数据,满足N ≤ 100;
对于80%的测试数据,满足N ≤ 10 000。
对于100%的测试数据,满足N ≤ 1 000 000,每名骑士的战斗力都是不大于 1 000 000的正整数。
我们可以看到一个人只有一个讨厌的人,因此,可以确定对于每个连通块,一定是一个基环树(连通块中只含一个环,环上的每个节点可能挂着有树)。如果不考虑环,这个题目和“没有上司的舞会”模型相同,即$dp[0/1][u]$表示$u$这个节点选或不选,其子树的最大权值,转移一遍DFS。现在考虑环,我们考虑把环的任意一条边断开,分别从两端跑DP,从两端的$dp[0][u]$中找出最大值即可。
复杂度$O(n)$。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
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 = 1000005;
struct Edge {
int to, nxt;
} gra[MAXN << 1];
int head[MAXN], tot;
inline void addedge(int u, int v) {
gra[tot] = Edge {v, head[u]}; head[u] = tot++;
gra[tot] = Edge {u, head[v]}; head[v] = tot++;
}
int n, w[MAXN];
LL dp[MAXN][2];
int cl, cr, ce;
bool vis[MAXN];
void dfs(int u, int pre) {
vis[u] = true;
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(i == (pre ^ 1)) continue;
if(vis[v]) {
cl = u; cr = v; ce = i; continue;
}
dfs(v, i);
}
}
void dfs_dp(int u, int pre) {
dp[u][0] = 0; dp[u][1] = w[u];
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(i == (pre ^ 1) || i == ce || i == (ce ^ 1)) continue;
dfs_dp(v, i);
dp[u][0] += std::max(dp[v][0], dp[v][1]);
dp[u][1] += dp[v][0];
}
}
int main() {
memset(head, -1, sizeof(head));
n = readint();
for(int i = 1; i <= n; i++) {
w[i] = readint(); int t = readint();
addedge(i, t);
}
LL ans = 0;
for(int i = 1; i <= n; i++) {
if(vis[i]) continue;
dfs(i, -1);
dfs_dp(cl, -1);
LL mx = dp[cl][0];
dfs_dp(cr, -1);
mx = std::max(mx, dp[cr][0]);
ans += mx;
}
printf("%lld", ans);
return 0;
}