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));  // 直接查询
}
posted on 2020-11-09 10:06  Orzlky  阅读(318)  评论(0编辑  收藏  举报