2020 CSP-J 题解(完结撒花)

T1

二进制拆分,有手就行。

#include<cstdio>
int n,i;
int main(){
    freopen("power.in","r",stdin);
    freopen("power.out","w",stdout);
    scanf("%d",&n);
    if(n&1){				//当n为奇数时,必有2^0项,无法分解
        puts("-1");
        return 0;
    }
    while(n){
        i=0;
        while(n>>i)i++;
        i--;
        printf("%d ",1<<i);
        n-=1<<i;		//考场上浪费1min在这里QAQ要减啊孩纸们
    }
    return 0;
}

T2

有点意思。

考场上:这不是一个插排85分到手吗?

#include<cstdio>
int a[1000005];
int n,w,p,l,r,mid,tmp;
int max(int x,int y){
	return x>y?x:y;
}
inline void read(int&x){
	x=0;
	bool f=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		x=(x<<1)+(x<<3)+(ch&15);
		ch=getchar();
	}
	if(f)x=-x;
	return;
}
int main(){
	freopen("live.in","r",stdin);
	freopen("live.out","w",stdout);
	read(n);read(w);
	for(int i=1;i<=n;++i){
		read(a[i]);
		l=1,r=i;mid=l+r>>1;
		while(l<r){				//二分优化,插入排序中已排序好的数组满足单调性
			mid=l+r>>1;
			if(a[i]==a[mid])break;
			else if(a[mid]<a[i])r=mid;
			else{
				if(l==r-1){
					mid=r;
					break;
				}
				else{
					l=mid+1;
					mid=l+r>>1;
				}
			}
		} 
		tmp=a[i];
		for(int j=i-1;j>=mid;--j)
			a[j+1]=a[j];
		a[mid]=tmp;
		p=max(1,i*w/100);
		printf("%d ",a[p]);
	}
	return 0;
}

注意,以上代码在牛客的掺水数据下是可以A的,CSP你就别做梦了~

正解

我@#)^*$..#^#^

看到题目底下:对于所有测试点,每个选手的成绩均为不超过 \(600\) 的非负整数

这不明摆着的桶排吗?

连初一的学长都想到桶排了一定是我太弱了555初一大佬tql%%%

#include<cstdio>
int a[605];
int n,w,p,x,s;
int max(int x,int y){
	return x>y?x:y;
}
inline void read(int&x){
	x=0;
	bool f=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		x=(x<<1)+(x<<3)+(ch&15);
		ch=getchar();
	}
	if(f)x=-x;
	return;
}
int main(){
	freopen("live.in","r",stdin);
	freopen("live.out","w",stdout); 
	read(n);read(w);
	for(int i=1;i<=n;++i){
		read(x);
        a[x]++;
		p=max(1,i*w/100);
        s=0;
        for(int j=600;~j;--j){	//分数从高到低计算
            s+=a[j];
            if(s>=p){
                printf("%d ",j);
                break;
            }
        }
	}
	return 0;
}

T3(正解来了)

先上一个部分分

只得20分是能力问题,只得30分是语文水平问题QwQ

分析这句话:对于 \(20\%\) 的数据,表达式中有且仅有与运算(&)或者或运算(|)。

你以为的意思:没有非(!),这组部分分有什么意义吗?
出题人的意思:整组数据只有与(&)或者只有或(|)

我**

对于二十分,很好想,几个变量统计一下就行了

在此,感谢GM在上半年与初赛前夕对我的教导,让我明白碰到后缀表达式就一定要朝栈的方向想
同时很对不起,你还讲过后缀表达式可以建一棵符号树,被我忘了QAQ

对于三十分:
每次询问用栈暴力,如何暴力GM上半年有在QQ群里发
《关于用栈把后缀表达式转成中缀表达式的方法》(标题是我乱取的)
没看去补,翻不到活该

50分暴力代码:

