据说今天都是水题,真的是这样吗?
T1:
长路
题目描述
一天,小Vasya发现自己在一个有(n+1)个房间的迷宫里。刚开始,Vasya在第一个房间,为了走出迷宫,他必须到达第(n+1)个房间。
迷宫的构造是这样的:每个房间有两个单向传送门。考虑第i个房间,我们可以使用第一个传送门前往第i+1个房间,也可以使用第二个传送门前往第pi个房间,这里 1≤pi≤i。
为了防止迷路,Vasya决定按以下方式走出迷宫:
每次进入某房间,就在天花板上画一个X。刚开始,Vasya在第一个房间画一个X。
假设Vasya在第i个房间,而且已经画了X。然后,如果天花板上有奇数个X,Vasya会使用第二个传送门,否则使用第一个。
帮助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~N的N个基地构成。编号相临的两个基地之间由地道相连接,而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);
第2行N-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路
题目描述
小X对RP十分有研究,现在他向你提出了一个问题。给出一棵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分;第三题调代码花费了很长的时间。写代码要仔细,勿焦躁。