NOIP2003提高组题解+反思

T1:神经网络

考察知识:图的基本知识,细节

算法难度:XX+ 实现难度:XX+ 

分析:

这道题不难,但是细节有点多(见反思)

我们考虑节点时需要使用上一个节点的值(所以要反向建图),所以可以用函数的递归调用实现

转移方程:C_i=\sum_{ji\epsilon E}^{ }C_j\times W_j_i - U_i

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=105;
#define ll long long
//j->i Ci=sum(Cj*w(j,i))-Ui
struct edge{
    int to,next;
    ll w;
}e[maxn*50];
int first[maxn],np;
void add(int u,int v,ll w){
    e[++np]=(edge){v,first[u],w};
    first[u]=np;
}
ll C[maxn],U[maxn];//谨慎一点,用long long 
int n,m,cd[maxn];
bool done[maxn];
void dfs(int i){
    if(C[i]||done[i]) return;
    for(int p=first[i];p;p=e[p].next){
        int j=e[p].to;
        ll w=e[p].w;
        dfs(j);
        if(C[j]<0) continue;//注意 
        C[i]+=C[j]*w;
    }
    C[i]-=U[i];
    done[i]=true;//类似于记忆化搜索 
}
int main(){
    int u,v;
    ll w;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%lld%lld",C+i,U+i);
    for(int i=1;i<=m;i++){
        scanf("%d%d%lld",&u,&v,&w);
        add(v,u,w);//反向建图 
        cd[u]++;
    }
    int cnt=0;
    for(int i=1;i<=n;i++) if(!C[i])
		dfs(i); 
    for(int i=1;i<=n;i++) if(C[i]>0&&cd[i]==0){
        printf("%d %lld\n",i,C[i]);cnt++;
    }
    if(!cnt) printf("NULL\n");
    return 0;
}

反思:

题目部分截图:

没有搞清楚题意!当场100-->20,惨不忍睹,T1差点爆零

我先解释一下图片表达的信息:

图一:说明神经元没有处于兴奋状态,它就不会向其他神经元传送信号(-40)

图二:只输出出度为0且满足Ci<0的点(-80)

情况三:考虑只有一个点的情况(-20)

然而我忽略了图一的信息,没有看见图二的要求,没有考虑情况三,于是错(W)误(A)百出,T_T

T2:  侦探推理

考察知识:字符串处理,枚举

算法难度:XXX 实现难度:XXXX

分析:面对这道题的题目描述我也是醉了,我觉得题目描述不够严谨,对于某些应该什么的情况没有说明(或者是我分析能力不行)

首先面对这道题的输入,我们应该应该考虑字符串处理(不要考虑gets()函数),但是我们怎么判断换行呢,如果我开始用ch=='\n'

洛谷上只有30分,在vijos80分,原来在洛谷上应该用 '\r' 判断

所以我们手动输入一行,用如下方法:

while((ch=getchar())!='\n'&&ch!='\r') s[p++]=ch;

然后我们怎么处理这些混乱的逻辑关系呢?首先我们判断是否有说话一定矛盾的人,如果有直接输出"Impossible"

没有的话,我们枚举凶手是谁,同时枚举星期几,然后统计说谎的人数,注意:可能有无法判断是否说谎的人,按照数据的答案,我们可以将其看作说谎,也可以不看作说谎,可是题目并没有说明这种情况

好吧,基本思想就是这样,但是具体的代码实现细节很多

