CSP-J2 2020 T3,T4 题解

T3

观察题目及表达式,建立表达式的二叉树,以运算符为非叶子节点,变量为叶子节点,其中 \(&\)\(|\) 运算符有左右两子树, \(!\) 只有左子树

发现每次询问需要 \(\mathcal{O}(\log n)\)\(\mathcal{O}(1)\) 的复杂度,考虑预处理答案

发现每个变量 \(x\) 可以或不可以改变答案,观察运算符的性质,可以发现(注:每个子树均能表示一个表达式):

  1. \(&\) 作为子树根节点时,只要有左子树或右子树的值为 \(1\) ,则右子树或左子树的值改变能改变该子树的值

  2. \(|\) 作为子树根节点时,只要有左子树或右子树的值为 \(0\) ,则右子树或左子树的值改变能改变该子树的值

  3. \(!\) 作为子树根节点时,其子树改变值能影响该树的值

第一遍 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 心态爆炸纪念

posted @ 2020-11-08 00:00  AxDea  阅读(349)  评论(0编辑  收藏  举报