#include<stack>
#include<cstdio>
#include<string>
#include<sstream>	//不懂的请自行BDFS
#include<iostream>
using namespace std;
bool my_or=1,my_and=1; //20分标记
int len,n,q,x,t,sum0,sum1;
string s,s1;
stack<bool>stk;
bool a[100005];
int main(){
	freopen("expr.in","r",stdin);
	freopen("expr.out","w",stdout);
	getline(cin,s1);
	len=s1.size();//加上空格,输入的总字符数
	for(int i=0;i<len;++i){
		if(s1[i]=='|'||s1[i]=='!')
			my_and=0;
		if(s1[i]=='&'||s1[i]=='!')
			my_or=0;
	}
	if(my_and){
		scanf("%d",&n);
		for(int i=1;i<=n;++i){
			scanf("%d",&a[i]);
			sum0+=1-a[i];
		}
		scanf("%d",&q);
		while(q--){
			scanf("%d",&x);
			if(sum0!=1)puts("0");
			else if(!a[x])puts("1");
			else puts("0");
		}
	}
	else if(my_or){
		scanf("%d",&n);
		for(int i=1;i<=n;++i){
			scanf("%d",&a[i]);
			sum1+=a[i];
		}
		scanf("%d",&q);
		while(q--){
			scanf("%d",&x);
			if(sum1!=1)puts("1");
			else if(a[x])puts("0");
			else puts("1");
		}
	}
	else{		//30分部分
		scanf("%d",&n);
		for(int i=1;i<=n;++i)
			scanf("%d",&a[i]);
		scanf("%d",&q);
		while(q--){
			scanf("%d",&x);
			a[x]=!a[x];	//先将指定位取反
			stringstream stre(s1);
			t=-1;//空格预备
			while(!stk.empty())stk.pop();//把栈清空
			while(stre){
				stre>>s;
				t+=s.size()+1;//加上每个字符串后的空格
				if(t>len)break;//不这样写会RE得很惨,考场上起码调了10mins
				if(s.size()==1){//懒得解释
					if(s=="!"){
						bool x=stk.top();
						stk.pop();
						stk.push(!x);
					}
					else if(s=="&"){
						bool x=stk.top();
						stk.pop();
						bool y=stk.top();
						stk.pop();
						stk.push(x&y);
					}
					else if(s=="|"){
						bool x=stk.top();
						stk.pop();
						bool y=stk.top();
						stk.pop();
						stk.push(x|y);
					}
				}
				else{
					int x=0;
					for(int i=1;i<s.size();++i)
						x=(x<<1)+(x<<3)+(s[i]&15);//类似于快读的写法
					stk.push(a[x]);
				}
			}
			printf("%d\n",stk.top());
			a[x]=!a[x];//记得把反过去的反回来
		}
	}
	return 0;
}

好了,让我们愉快地问候出题人的老母

建一棵符号树。。。

树中的每个节点用结构体存放:
左儿子、右儿子、运算符、值、下标。

具体见代码。

