NOIP2017赛前模拟1:总结
题目:
1.造盒子
题目描述
企鹅豆豆收到了面积为 K 的一块橡皮泥。但是他没有合适的盒子来装下这个橡皮泥。所以他打算造一个盒子。
制造台是有方形网格的平台,每个小正方形边长为 1 。现在豆豆有两类木板,一类只能放在小正方形的边上,一类只能放在小正方形的对角线上。
现在豆豆想知道最少需要用多少块木板来制造一个封闭的盒子来把橡皮泥放下去。
输入格式
第一行一个整数 T 表示数据组数。
对于每组数据的第一行一个整数 K ,表示橡皮泥的大小。
输出格式
输出一个整数表示最少需要的木板数目
样例数据 1
备注
【数据范围】
对于 50% 的数据,K≤20。
对于 100% 的数据,K≤109。
2.分玩具
题目描述
豆豆和豆沙正在分一些玩具,每个玩具有一个好玩值,每个人可以拿走任意数量的玩具,获得的愉快度为最小的好玩值。现在豆豆先拿,每个人轮流操作,直到没有玩具可以拿。豆豆想知道他能比豆沙多出多少愉快度?
输入格式
第一行 N 表示玩具个数。
接下来一行 N 个整数表示第 i 个玩具的好玩值。
输出格式
输出一个整数表示最多多出的愉快度。
样例数据 1
备注
【数据范围】
对于 30% 的数据,N≤10。
对于 70% 的数据,N≤1000。
对于 100 %的数据,N≤1000000,0≤数值范围≤109。
3.problem
题目描述
今天豆豆在做作业的时候遇到这么一个问题:
给出 N 个正整数 a1..aN ,再给出一个正整数 k ,现在可以进行如下操作:每次选择一个大于 k 的正整数 ai,将 ai 减去 1 ,选择 a[i-1] 或 a[i+1] 中的一个加上 1 。经过一定次数的操作后,最大能够选出多长的一个连续子序列,使得这个子序列的每个数都不小于 k 。
总共有 M 次询问,每次询问给出的一个 k ,回答这个询问。
输入格式
第一行两个整数 N , m 代表数组长度和询问个数。
接下来一行,第 i 个正整数表示 ai 。
第三行 M 个正整数,第 i 个正整数表示第 i 次询问的 k 。
输出格式
对于每个询问,输出一个整数表示问题的答案。
样例数据 1
备注
对于 20% 的数据,N≤10。
对于 40% 的数据,N≤1000。
对于 50% 的数据,N≤100000。
对于 100% 的数据,N≤1000000,M≤20,ai≤109,k≤109。
请选手注意自己的常数。
题解:
1.找规律
首先找到最‘划算’的填补方式·····当面积等于k*k*2时··无疑是斜着的正方形最优··然后沿着四条边一次填补·····于是规律就可以找到了··
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cctype> #include<cstring> #include<string> #include<algorithm> using namespace std; int a,t; int ans[21]={0,4,4,6,6,7,8,8,8,9,10,10,10,11,11,12,12,12,12,13,13}; inline int R() { char c;int f=0; for(c=getchar();c<'0'||c>'9';c=getchar()); for(;c<='9'&&c>='0';c=getchar()) f=(f<<3)+(f<<1)+c-'0'; return f; } int main() { t=R(); while(t--) { a=R(); if(a<=20) cout<<ans[a]<<endl; else { int k=(int)sqrt(a/2); if(k*k*2==a) cout<<k*4<<endl; else if(a>k*k*2&&a<=(2*k*k+(k-1))) cout<<k*4+1<<endl; else if(a>(2*k*k+k-1)&&a<=(2*k*k+2*k)) cout<<k*4+2<<endl; else if(a>(2*k*k+2*k)&&a<=(2*k*k+3*k)) cout<<k*4+3<<endl; else if(a>(2*k*k+3*k)&&a<=(2*k*k+4*k+1)) cout<<k*4+4<<endl; } } return 0; }
2.dp
先将玩具从大到小排序然后离散化·····用dp[i]表示先手选择i-n范围内的玩具时最多多出的好玩度·····得出dp方程:
dp[i]=max(maxx,num[i]-dp[i-1])
其中maxx是max(dp[i-1——n])(因为两个人都很聪明··肯定会选最大值····)
md这个dp方程太巧妙了···
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cctype> #include<cstring> #include<string> #include<algorithm> using namespace std; const int N=1e6+5; int ans,maxx,n,dp[N],num[N]; inline int R() { char c;int f=0; for(c=getchar();c<'0'||c>'9';c=getchar()); for(;c<='9'&&c>='0';c=getchar()) f=(f<<3)+(f<<1)+c-'0'; return f; } bool cmp(int a,int b) { return a>b; } int main() { n=R(); for(int i=1;i<=n;i++) num[i]=R(); sort(num+1,num+n+1,cmp); n=unique(num+1,num+n+1)-num-1; dp[n]=num[n]; for(int i=n;i>=1;i--) dp[i]=maxx=max(maxx,num[i]-dp[i+1]); cout<<dp[1]<<endl; return 0; }
3.单调栈
这道题就是找最长的一段平均值大于k的长度·····
为了消除k的影响,我们将每个数减去k··这样就是找最长的一段平均值大于0的长度····
然后计算前缀和···我们对于sum[i],我们要找到sum[1-i-1]中小于sum[i]且最靠近左边的j用i-j更新答案···
因此我们一边从1到n枚举i,一边维护一个sum递减的单调队栈···
然后记录一个指针tail,如果sum[i]>sum[tail],就一直将指针向左移动找到边界更新答案··反之就往左移···
这样由于每次往右移动最多一次(保证答案最优)···而往左移动的次数是小于等于往右移动的次数的···所以每次询问的复杂度为O(n)·······
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cctype> #include<cstring> #include<string> #include<algorithm> using namespace std; const int N=1e6+5; long long num[N],sum[N]; int n,m,k,tot,tail,ans,stack[N]; inline int R() { char c;int f=0; for(c=getchar();c<'0'||c>'9';c=getchar()); for(;c<='9'&&c>='0';c=getchar()) f=(f<<3)+(f<<1)+c-'0'; return f; } int main() { //freopen("a.in","r",stdin); n=R();m=R(); for(int i=1;i<=n;i++) num[i]=R(),num[i]+=num[i-1]; while(m--) { k=R();ans=0;tot=0; for(int i=1;i<=n;i++) sum[i]=num[i]-(long long)k*i; for(int i=1;i<=n;i++) { if(sum[i]>=0) ans=max(ans,i); if(!tot) stack[++tot]=i,tail=tot; else if(sum[i]<sum[stack[tot]]) { stack[++tot]=i,tail=tot; if(sum[i]-sum[i-1]>=0) ans=max(ans,1); } else { if(sum[stack[tail]]>sum[i]) { if(tail+1<=tot) tail++; if(sum[stack[tail]]<=sum[i]) ans=max(ans,i-stack[tail]); } else { ans=max(ans,i-stack[tail]); while(sum[stack[tail-1]]<=sum[i]&&tail-1>0) tail--; ans=max(ans,i-stack[tail]); } } } cout<<ans<<" "; } return 0; }