2016.7.12 NOIP2013提高组day1解题报告(未完成版)
终于开始写我的第一篇博客了,激动*n;
本次做+改+调试一共用到的资源有:
1.老师提供的题一套;
2.解题报告1号(http://wenku.baidu.com/link?url=oYj6SQF5HzWE6Qzn6psZRX7GabOvacaILwx9yVRf9V2f1x7fY6djnSPwjHqrPBwDkGDUYMz9jLCR4ftA9nXldpeAPWvs9crgLhtbo3e08Sy);
3.解题报告2号 http://tieba.baidu.com/p/2714238975;
4.敬业的教练一个。
现在开始列题:
一.转圈游戏
题意:n,人数(周期数)(被模的数);m,步数(改变数)(乘数);k,圈数的幂(要计算的);x,当前位置(被加上的);
1<𝑛<1,000,000,0<𝑚<𝑛,1≤x≤n,0<𝑘<1e9。
知识点:快速幂或周期规律;
总结:先看到这道题决定模拟,毕竟第一道题一般还是比较简单,然后发现会超时,然后就想到分步,一开始打算分intmaxn也就是1e9左右,然后发现还是会超,因为现在还比较早,很多同学也没想起快速幂,这次我们小考考得很惨烈hhh,然后我想起这个方法,但因为不知道这是种规范的算法,以为只是自己乱想的又觉得不好写就放弃了,后来突然想起用longlongmaxn 1e18 分布,因为操作数很少,在我们的破机子上都过了。但最开始这个程序是错了的(而且还花了我大把的时间来调试),后来发现分步时的模相乘我写成了相加,而且没模完所有地方(因为后者我丢过不少分),血的教训啊。
分步程序:
#include<cstdio>//快速幂(二分)/找规律 #include<iostream>//循环长度一定不会超过n #include<cmath> #include<cstdlib> #include<cstring> #include<string> #include<algorithm> #include<queue> #include<set> using namespace std; long long n,m,k,x; long long h; int main() { freopen("circle.in","r",stdin); freopen("circle.out","w",stdout); cin>>n>>m>>k>>x;//renshu,bushu,cishu,haoshu; h=1;//初始值 while(k!=0) { if(k>=18) { long long zs=n; long long zs2=1e18; k-=18;h*=zs2%zs;h%=zs;//模要乘!logic } else { long long zs=n; long long cs=1; for(int i=1;i<=k;i++) cs*=10; k=0;cs%=zs;h*=cs;h%=zs; } } m%=n; x+=h*m%n;x%=n;//把所有你能看到的地方都模掉 cout<<x; return 0; }
快速幂程序:
#include <cstdio> typedef long long LL; int m,n,k,x; LL qmul(int p,int k) { LL temp=p,s=1; while(k!=0) { if(k%2==1) //每次选取最后一项乘,二进制表示法; s=(s*(temp%n))%n; temp=(temp*temp)%n; k=k/2; } return s; } int main() { freopen("circle.in","r",stdin); freopen("circle.out","w",stdout); scanf("%d%d%d%d",&n,&m,&k,&x); printf("%I64d",(m*qmul(10,k)+x)%n); return 0; }
(来自参考题解1)
二.火柴配对
题意:两盒火柴,每盒n根,差的平方(其实就是差)最小,
1≤n≤100,000,0≤火柴高度 火柴高度 ≤longlongmaxn。
分析:其实我有一个(自认为很好的)习惯就是观察数据范围,看到这道题的书据我就知道我愚蠢的程序肯定过不完,然而因为只有两小时的做题时间(肚子痛请假了),所以没有时间分析最后交了一个只!过!了!一!组!的神奇程序......就是先暴力找最小(n立方左右),然后冒泡找步骤,嗯,死得很惨烈。
前辈曾经建议说,改程序最好的办法是找出自己思维止步的地方,第一道题我是没想到二进制表示(一个常见且常想不到的优化利器(除此外我最近接触到的还有二分查找,前缀和以及正解打表)),第二道题是第一步,也就是排序不等式(班上大神装叉用的名词)就没观察出来,所以做题时不要心急,仔细观察数据的特点,性质及规律。(排序不等式的简单证明;求距离最小值,即是求 AiBi的最大值,a>b,c>d,则ac+bd>ad+bc),接着的逆序对当初学的时候没掌握好,也给我提了个醒,应该补一补原来的漏洞了。(逆序对是最少交换操作数)
过一组的神奇程序(其实我觉得这个程序还是有一定的参考价值的,但没时间就不用看了orz)
#include<cstdio>//排序不等式(找规律找性质) //最少操作次数即是逆序对数。 //欲求距离最小值,即是求 AiBi的最大值 //这样推理过程就和去年的“国王游戏” 证明过程基本一样了,即 //a>b,c>d,则ac+bd>ad+bc(证明 #include<iostream> #include<cmath> #include<cstdlib> #include<cstring> #include<string> #include<algorithm> #include<queue> #include<set> using namespace std; int flg=0,ans,n,mini=2000000000,roa2[1005],used[1005],f[1005][1005]; long long a[1005],b[1005]; struct BZ { int xs; }bz[1005]; void res(int k,long long tot) { if(k==n+1) { if(tot<mini) { for(int i=1;i<=n;i++) bz[roa2[i]].xs=i; mini=tot; } } else { for(int i=1;i<=n;i++) if(!used[i]) { roa2[k]=i;used[i]=1; res(k+1,tot+f[k][i]); used[i]=0; } } } void res2() { while(!flg) { flg=1; for(int i=2;i<=n;i++) { if(bz[i-1].xs>bz[i].xs) { BZ t;t=bz[i];bz[i]=bz[i-1];bz[i-1]=t; ans++;ans%=99999997; flg=0; } } } } int main() { freopen("match.in","r",stdin); freopen("match.out","w",stdout); ios::sync_with_stdio(false); cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) cin>>b[i]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) f[i][j]=(a[i]-b[j])*(a[i]-b[j]); res(1,0); res2(); cout<<ans; return 0; }
然后是正解的归并排序,由于请假的缘故,这是我第一次编写归并排序,以后还需要多熟练一下;
//可以直接记归并左比右大数,也可以分治求;
//顺便注释一下:归并快很大一部分原因是应用了“有序”的思想,这个思想不容易想到,其实二分查找运用的也是“有序”的思想;
// 因为如果a[i]此时比右数组的当前元素a[j]大,那么左数组中a[i]后面的元素就都比a[j]大 【因为数组此时是有序数组】(定一个数)
//参考:http://blog.csdn.net/imzoer/article/details/8050224(求逆序对的个数)
#include<cstdio> #include<iostream> #include<cmath> #include<cstdlib> #include<cstring> #include<string> #include<algorithm> #include<queue> #include<set> using namespace std; int sx[100005],m=99999997,ans,n,sd[100005];//直接修改全局变量 struct A { long long shuzhi; int srsx; }a[100003],b[100005]; bool cmp(A x,A y) { if(x.shuzhi>y.shuzhi)return true; else return false; } bool cmp2(A x,A y) { if(x.srsx<y.srsx)return true; else return false; } void qnxd(int lef,int rig) { //可以直接记归并左比右大数,也可以分治求;顺便注释一下:归并快很大一部分原因是应 //用了“有序”的思想,这个思想不容易想到,其实二分查找运用的也是“有序”的思想; //因为如果a[i]此时比右数组的当前元素a[j]大,那么左数组中a[i]后面的元素就都比a[j] //大【因为数组此时是有序数组】(定一个数) int mid=(lef+rig)/2; if(lef>=rig-1) { if(b[lef].shuzhi<b[rig].shuzhi) { ans++;ans%=m; A z=b[rig];b[rig]=b[lef];b[lef]=z; } } else { qnxd(lef,mid);qnxd(mid+1,rig); int z=lef,y=mid+1,t=lef; while(z!=mid+1||y!=rig+1) { if(z==mid+1||(y!=rig+1&&b[y].shuzhi>b[z].shuzhi)) { ans+=mid-z+1;ans%=m; sd[t++]=b[y++].shuzhi; } else sd[t++]=b[z++].shuzhi; } for(int i=lef;i<=rig;i++) b[i].shuzhi=sd[i]; //复制:这个表以后还要用(否则仅二路归并!=merage } } int main() { freopen("match.in","r",stdin); freopen("match.out","w",stdout); ios::sync_with_stdio(false); cin>>n; for(int i=1;i<=n;i++) { cin>>a[i].shuzhi; a[i].srsx=i; } for(int i=1;i<=n;i++) { cin>>b[i].shuzhi; b[i].srsx=i; } sort(a+1,a+1+n,cmp); sort(b+1,b+1+n,cmp); for(int i=1;i<=n;i++) sx[a[i].srsx]=i; for(int i=1;i<=n;i++) b[sx[i]].shuzhi=n-i+1; sort(b+1,b+1+n,cmp2); qnxd(1,n); cout<<ans; return 0; }
(由于对归并求逆序对的不熟练加上做题之前没有清晰规划,所以这个程序虽然AC了但真的思维不清晰......警示了我们常用算法一定要记清+做题之前一定要确保正确!)(顺便,每次把这种比较凌乱的程序一次过的时候我都感受到了世界上最满足的愉悦orz)
三.火车运输
题意: