前言
曾老放出了大招,甩了一套2016真题。(不过貌似很久以前做过?(一年以内))
。。。结果还行,前几天恶补的模型起效果了,虽然最后只拿了125分。。最后还是没能上200还是可惜,否则235怎么说底气更足一点。
最搞笑的是这次的难度梯度是:T2>T3>T1。
Day2 (120/300)
T1 玩具谜题【NOIP2016提高组】(100/100)
题目背景
NOIP2016 提高组 Day1 T1
题目描述
小南有一套可爱的玩具小人,它们各有不同的职业。
有一天,这些玩具小人把小南的眼镜藏了起来。小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外。如下图:
这时 singer 告诉小南一个谜题:“眼镜藏在我左数第 3 个玩具小人的右数第 1 个玩具小人的左数第 2 个玩具小人那里。”
小南发现,这个谜题中玩具小人的朝向非常关键,因为朝内和朝外的玩具小人的左右方向是相反的:面朝圈内的玩具小人,它的左边是顺时针方向,右边是逆时针方向;而面向圈外的玩具小人,它的左边是逆时针方向,右边是顺时针方向。
小南一边艰难地辨认着玩具小人,一边数着:
“singer 朝内,左数第 3 个是 archer。
“archer 朝外,右数第 1 个是 thinker。
“thinker 朝外,左数第 2 个是 writer。
“所以眼镜藏在 writer 这里!”
虽然成功找回了眼镜,但小南并没有放心。如果下次有更多的玩具小人藏他的眼镜,或是谜题的长度更长,他可能就无法找到眼镜了。所以小南希望你写程序帮他解决类似的谜题。这样的谜题具体可以描述为:
有 n 个玩具小人围成一圈,已知它们的职业和朝向。现在第 1 个玩具小人告诉小南一个包含 m 条指令的谜题,其中第 i 条指令形如“左数/右数第 si 个玩具小人”。你需要输出依次数完这些指令后,到达的玩具小人的职业。
输入格式
输入的第一行包含两个正整数 n ,m ,表示玩具小人的个数和指令的条数。
接下来 n 行,每行包含一个整数和一个字符串,以逆时针为顺序给出每个玩具小人的朝向和职业。其中 0 表示朝向圈内,1 表示朝向圈外。保证不会出现其他的数。字符串长度不超过 10 且仅由小写字母构成,字符串不为空,并且字符串两两不同。整数和字符串之间用一个空格隔开。
接下来 m 行,其中第 i 行包含两个整数 ai,si ,表示第 i 条指令。若 ai = 0 ,表示向 左数 si 个人;若 ai = 1 ,表示向右数 si 个人。保证 ai 不会出现其它的数,1 ≤ si < n。
输出格式
输出一个字符串,表示从第一个读入的小人开始,依次数完 m 条指令后到达的小人的职业。
样例数据 1
输入 [复制]
7 3
0 singer
0 reader
0 mengbier
1 thinker
1 archer
0 writer
1 mogician
0 3
1 1
0 2
输出
writer
样例数据 2
输入 [复制]
10 10
1 c
0 r
0 p
1 d
1 e
1 m
1 t
1 y
1 u
0 v
1 7
1 1
1 4
0 5
0 3
0 1
1 6
1 2
0 8
0 4
输出
y
。。个人觉得做起了这道题只能说明学了一学期noip的我还没有智障到连模拟都不会得全分的地步。。
PS: 这张图适合做noip专属表情包
STD.CPP
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
int n,m,dir,cnt,pos=1;
struct node{
int face;
char name[15];
int len;
}man[100005];
int main()
{
cin >> n >>m;
for(int i=1;i<=n;i++)
{
scanf("%d%s",&man[i].face,&man[i].name);
}
for(int i=1;i<=m;i++)
{
cin >> dir >> cnt;
if(dir!=man[pos].face) pos += cnt,pos = (pos==n)?(n):(pos%n);
else pos += n-cnt,pos = (pos==n)?(n):(pos%n);
}
for(int i=0;i<strlen(man[pos].name);i++)
cout << man[pos].name[i];
}
T2 天天爱跑步【NOIP2016提高组】(25/100)
题目背景
NOIP2016 提高组 Day1 T2
题目描述
小 C 同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一一棵包含 n 个结点和 n-1 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从 1 到 n 的连续正整数。
现在有 m 个玩家,第 i 个玩家的起点为 Si ,终点为 Ti 。每天打卡任务开始时,所有玩家在第 0 秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以每个人的路径是唯一的)
小 C 想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点 j 的观察员会选择在第 Wj 秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第 Wj 秒也正好到达了结点 j 。 小 C 想知道每个观察员会观察到多少人?
注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一段时间后再被观察员观察到。 即对于把结点 j 作为终点的玩家: 若他在第 Wj 秒前到达终点,则在结点 j 的观察员不能观察到该玩家;若他正好在第 Wj 秒到达终点,则在结点 j 的观察员可以观察到这个玩家。
输入格式
第一行有两个整数 n 和 m 。其中 n 代表树的结点数量, 同时也是观察员的数量, m 代表玩家的数量。
接下来 n-1 行每行两个整数 u 和 v ,表示结点 u 到结点 v 有一条边。
接下来一行 n 个整数,其中第 j 个整数为 Wj, 表示结点 j 出现观察员的时间。
接下来 m 行,每行两个整数 Si 和 Ti ,表示一个玩家的起点和终点。
对于所有的数据,保证 1≤Si,Ti≤n,0≤Wj≤n 。
输出格式
输出 1 行 n 个整数,第 j 个整数表示结点 j 的观察员可以观察到多少人。
样例数据 1
输入 [复制]
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
输出
2 0 0 1 1 1
样例数据 2
输入 [复制]
5 3
1 2
2 3
2 4
1 5
0 1 0 3 0
3 1
1 4
5 5
输出
1 2 1 0 1
备注
【样例1说明】
对于 1 号点,W1 = 0 ,故只有起点为 1 号点的玩家才会被观察到,所以玩家 1 和玩家 2 被观察到,共有 2 人被观察到。
对于 2 号点,没有玩家在第 2 秒时在此结点,共 0 人被观察到。
对于 3 号点,没有玩家在第 5 秒时在此结点,共 0 人被观察到。
对于 4 号点,玩家 1 被观察到,共 1 人被观察到。
对于 5 号点,玩家 1 被观察到,共 1 人被观察到。
对于 6 号点,玩家 3 被观察到,共 1 人被观察到。
MY.CPP
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
using namespace std;
const int kkk = 300000;
int n,m,final[kkk];
int w[kkk],s,t;
struct node{
int u,v,val,next;
}side[2*kkk];
int cnt=0,first[kkk];
void addedge(int u,int v,int val)
{
cnt += 1;
side[cnt].u = u;
side[cnt].v = v;
side[cnt].val =val;
side[cnt].next = first[u];
first[u] = cnt;
}
int ans[kkk],dis[kkk];
void dfs(int hi,int t)
{
if(dis[hi]==w[hi]) ans[hi]+=1;
if(hi!=t) dfs(side[final[hi]].u,t);
}
bool visit[kkk];
void dij(int k,int t)
{
memset(final,0,sizeof(final));
memset(dis,127,sizeof(dis));
memset(visit,false,sizeof(visit));
priority_queue< pair<int,int> >que;
que.push(make_pair(0,k));
dis[k] = 0;
for(int i=1;i<=n;i++)
{
pair<int,int> s = que.top();
que.pop();
visit[s.second] = true;
for(int u=first[s.second];u;u=side[u].next)
{
int v = side[u].v;
if(!visit[v]&&dis[v]>dis[s.second]+side[u].val)
{
dis[v] = dis[s.second]+side[u].val;
final[v] = u;
que.push(make_pair(-dis[v],v));
}
}
}
dfs(t,k);
}
int main()
{
//freopen("test.in","r",stdin);
cin >> n >> m;
for(int i=1;i<n;i++)
{
int x,y;
cin >> x >> y ;
addedge(x,y,1);
addedge(y,x,1);
}
for(int i=1;i<=n;i++) cin >> w[i];
for(int i=1;i<=m;i++)
{
cin >> s >> t;
dij(s,t);
}
for(int i=1;i<=n;i++) cout << ans[i] << " ";
}
Dij大法好(总之后来我试了一下SPFA好像要遭),暴力过了1/4的数据。
结果这道题是基于LCA做的。。。体现了noip技巧少但不好想的特点。
STD.CPP
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#define N 300010
// test for std ans
using namespace std;
int n,m,tot;
int first[N],jump[N][20],deep[N],w[N],ans[N],num[N<<2];
struct node1{int next,to;};
node1 bian[N<<1];
struct node2{int s,t;};
vector <node2> dian[N];
int insert(int s,int t)
{
bian[++tot].to=t;
bian[tot].next=first[s];
first[s]=tot;
}
int read()
{
int k=0,f=1;char c=getchar();
while(c>'9'||c<'0') {if(c=='-') f=-1; c=getchar();}
while(c>='0'&&c<='9') {k=k*10+(c-'0'); c=getchar();}
return k*f;
}
void dfs(int x)
{
for(int i=1;i<=18;i++)
{
jump[x][i]=jump[jump[x][i-1]][i-1];
if(!jump[x][i]) break;
}
for(int i=first[x];i;i=bian[i].next)
if(bian[i].to!=jump[x][0])
{
deep[bian[i].to]=deep[x]+1;
jump[bian[i].to][0]=x;
dfs(bian[i].to);
}
}
int lca(int a,int b)
{
if(deep[a]<deep[b]) swap(a,b);
int k=deep[a]-deep[b];
for(int i=18;i>=-1;i--)
if(k&(1<<i)) a=jump[a][i];
if(a==b) return a;
for(int i=18;i>=-1;i--)
if(jump[a][i]!=jump[b][i]) {a=jump[a][i];b=jump[b][i];}
return jump[a][0];
}
void work(int x)
{
int last=num[deep[x]+w[x]]+num[w[x]-deep[x]+n*3+1];
int k=dian[x].size();
for(int i=0;i<k;i++)
{
node2 u=dian[x][i];
num[u.s]+=u.t;
}
for(int i=first[x];i;i=bian[i].next)
if(bian[i].to!=jump[x][0]) work(bian[i].to);
ans[x]=num[deep[x]+w[x]]+num[w[x]-deep[x]+n*3+1]-last;
}
int main()
{
////////////////////////////// 以下一串莫名其妙的代码。。。////////////////////////////////
int _q=20<<20;
char *_p=(char*)malloc(_q)+_q;
__asm__("movl %0, %%esp\n"::"r"(_p));
//////////////////////////////////////////////////////////////////////////////////////
int i,j,k,s,t;node2 u;
n=read();m=read();
for(i=1;i<=n-1;i++)
{
s=read();t=read();
insert(s,t);insert(t,s);
}
dfs(1);
for(i=1;i<=n;i++) w[i]=read();
for(i=1;i<=m;i++)
{
s=read();t=read();
k=lca(s,t);
u.s=deep[s];u.t=1;
dian[s].push_back(u);
u.t=-1;
dian[jump[k][0]].push_back(u);
u.s=deep[s]-deep[k]*2+n*3+1;u.t=1;
dian[t].push_back(u);
u.t=-1;
dian[k].push_back(u);
}
work(1);
for(i=1;i<=n;i++) cout << ans[i] << " ";
return 0;
}
T3 换教室【NOIP2016提高组】(0/100)
题目背景
NOIP2016 提高组 Day1 T3
题目描述
对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程。
在可以选择的课程中,有 2n 节课程安排在 n 个时间段上。在第 i(1≤i≤n)个时间段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教室 ci 上课,而另一节课程在教室 di 进行。
在不提交任何申请的情况下,学生们需要按时间段的顺序依次完成所有的 n 节安排好的课程。如果学生想更换第 i 节课程的教室,则需要提出申请。若申请通过,学生就可以在第 i 个时间段去教室 di 上课,否则仍然在教室 ci 上课。
由于更换教室的需求太多,申请不一定能获得通过。通过计算,牛牛发现申请更换第 i 节课程的教室时,申请被通过的概率是一个已知的实数 ki ,并且对于不同课程的申请,被通过的概率是互相独立的。
学校规定,所有的申请只能在学期开始前一次性提交,并且每个人只能选择至多 m 节课程进行申请。这意味着牛牛必须一次性决定是否申请更换每节课的教室,而不能根据某些课程的申请结果来决定其他课程是否申请;牛牛可以申请自己最希望更换教室的 m 门课程,也可以不用完这 m 个申请的机会,甚至可以一门课程都不申请。
因为不同的课程可能会被安排在不同的教室进行,所以牛牛需要利用课间时间从一间教室赶到另一间教室。
牛牛所在的大学有 v 个教室,有 e 条道路。每条道路连接两间教室,并且是可以双向通行的。由于道路的长度和拥堵程度不同,通过不同的道路耗费的体力可能会有所不同。当第 i(1≤i≤n-1)节课结束后,牛牛就会从这节课的教室出发,选择一条耗费体力最少的路径前往下一节课的教室。
现在牛牛想知道,申请哪几门课程可以使他因在教室间移动耗费的体力值的总和的期望值最小,请你帮他求出这个最小值。
输入格式
第一行四个整数 n,m,v,e 。n 表示这个学期内的时间段的数量;m 表示牛牛最多可以申请更换多少节课程的教室;v 表示牛牛学校里教室的数量;e 表示牛牛的学校里道路的数量。
第二行 n 个正整数,第 i(1≤i≤n)个正整数表示 ci ,即第 i 个时间段牛牛被安排上课的教室;保证 1≤ci≤v。
第三行 n 个正整数,第 i(1≤i≤n)个正整数表示 di ,即第 i 个时间段另一间上同样课程的教室;保证 1≤di≤v。
第四行 n 个实数,第 i(1≤i≤n)个实数表示 ki ,即牛牛申请在第 i 个时间段更换教室获得通过的概率。保证 0≤ki≤1。
接下来 e 行,每行三个正整数 aj ,bj ,wj ,表示有一条双向道路连接教室 aj ,bj ,通过这条道路需要耗费的体力值是 wj;保证 1≤aj,bj≤v,1≤wj≤100
保证 1≤n≤2000,0≤m≤2000,1≤v≤300,0≤e≤90000。
保证通过学校里的道路,从任何一间教室出发,都能到达其他所有的教室。
保证输入的实数最多包含 3 位小数。
输出格式
输出一行,包含一个实数,四舍五入精确到小数点后恰好 2 位,表示答案。你的输出必须和标准输出完全一样才算正确。
测试数据保证四舍五入后的答案和准确答案的差的绝对值不大于 4×10-3。(如果你不知道什么是浮点误差,这段话可以理解为:对于大多数的算法,你可以正常地使用浮点数类型而不用对它进行特殊的处理)
样例数据 1
输入 [复制]
3 2 3 3
2 1 2
1 2 1
0.8 0.2 0.5
1 2 5
1 3 3
2 3 1
输出
2.80
备注
1.道路中可能会有多条双向道路连接相同的两间教室。也有可能有道路两端连接的是同一间教室。
2.请注意区分 n,m,v,e 的意义,n 不是教室的数量,m 不是道路的数量
基于对动态规划并不很了解以及对于概率问题(期望)的畏惧我就没做,后来有个人只输出了没换的情况竟然得了25分也是服气。。。
STD.CPP
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<string>
#include<algorithm>
#include<ctime>
using namespace std;
const int inf = 99999999;
const int kkk = 2005;
int n,m,v,e;
double k[kkk],dp[kkk][kkk][2];
int c[kkk],d[kkk],f[305][305];
int main()
{
cin >> n >> m >> v >> e;
for(int i=1;i<=n;i++)cin >> c[i];
for(int i=1;i<=n;i++)cin >> d[i];
for(int i=1;i<=n;i++)cin >> k[i];
for(int i=1;i<=v;i++)
for(int j=1;j<=v;j++)
if(i!=j)f[i][j] = inf;
for(int i=1;i<=e;i++)
{
int a,b,w;
cin >> a >> b >> w;
f[a][b] = min(f[a][b],w);
f[b][a] = min(f[b][a],w);
}
for(int a=1;a<=v;a++)
for(int i=1;i<=v;i++)
for(int j=1;j<=v;j++)
f[i][j] = min(f[i][j],f[i][a]+f[a][j]);
///////////////////////////////////
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
dp[i][j][0]=dp[i][j][1]=inf;
dp[1][1][1]=dp[1][0][0]=0;
///////////////////////////////////
for(int i=1;i<n;i++)
for(int j=0;j<=m;j++)
{
dp[i+1][j][0] = min(dp[i][j][0] + f[c[i]][c[i+1]],
dp[i][j][1] + k[i]*f[d[i]][c[i+1]] + (1.0-k[i])*f[c[i]][c[i+1]]);
dp[i+1][j+1][1] = min(dp[i][j][0] + k[i+1]*f[c[i]][d[i+1]] + (1.0-k[i+1])*f[c[i]][c[i+1]],
dp[i][j][1] + k[i]*k[i+1]*f[d[i]][d[i+1]] + k[i]*(1.0-k[i+1])*f[d[i]][c[i+1]] + (1.0-k[i])*k[i+1]*f[c[i]][d[i+1]] + (1.0-k[i])*(1.0-k[i+1])*f[c[i]][c[i+1]]);
}
double ans=inf;
for(int i=0;i<=m;i++)
ans = min(ans,min(dp[n][i][0],dp[n][i][1]));
printf("%.2f",ans);
}
这道题的dp只是略长一点罢了。
这是一次测验吗?
当然不是,这只是一次试验NOIP的难度而已,况且这套题可是有人做过原题的,so。。。
我还要煎熬一天才能测完orz。