这道题是一原题,地址如下:
CF1010D Mars rover
B
题目描述
给你一棵二叉有根树,根为1,节点数为n。
每个节点有五种情况,AND表示&运算有两个子节点,OR表示|运算有两个子节点,XOR表示^运算有两个子节点,NOT表示!运算有一个子节点,和IN表示这个节点没有子节点,是初始节点,01状态由读入决定。
现在,我们并不能局限于现在的树,我们要进行改变!我们同时只可以改变一个叶子节点的01状态,求按照节点编号顺序,分别改变叶子节点的01状态后,根节点的01状态。
输入格式
第一行一个正整数 n 意义如题目所述。
2-n+1行每行在AND x y OR x y XOR x y NOT x IN 0/1这五种情况中选择。
输出格式
一行,表示按顺序的根节点01状态
样例
样例输入
10
AND 9 4
IN 1
IN 1
XOR 6 5
AND 3 7
IN 0
NOT 10
IN 1
IN 1
AND 2 8
样例输出
10110
大样例见选手下发文件中b2.in与b2.out
数据范围
子任务 | 分值 | 限制 |
---|---|---|
1 | 20 | n≤50 |
2 | 10 | n≤5000 |
3 | 20 | n≤40000 |
4 | 20 | n≤1000000,树随机生成 |
5 | 30 | n≤1000000 |
看到这道题目的时候,我笑了。虽然我十分讨厌有关于树的题目,但是二叉树的题目相对来讲还是很好做的。但是测试完之后我就笑不出来了,因为我又爆零了,退役了退役了。
好吧,话题回到这道题上。我看到根节点确定是1的时候我又笑了。不我没资格笑。反正我是带着愉快的心情打完这道题的代码的。真不知道自己哪来的信心觉得能过。
我们先按照题目的意思,构建出一棵二叉树,其中每一个节点记录的数据为当前节点的父节点、子节点、操作符以及 0 / 1 0/1 0/1。我在打这道题的时候还用了一个 v e c t o r vector vector按节点编号次序记录了叶节点的编号,到时候就可以直接枚举叶节点并进行操作了。
记录好每一个节点的信息后,进行一次 d f s dfs dfs,求出每一个节点在叶节点的值都未改变时的原始值。
哟西,处理好二叉树的各项值了,再枚举每一个叶子结点改变之后,往上继续运算,得到最后根节点的值。
我在考试的时候写的代码就是按照这个思路来的,本来是可以拿 30 30 30分的,因为有一个剪枝没打。
下面贴上我考试的时候写的代码为啥爆零啊我好迷惑,按理来说它应该
30
30
30分但是它爆零了:
#include<bits/stdc++.h>
#define N 1000000+10
using namespace std;
int n;
char ch[4];
struct node{
int lc,rc;
int fa;
int dat;
char a;
}t[N];
vector<int>leaf;
void dfs(int i){
if(t[i].dat!=-1)return;
if(t[i].a=='&'){
dfs(t[i].lc);
dfs(t[i].rc);
t[i].dat=t[t[i].lc].dat&t[t[i].rc].dat;
}
if(t[i].a=='|'){
dfs(t[i].lc);
dfs(t[i].rc);
t[i].dat=t[t[i].lc].dat|t[t[i].rc].dat;
}
if(t[i].a=='^'){
dfs(t[i].lc);
dfs(t[i].rc);
t[i].dat=t[t[i].lc].dat^t[t[i].rc].dat;
}
if(t[i].a=='~'){
dfs(t[i].lc);
t[i].dat=!(t[t[i].lc].dat);
}
}
int main(){
// freopen("b.in","r",stdin);
// freopen("b.out","w",stdout);
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++){
cin>>ch;
int x,y;
if(ch[0]=='A'){
cin>>x>>y;
t[i].lc=x;t[i].rc=y;
t[x].fa=i;t[y].fa=i;
t[i].a='&';t[i].dat=-1;
}
if(ch[0]=='O'){
cin>>x>>y;
t[i].lc=x;t[i].rc=y;
t[x].fa=i;t[y].fa=i;
t[i].a='|';t[i].dat=-1;
}
if(ch[0]=='X'){
cin>>x>>y;
t[i].lc=x;t[i].rc=y;
t[x].fa=i;t[y].fa=i;
t[i].a='^';t[i].dat=-1;
}
if(ch[0]=='N'){
cin>>x;
t[i].lc=x;;
t[x].fa=i;
t[i].a='~';t[i].dat=-1;
}
if(ch[0]=='I'){
cin>>x;
leaf.push_back(i);
t[i].dat=x;
}
}
dfs(1);
for(int i=0;i<leaf.size();i++){
int x=leaf[i];
int ans=(!t[x].dat);
int flag=0;
for(;x!=1;x=t[x].fa){
int f=t[x].fa;
int ao;
if(t[f].lc==x)ao=t[t[f].rc].dat;
else ao=t[t[f].lc].dat;
if(t[f].a=='&')ans=ans&ao;
if(t[f].a=='|')ans=ans|ao;
if(t[f].a=='^')ans=ans^ao;
if(t[f].a=='~')ans=(!ans);
}
cout<<ans;
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
机房某大佬讲解,就是我们修改了某一个叶子结点的之后往上走的时候与兄弟节点运算的结果可能与没修改过的时候父节点的值一样,那么这个时候我们就没有必要继续往上走了,直接输出t[1].dat
就完事儿了。
于是我在我原来的代码的基础上加上了4行剪枝的代码,然后上洛谷提交, A C AC AC了,一遍过,看得我眼泪直流,太难了!
这是我的 A C AC AC代码:
#include<bits/stdc++.h>
#define N 1000000+10
using namespace std;
int n;
char ch[4];
struct node{
int lc,rc;
int fa;
int dat;
char a;
}t[N];
vector<int>leaf;
void dfs(int i){
if(t[i].dat!=-1)return;
if(t[i].a=='&'){
dfs(t[i].lc);
dfs(t[i].rc);
t[i].dat=t[t[i].lc].dat&t[t[i].rc].dat;
}
if(t[i].a=='|'){
dfs(t[i].lc);
dfs(t[i].rc);
t[i].dat=t[t[i].lc].dat|t[t[i].rc].dat;
}
if(t[i].a=='^'){
dfs(t[i].lc);
dfs(t[i].rc);
t[i].dat=t[t[i].lc].dat^t[t[i].rc].dat;
}
if(t[i].a=='~'){
dfs(t[i].lc);
t[i].dat=!(t[t[i].lc].dat);
}
}
int main(){
// freopen("b.in","r",stdin);
// freopen("b.out","w",stdout);
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++){
cin>>ch;
int x,y;
if(ch[0]=='A'){
cin>>x>>y;
t[i].lc=x;t[i].rc=y;
t[x].fa=i;t[y].fa=i;
t[i].a='&';t[i].dat=-1;
}
if(ch[0]=='O'){
cin>>x>>y;
t[i].lc=x;t[i].rc=y;
t[x].fa=i;t[y].fa=i;
t[i].a='|';t[i].dat=-1;
}
if(ch[0]=='X'){
cin>>x>>y;
t[i].lc=x;t[i].rc=y;
t[x].fa=i;t[y].fa=i;
t[i].a='^';t[i].dat=-1;
}
if(ch[0]=='N'){
cin>>x;
t[i].lc=x;;
t[x].fa=i;
t[i].a='~';t[i].dat=-1;
}
if(ch[0]=='I'){
cin>>x;
leaf.push_back(i);
t[i].dat=x;
}
}
dfs(1);
for(int i=0;i<leaf.size();i++){
int x=leaf[i];
int ans=(!t[x].dat);
int flag=0;
for(;x!=1;x=t[x].fa){
int f=t[x].fa;
int ao;
if(t[f].lc==x)ao=t[t[f].rc].dat;
else ao=t[t[f].lc].dat;
if(t[f].a=='&')ans=ans&ao;
if(t[f].a=='|')ans=ans|ao;
if(t[f].a=='^')ans=ans^ao;
if(t[f].a=='~')ans=(!ans);
if(ans==t[f].dat){//剪枝
cout<<t[1].dat;
flag=1;
break;
}
}
if(flag)continue;
cout<<ans;
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
附图:
128 128 128个测试点看得我胆颤心惊。
A
A
A完后全身舒爽,但转念一下,我还没有找到我爆零的原因!于是我又把那几行去掉提交了一遍。果不其然
T
L
E
TLE
TLE了,但前17个点都过了,第18个点才超时,怎么就爆零了呢?Winnie陈的数据果真玄学。
最后,附上Winnie陈的美丽高级代码:
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <iostream>
using namespace std;
#define N 1000005
#define get(x) (son[fa[x]][0]!=x)
int f[N][2],fa[N],a[N],son[N][2],p[N],n;char s[100];
int dfs(int x)
{
if(a[x]==1)return p[x];
if(a[x]==2)
{
int tmp=(dfs(son[x][0])&dfs(son[x][1]));
p[x]=tmp;return tmp;
}
if(a[x]==3)
{
int tmp=(dfs(son[x][0])|dfs(son[x][1]));
p[x]=tmp;return tmp;
}if(a[x]==4)
{
int tmp=(dfs(son[x][0])^dfs(son[x][1]));
p[x]=tmp;return tmp;
}
return p[x]=(!dfs(son[x][0]));
}
int get_ans(int x,int now)
{
if(x==1)return now;
if(f[x][now]!=-1)return f[x][now];
int k=get(x),u=fa[x];
if(a[u]==2)return f[x][now]=get_ans(u,now&p[son[u][!k]]);
else if(a[u]==3)return f[x][now]=get_ans(u,now|p[son[u][!k]]);
else if(a[u]==4)return f[x][now]=get_ans(u,now^p[son[u][!k]]);
return f[x][now]=get_ans(u,!now);
}
int main()
{
//freopen("b.in","r",stdin);freopen("b.out","w",stdout);
scanf("%d",&n);memset(f,-1,sizeof(f));
for(int i=1,x,y;i<=n;i++)
{
scanf("%s%d",s,&x);
if(s[0]=='I')p[i]=x,a[i]=1;
else if(s[0]=='N')a[i]=5,son[i][0]=x,fa[x]=i;
else
{
scanf("%d",&y);fa[x]=i,fa[y]=i;
son[i][0]=x,son[i][1]=y;
if(s[0]=='A')a[i]=2;
else if(s[0]=='X')a[i]=4;
else a[i]=3;
}
}p[1]=dfs(1);
for(int i=1;i<=n;i++)f[i][p[i]]=p[1];
for(int i=1;i<=n;i++)if(a[i]==1)printf("%d",get_ans(i,!p[i]));
}
小贴士:ios::sync_with_stdio(false);
是我隔壁那位教我的增快输入速度的方法。有了这个,用cin
和用scanf
的时间就差不多了。
完结撒花!