2020 CSP-J 题解1
A 优秀的拆分
题意简述:
给定一个正整数,要求将其拆分成若干个数的和,满足这些数都可以表示为 \(2^i\) (\(1\leq i\leq \log{10^7}\))。
题解:
我们都知道,任何一个正整数都可以被表示成若干个二的整数次幂的和,如 \(12=2^3+2^2\),\(18=2^4+2^1\) 。但是根据题目要求,我们并不能使用 \(2^0\)。因此,假如数据的 \(n\) 为奇数,应当输出 \(-1\) 。判断完后,只要对 \(n\) 进行二进制分解即可,复杂度 \(O(\log n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
int n;
int ans[41],top=0;//因为要求用从大到小输出,用栈存储答案
int main()
{
scanf("%d",&n);
if(n&1)
return puts("-1"),0;//不合法
while(n)
{
ans[++top]=n&-n; // 其中 lowbit,即 n&-n 可以求出目前最低一位的大小。
n-=n&-n; // 减去答案
}
while(top)
printf("%d ",ans[top--]);//输出
}
B 直播获奖
题意简述
按照给定的顺序插入 \(n\) 个正整数,要求出每次插入第 \(i\) 个数后,第 \(\lfloor{\frac{i\times w}{100}}\rfloor\) 大的数的大小。
题解
我们可以想到通过堆来做这道题。我们可以开一个小根堆 \(P\),来存储被选上的数字。再开一个大根堆 \(Q\),来存储未被选上的数字。当 \(P\) 中的数字不足 \(\lfloor{\frac{i\times w}{100}}\rfloor\) 个的时候,从 \(Q\) 中取数字放入 \(P\) 中。插入的时候,直接将数字放入 \(Q\) 中。此外,假如 \(P\) 顶的元素小于 \(Q\) 顶的元素,则交换两个堆顶的元素,这样可以保证 \(P\) 中任意数字大于等于 \(Q\) 中所有数字。输出的答案显然就是 \(P\) 的堆顶元素。复杂度 \(O(n\log n)\)
代码
// 代码中的 p,q 与题解相反,请注意。
#include<bits/stdc++.h>
using namespace std;
priority_queue<int,vector<int>,greater<int> > q;
priority_queue<int> p;
int sz=0,w,n;
int main()
{
scanf("%d%d",&n,&w);
for(int i=1;i<=n;i++)
{
int mxsz=max(i*w/100,1);
int x;
scanf("%d",&x);
p.push(x);
while(sz<mxsz)
{
sz++;
q.push(p.top());
p.pop();
}
// 向 P 中丢入 Q 顶元素
while(q.top()<p.top())
{
int x=q.top(),y=p.top();
q.pop();
p.pop();
p.push(x);
q.push(y);
}
// 交换两堆堆顶元素
printf("%d ",q.top());
}
}
C 表达式
题意简述
给你一个后缀表达式,每次询问将一个数取反后这个后缀表达式的值。
题解
我们可以将这个表达式的表达式树建出来,令 \(V_i\) 为点 \(i\) 的子树的值, \(T_i\) 为点 \(i\) 的状态,\(Fa_i\)是点 \(i\) 的父亲节点,表达式树的点按照读入的顺序编号,且有 \(n\) 个点,当 \(T_i=1\) 时,则 \(i\) 取反会导致 \(V_n\) 取反。显然,\(T_n=1\)。转移的时候,假如点 \(i\) 非叶子节点,则点 \(i\) 一定是个运算符。
当点 \(i\) 操作符为 \(not\) 时,显然 \(T_{Son_i}=T_i\)。
当 \(i\) 为 \(or\) 时,若有一个子节点为 \(1\) 且另一个为 \(0\) 则值为 \(1\) 的 \(T\) 为 \(1\) 。如果两个都为 \(1\),则所有儿子的 \(T\) 都为 \(0\)。若两个都为 \(0\) 则 \(T\) 都为 1。
当 \(i\) 为 \(and\) 的时候,讨论的东西与 \(or\) 恰好相反,就不在此赘述。
因此,只需要在建树后,从根节点 \(n\) 开始 dfs 一遍即可。复杂度 \(O(|s|+q)\)
代码
#include<bits/stdc++.h>
using namespace std;
char s[1000010][8];
int fa[1000010],son[1000010][2],v[1000010];
bool vis[1000010];
int cnt=0;
int n,a[1000010],t[1000010],to[1000010];
int st[1000010],top=0;
int get(int x)
{
int len=strlen(s[x]+1);
int syn=0;
for(int i=2;i<=len;i++)
syn=syn*10+s[x][i]-'0';
return syn;
}
void query(int x)
{
int ls=v[son[x][0]],rs=v[son[x][1]];
if(s[x][1]=='x')
{
t[x]=1;
return ;
}
if(s[x][1]=='!')
{
query(son[x][0]);
return ;
}
if(s[x][1]=='|')
{
if(ls==0&&rs==1)
query(son[x][1]);
if(ls==1&&rs==0)
query(son[x][0]);
if(ls==0&&rs==0)
query(son[x][1]),query(son[x][0]);
return ;
}
if(s[x][1]=='&')
{
if(ls==0&&rs==1)
query(son[x][0]);
if(ls==1&&rs==0)
query(son[x][1]);
if(ls==1&&rs==1)
query(son[x][1]),query(son[x][0]);
return ;
}
}
int main()
{
while(1)
{
scanf("%s",s[++cnt]+1);
char c=getchar();
if(c!=' ')
break;
}
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=cnt;i++)
{
if(s[i][1]=='!')
{
son[i][0]=st[top];
fa[st[top--]]=i;
v[i]=!(v[son[i][0]]);
st[++top]=i;
}
else if(s[i][1]=='|')
{
son[i][0]=st[top];
fa[st[top--]]=i;
son[i][1]=st[top];
fa[st[top--]]=i;
v[i]=v[son[i][0]]|v[son[i][1]];
st[++top]=i;
}
else if(s[i][1]=='&')
{
son[i][0]=st[top];
fa[st[top--]]=i;
son[i][1]=st[top];
fa[st[top--]]=i;
v[i]=v[son[i][0]]&v[son[i][1]];
st[++top]=i;
}
else
st[++top]=i,v[i]=a[get(i)],to[get(i)]=i;
}
query(cnt);
int q;
scanf("%d",&q);
while(q--)
{
int x;
scanf("%d",&x);
printf("%d\n",t[to[x]]^v[cnt]);
}
}
D 方格取数
题意简述
你想从图的左上角走到右下角,每一步只能向上、向下或向右走一格,不能重复经过已经走过的方格,也不能走出边界。求经过的格子的数的和的最大值。
题解
一道显然的 dp 题。我们记录状态 \(f_{i,j,k}\),表示此时的你在第 \(i\) 行,第 \(j\) 列,从方向 \(k\) 处走过来,则有下面三条状态转移方程:
\(f_{i,j,0}=max(f_{i,j+1,0},f_{i-1,j,1},f_{i+1,j,2})+V_{i,j}\)
\(f_{i,j,1}=max(f_{i,j+1,0},f_{i-1,j,1})+V_{i,j}\)
\(f_{i,j,2}=max(f_{i,j+1,0},f_{i+1,j,2})+V_{i,j}\)
\(f_{n,m,k}=V_{n,m}\)
\(f_{i,m+1,k}=f_{n+1,j,k}=f_{0,j,k}=f_{i,0,k}=-\inf\)
答案就是 \(max(f_{1,1,0},f_{1,1,1},f_{1,1,2})\)
代码
#include<bits/stdc++.h>
using namespace std;
long long f[1010][1010][3];
bool vis[1010][1010][3];
int v[1010][1010];
int n,m;
long long dfs(int x,int y,int opt)
{
if(x>n||y>m||x<1||y<1)
return -1e11; // 走出去了
if(x==n&&y==m)
return v[x][y]; //走到终点了
if(vis[x][y][opt])
return f[x][y][opt]; // 以前走过了
vis[x][y][opt]=1;
f[x][y][opt]=-1e11;
if(opt!=1)
f[x][y][opt]=max(dfs(x-1,y,2),f[x][y][opt]);
if(opt!=2)
f[x][y][opt]=max(dfs(x+1,y,1),f[x][y][opt]);
f[x][y][opt]=max(dfs(x,y+1,0),f[x][y][opt]);
// dp
f[x][y][opt]+=v[x][y];// 加上该点
return f[x][y][opt];
}
signed main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&v[i][j]);
printf("%lld\n",dfs(1,1,0)); // 直接查询
}