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;
}
应该写的很清楚了吧。。代码应该很通俗易懂吧。。。
不懂问我。
—— · EOF · ——
真的什么也不剩啦 😖