NOIP模拟21题解
来源:互联网 发布:高中毕业旅行知乎 编辑:程序博客网 时间:2024/04/29 11:19
Contents
- Contents
- 六元组
- Description
- Input
- Output
- 数据范围与约定
- Solution
- 牛排序
- Description
- Input
- Output
- 样例解释
- Solution
- Step1
- Step2
- Step3
- Step4
- 打砖块
- Description
- Input
- Output
- 数据范围与约定
- Solution
- 六元组
1.六元组
(six.c/.cpp/.pas)
Description
有n个整数,现在想知道有多少个六元组(a,b,c,d,e,f)
满足:(a × b + c) ÷ d – e = f
Input
输入文件名为(six.in)。
第一行:n(1<=n<=10)
第二行n个数ai(-30000<=ai<=30000)
Output
输出文件名为(six.out)。
输出这样的六元组的个数
2
2 3 4
数据范围与约定
对于30%的数据,1 <= n <= 10
对于100%的数据,1 <= n <= 100
Solution:
刚开始以为题意是:给定的ai就是a,所以要找出满足所有ai的b,c,d,e,f
鬼能做出来啊。。
最后才理清了题意,a,b,c,d,e,f就是n个数的任意一个,只要满足要求就好。这样就变得十分简单了。但是看数据范围就可以知道,六维的枚举只能得30分,而要想得100分就必须压到至多三维。但这样也并不麻烦,化简可以得到
a*b+c=d*(e+f)这样,就可以同时枚举三个数a[i],a[j],a[k]。假如把等式左边的数放到数组l[],右边放到r[]中,如果l[]中的一个数在r[]中出现过,累加次数就是最后的答案了。
但是需要注意的是,不能直接枚举,这样会TLE,可以用STL中的map或者multiset,但事实证明后者不可行。。同样超时。下面是两种方法的代码:
Code1【map】:
#include <stdio.h>#include <string.h>#include <map>#define MAXN 1001000typedef long long ll;using namespace std;int n,ans;int a[110],l[MAXN],r[MAXN];map<int,int>mp;int main(){ freopen("six.in","r",stdin); freopen("six.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { for(int k=1;k<=n;k++) { mp[a[i]*a[j]+a[k]]++; } } } for(int i=1;i<=n;i++) { if(!a[i]) continue; for(int j=1;j<=n;j++) { for(int k=1;k<=n;k++) { int x=a[i]*(a[j]+a[k]); ans+=mp[x]; } } } printf("%d\n",ans); return 0;}
Code2:【multiset】
#include <stdio.h>#include <string.h>#include <set>#define MAXN 1001000typedef long long ll;using namespace std;ll n,ans;ll a[110],l[MAXN],r[MAXN];multiset<int>s;int main(){ freopen("six.in","r",stdin); freopen("six.out","w",stdout); scanf("%lld",&n); for(ll i=1;i<=n;i++) { scanf("%lld",&a[i]); } ll cnt_l=0,cnt_r=0; for(ll i=1;i<=n;i++) { for(ll j=1;j<=n;j++) { for(ll k=1;k<=n;k++) { s.insert(a[i]*a[j]+a[k]); } } } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { for(int k=1;k<=n;k++) { ll x=a[i]*(a[j]+a[k]); if(s.count(x)!=0) { ans+=s.count(x); } } } } printf("%lld\n",ans); return 0;}
2.牛排序
(cowsort.c/.cpp/.pas)
Description
农夫JOHN准备把他的 N(1 <= N <= 10,000)头牛排队以便于行动。因为脾气大的牛有可能会捣乱,JOHN想把牛按脾气的大小排序。每一头牛的脾气都是一个在1到100,000之间的整数并且没有两头牛的脾气值相同。在排序过程中,JOHN可以交换任意两头牛的位置。因为脾气大的牛不好移动,JOHN需要X+Y秒来交换脾气值为X和Y的两头牛。
请帮JOHN计算把所有牛排好序的最短时间。
Input
输入文件名为(cowsort.in)。
第1行: 一个数N。
第2~N+1行: 每行一个数,第i+1行是第i头牛的脾气值。
Output
输出文件名为(cowsort.out)。
第1行: 一个数,把所有牛排好序的最短时间。
3
2
3
1 7
样例解释
队列里有三头牛,脾气分别为 2,3, 1。
2 3 1 : 初始序列
2 1 3 : 交换脾气为3和1的牛(时间=1+3=4).
1 2 3 : 交换脾气为1和2的牛(时间=2+1=3).
Solution:
刚开始以为这道题跟快排好像啊,然后就手写了一个快排,然后加上变化的脾气值。但是想想就知道了,这样只维护了交换次数最少,但不一定是交换的总价值和最小。
唉(。・∀・)ノ゙,还是介绍一下正解吧:
一共分为4步:
Step1:
Two Example
第一步,很简单了~单独排个序。
Step2:
可以感受一下,(3 4 5)(8 2 7)是第一组数据的两个循环;(1)(8 9 7 6)是第二组数据的两个循环。学名叫做置换群(这样更好理解?)
所以我们的第二步就是找到所谓的置换群。
Step3:
通常在一个循环中,用min置换其余的所有数一定为最优方案。总代价为:
sum-min+(len-1)*min=>sum+(len-2)*min;
Step4:
但是如1 8 9 7 6上述方法并不成立,与其令(1)单独成为一组不如把6替换出来。也就是(6)(1 8 9 7)这样的代价为sum+min+(len-1)*smallest;
因此对于每一个循环来说,ans+=min{sum+(len-2)*min ,sum+min+(len-1)*smallest}
Code:
#include <stdio.h>#include <string.h>#include <algorithm>#define INF 9999999#define MAXN 1000000using namespace std;struct node{ int len; int mi; int sum;}cir[MAXN];int n,cnt,ans;int a[MAXN],tmp[MAXN],pos[MAXN],visit[MAXN];int cmp(int a,int b){return a<b?1:0;}int min(int a,int b){return a<b?a:b;}void pre_init(){ for(int i=1;i<=n;i++) { cir[i].mi=INF; }}int find(int x){ int l=1,r=n; while(l<r) { int mid=(l+r)>>1; if(tmp[mid]>=x) { r=mid; } else l=mid+1; } return r;}int main(){ //freopen("cowsort.in","r",stdin); //freopen("cowsort.out","w",stdout); scanf("%d",&n); pre_init(); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); tmp[i]=a[i]; } sort(tmp+1,tmp+1+n,cmp); for(int i=1;i<=n;i++) { pos[i]=find(a[i]); } for(int i=1;i<=n;i++) { if(!visit[i]&&a[i]!=tmp[i]) { visit[i]=1,cir[++cnt].len=1; cir[cnt].sum+=a[i]; cir[cnt].mi=min(cir[cnt].mi,a[i]); int t=i; while(a[pos[t]]!=a[i]) { t=pos[t];visit[t]=1; cir[cnt].len++;cir[cnt].sum+=a[t];cir[cnt].mi=min(cir[cnt].mi,a[t]); } } } for(int i=1;i<=cnt;i++) { int t1=cir[i].sum+(cir[i].len-2)*cir[i].mi; int t2=cir[i].sum+cir[i].mi+(cir[i].len+1)*tmp[1]; ans+=min(t1,t2); } printf("%d\n",ans); return 0;}
3.打砖块
(brike.c/.cpp/.pas)
Description:
在一个凹槽中放置了n层砖块,最上面的一层有n块砖,第二层有n-1块,……,最下面一层仅有一块砖。第i层的砖块从左至右编号为1,2,……,i,第i层的第j块砖有一个价值a[i,j](a[i,j]<=50)。下面是一个有5层砖块的例子:
如果要敲掉第i层的第j块砖的话,若i=1,可以直接敲掉它,若i>1,则必须先敲掉第i-1层的第j和第j+1块砖。
你的任务是从一个有n(n<=50)层的砖块堆中,敲掉(m<=500)块砖,使得被敲掉的这些砖块的价值总和最大。
Input:
输入文件名为(brike.in)。
第一行为两个正整数,分别表示n,m,接下来的第i每行有n-i+1个数据,分别表示a[i,1],a[i,2]……a[i,n – i + 1]。
Output:
输出文件名为(brike.out)。
仅有一个正整数,表示被敲掉砖块的最大价值总和。
4 5
2 2 3 4
8 2 7
2 3
49 19
数据范围与约定
对于20%的数据,满足1≤n≤10,1≤m≤30
对于100%的数据,满足1≤n≤50,1≤m≤500
Solution:
直接粘题解吧:
发现同一行可以被打掉的砖块是可以无规律分布的,但每一列只能打掉从上往下连续的砖块,所以考虑按照每一列DP。
设计状态f[i][j][k]表示打到第i列时、当前这一列打掉从上往下连续j块砖、包括这一列打掉的这一列在内共计打掉了k块砖时,最大的价值总和。对于打掉砖的条件,是这块砖上面所有的砖都被打掉、以及前一列在这块砖上面的砖也都被打掉,前者在状态中已经表示了,后者可以在转移时加以限制。
对于计算每一列前j块砖的价值总和,前缀和处理后就可以O(1)得到了。
状态转移方程:f[i][j][k]=Max(f[i-1][p][k-j]+sum[i][j]),j-1≤p≤i-1,其中sum[i][j]表示第i列打掉前j块砖的价值总和。
Code:
#include <stdio.h>#include <string.h>int n,m,ans=-1;int a[60][60],sum[60][60],s[50],f[60][60][550];int max(int a,int b){return a>b?a:b;} //f[i][j][k]打到第i列时,从上往下打掉了j块砖,共计打了k块砖的最大价值 //sum[i][j] 表示第i行打掉前j块砖的价值 int main(){ //freopen("brike.in","r",stdin); //freopen("brike.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { for(int j=i;j<=n;j++) { scanf("%d",&a[i][j]); sum[i][j]=sum[i-1][j]+a[i][j]; } } for(int i=1;i<=n;i++) { s[i]=s[i-1]+i; } for(int i=1;i<=n;i++) { for(int j=0;j<=i;j++) { for(int p=max(j-1,0);p<=i-1;p++) { for(int k=j+s[p];k<=m;k++) { f[i][j][k]=max(f[i][j][k],f[i-1][p][k-j]+sum[j][i]); ans=max(ans,f[i][j][m]); } } } } printf("%d\n",ans); return 0;}
- NOIP模拟21题解
- NOIP模拟题题解
- 【NOIP模拟】20140809 题解 & 总结
- 【NOIP模拟】20140812 题解 & 总结
- 【NOIP模拟】20140813 题解 & 总结
- 【NOIP模拟】20140814 题解 & 总结
- 【NOIP模拟】20140815 题解 & 总结
- 【NOIP模拟】20140817 题解 & 总结
- 【NOIP模拟】20140907 题解 & 总结
- 【NOIP模拟】20140913 题解 & 总结
- 【20150912】NOIP模拟 题解 & 总结
- 【NOIP模拟试题10.17】题解
- noip模拟题—跳跃版图 题解
- 2016 NOIP模拟赛[巴蜀题]题解&&总结
- NOIP 2015模拟赛[八中题]题解&总结
- NOIP 2016模拟赛[八中题]题解&总结
- NOIP 2015模拟赛 题解&总结
- 【9.14NOIP模拟pj】wtaxi 题解
- 1382 沙子合并
- android开发之Toast的多种应用
- THINKPHP 验证码类在SAE上的使用
- JProfiler入门笔记
- intel实习第二个月总结
- NOIP模拟21题解
- 1033. 旧键盘打字(20)
- CF 453B状态压缩dp
- HDU5339
- TCP协议三次握手过程分析
- AdapterViewFlipper组件学习笔记
- HDU 1166 敌兵布阵
- 订阅电视,有希望活下来吗?
- HDU 1081 To The Max(二维最大字段和,转化为一维)