@(K ACMer)
A. Kefa and First Steps
题意:
求连续最长非递减子序列的长度.
分析:
从左到右扫描即可.这里差点理解成经典DP题LIS.
这里复习一下LIS:
LIS有两种方法,一种是比较直观地
O(n2)
的方法.
定义:dp[i]为以i结尾的LIS的长度,有状态转移方程:
dp[i] = max(dp[j]+1 | a[j]<a[i],j= 0....i−1)
然后是 O(nlogn) 的做法.
定义:dp[i]为长度为i的LIS的结尾的数的最小值.每次加一个数都二分的到DP数组中去查找它大于的第一个数并更新.
//LIS的代码
int dp[M], a[M], n;
int LIS(void) {
fill(dp, dp + n + 1, INF);
for (int i = 0; i < n; i++) {
*lower_bound(dp, dp + n, a[i]) = a[i];
}
return lower_bound(dp, dp + n, INF) - dp;
}
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int M = int(1e5) + 9,mod = int(1e9) + 7, INF = 0x3fffffff;
int main(void) {
int n, a[M];
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
int key = 1, sum = 1;
for (int i = 1; i < n; i++) {
if (a[i] < a[i - 1]) {
key = 1;
} else key++;
sum = max(sum, key);
}
cout << sum << endl;
return 0;
}
B. efa and Company
题意:每一个人都有它的身价和友谊度,要求一个人的集合让他们的最大身价差小于m,且友谊度和最大.
分析:
典型的尺取法.排序,然后用尺取法即可,然后求和的时候用前缀数组,
O(1)
的查询任意区间和即可.
Code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int M = int(1e5) + 9,mod = int(1e9) + 7, INF = 0x3fffffff;
struct people{int m, f;}v[M];
long long pre[M];
bool cmp(const people & a, const people & b) {
return a.m < b.m;
}
int main(void) {
int n, d;
scanf("%d%d", &n, &d);
for (int i = 0; i < n; i++) {
scanf("%d%d", &v[i].m, &v[i].f);
}
sort(v, v + n, cmp);
for (int i = 0; i < n; i++) {
if (i) pre[i] += pre[i - 1];
pre[i] += v[i].f;
}
long long sum = 0;
for (int i = 0, j = 0; j < n && i < n;) {
if (v[j].m - v[i].m < d) {
sum = max(sum, pre[j] - pre[i - 1]);
j++;
} else i++;
}
cout << sum << endl;
return 0;
}
C. Kefa and Park
题意:给你一颗数,家在编号为1的根节点,所有的饭店在叶子节点,每个节点有猫或者没有猫,如果从家走到饭店的连续猫的数量不超过m就可以到该饭店,求可到达的饭店个数.
分析:
DFS来遍历树即可.
注意一个wa点,就是判断该顶点是否是叶子节点的时候用的数度数为1,这样有可能把根也当做叶子节点…比赛的时候就是wa在这里,找了好久的错=_=.
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int M = int(1e5) + 9,mod = int(1e9) + 7, INF = 0x3fffffff;
vector<int> v[M];
int a[M], n, m, sum;
void dfs(int cur, int par, int cd) {
if (a[cur]) cd++;
else cd = 0;
if (cd > m) return;
if (v[cur].size() == 1 && cur != 1) {
sum++;
return;
}
for (int i = 0; i < v[cur].size(); i++) {
if (v[cur][i] == par) continue;
dfs(v[cur][i], cur, cd);
}
return;
}
int main(void) {
scanf("%d%d", &n, &m);
sum = 0;
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for (int i = 0; i < n - 1; i++) {
int x, y;
scanf("%d%d", &x, &y);
v[x].push_back(y);
v[y].push_back(x);
}
dfs(1, -1, 0);
printf("%d\n", sum);
return 0;
}
D. Kefa and Dishes
题意:
总共有n道菜,每一道菜有其相应的愉悦度,而你要点m道菜,让其愉悦度最大.这里有k个限制条件形如:
x y c
表示在点完x后点y就可以增加额外的c个愉悦度.
分析
根据题目数据范围很容易猜测到状压DP,然而事实也是比较裸的状态压缩DP.
定义:
dp[s][j]
为当前已经选的菜的集合为s,最后一个选的菜为j所拥有的愉悦度.容易得到状态转移方程:
在打出所有的DP表之后,需要统计含有元素为个数为m的所有集合s中的愉悦度最大值.
知识补充:
状态压缩DP其实就是,对有一个DP维度的压缩,这个维度十分复杂,不能直观地表式,我们就把它要缩到一个二进制数的表达里.通过这个二进制数的位表示,来表示这个困难的状态.
然后注意状态压缩的标志就是数据范围,在18左右.
code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <queue>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int M = int(1e0) + 17,mod = int(1e9) + 7, INF = 0x3fffffff;
long long d[M][M], dp[1 << M][M], n, m, k;
bool cal(int x) { //检测是否恰好选择了m个顶点
int ret = 0;
while (x) {
if (x & 1) ret++;
x >>= 1;
}
return ret == m;
}
void solve(void) {
for (int i = 1; i < (1 << n); i++)
fill(dp[i], dp[i] + n, -INF);
for (int s = 1; s < (1 << n); s++) {
for (int v = 0; v < n; v++) {
for (int u = 0; u < n; u++) {
int x = (1 << v);
x = ~x; //为了将集合中的v点去除,而构造的x点.
if ((s >> v) & 1 && (s >> u) & 1) { //v和u都必须在集合中
dp[s][v] = max(dp[s][v], dp[s & x][u] + d[u][v]);
}
}
}
}
long long ret = -INF;
for (int i = 0; i < (1 << n); i++) {
if (cal(i)) {
for (int j = 0; j < n; j++)
ret = max(ret, dp[i][j]);
}
}
cout << ret << endl;
}
int main(void) {
cin >> n >> m >> k;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
for (int j = 0; j < n; j++)
d[j][i] = x;
}
for (int i = 0; i < k; i++) {
int x, y, c;
cin >> x >> y >> c;
d[x - 1][y - 1] += c;
}
solve();
return 0;
}
E. Kefa and Watch
题意:
给你一个只包含数字0到9的长度为1e6字符串.让你支持两种操作:
- (1, l, r, x)把l到r的数字全部更新为x.
- (2, l, r, x)l到r之间的字符串是否具有长度x的循环节?
分析:
- 1e6的数据量,显然需要
O(nlogn)
的做法.这里比较字符串是否具有长度为x的循环节,需要借助一个错位判断周期的思想.也就是如果:
l到r−x之间的字符串和l−x到r之间的字符串相同,就具有周期x
- 然后我们怎么去判断两段字符串是否相同呢?显然暴力的逐个比较是不行的,那么久可以用字符串hash来解决,如果两个字符串它们的hash值相同就可以了.但是计算hash值也需要逐个计算.这里我们考虑到每次只是对其中一个区间做了改变,其它区间的字符串是不变的,这样我们就可以用一颗线段树来维护区间的hash值了,修改和查找的复杂度都是 O(nlogn) ,不是正好么!
- 线段树来维护区间hash值的话.有一些技巧.我们用pow[i]数组来储存每种长度的字符串在hash的时候需要的权重.sum[i]来储存权重的前缀和,便于区间查寻.这样对于两个字符串区间,(l ,mid) 和(mid, r)我们在合并的时候(l, r)区间的hash值就等于: hash[(l,mid)]∗pow[r−mid]+hash[(mid,r)]
- 这里有必要补充一下字符串hash的思想.字符串hash相当于一个把字符串看做一个进制表达的数,每一位数给一个相应地位权,这样根据位权乘以各个字符串的ASCII码所得到的值的和就是该字符串的hash值.对于一个字符串其左边的字符权位总是比右边高,所以在合并左右区间的时候我们只在左边的hash值上乘以了右边区间长度的位权,这个类比2进制的表达很容易理解的.
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef unsigned long long ull;
const int M = int(1e5) + 17, seed = 13131, mod = 998244353;
int lazy[M << 2], n, m, k, mid;
ull pow[M << 2], sum[M << 2], d[M << 2];
char str[M];
void init() {
pow[0] = sum[0] = 1;
memset(lazy, -1, sizeof(lazy));
for (int i = 1; i <= n; i++) {
pow[i] = (pow[i - 1] * seed) % mod;
sum[i] = (sum[i - 1] + pow[i]) % mod;
}
}
void pushup(int k, int l, int r) {
d[k] = (d[k << 1] * pow[r - (l + r) / 2] + d[k << 1 | 1]) % mod;
}
void build(int k, int l, int r) {
if (l == r) d[k] = str[l] - '0';
else {
build(k * 2, l, (l + r) / 2);
build(k * 2 + 1, (l + r) / 2 + 1, r);
pushup(k, l, r);
}
}
void pushdown(int k, int l, int r) {
if (lazy[k] != -1) {
lazy[k << 1] = lazy[k << 1 | 1] = lazy[k];
lazy[k] = -1;
d[k << 1] = lazy[k << 1] * sum[(l + r) / 2 - l ] % mod;
d[k << 1 | 1] = lazy[k << 1 | 1] * sum[r - (l + r) / 2 - 1] % mod;
}
}
void update(int k, int a, int b, int x, int l, int r) {
if (a <= l && b >= r) {
lazy[k] = x;
d[k] = x * sum[r - l] % mod;
} else {
pushdown(k, l, r);
if (a <= (l + r) / 2)update(k << 1, a, b, x, l, (l + r) / 2);
if (b > (l + r) / 2) update(k << 1 | 1, a, b, x, (l + r) / 2 + 1, r);
pushup(k, l, r);
}
}
ull query(int k, int a, int b, int l, int r) {
if (a <= l && b >= r) return d[k];
else {
pushdown(k, l, r);
ull ret = 0;
if (a > (l + r) / 2) ret = query(k << 1 | 1, a, b, (l + r) / 2 + 1, r);
else if (b <= (l + r) / 2) ret = query(k << 1 , a, b, l, (l + r) / 2);
else ret = (query(k << 1 , a, (l + r) / 2, l, (l + r) / 2) * pow[b - (r + l) / 2] % mod + query(k << 1 | 1, (l + r) / 2 + 1, b, (l + r) / 2 + 1, r)) %mod;
//pushup(k, k << 1, k << 1 | 1);
return ret ;
}
}
int main(void) {
scanf("%d%d%d%s", &n, &m, &k, str + 1);
init();
build(1, 1, n);
for (int i = 0; i < m + k; i++) {
int op, l, r, x;
cin >> op >> l >> r >> x;
if (op == 1) {
update(1, l, r, x, 1, n);
} else {
if (r - l + 1 == x) puts("YES");
else if (r - l + 1 < x) puts("NO");
else if (query(1, l, r - x, 1, n) == query(1, l + x, r, 1, n)) puts("YES");
else puts("NO");
}
}
return 0;
}