sc测试一

     据说今天都是水题,真的是这样吗?

T1

长路

题目描述

一天,小Vasya发现自己在一个有(n+1)个房间的迷宫里。刚开始,Vasya在第一个房间,为了走出迷宫,他必须到达第(n+1)个房间。

迷宫的构造是这样的:每个房间有两个单向传送门。考虑第i个房间,我们可以使用第一个传送门前往第i+1个房间,也可以使用第二个传送门前往第pi个房间,这里 1≤pi≤i

为了防止迷路,Vasya决定按以下方式走出迷宫:

每次进入某房间,就在天花板上画一个X。刚开始,Vasya在第一个房间画一个X
假设Vasya在第i个房间,而且已经画了X。然后,如果天花板上有奇数个XVasya会使用第二个传送门,否则使用第一个。
帮助Vasya计算他需要使用多少次传送门才能到达第n+1个房间。
数据范围

测试点 n
1~2 ≤5
3~5 ≤20
6~7 ≤100
8~15 ≤1000
16~20 ≤10^6

输入格式 2072.in

第一行一个整数n n ≥ 1

第二行n个整数,第i个表示pi

输出格式 2072.out

你的答案除以1 000 000 007的余数。

输入样例 2072.in

2
1 2

输出样例 2072.out

4

               这题目第一眼看过去就是水题嘛。只有一条路径,似乎直接纯模拟便能AC。但是会注意到答案要取余,说明会很大,硬模拟肯定会超时。

    在测试的时候我没有细想,看了看便遵循lgj的说法,难度不按顺序,然后就去看了第二题。

    等我后来回过头再去做第一题的时候,索性随便打了个暴力模拟。很悲催,我暴力还打错了。

错的原因:

    没有审清楚题目,想当然地认为x是一直累加,后来同学才告诉我是每到一个格子,是那个格子的x++。比赛结束后,在修改的时候,因为心烦意乱,也一直没有看出错误,改了很久才拿到了暴力分。

部分分:

    25分 暴力模拟。

    65分 正解部分。

    100分 细节处理。

正解:

    正解是DP。f[i]为由第i个点到下一个点要走多少步。由于题目说到,1≤pi≤i并且到达一个格子的时候第一步一定会是单数。要到下一个点,一定要通过第一个操作才能达到。那么,肯定要回到前面的某个点再往回走到当前的点i

    我们可以用部分和来记录下f[1]……到f[i-1]的总和,这样便能很快地计算出再一次回到i要多少步,但是要注意的是,过去pi的点,和接着到下一个点,同样耗时,最后f[i]的结果还要加上2。

如图所示:

(表示画图软件实在是有问题)

利用部分和,sum[i-1]-sum[p[i]-1]即可得出中间要走的步数。

细节处理:

    后来搞定了以后,却没有AC。经过wyy的提醒,mod了以后,有可能会出先sum[i-1]反而要小的情况,我举个简单的例子,305%100=5 , 125%100=25 ,明明本身是305要大一些,这样一mod却相反了。

     我用100来mod算了一下,发现再加上一个mod再做减法运算得出的结果恰好。

代码如下:

