2020 CSP-J 题解1

A 优秀的拆分

题目链接

题意简述:

给定一个正整数,要求将其拆分成若干个数的和,满足这些数都可以表示为 (2^i) ((1leq ileq 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 imes w}{100}} floor) 大的数的大小。

题解

我们可以想到通过堆来做这道题。我们可以开一个小根堆 (P),来存储被选上的数字。再开一个大根堆 (Q),来存储未被选上的数字。当 (P) 中的数字不足 (lfloor{frac{i imes w}{100}} floor) 个的时候,从 (Q) 中取数字放入 (P) 中。插入的时候,直接将数字放入 (Q) 中。此外,假如 (P) 顶的元素小于 (Q) 顶的元素,则交换两个堆顶的元素,这样可以保证 (P) 中任意数字大于等于 (Q) 中所有数字。输出的答案显然就是 (P) 的堆顶元素。复杂度 (O(nlog 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
",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
",dfs(1,1,0));  // 直接查询
}
原文地址:https://www.cnblogs.com/Orzlky/p/2020CSP-SOLUTION.html