我用结构体储存每个人的证词,写自定义函数判断是否说谎,

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<string>
#include<map>
using namespace std;
struct statement{
    int guilty,day;
    bool notg[21];
    statement(){guilty=day=0;memset(notg,0,sizeof(notg));}
    bool add_day(int Day){//true表示说谎 
        if(day&&day!=Day) return true;
        day=Day;return false;
    }
    bool add_guilty(int Guilty){
        if(guilty&&guilty!=Guilty) return true;
        guilty=Guilty;return false;
    }
    bool add_notg(int id){
        if(guilty==id) return true;
        notg[id]=true;return false;
    }
}st[21];
map<string,int>mp;
void ready(){
    mp["Today is Sunday."]=-1,mp["Today is Monday."]=-2;
    mp["Today is Tuesday."]=-3,mp["Today is Wednesday."]=-4;
    mp["Today is Thursday."]=-5,mp["Today is Friday."]=-6;
    mp["Today is Saturday."]=-7,mp["I am guilty."]=-8;
    mp["I am not guilty."]=-9;
}
int n,m,p;
bool liar[21];
string Nam[21];
void get_line(string& str){
    char s[256],ch=getchar();
    int p=0;
    while(!isalpha(ch)) ch=getchar();
    s[p++]=ch;
    while((ch=getchar())!='\n'&&ch!='\r') s[p++]=ch;
    s[p]='\0';
    str=s;
}
int judge(int i,int Guilty,int Day){//1真话,0不知道,-1假话,-2自相矛盾 
    int ret=0;
    if(st[i].guilty&&st[i].guilty!=Guilty) ret=-1;
    else if(st[i].guilty==Guilty) ret=1;
    if(st[i].day&&st[i].day!=Day){
        if(ret==1) return -2;
        ret=-1;
    }
    else if(st[i].day==Day){
        if(ret==-1) return -2;
        ret=1;
    }
    if(st[i].notg[Guilty]){
        if(ret==1) return -2;
        ret=-1;
    }
    for(int j=1;j<=m;j++) if(st[i].notg[j]&&j!=Guilty){
        if(ret==-1) return -2;
        ret=1;
    }
    return ret;
}
int main(){
//	freopen("in.txt","r",stdin);
//	ios::sync_with_stdio(false);//cin加速 
    ready();
    cin>>m>>n>>p;
    for(int i=1;i<=m;i++){
        string name;
        cin>>Nam[i];
        mp[Nam[i]]=i;
    }
    for(int i=1;i<=p;i++){
        string name_,str;
        cin>>name_;
        get_line(str); 
        name_.erase(name_.end()-1); 
        int name=mp[name_],id=mp[str];
//		cout<<name_<<endl<<str<<endl;
//		cout<<id<<endl;
        if(liar[name]) continue;//如果某人说话矛盾,就是在说谎 
        if(mp.count(str)&&id){
            if(id==-8) liar[name]=st[name].add_guilty(name);
            else if(id==-9) liar[name]=st[name].add_notg(name);
            else liar[name]=st[name].add_day(-id); 
        }
        else{
            for(int ii=1;ii<=m;ii++){
                if(str==Nam[ii]+" is guilty.") liar[name]=st[name].add_guilty(ii);
                else if(str==Nam[ii]+" is not guilty.") liar[name]=st[name].add_notg(ii);
            }
        }
    }
/*		
    for(int i=1;i<=m;i++){
        cout<<"Information:"<<endl;
        if(liar[i]) {cout<<"\tLiar"<<endl;}
        cout<<"\tname:  "<<Nam[i]<<endl;
        cout<<"\tguilty:  "<<Nam[st[i].guilty]<<endl;
        cout<<"\tday:  "<<st[i].day<<endl;
        for(int j=1;j<=m;j++) if(st[i].notg[j])
            cout<<"\t"<<Nam[j]<<" is not guilty."<<endl; 
    }
*/	
    int cnt=0,pos; 
    for(int i=1;i<=m;i++) if(liar[i]){//有人说话矛盾就无解 
        cout<<"Impossible"<<endl;return 0;
    }
    for(int Guilty=1;Guilty<=m;Guilty++)
    for(int Day=1;Day<=7;Day++){
        int Liar=0,cnt_sb=0;
        bool Err=false;
        for(int i=1;i<=m;i++){//枚举找答案 
            int res=judge(i,Guilty,Day);
//			cout<<Guilty<<"&"<<Day<<Nam[i]<<"::"<<res<<endl;
            if(res==-1) Liar++;
            else if(res==0) cnt_sb++;//无法判断是否说谎 
            else if(res==-2) Err=true; //与枚举条件矛盾 
        }
        if(Err) continue;
        if(Liar<=n&&Liar+cnt_sb>=n) cnt++,pos=Guilty,Day=8; 
        if(cnt>1) break;
    }
    if(cnt==1) cout<<Nam[pos]<<endl;
    else if(cnt>1) cout<<"Cannot Determine"<<endl;
    else cout<<"Impossible"<<endl;
    return 0;
}

