CSP-J2 2020 T3,T4 题解
T3
观察题目及表达式,建立表达式的二叉树,以运算符为非叶子节点,变量为叶子节点,其中 \(&\) 和 \(|\) 运算符有左右两子树, \(!\) 只有左子树
发现每次询问需要 \(\mathcal{O}(\log n)\) 或 \(\mathcal{O}(1)\) 的复杂度,考虑预处理答案
发现每个变量 \(x\) 可以或不可以改变答案,观察运算符的性质,可以发现(注:每个子树均能表示一个表达式):
-
\(&\) 作为子树根节点时,只要有左子树或右子树的值为 \(1\) ,则右子树或左子树的值改变能改变该子树的值
-
\(|\) 作为子树根节点时,只要有左子树或右子树的值为 \(0\) ,则右子树或左子树的值改变能改变该子树的值
-
\(!\) 作为子树根节点时,其子树改变值能影响该树的值
第一遍 dfs 求出每个子树(子表达式)的值,第二遍 dfs 按上述性质标记能影响到总表达式值的叶子节点
对于每次询问,若该变量被标记,即为被取反的总表达式的值,否则就是原表达式的值
总复杂度 \(\mathcal{O}(n+q)\)
#include<bits/stdc++.h>
#define forn(i,s,t) for(int i=(s);i<=(t);++i)
#define form(i,s,t) for(int i=(s);i>=(t);--i)
using namespace std;
const int N = 1e6+3;
char ch[N],tmp[80];
int n,q,a,b,k,tree[N],L[N],R[N],op[N],rt,sl; // tree: 若该节点是变量,存变量编号 op:若该节点是符号则存符号 L,R: 左右子树
bool val[N]; // 1表示| 2表示& 0表示!
int vis[N],x[N]; // val: 子树求出的值 vis: 是否打标记
void dfs(int &p) { // 建树 (全网最奇葩
if(!k) return ;
p = ++sl;
if(ch[k] == ' '&&k > 0) --k;
int a = 0;
while(ch[k] != ' ') tmp[++a] = ch[k--];
if(tmp[a] == '!') op[p] = 0;
else if(tmp[a] == '|') op[p] = 1;
else if(tmp[a] == '&') op[p] = 2;
else if(tmp[a] == 'x') {
int num = 0;
form(i,a-1,1) num = num*10+tmp[i]-48;
tree[p] = num,val[p] = x[num];
}
if(tmp[a]!='x') {
if(tmp[a] == '!') {
dfs(L[p]);
} else {
dfs(L[p]);
dfs(R[p]);
}
}
}
int solve(int p) { // 求每个表达式的值
if(op[p]!=-1) {
if(op[p] == 0) return val[p] = 1^solve(L[p]);
else if(op[p] == 1) return val[p] = solve(L[p])|solve(R[p]);
else return val[p] = solve(L[p])&solve(R[p]);
} else return val[p];
}
void tag(int p,bool kk) { // 打标记
if(op[p]!=-1) {
if(op[p] == 0) tag(L[p],1&kk);
else if(op[p] == 1) {
if(!val[L[p]]) tag(R[p],1&kk);
if(!val[R[p]]) tag(L[p],1&kk);
} else {
if(val[L[p]]) tag(R[p],1&kk);
if(val[R[p]]) tag(L[p],1&kk);
}
} else {
vis[tree[p]] = kk;
}
}
int main() {
freopen("expr.in","r",stdin);
freopen("expr.out","w",stdout);
int len = 0;ch[0] = ' ';
forn(i,1,N+1) {
ch[++len] = getchar();
if(ch[len] == '\n') break ;
}
scanf("%d",&n);
forn(i,1,n) scanf("%d",&x[i]);
memset(tree,-1,sizeof tree);
memset(op,-1,sizeof op);
k = len,rt = 1;
dfs(rt);
solve(rt);
tag(rt,1);
scanf("%d",&q);
int pos;
forn(i,1,q) {
scanf("%d",&pos);
printf("%d\n",vis[pos]?(1^val[rt]):val[rt]);
}
return 0;
}
T4
设 \(f(i,j)\) 表示第 \(i\) 列,第 \(j\) 行时的最优解
发现对于每列只能用一个方向取数,用两个数组分别表示向上取和向下取的最优解,
设 \(g_j\) 表示向下取的最优解,有 \(g(j) = \max \{g(j+1),f(i-1,j)\} + A_{i,j}\)
设 \(h_j\) 表示向上取的最优解, 有 \(h(j) = \max \{h(j-1),f(i-1,j)\} + A_{i,j}\)
则有 \(f(i,j) = \max \{g(j),h(j)\}\)
总复杂度 \(\mathcal{O}(n\times m)\)
考场代码和上述描述的不太一样,毕竟10min干掉的,最后注意边界
#include<bits/stdc++.h>
#define forn(i,s,t) for(int i=(s);i<=(t);++i)
#define form(i,s,t) for(int i=(s);i>=(t);--i)
using namespace std;
typedef long long LL;
const int N = 1e3+3;
int n,m;
LL A[N][N],f[N][N],g[N],h[N],Ans,INF = 1e18;
int main() {
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
scanf("%d%d",&n,&m);
forn(i,1,n) forn(j,1,m) scanf("%lld",&A[j][i]);
forn(i,0,m) forn(j,0,n) f[i][j] = -INF;
forn(i,1,m) {
if(i == 1) g[0] = 0;
else g[0] = -INF;
h[n+1] = -INF;
forn(j,1,n) g[j] = max(f[i-1][j],g[j-1])+A[i][j];
form(j,n,1) h[j] = max(f[i-1][j],h[j+1])+A[i][j];
forn(j,1,n) f[i][j] = max(g[j],h[j]);
}
printf("%lld\n",f[m][n]);
return 0;
}
PJ AK纪念,TG 心态爆炸纪念