#include<cstdio>
#include<string>
#include<iostream>
using namespace std;
const int maxn=1e6+5;
struct node{
	int l,r,o,v,id;
	//l=>左儿子
	//r=>右儿子
	//o=>运算符,o=-1表示这是数,0表示取反,1表示&,2表示|
}a[maxn]	//符号树
string s;	//上面用stringstream完全就是为了装×
bool vis[maxn];	//记录一个值取反是否会影响答案
int cnt,n,q,x,t;
int b[maxn],stk[maxn];	//我再也不用STL了QAQ
void dfs(int x){
	if(a[x].o==-1){
		vis[a[x].id]=1;	//此时这个数就会影响答案了
		return;
	}
	if(!a[x].o)dfs(a[x].l);	//非运算,把取反的操作数默认放到左子树
	else if(a[x].o==1){	//与运算
		int l=a[x].l;
		int r=a[x].r;
		//只有在左儿子、右儿子都是0的情况下,取反才不会发生任何改变
		if(a[l].v+a[r].v==2){
			dfs(l);dfs(r);
		}
		else if(a[l].v)dfs(r);
		else if(a[r].v)dfs(l);
	}
	else{	//或运算
		int l=a[x].l;
		int r=a[x].r;
		//同理,只有在左儿子、右儿子都是1的情况下,取反才不会发生任何改变
		if(a[l].v+a[r].v==0){
			dfs(l);dfs(r);
		}
		else if(!a[l].v)dfs(r);
		else if(!a[r].v)dfs(l);
	}
	return;
}
int main(){
	freopen("expr.in","r",stdin);
	freopen("expr.out","w",stdout);
	getline(cin,s);
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
		scanf("%d",&b[i]);
	for(int i=0;i<s.length();++i){
		switch(s[i]){	//多个if-else换成switch-case常数会小哦
			case 'x':{
				int x=0;
				while(s[i+1]<='9'&&s[i+1]>='0'){
					x=(x<<1)+(x<<3)+(s[i+1]&15);
					i++;
				}
				cnt++;
				a[cnt].v=b[x];
				a[cnt].id=x;
				a[cnt].o=-1;
				stk[++t]=cnt;
				break;	//if-else没有,这个break是用来break掉switch的
			}
			case '!':{
				cnt++;
				a[cnt].v=!a[stk[t]].v;
				a[cnt].l=stk[t--];
				a[cnt].o=0;
				stk[++t]=cnt;
				break;
			}
			case '&':{
				cnt++;
				a[cnt].v=a[stk[t]].v&a[stk[t-1]].v;
				a[cnt].o=1;
				a[cnt].l=stk[t--];
				a[cnt].r=stk[t--];
				stk[++t]=cnt;
				break;
			}
			case '|':{
				cnt++;
				a[cnt].v=a[stk[t]].v|a[stk[t-1]].v;
				a[cnt].o=2;
				a[cnt].l=stk[t--];
				a[cnt].r=stk[t--];
				stk[++t]=cnt;
				break;
			}
		}
	}
	dfs(cnt);
	bool ans=a[cnt].v;	//不作任何改变的答案
	scanf("%d",&q);
	while(q--){
		scanf("%d",&x);	//通俗易懂,懒得解释
		if(vis[x])printf("%d\n",!ans);
		else printf("%d\n",ans);
	}
	return 0;
}

T4

好家伙,DP。

在此,向没开long long见祖宗的学长们默哀。

难点在于既可以向上,也可以向下,需要标记一下啊。

状态:

f[i][j][0]表示已经走到了i行j列,且最后一步向上走的最大和
f[i][j][1]表示已经走到了i行j列,且最后一步向下走的最大和

Q:为什么没有向右走?

A:那不是一个 \(\max(f[i][j-1][0],f[i][j-1][1])+a[i][j]\) 就能解决嘛。

状态转移方程:

放在一个框里怕是有点恼火,见代码吧。

Code
#include<cstdio>
typedef long long ll;
const ll inf=1e18;
int n,m;
int a[1005][1005];
ll f[1005][1005][2];
ll max(ll x,ll y){
	return x>y?x:y;
}
int main(){
	freopen("number.in","r",stdin);
	freopen("number.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			scanf("%d",&a[i][j]);
			f[i][j][0]=f[i][j][1]=-inf;
		}
	}
	f[1][1][0]=f[1][1][1]=a[1][1];			//处理边界:左上角直接等于自己
	for(int i=2;i<=n;++i)
		f[i][1][1]=f[i-1][1][1]+a[i][1];	//处理边界:第一列只能从上边走下来
	for(int j=2;j<=m;++j){					//第一重循环为列
		f[1][j][1]=f[1][j][0]=max(f[1][j-1][0],f[1][j-1][1])+a[1][j];	//处理边界:第一行
		for(int i=2;i<=n;++i){
			f[i][j][0]=max(f[i][j-1][0],f[i][j-1][1])+a[i][j];	//从左边向右走
			f[i][j][1]=max(f[i][j][0],f[i-1][j][1]+a[i][j]);	//从上边向下走
		}
		for(int i=n-1;i;--i)			//从下边往上走,注意循环方向
			f[i][j][0]=max(f[i][j][0],f[i+1][j][0]+a[i][j]);
	}
	printf("%lld",max(f[n][m][0],f[n][m][1]));		//取最大值输出
	return 0;
}

应该写的很清楚了吧。。代码应该很通俗易懂吧。。。

不懂问我。

posted @ 2020-11-08 20:33  XSC062  阅读(186)  评论(0编辑  收藏  举报