zoukankan      html  css  js  c++  java
  • 【题解】 「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|) 的
    
  • 相关阅读:
    Android Apk获取包名和Activity名称
    SoupUI接口测试学习分享
    Android 手机自动化测试工具有哪几种?
    SVN和CVS的区别
    名词
    本地解析地址步骤
    python3.6.1+selenium3.0环境安装问题及解决方法
    简述企业信息化与企业架构关系
    企业架构实践的线下公开课学习感悟
    golang 12.9 go mod 实践
  • 原文地址:https://www.cnblogs.com/imakf/p/14383328.html
Copyright © 2011-2022 走看看