T3:加分二叉树

考察知识:二叉树的遍历序列型动态规划

算法难度:XX+ 实现难度:XXX

分析:

考察二叉树的遍历:

中序遍历先遍历左子树然后当前根节点再然后右子树

前序遍历先遍历当前根节点然后左子树再然后右子树

二叉树的遍历是递归定义的,所以我们可以递归解决(加记忆化节省时间)

而这道题求最大分数,用动态规划解决:

定义状态方程:f(i,j)表示中序遍历序列为i到j的二叉树的最大得分

状态转移方程:f(i,j)=f(i,k-1)\times f(k+1,j)||k\epsilon (i,j)

细节见代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn=35;
ll ans,f[maxn][maxn];
bool vis[maxn][maxn];
int n,a[maxn],root[maxn][maxn];
ll dp(int l,int r){
	if(l>r) return 1;
	if(l==r) return (ll)a[l];
	if(vis[l][r]) return f[l][r];
	ll T;
	for(int rt=l;rt<=r;rt++){
		T=dp(l,rt-1)*dp(rt+1,r)+a[rt];
		if(T>f[l][r]) f[l][r]=T,root[l][r]=rt;//记忆路径 
	}
	vis[l][r]=true;
	return f[l][r];
}
void out(int l,int r){//输出路径 
	if(l>r) return;
	if(l==r){printf("%d ",l);return;}
	int rt=root[l][r];
	printf("%d ",rt);//相当于二叉树的前序遍历 
	out(l,rt-1);
	out(rt+1,r);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",a+i);
	printf("%lld\n",dp(1,n));
	out(1,n);
	return 0;
}

T4:传染病控制

考察知识:树的基本知识,搜索+剪枝

算法难度:XXXX 实现难度:XXX

分析:这道题用不加剪枝的搜索往往都能得50分以上(我得了80分)

但是这道题的搜索方法真的不是非常好想,但想出来这道题也就不难了

单纯用这种方法不加剪枝就可以得80分,那么我们怎么加剪枝呢?

我们预处理每层的最大sz[i],如果搜索到某一层,剩下的所有节点选最大的删除还达不到之前得到的最大值,则剪枝。

剪枝后速度快了进10倍

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=305;
int n,m,g[maxn][maxn],sz_g[maxn];
int fa[maxn],sz[maxn],dep[maxn],ans;
int SUM[maxn];
bool vis[maxn];
void ready(int i,int Fa,int Dep){//预处理 
	fa[i]=Fa,sz[i]=1,dep[i]=Dep;
	for(int p=sz_g[i];p>0;p--){
		int j=g[i][p];
		if(j!=Fa){
			ready(j,i,Dep+1);
			sz[i]+=sz[j];	
		}
	}
}
bool check(int i){
	while(i) if(vis[i=fa[i]]) return false;
	return true;
}
void dfs(int Dep,int sum){
	if(sum+SUM[Dep]<=ans) return; //剪枝 
	ans=max(ans,sum);
	for(int i=1;i<=n;i++) if(dep[i]==Dep&&check(i)){
		vis[i]=true;
		dfs(Dep+1,sum+sz[i]);
		vis[i]=false;
	}
}
int main(){
	int u,v;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		sz_g[u]++,sz_g[v]++;
		g[u][sz_g[u]]=v,g[v][sz_g[v]]=u;
	}
	ready(1,0,0);
	for(int Dep=n;Dep>=1;Dep--)//预处理剪枝条件 
	  for(int i=1;i<=n;i++) if(dep[i]==Dep)
		SUM[Dep]=max(SUM[Dep],sz[i]);
	for(int i=n-1;i>=1;i--) SUM[i]+=SUM[i+1];
	dfs(1,0);
	printf("%d\n",n-ans);
	return 0;
}

总结:

T_T终于做完了,被T2卡了很久,交了10次才AC,其中被输入卡了5次,呜呜呜... ...

2002年没有一道数据结构题,2003年四道有三道根数据结构有关,这个出题的知识点覆盖真的奇怪。。。

2003年的题因为我没有审题(或者语文太差,我语文真的菜T_T),或者考虑不够严谨几乎完了

第一次提交分数:20+10+100+40=170 我真的菜啊T_T。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值