【题解】 「WC2021」表达式求值 按位+表达式树+树形dp LOJ3463

Legend

Link ( extrm{to LOJ})

Editorial

想想不含有 ? 的怎么做?考虑数组的每一位是独立的,不妨一位一位解决。我们只要求每一位最终是哪一个数字最后对答案造成了贡献。

不难想到二分答案,把 (ge mid) 的值设置成 (1)(< mid) 的值设置成 (0),取 (max) 就变成了或,取 (min) 就变成了与。

在表达式树上算出答案的最终结果,如果是 (1),就说明答案比当前二分值大了或者恰好,否则说明答案比当前二分值小了。

但是由于 (m=10) 我们没有必要二分,直接对每一个情况算出了都是可以的。

但是对于 (n=50000) 个位分开做这个事情就太慢了,于是我就用 bitset 解决了这个问题。

(显然就是这一步把我从前往正解的路上带歪了)

但是你发现虽然 (n=50000),但本质不同的 01 大小关系只有 (2^m) 个,于是直接算这 (2^m) 个就行了。

囧……

推广到正解就在表达式树上 dp 出方案就行了。

复杂度 (O(2^m|E|+nm^2))

Code

#include <bits/stdc++.h>

#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)
	freopen(#x".in" ,"r" ,stdin);
	freopen(#x".out" ,"w" ,stdout)
#define LL long long

const int MX = 1e5 + 23;
const LL MOD = 1e9 + 7;

using namespace std;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

int n ,m;
int A[10][MX];
char S[MX]; int slen;
char suf[MX];
char stk[MX];
int cnt ,top;

int ch[MX][2] ,val[MX] ,tot ,rt;
bitset<50000> AAA[10] ,ans[10];
int qwq ,fa[MX];
void init(){for(int i = 0 ; i < MX ; ++i) fa[i] = i;}
int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);}

int getid(char k){
	if(k == '>') return 1;
	if(k == '<') return 0;
	return 2;
}
int Build(){
	stack<int> tmp;
	int sz = 0;
	for(int i = 1 ; i <= cnt ; ++i){
		if(isdigit(suf[i])){
			val[++sz] = suf[i] - '0';
			tmp.push(sz);
		}
		else{
			val[++sz] = getid(suf[i]);
			ch[sz][1] = find(tmp.top());
			fa[find(tmp.top())] = sz; tmp.pop();
			ch[sz][0] = find(tmp.top());
			fa[find(tmp.top())] = sz; tmp.pop();
			tmp.push(sz);
		}
	}
	return sz;
}

#define lc ch[x][0]
#define rc ch[x][1]

