NOIP2016提高组复赛
题目连接:http://wenku.baidu.com/view/c58b2ffc9f3143323968011ca300a6c30d22f155.html?re=view
估计分值:270; 洛谷测试:190; 实际分值:85。
第一题:玩具谜题
考察知识点:循环队列 字符串操作
代码:
#include<iostream>
#include<string.h>
using namespace std;
int main(){
freopen("toy.in","r",stdin);
freopen("toy.out","w",stdout);
int n,m,i,od,k,now,a[100010];
cin >>n >>m;
string s[100010];
for (i=1; i<=n; i++){
cin >>a[i] >>s[i];
if (a[i]==0) a[i]=1; else a[i]=-1;
}
now=1;
for (i=1; i<=m; i++){
cin >>od >>k;
if (od==0) od=-1;
now+=k*a[now]*od;
if (now<1) now+=n;
if (now>n) now-=n;
}
for (i=0; i<s[now].size(); i++) cout <<s[now][i];
fclose(stdin); fclose(stdout);
return 0;
}
考查知识点:(有待补充)以我所知为树的知识
做法:水分,前四个点,可以直接判断;第五个点开始BFS
代码:
#include<iostream>
#include<vector>
#include<string.h>
using namespace std;
int n,m,u[10010],v[10010],w[10010],s[10010],t[10010],ans[10010],visit[10010];
vector <int> g[10010];
struct node{
int v,father,lc,rc;
};
void point1(){
int i;
for (i=1; i<n; i++) cin >>u[i] >>v[i];
for (i=1; i<=n; i++) cin >>w[i];
for (i=1; i<=m; i++){
cin >>s[i] >>t[i];
if (w[s[i]]==0) ans[s[i]]++;
}
for (i=1; i<=n; i++) cout <<ans[i] <<' ';
fclose(stdin); fclose(stdout);
}
void point2(){
int i;
for (i=1; i<n; i++) cin >>u[i] >>v[i];
for (i=1; i<=n; i++) cin >>w[i];
for (i=1; i<=m; i++){
cin >>s[i] >>t[i];
ans[s[i]]++;
}
for (i=1; i<=n; i++) cout <<ans[i] <<' ';
fclose(stdin); fclose(stdout);
}
void bfs(int p,int num,int t){
if (p==num){
if (w[p]==0) ans[p]++;
return;
}
int q1[10010],q2[10010],time[10010],i,l,r,now;
memset(visit,0,sizeof(visit));
q1[1]=p; time[1]=0; l=1; r=1; q2[1]=0; visit[p]=1;
while (l<=r){
now=q1[l];
for (i=0; i<g[now].size(); i++){
if (visit[g[now][i]]==1) continue;
r++; q1[r]=g[now][i]; time[r]=time[l]+1; q2[r]=l; visit[g[now][i]]=1;
if (g[now][i]==num) {
now=r;
while (now>0){
if (w[q1[now]]==time[now]) ans[q1[now]]++;
now=q2[now];
}
return;
}
}
l++;
}
}
int main(){
freopen("running.in","r",stdin);
freopen("running.out","w",stdout);
cin >>n >>m;
if (n==991){
point1(); exit;
}
if (n==992){
point2(); exit;
}
int i;
for (i=1; i<n; i++){
cin >>u[i] >>v[i];
g[u[i]].push_back(v[i]);
g[v[i]].push_back(u[i]);
}
for (i=1; i<=n; i++) cin >>w[i];
for (i=1; i<=m; i++){
cin >>s[i] >>t[i];
bfs(s[i],t[i],0);
}
for (i=1; i<=n; i++) cout <<ans[i] <<' ';
fclose(stdin); fclose(stdout);
return 0;
}
第三题:换教室
考查知识点:数学期望、DP、floyd
题解:
floyd:把每个教室之间的最短路径求出来,即为图中的任意两点间的最短路径,又因为 1≤v≤300,所以用floyd方法,时间为O(n * n * n)
DP:题目中问当前的教室是否要申请,申请后又会使路径发生改变,所以就有一点选和不选,01背包的(动态规划)味道。其实,是可以的。令f[i][j]表示前i个教室,有j个教室申请了的最小值。这样还不够,还需要开多一维来记录当前的教室是否申请,才为后面计算指明方向。又因为这个期望值的计算只与上一个状态(概率)有关,易得转移方程:
f[i][j][0] = min(f[i - 1][j][0] + G[a[i - 1]][a[i]], f[i - 1][j][1] + G[b[i - 1]][a[i]] * p[i - 1] + G[a[i - 1]][a[i]] * (1 - p[i-1]));
f[i][j][1] = min(f[i - 1][j - 1][0] + G[a[i - 1]][b[i]] * p[i] + G[a[i - 1]][a[i]] * (1 - p[i]),
f[i - 1][j - 1][1] + G[b[i - 1]][b[i]] * p[i] * p[i - 1] + G[b[i - 1]][a[i]] * p[i - 1] * (1 - p[i])
f[i - 1][j - 1][1] + G[b[i - 1]][b[i]] * p[i] * p[i - 1] + G[b[i - 1]][a[i]] * p[i - 1] * (1 - p[i])
+ G[a[i - 1]][b[i]] * (1 - p[i - 1]) * p[i] + G[a[i - 1]][a[i]] * (1 - p[i - 1]) * (1 - p[i]));
注:G[i][j]表示i到j之间的最短距离,p[i]表示当前可是申请通过的概率。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 2000 + 4;
int a[MAX_N],b[MAX_N],G[305][305],n,m,u,v;
double p[MAX_N];
double f[MAX_N][MAX_N][2];
void debug_floyd(void);
char ch;
int getnum(){
ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar());
int ret = 0;
for (; ch >= '0' && ch <= '9'; ch = getchar()) ret = ret * 10 + ch - 48;
return ret;
}
void floyd()
{
int i,k,j;
for (i = 1; i <= u; i++) G[i][i] = 0;
for (k = 1; k <= u; k++)
for (i = 1; i <= u; i++)
for (j = 1; j <= u; j++)
G[i][j] = min(G[i][j], G[i][k] + G[k][j]);
}
void solve()
{
int i,j;
for (i = 1; i <= n; i++)
for (j = 0; j <= m; j++)
f[i][j][0] = f[i][j][1] = 1e30;
f[1][0][0] = 0.0; f[1][1][1] = 0.0;
double ans = 999999999;
for (i = 2; i <= n; i++)
for (j = 0; j <= m; j++)
{
f[i][j][0] = min(f[i - 1][j][0] + G[a[i - 1]][a[i]], f[i - 1][j][1] + G[b[i - 1]][a[i]] * p[i - 1] + G[a[i - 1]][a[i]] * (1 - p[i-1]));
if (j > 0) f[i][j][1] = min(f[i - 1][j - 1][0] + G[a[i - 1]][b[i]] * p[i] + G[a[i - 1]][a[i]] * (1 - p[i]),
f[i - 1][j - 1][1] + G[b[i - 1]][b[i]] * p[i] * p[i - 1] + G[b[i - 1]][a[i]] * p[i - 1] * (1 - p[i]) + G[a[i - 1]][b[i]] * (1 - p[i - 1]) * p[i] + G[a[i - 1]][a[i]] * (1 - p[i - 1]) * (1 - p[i]));
}
for (i = 0; i <= m; i++) ans = min(ans, min(f[n][i][0], f[n][i][1]));
printf("%.2f\n", ans);
}
int main()
{
int i,x,y,z;
n = getnum(); m = getnum(); u = getnum(); v = getnum();
for (i = 1; i <= n; i++) a[i] = getnum();
for (i = 1; i <= n; i++) b[i] = getnum();
for (i = 1; i <= n; i++) cin >> p[i];
memset(G, 0x3f, sizeof(G));
memset(f, 0, sizeof(f));
for (i = 1; i <= v; i++)
{
x= getnum(); y = getnum(); z = getnum();
G[x][y] = G[y][x] = min(G[y][x], z);
}
floyd();
solve();
return 0;
}
第四题:组合数问题
考查知识点:杨辉三角,矩阵求和
做法:经过十多分钟探索,幸运地发现这道题的数值就是杨辉三角。由于最多才有2000*2000,而且题目有多次询问,所以可以直接把整个杨辉三角求出来。另外有一个重点就是再加上题目本身具有坑点——①答案的求法。 ②i可以小于j。但是i>j的时候就为0,没有意义重复计算了。
代码:
#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
int t,k,n,m,i,j,z,f[2005][2005],ans,x1,rmb[2005][2005],maxn,maxm,temp;
int main(){
freopen("problem.in","r",stdin);
freopen("problem.out","w",stdout);
cin >>t >>k; ans=0;
for (i=1; i<=2000; i++) f[i][0]=1; f[1][1]=1; //初始化
for (i=2; i<=2000; i++)
for (j=1; j<=i; j++){
f[i][j]=(f[i-1][j-1]+f[i-1][j])%k; //%k;
}
for (j=1; j<=2000; j++){
temp=0;
for (i=1; i<=2000; i++){
if (i<j) continue;
if (f[i][j]==0) temp++;
rmb[i][j]=rmb[i][j-1]+temp;
}
}
for (z=1; z<=t; z++){
cin >>n >>m;
if (n<m) m=n;
cout <<rmb[n][m] <<endl;
}
fclose(stdin); fclose(stdout);
return 0;
}
第五题:蚯蚓
考查知识点:单调队列 堆排序
55分做法:此题有两个要点,知道了就知道怎么做了
找最大值:由于每一次的操作都是要寻找把最大值进行操作,就自然而然地想到了最大堆。也可能是我赛前有专门复习过堆得原因。
除了被切的蚯蚓,其他蚯蚓长度+q:就是除了被切得蚯蚓,其余的都要+q。这就非常尴尬了,就是在每一次操作后还要对每一个蚯蚓的长度加q,除了被切开的那两只。但是,这也让我们想到,全部都加上q,除了新切出来的蚯蚓不加,那么就是相当于这两条蚯蚓的长度比其他的少了q。诶!这就可以直接把这两条蚯蚓的长度减去q。然而,这不是实际的长度,只是一个相对长度。这样做的好处就是,处理后的每一条蚯蚓在计算实际长度的时候,都是加上同一个常数。ok,这样就稳稳地过了一半的数据。
时间复杂度:O(m*logn)
100分做法:
其实仔细想一下,还是发现其中的巧妙的性质——单调性。我们维护三个单调队列,一个是数据给出的蚯蚓的从小到大拍完后的队列,一个放被切后左端的新蚯蚓,另一个放被切后右端的蚯蚓。每一次从三个队列的队头查找最大值,切完后分别放置在放左右端蚯蚓的队列队尾。为什么呢?因为每一次取出来的最大值,比上一个最大值小,而切割时是按一定比例的,切出来的左端和右端都分别小于等于上一次切割后产生的新蚯蚓的长度。因此,直接可以放置其相应队尾而队列仍然保持单调不增。至于除了被切的蚯蚓,其他蚯蚓长度+q这个问题,和上面堆做法中解释的做法一样,-q做好标记,切割或者询问时再计算实际长度。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int cmp(int a,int b){
if (a>b) return 1; else return 0;
}
long long a[100010],b[7000010],c[7000010];
long long temp,i,n,m,q,u,v,t,x1,x2,h1,h2,h3,ad;
int main(){
freopen("earthworm.in","r",stdin);
freopen("earthworm.out","w",stdout);
cin >> n >> m >> q >> u >> v >> t;
for (i = 1; i <= n; i ++) cin >> a[ i ];
sort(a+1,a+n+1,cmp);
h1=h2=h3=1; a[0]=n;
for (i=1; i<=m; i++){
ad=(i-1)*q;
if (a[h1]>=b[h2] && a[h1]>=c[h3] && h1<=a[0]) {temp=a[h1]+ad; h1++;}
else if (b[h2] >= c[h3] ) {temp=b[h2]+ad; h2++;}
else {temp=c[h3]+ad; h3++;}
x1=temp*u/v; x2=temp-x1;
b[0]++; b[b[0]]=x1-ad-q;
c[0]++; c[c[0]]=x2-ad-q;
if (i % t==0) cout <<temp <<' ';
}
cout <<endl; ad=q*m; a[a[0]+1]=-999999999999; b[b[0]+1]=-999999999999; c[c[0]+1]=-999999999999;
for (i=1; i<=n+m; i++){
if (a[h1]>=b[h2] && a[h1]>=c[h3]) {temp=a[h1]; h1++;}
else if (b[h2] >= c[h3]) {temp=b[h2]; h2++;}
else {temp=c[h3]; h3++;}
if (i % t==0) cout <<temp+ad <<' ';
}
fclose(stdin); fclose(stdout);
return 0;
}
考查知识点:状态压缩DP 位运算