2020 CCF CSP-J2 题解
## 打工人(Tyher)更博了。
T1 优秀的拆分
一句话题面: 将正整数(n)拆分成(k)个(2)的正整数幂形式。 (1≤n≤1×10^7)。
奇数的时候输出 (-1),偶数就把二进制下为 (1) 的位输出出来即可。复杂度(O(logn))
T2 直播获奖
一句话题面: 依次给定(n)个数(w_i),实时询问当前数集第(k)大。(n=100000 , w<600)
注意值域很小,考虑对于每次给定(w_i)插入,遍历值域数组。复杂度(O(nw))
拓展:考虑(w_i)无限制,可离散化后值域线段树优化以做到(O(n*logwi))
#include<bits/stdc++.h>
#define N 100001
using namespace std;
int n,w,now,Mx,low,res,G[N];
int main(){
cin>>n>>w;
for(int i=1;i<=n;++i){
cin>>now,G[now]++,Mx=max(now,Mx);
low=max(1,i*w/100),res=0;
for(int j=Mx;j>=0;--j){
res+=G[j];
if(res>=low){
printf("%d ",j);
break;
}
}
}
return 0;
}
T3 表达式
一句话题面: 给定后缀表达式(S),内含运算$and or xor $ , 依次改变(x_i)的值并询问当前表达式的结果。
保证(x_i)只取(0)或(1)且只出现一次,(|s| le 10^6 ,q le 1 imes 10^5,n le 1 0^5。)
有一定思维。
本题的核心在于:数据保证每个变量在表达式中出现恰好一次。
考虑对于当前后缀表达式建立二叉树,即叶子节点均为数值,其余节点为运算符。
由于每次只对单个叶子结点进行操作,因此对于单个节点的与运算、或运算和取反运算而言,只可能出现答案改变或者不改变。
不妨只考虑当数据改变的时候对最终答案的影响。因此我们可以在读取表达式的时候,可以对每个结点进行标记。
- 对于 & 操作,如果一个子树是 (0),哪么另外一个子树内的所有节点都是无用的,也就是不管数据是否改变,结果不会发生变化。
- 对于 | 操作,如果一个子树是 (1),哪么另外一个子树内的所有节点都是无用的,也就是不管数据是否改变,结果不会发生变化
故先计算出原表示答案ans
,这样我们在查询的时候,没被标记的就说明它往上到根节点都不存在一种让它变成失效的运算,所以答案是!ans
,如果有标记则答案依旧为ans
。
输入处理有点麻烦,代码实现有细节。
复杂度(O(|S|+n+q))
#include<bits/stdc++.h>
#define N 3000001
using namespace std;
int cnt,n,tp,nod;
int G[N],ls[N],rs[N],fa[N];
int Std[N],STK[N],op[N],fw[N];
//原本 当下 STK op
char ch;
void Build(int x){
if((!ls[x])&&(!rs[x]))return;
if(op[x]==-3){
if(ls[x])Build(ls[x]),Std[x]=(!Std[ls[x]]);
if(rs[x])Build(rs[x]),Std[x]=(!Std[rs[x]]);
}
else {
Build(ls[x]),Build(rs[x]);
if(op[x]==-1)Std[x]=(Std[rs[x]]&Std[ls[x]]);
else Std[x]=(Std[rs[x]]|Std[ls[x]]);
}
if(op[x]==-1){
if(Std[ls[x]]==0)fw[rs[x]]=1;
if(Std[rs[x]]==0)fw[ls[x]]=1;
}
if(op[x]==-2){
if(Std[ls[x]]==1)fw[rs[x]]=1;
if(Std[rs[x]]==1)fw[ls[x]]=1;
}
}
void Go(int x,int p){
fw[x]|=p;
if(ls[x])Go(ls[x],fw[x]|p);
if(rs[x])Go(rs[x],fw[x]|p);
}
int main(){
freopen("expr.in","r",stdin);
// freopen("expr.out","w",stdout);
while(1){
cin>>ch;
if(ch=='x')scanf("%d",&G[++cnt]),n=max(n,G[cnt]);
else if(ch=='&')G[++cnt]=-1;
else if(ch=='|')G[++cnt]=-2;
else if(ch=='!')G[++cnt]=-3;
else {
if(ch-'0'==n)break;
string rubbish;cin>>rubbish;break;
}
}
for(int i=1;i<=n;++i)cin>>Std[i];
nod=n;
for(int i=1;i<=cnt;++i){
if(G[i]>0)STK[++tp]=G[i];
else {
if(G[i]==-3){
nod++,op[nod]=G[i];
int Ls=STK[tp];
fa[Ls]=nod;ls[nod]=Ls;STK[tp]=nod;
}
else {
nod++,op[nod]=G[i],tp--;
int Ls=STK[tp+1],Rs=STK[tp];
fa[Ls]=fa[Rs]=nod;
ls[nod]=Ls,rs[nod]=Rs;
STK[tp]=nod;
}
}
}
Build(nod),Go(nod,0);
int q;cin>>q;
for(int i=1;i<=q;++i){
int now;cin>>now;
if(fw[now])printf("%d
",Std[nod]);
else printf("%d
",Std[nod]^1);
}
return 0;
}
T4 方格取数
一句话题面: 给定棋盘,求从左上至右下路径使得所经过数字和最大,路径不回头,不要求路径最短。 (n,m<10^3)
一个比较好的性质是路径不回头,即在当前列如果向上走,则不能向下走,反之亦然。
考虑(dp[i][j][0/1/2])表示当前位置((i,j)),向左,向上,向下的最大值。
转移
[f[i][j][0]=max(f[i][j-1][0/1/2])+now
]
[f[i][j][1]=max(f[i-1][j][0/1])+now
]
[f[i][j][2]=max(f[i+1][j][0/2])+now
]
细节:直接(dp)转移不太好实现,考虑记忆化搜索形式,初始值(-inf),一定记得开(longlong)
复杂度(O(n*m*3))
#include<bits/stdc++.h>
#define N 2001
#define ll long long
const ll inf=1e12;
int n,m,G[N][N];ll f[N][N][3];
//0 left 1 up 2 down
ll Go(int x,int y,int op){
if(f[x][y][op])return f[x][y][op];
if(x==1&&y==1)return G[x][y];
f[x][y][op]=-inf;
if(op==0){
for(int k=0;k<3;++k)
f[x][y][0]=max(f[x][y][0],Go(x,y-1,k));
f[x][y][0]+=G[x][y];
}
if(op==1)
f[x][y][1]=max(Go(x-1,y,0),Go(x-1,y,1))+G[x][y];
if(op==2)
f[x][y][2]=max(Go(x+1,y,0),Go(x+1,y,2))+G[x][y];
return f[x][y][op];
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&G[i][j]);
for(int i=1;i<=n;++i)
for(int j=0;j<3;++j)f[i][0][j]=-inf;
for(int j=1;j<=m;++j)
for(int p=0;p<3;++p)
f[0][j][p]=f[n+1][j][p]=-inf;
printf("%lld",max(Go(n,m,0),Go(n,m,1)));
return 0;
}