A - Single Wildcard Pattern Matching
题意:*可以换成任意的一段字符串,s串只有一个*,t串没有*,问是否可以从s串变为t串。
题解:意思就是*前面的要和t的前段完全匹配,*后面的也要和t的后段完全匹配,且两端不能有交叉。
char s[200005], t[200005];
void test_case() {
int n, m;
scanf("%d%d%s%s", &n, &m, s + 1, t + 1);
if(m < n - 1) {
puts("NO");
return;
}
int x = -1;
for(int i = 1; i <= n; ++i) {
if(s[i] == '*') {
x = i;
break;
}
}
if(x == -1) {
if(strcmp(s + 1, t + 1) == 0)
puts("YES");
else
puts("NO");
return;
}
for(int i = 1; i <= x - 1; ++i) {
if(s[i] != t[i]) {
puts("NO");
return;
}
}
for(int i = n, j = m; i >= x + 1; --i, --j) {
if(s[i] != t[j]) {
puts("NO");
return;
}
}
puts("YES");
}
B - Pair of Toys
题意:用两个不同的且不超过n的数a,b凑出a+b=k,求有多少种方法。
题解:假如去掉所有限制,则就是k-1种方法。k是偶数会额外丢失一种相等的方法。由于n不够大会截断最开始的 1 k-1 这样悬殊的方法。
void test_case() {
ll n, k;
scanf("%lld%lld", &n, &k);
if(n >= k - 1) {
printf("%lld
", (k - 1) / 2);
return;
}
if(n <= k / 2) {
printf("0
");
return;
}
ll q = k - (n + 1);
printf("%lld
", (k - 1) / 2 - q);
}
C - Bracket Subsequence
题意:给一个n长度的合法括号串,选其中一个长度恰好为k(k<=n)的子序列,这个子序列也是合法括号串。
题解:每次去掉一对括号不就减少了2长度了吗?可以每次检测到匹配的时候优先删除。
char s[200005];
char t[200005];
void test_case() {
int n, k;
scanf("%d%d%s", &n, &k, s + 1);
int top = 0;
int rest = n - k;
for(int i = 1; i <= n; ++i) {
if(s[i] == '(')
t[++top] = '(';
else {
if(rest) {
t[top]=' ';
--top;
rest -= 2;
} else
t[++top] = ')';
}
}
puts(t + 1);
}
注意打上结尾符。
D - Array Restoration
题意:一个n个元素的数组,恰好进行q次修改,第i次修改可以把连续的一段区间赋值为i。求是否可以构造出题目提供的数组,0表示可以填任意值。
题解:
先解决没有0的情况的问题。每次都是区间更新一次,那么查询到最左的i和最右的i(假如存在),那么中间部分的最小值必须>=i,这个看起来就像线段树查询,实际上用st表也可以。当所有的查询都满足并且最大值q存在,那么合法(中间缺的值全都由最大值覆盖掉)。
有0的情况稍微复杂一些,首先假如最大值q不存在,那么随便赋值任意一个0变成最大值就可以了。如果中间的段有0的话,就把这些0强制赋值为i,注意到这样子不会使区间的最小值变大,也就是这样构造是最优的,既使得lr之间连续,也满足无后效性。假如还有0剩余就把0赋值为左右相同的值,但是假如左右也是0怎么办呢?不妨把a[0]设为1,然后每个0的值都从其左边继承。
实现的时候就是一棵最小值线段树和一棵区间更新线段树。最后把标记全部下传给叶子。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 200000;
const int INF = 0x3f3f3f3f;
int a[MAXN + 5];
struct SegmentTree1 {
#define ls (o<<1)
#define rs (o<<1|1)
static const int MAXN = 200000;
static const int INF = 0x3f3f3f3f;
int mi[(MAXN << 2) + 5];
void PushUp(int o) {
mi[o] = min(mi[ls], mi[rs]);
}
void Build(int o, int l, int r) {
if(l == r) {
mi[o] = (a[l] == 0 ? INF : a[l]);
} else {
int m = l + r >> 1;
Build(ls, l, m);
Build(rs, m + 1, r);
PushUp(o);
}
}
int QueryMin(int o, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) {
return mi[o];
} else {
int m = l + r >> 1;
int res = INF;
if(ql <= m)
res = QueryMin(ls, l, m, ql, qr);
if(qr >= m + 1)
res = min(res, QueryMin(rs, m + 1, r, ql, qr));
return res;
}
}
#undef ls
#undef rs
} st1;
struct SegmentTree2 {
#define ls (o<<1)
#define rs (o<<1|1)
static const int MAXN = 200000;
static const int INF = 0x3f3f3f3f;
int a[MAXN + 5], lz[(MAXN << 2) + 5];
void PushDown(int o, int l, int r) {
if(lz[o]) {
lz[ls] = lz[o];
lz[rs] = lz[o];
lz[o] = 0;
}
}
void Build(int o, int l, int r) {
if(l == r) {
a[l] = 0;
} else {
int m = l + r >> 1;
Build(ls, l, m);
Build(rs, m + 1, r);
}
lz[o] = 0;
}
void Update(int o, int l, int r, int ql, int qr, int v) {
if(ql <= l && r <= qr) {
lz[o] = v;
} else {
PushDown(o, l, r);
int m = l + r >> 1;
if(ql <= m)
Update(ls, l, m, ql, qr, v);
if(qr >= m + 1)
Update(rs, m + 1, r, ql, qr, v);
}
}
void PushDownAll(int o, int l, int r) {
if(l == r) {
a[l] = lz[o];
} else {
PushDown(o, l, r);
int m = l + r >> 1;
PushDownAll(ls, l, m);
PushDownAll(rs, m + 1, r);
}
}
#undef ls
#undef rs
} st2;
int n, q, maxai, lm[MAXN + 5], rm[MAXN + 5];
void solve0() {
if(maxai != q) {
puts("NO");
return;
}
st1.Build(1, 1, n);
for(int i = 1; i <= q; ++i) {
if(lm[i] == 0)
continue;
if(st1.QueryMin(1, 1, n, lm[i], rm[i]) < i) {
puts("NO");
return;
}
}
puts("YES");
for(int i = 1; i <= n; ++i)
printf("%d%c", a[i], "
"[i == n]);
}
void solve1() {
st1.Build(1, 1, n);
for(int i = 1; i <= q; ++i) {
if(lm[i] == 0)
continue;
if(st1.QueryMin(1, 1, n, lm[i], rm[i]) < i) {
puts("NO");
return;
}
}
if(lm[q] == 0)
a[lm[0]] = q;
st2.PushDownAll(1, 1, n);
a[0] = 1;
for(int i = 1; i <= n; ++i) {
if(a[i] == 0)
a[i] = st2.a[i];
if(a[i] == 0)
a[i] = a[i - 1];
}
puts("YES");
for(int i = 1; i <= n; ++i)
printf("%d%c", a[i], "
"[i == n]);
}
void test_case() {
scanf("%d%d", &n, &q);
maxai = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
if(a[i] > maxai)
maxai = a[i];
if(lm[a[i]] == 0)
lm[a[i]] = i;
rm[a[i]] = i;
}
if(lm[0] == 0)
solve0();
else
solve1();
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t = 1;
//scanf("%d", &t);
for(int ti = 1; ti <= t; ++ti) {
//printf("Case #%d: ", ti);
test_case();
}
}
/*
1. 小数据问题退化:
输入为0或1会不会有特殊情况?其他的比如最小环要有三个点,强连通分量缩到最后一个点等等。
2. 内存:
内存的空间有没有开够?有时有反向边,有时有额外新加的边。线段树开了4倍了吗?
可持久化数据结构会不会内存溢出?多组数据时vector会不会翻车?字符大小写有考虑吗?
多组数据有进行初始化吗?memset会不会翻车?
3. 算术溢出:
乘法上溢出了?忘记取模了?输入输出用了%d?让无符号数越到负数了?
4. 习惯:
函数都有指定返回值吗?返回值非void函数的每一个分支都有显式的返回值吗?确定不会进入的分支可以assert一把。
Yes和No有没有反,大小写有没有错?有没有搞错n和m?离散化之后的cn有没有错?换行和空格要怎么整?priority要把符号反过来。
5. 其他bug:
基环树是树加环,不是链加环。
*/
修改0的时候可以使用差分来做,但是没必要,合法的操作序列一定是像一个括号序列一样的,不会有4545这样的修改,所以可以用个栈来记录左右端点,把中间遇到的0全部设置为栈顶。但是第一步的查询最小值貌似是莫队可以搞,说起来还没学过莫队。
总结:不带查询的区间修改永远不需要线段树,直接打差分就可以了,不过没必要。
E - Down or Right
交互题,真恶心。
不会做这种恶心题,听敦爷的,直接看题解。
题意:有一个n*n(n<=500)的矩阵。每次只能向下走或者向右走,每次询问起点和终点,评测机告诉能不能走。最多询问4*n次,每次询问的起点终点的曼哈顿距离必须超过n-1,最后给出从(1,1)到(n,n)的路线。保证必定存在至少一条路线。
题解:若去除距离限制,那么每次询问当前点(r,c)的下边点(r+1,c)是否可达(n,n),可达则向下走,否则一定可以向右走。这样可以一直走到反对角线上最左侧的可达点。然后反过来每次询问(1,1)是否能够到达当前点(r,c)的左边点(r,c-1),可达则向左走,否则一定可以向上走。这样会在反对角线上最左侧的可达点汇合。花费2*(n-1)次询问。
int n;
char s[105];
bool query(int a, int b, int c, int d) {
printf("? %d %d %d %d
", a, b, c, d);
fflush(stdout);
scanf("%s", s);
return s[0] == 'Y';
}
char ans1[505];
char ans2[505];
void test_case() {
scanf("%d", &n);
int r = 1, c = 1, t = 0;
while(r + c <= n) {
if(r + 1 <= n && query(r + 1, c, n, n)) {
++r;
ans1[++t] = 'D';
} else {
++c;
ans1[++t] = 'R';
}
}
r = n, c = n, t = 0;
while(r + c - 1 > n) {
if(c - 1 >= 1 && query(1, 1, r, c - 1)) {
--c;
ans2[++t] = 'R';
} else {
--r;
ans2[++t] = 'D';
}
}
reverse(ans2 + 1, ans2 + 1 + t);
printf("! ");
for(int i = 1; i <= t; ++i)
putchar(ans1[i]);
for(int i = 1; i <= t; ++i)
putchar(ans2[i]);
putchar('
');
fflush(stdout);
}
F - Mobile Phone Network
题意:有n个点,你的竞争对手有m条边(u,v,w),你有k条边(u,v)组成一个森林,给这些边定价,使得这个图的MST包含你所有的边,注意当你的边和竞争对手的边价格相同时,MST还是会自动选择你的边。
题解:先把你的所有边的森林弄出来,加入对手的边连成一棵生成树。然后再加入对手的边e就一定会成环,那么在这个环上的你的边的价格就不能超过e的w,所以可以用一个巨复杂的边树剖来维护这个东西。注意到线段树可以尝试区间修改一段区间的最大值限制,一开始初始化最大值限制为无穷,然后每次当前的lazy比更新值v大的时候就把整个lazy换掉。最后把所有lazy下推统计。
那么细节就有好几个点:
1.在连成生成树之前有可能会成环,这个时候可以先跳过这个环,等生成树建出来后再更新最小值。
2.怎么区分自己的边和对手的边?直接一视同仁建在树里面更新最大值,不过最后统计的时候把这些边去除。