#include
   
   
    
    
#include
    
    
     
     
#include
     
     
      
      
#include
      
      
       
       
using namespace std;
const int MAXN=1000005,mod=1000000007;
long long n,a[MAXN],f[MAXN],sum[MAXN];//f为到达下一个点所需要的步数 
int main()
{
	freopen("2072.in","r",stdin);
	freopen("2072.out","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	sum[1]=2;
	f[1]=2;
	for(int i=2;i<=n;i++)
	{
		f[i]=((mod+sum[i-1]-sum[a[i]-1])%mod+2)%mod;
		sum[i]=(sum[i-1]+f[i])%mod;
	}
	cout<
       
       
         < 
         
       
      
      
     
     
    
    
   
   

反思:

    做这题的时候比较急躁而且草率,没有努力想正解,想投机取巧地骗个部分分,没有DP的想法。审题不够仔细,急躁容易看错题。



T2

Bug

题目描述

这天,Bug终于梦到自己变成了一只大bug,在前线指挥着虫族的军队抵抗人族的进攻。

虫族的防线由在一条直线上依次编号为1~NN个基地构成。编号相临的两个基地之间由地道相连接,而Bug所在的母巢位于防线一端的编号为1的基地。

Bug
发现,人族的部队每次只会选择一个虫族基地进行集中攻击。所以,他每次都会从母巢赶到被攻击的基地亲临前线指挥战斗。但是,在地道中移动是很耗时的,为了能更快地赶到任何一个可能被攻击的基地,Bug决定修建一对虫洞。

一对虫洞由两个入口组成,它能实现在这两个入口间的瞬间转移。出于安全考虑,这两个入口一定要建造在基地上,而不能建在地道中或其他地方。Bug想选择两个合适的基地建造这一对虫洞,使得他从母巢赶到任何一个基地所需要的移动距离最远的那个最短。假设Bug足够聪明,每次都会选择最短的路径前往被攻击的基地。

Bug
梦中的你,能帮助他吗?
数据范围

测试点 N
1~5 ≤1 000
6~8 ≤10 000
9~10 ≤100 000

输入格式 2073.in

1行一个整数N(1 ≤ N);

2N-1个正整数,其中第i个数表示编号为i的基地与编号为i + 1的基地之间的地道长度,此长度不会超过2 147 483 647

输出格式 2073.out

一个表示你的答案的整数。

输入样例 2073.in

3
1 20

输出样例 2073.out

1

题目大意:

    即给出n个洞,每个洞之间有一定的距离,可以修建一个虫洞(虫洞的距离为0 瞬移),求出由母巢(0号点),到每一个点里面的距离最长的那个,并在各种情况中求出最长路线最短的情况。

关于题目的坑:

    当时考试的时候,要不是zd喊了一嗓子,“可以走回头路?!”,估计醒悟过来的人也没那么多,然而我就是没有醒悟的少部分。很简单地打了一个相当不靠谱的暴力,本来打算骗个30分或者50分,结果没考虑回头路,得出成绩正好是OI模式显示出来的10分,心中有一百匹草泥马在奔腾。。

回头路最优例:

    举个例子 4个点,路径长分别为:30、20、10。

    补充说明:0—1(30) 1—2(20) 2—3(10)

如果是不走回头路的情况,那么最好的方法就是把虫洞建在0与2。那么要走到第1,直线的方法是30,回头路就是由2到1,为20。

部分分:

    10分 不考虑回头路

    30分 纯暴力 三重for  O(N^3)

    80分 减少一个for  O(N^2)

    100分 正解 二分

为什么可以减少一重for:

    所谓的两重for循环,便是剩下一个枚举虫洞的结束点,另一个枚举到达的每一个洞k。而起始点固定在母巢的位置。

    原理是。当起点固定在母巢时,在母巢里面的点有两种方案,直线走或者回头路;不固定在母巢时,同样是有两种方案,直线走或者走回头路。至于后面的部分,反倒是虫洞区域大的,能省更多的路程。

正解:

    即在两重枚举的基础上,二分点k。首先,在虫洞的结束点以后的点,越远是会距离越远,所以直接算最后一个点的距离即可。

    除此之外,还有在虫洞之间的点要计算。这些点可以走直线也可以走回头路,会发现,中间必定有一个点的路程是最长的。

    具有单调性,用二分查找到这个点,直线走和回头路的路程最接近的点。取直线走和走回头路的最优值,与虫洞之后的点的最远路径取最大值,就是由母巢到每一个点的最长路径。

最后把所有情况的最长路径取个min,就是最优的方案。

代码如下:

#include
     
     
      
      
#include
      
      
       
       
using namespace std;
long long n,sum[100005],a[100005];
long long ans=0,zans,v,j;
int main()
{
	freopen("2073.in","r",stdin);
	freopen("2073.out","w",stdout);
	cin>>n;
	for(int i=1;i
       
       
        
        >a[i];
		sum[i]=sum[i-1]+a[i];		
	}
	zans=sum[n-1];
	long long now=0;
	for(j=1;j
        
        
         
         sum[mid]) lo=mid;
			else hi=mid;
		}
		
		if(hi==0) ans=max(sum[j-1],sum[n-1]-sum[j]);
		else
		{
			ans=max(sum[hi-1],sum[j]-sum[hi]);
			ans=max(ans,sum[n-1]-sum[j]);
		}
		zans=min(ans,zans);
	}	
	cout<
         
         
           < 
           
         
        
        
       
       
      
      
     
     

反思:

    注意题目有没有坑;当有多重for循环的时候,看看有没有什么特殊的方法,固定一个端点来减少一重循环;二分是一个很好的节省时间的方法。

T3:

RP

题目描述

XRP十分有研究,现在他向你提出了一个问题。给出一棵N个节点的树,有些边是RP边,至少经过一条RP边的路径定义为RP路,其他路径定义为非RP路。现在,你需要令最多一条RP边变为非RP边,使得RP路的数量最少。
数据范围

测试点 N
1~2 ≤100
3~5 ≤5 000
6 ≤10 000
7~10 ≤100 000

输入格式 2074.in

第一行一个正整数N

接下来N-1行,每行三个整数A,B,C。表示节点A,B间存在一条边,如果是RP边,那么C=1,否则C=0。节点从1开始到N标号。

输出格式 2074.out

一个表示你的答案的整数。

输入样例 2074.in

5
3 5 1
1 4 1
5 2 1
2 1 0

输出样例 2074.out

7

前记

    虽然老师说他们认为题目的难度是倒叙的,但是在讲题的时候,这题我还是没怎么听懂。

当时的情况:

    我还是花费了比较多的时间耗在这题上,但是却没有修得正果。我一开始想先算出一共有多少条rp路径,然后再计算每一条rp边经过的次数,最后减掉最多的次数既为正解。

     但是遇到了很多不可实现的地方。首先,要计算有多少条rp路径非常困难,我扫了几次dfs修改了很多次都没有成功。计算经过的次数反倒可能好算一点。越写却发现for循环愈加的多,肯定会超时得很严重。在坚持不懈地调了一个多钟以后,终于决定放弃。

部分分:

    50分传说中的dfs

正解:

    既然要求最大的rp路径,反之为求最小的非rp路径。先算出一开始没有减少rp边时的非rp路径,用并查集很容易得出非rp路径的联通块。


     由小学的数学可以算出,所有的路径的总和为:n(点数)*(n-1)/2。同样的,假设由并查集合并以后的点数为v,那么非rp路径的路径数为v*(v-1)/2。

     由总的路径数减去非rp路径数则是rp路径的数量。可是要怎样求出每一条路径被经过的次数呢?

                                    

     图中实线为rp边,A*B便是经过这条rp边的路径数,因此,再跑一次dfs,遇到一条rp路径便求出相对应的A*B的值,求出最大的,由原来的rp路径数减去即是正解。

    注意要用longlong

补充一个小错误:

    后来我怎么提交都是60分,在lkb的帮助下,发现我重复定义了一个n,真是害惨人呐。


代码如下:

#include
     
     
      
      
#include
      
      
       
       
#include
       
       
        
        
using namespace std;
const int MAXN=100005;
long long n,a,b,c,ma=0,father[MAXN],num[MAXN],nn[MAXN];
bool vv[MAXN];
struct tree
{
	long long to,z;
};
vector
        
        
         
         q[MAXN];
long long finda(long long x)
{
	if(father[x]==x) return x;
	return father[x]=finda(father[x]);
}
void hebin(long long x,long long y)
{
	long long a=finda(x);
	long long b=finda(y);
	if(a!=b) father[a]=b;
}
void dfs(long long now)
{
	long long l=q[now].size();
	for(int i=0;i
         
         
           >n; for(int i=1;i<=n;i++) father[i]=i; for(int i=1;i 
          
            >a>>b>>c; q[a].push_back((tree){b,c}); q[b].push_back((tree){a,c}); if(c==0) hebin(a,b); } long long rp,v=0,zong; zong=n*(n-1)/2; for(int i=1;i<=n;i++) { father[i]=finda(i); num[father[i]]++; } for(int i=1;i<=n;i++) { nn[i]=num[i]*(num[i]-1)/2; zong-=nn[i]; } rp=zong; vv[1]=true; dfs(1); cout< 
           
             < 
             
            
           
         
        
        
       
       
      
      
     
     

反思:

    写完发现,正解和我一开始的思路反倒有异曲同工之妙,但是就差在我不会巧算计算路径数(怀疑小学学了假数学)。并且没有想到用并查集或强连通分量缩点。考试的时候时间没有把握,当调不对这一题再去弄前面两题的时候,所余时间就不多了。

 

小结:

    最后测试的结果很差,用oi模式,第一题暴力没有弄对,最后正好是显示出来的5分;第二题也没有想太多,10分;第三题调代码花费了很长的时间。写代码要仔细,勿焦躁。




评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值