6月28日考试 题解(GCD约分+动态规划+树状数组二维偏序)
前言:考的一般般吧……T3暴力没打上来挺可惜的,到手的75分没了。
----------------------------------
T1 【JZOJ4745】看电影
Description
听说NOIP2016大家都考得不错,于是CCF奖励省常中了 K 张变形金刚5的电影票奖励OI队的同学去看电影。可是省常中OI队的同学们共有 N(N >= K)人。于是机智的你想到了一个公平公正的方法决定哪K人去看电影。
N个人排成一圈,按顺时针顺序标号为1 - N,每次随机一个还存活的人的编号,将这个人踢出。继续上述操作,直到剩下K个人。
但这样显然太无聊了,于是小S又想出一个牛逼的方法。
N个人排成一圈,按顺时针顺序标号为1 - N,每次随机一个1 - N的编号,假设随机到的编号是X,如果编号为X人还未踢出,则将这个人踢出,否则看编号为X % N + 1(即顺时针顺序下一个编号)的人是否存活,如果还未踢出则将他踢出,否则继续看编号(X + 1)% N +1的人,如果已被踢出看顺时针的下一个…………,以此类推,直到踢出一个人为止。重复上述操作,直到剩下K个人。
已知小S的编号是Id,问按照小S的方法来他有多少的概率可以不被踢出,成功得到看电影的机会。
Input
第一行包括三个正整数,N,K,Id(1<=K<=N<=10^9,1<=ID<=N )
Output
一行一个最简分数,表示小S可以看到电影的概率。
(如果概率为1或0,请输出1/1或0/1)
---------------------------
因为在环上每个人的位置和被选中的概率是相同的。所以所有人能看电影的概率和为K。即一个人看电影的概率是K/N。约分后输出即可。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n,m,k,ld; int gcd(int a,int b) { if(b==0)return a; return gcd(b,a%b); } int main() { scanf("%d%d%d",&n,&m,&ld); k=gcd(n,m); printf("%d/%d",m/k,n/k); return 0; }
T2 【JZOJ4746】树塔狂想曲
相信大家都在长训班学过树塔问题,题目很简单求最大化一个三角形数塔从上往下走的路径和。走的规则是:(i,j)号点只能走向(i+1,j)或者(i+1,j+1)。如下图是一个数塔,映射到该数塔上行走的规则为:从左上角的点开始,向下走或向右下走直到最底层结束。
1
3 8
2 5 0
1 4 3 8
1 4 2 5 0
路径最大和是1+8+5+4+4 = 22,1+8+5+3+5 = 22或者1+8+0+8+5 = 22。
小S觉得这个问题so easy。于是他提高了点难度,他每次ban掉一个点,然后询问你不走该点的最大路径和。
当然他上一个询问被ban掉的点过一个询问会恢复(即每次他在原图的基础上ban掉一个点,而不是永久化的修改)。
n<=1000 m<=50W
----------------------------------
看完题目后不难想到要维护最大值和次大值。我们不妨从上往下DP一次,从下往上DP一次,来维护$(i,j)$的最大值。
写法就是$tot[i][j]=\max(f[i-1][j-1],f[i-1][j])+\max(ff[i+1][j],ff[i+1][j+1])+a[i][j]$
最大值和次大值再遍历一遍就可以求出来了。
#include<bits/stdc++.h> using namespace std; int f[1005][1005],ff[1005][1005],sum[1005],ans[1005],tot[1005][1005],second[1005]; int n,m,a[1005][1005]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } void dp() { f[1][1]=a[1][1]; for (int i=1;i<=n;i++) for (int j=1;j<=i;j++) f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j]; for (int i=n;i>=1;i--) for (int j=i;j>=1;j--) ff[i][j]=max(ff[i+1][j],ff[i+1][j+1])+a[i][j]; for (int i=1;i<=n;i++) for (int j=1;j<=i;j++) tot[i][j]=max(f[i-1][j-1],f[i-1][j])+max(ff[i+1][j],ff[i+1][j+1])+a[i][j]; for (int i=1;i<=n;i++) for (int j=1;j<=i;j++) if (tot[i][j]>ans[i]) ans[i]=tot[i][j],sum[i]=j; for (int i=1;i<=n;i++) for (int j=1;j<=i;j++) if (j!=sum[i]) if (tot[i][j]>second[i]) second[i]=tot[i][j]; } int main() { n=read(),m=read(); for (int i=1;i<=n;i++) for (int j=1;j<=i;j++) a[i][j]=read(); dp(); for (int i=1;i<=m;i++) { int x=read(),y=read(); if (x==1&&y==1){ cout<<-1<<endl; continue; } if (ans[x]==tot[x][y]) cout<<second[x]<<endl; else cout<<ans[x]<<endl; } return 0; }
T3 【JZOJ4747】被粉碎的线段树
--------------------------------
我是没有看懂正解……贴一个正解连接吧:https://blog.csdn.net/dianning8393/article/details/101620538
不难想到75分的做法:同样使用线段树,只不过把每个区间的mid改为他给出的mid即可。区间权值设为1。注意要使用动态开点,如果使用2倍编号类型的线段树会挂25分,因为他给出的树不是完全二叉树。
貌似再加一些限制语句就可以到85分了,不过我没试过。
#include<bits/stdc++.h> using namespace std; struct node { int l,r,sum,lc,rc; }tree[4000005]; int n,m,a[1000005],cnt,cut[4000005],tot; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void build(int index,int l,int r) { tree[index].l=l;tree[index].r=r; if (l==r){ tree[index].sum=1; return; } int mid=a[++cnt];cut[index]=mid; if (!tree[index].lc) tree[index].lc=++tot,build(tree[index].lc,l,mid); if (!tree[index].rc) tree[index].rc=++tot,build(tree[index].rc,mid+1,r); tree[index].sum=1; } inline int query(int index,int l,int r) { if (l<=tree[index].l&&tree[index].r<=r) return tree[index].sum; int mid=cut[index],res=0; if (l<=mid) res+=query(tree[index].lc,l,r); if (r>mid) res+=query(tree[index].rc,l,r); return res; } int main() { n=read(),m=read(); for (register int i=1;i<n;i++) a[i]=read(); tot=1; build(1,1,n); for (register int i=1;i<=m;i++) { int x=read(),y=read(); printf("%d\n",query(1,x,y)); } return 0; }