namespace STD{
	int dp[MX][2] ,ans[1 << 10];
	void dapai(int x ,int Z){
		if(!ch[x][0]){
			dp[x][1] = (Z >> val[x]) & 1;
			dp[x][0] = !dp[x][1];
			return;
		}
		dapai(ch[x][0] ,Z);
		dapai(ch[x][1] ,Z);
		if(val[x] == 0){ // & op
			dp[x][1] = 1LL * dp[lc][1] * dp[rc][1] % MOD;
			dp[x][0] = (1LL * (dp[lc][1] + dp[lc][0]) 
					* (dp[rc][1] + dp[rc][0]) - dp[x][1] + MOD) % MOD;
		}
		if(val[x] == 1){
			dp[x][0] = 1LL * dp[lc][0] * dp[rc][0] % MOD;
			dp[x][1] = (1LL * (dp[lc][1] + dp[lc][0])
					* (dp[rc][1] + dp[rc][0]) - dp[x][0] + MOD) % MOD;
		}
		if(val[x] == 2){
			dp[x][0] = (1LL * dp[lc][0] * dp[rc][0]
					+ 1LL * dp[lc][0] * dp[rc][1]
					+ 1LL * dp[lc][1] * dp[rc][0] /* AND  */
					+ 1LL * dp[lc][0] * dp[rc][0]) % MOD; /* OR */
			dp[x][1] = (1LL * dp[lc][1] * dp[rc][1] /* AND */
					+ 1LL * dp[lc][1] * dp[rc][1] /* OR */
					+ 1LL * dp[lc][1] * dp[rc][0]
					+ 1LL * dp[lc][0] * dp[rc][1]) % MOD;
			/*dp[x][0] %= MOD;
			dp[x][1] %= MOD;
			*/
		}
	}
	void Main(){
		init();
		rt = Build();
		for(int i = 0 ; i < (1 << m) ; ++i){
			dapai(rt ,i);
			ans[i] = dp[rt][1];
		}
		LL ret = 0;
		for(int i = 0 ; i < n ; ++i){
			int tmp[10];
			for(int j = 0 ; j < m ; ++j){
				tmp[j] = A[j][i];
			}
			std::sort(tmp ,tmp + m);
			int las = 0;
			for(int j = m - 1 ; ~j ; --j){ // 枚举排名 >= 的都变成 1 
				int st = 0;
				for(int k = 0 ; k < m ; ++k){
					st |= (A[k][i] >= tmp[j]) << k;
				}
				ret = (ret + 1LL * tmp[j] * (ans[st] - las + MOD)) % MOD;
				las = ans[st];
			}
		}
		printf("%lld
" ,ret);
	}
}

int main(){
	// __FILE(expr);
	n = read() ,m = read();
	for(int i = 0 ; i < m ; ++i){
		for(int j = 0 ; j < n ; ++j){
			A[i][j] = read();
		}
	}
	scanf("%s" ,S); slen = strlen(S);

	int ques = 0;
	for(int i = 0 ; i < slen ; ++i){
		if(isdigit(S[i])){
			suf[++cnt] = S[i];
			if(top && stk[top] != '(') suf[++cnt] = stk[top--];
		}
		else{
			if(S[i] == '?') ques = 1;
			if(S[i] == ')'){
				--top;
				if(top && stk[top] != '('){
					suf[++cnt] = stk[top--];
				}
			}
			else stk[++top] = S[i];
		}
	}
	STD::Main();
	return 0;
}

Note

贴一下我考试的时候写的速记,打了 85 分的部分分。但最后因为 (n=2) 的时候树上 dp,把 long long 的结果赋值给了 int,直接暴毙 20 分。

T2 不含问号的做法:

把表达式树建立出来。

每一位独立考虑:

显然 10 个数字可以按大小排序,二分一下最终答案是谁,小于的设置成 0,大于等于的设置成 1。

这样子取 max 操作也就变成了按位或,取 min 操作就是按位与。

变成了按位操作之后就发现可以把位合并了,把二分换成暴力枚举,用 bitset 加速可以做到 O(mn|E| / w)

如果 i -> i - 1 时的答案从 0 变成了 1,则该位最终答案就是排名第 i - 1 的那个数。

这样子就可以对每一位算出是谁造成的贡献了。

至少 50 分是可以拿到的。

------------------

n=2 且没有括号:

因为 n 小,所以可以直接对每一位算贡献,算出答案 >= k 的方案,然后通过差分得到答案 =k 的方案。

不难发现可以用寻宝游戏的那个套路,把数字串从右到左看成是个二进制数;

把操作符串也看成二进制数 |->0 &->1,则操作符串严格小于数字串时最终结果是 1。

等于说给定两个二进制数 A,B。B 有一些位置不确定,问多少种使得 B<A 的方案。

显然,直接枚举第一个二进制位小于的地方,然后后面随便放。

因为 n=2 所以 复杂度依然正确。

------------------

但实际上,感觉上文那个做法有点小题大做的感觉。

你发现只要一遍表达式树上的 dp 就可以算出根节点是 1 的方案。

这玩意复杂度是 O(nm|E|) 的
原文地址:https://www.cnblogs.com/imakf/p/14383328.html