XXY 的 NOIP 模拟赛 1
题目 |
| 全排列 |
|
| 走楼梯 | 部落 |
|
|
|
|
|
| |
|
|
|
|
|
|
|
|
|
|
|
| stair | people |
英文题目与子目录名 |
| permutation |
| |||
|
| |||||
|
|
|
|
|
|
|
单个测试点时间限制 |
| 1秒 |
|
| 1秒 | 1秒 |
|
|
|
|
|
|
|
内存限制 |
| 128M |
|
| 128M | 128M |
|
|
|
|
|
| |
测试点数目 |
| 10 |
| 10 | 10 | |
|
|
|
|
|
| |
每个测试点分值 |
| 10 |
| 10 | 10 | |
|
|
|
|
| ||
比较方式 |
| 全文比较(过滤行末空格及文末回车) | ||||
|
|
|
|
|
|
|
题目类型 |
| 传统 |
|
| 传统 | 传统 |
|
|
|
| |||
|
|
|
|
|
|
|
全排列
Description
从 n 个不同元素中任取 m(m≤n)个元素,按照一定的顺序排列起来,叫做从 n 个不同元素中取出 m 个元素的一个排列。当 m=n 时所有的排列情况叫全排列。把全排列按字典序排序,现在 xxy 想问你第 x 个全排列是什么,或给定的排列 p 是第几个全排列
Input
第一行有两个整数 n 和 m 表示元素个数和询问个数接下来 m 行,
如果输入的字母为 P,接下来有一个整数 x,表示询问第 x 个全排列如果输入的字母为 Q,接下来有一个 n 元素的排列,表示询问这是第几个全排列
Output
对于 P 询问,输出一个 n 元素的排列。对于 Q 询问,输出一个整数 x
保证答案在 long long 范围内
Example input
5 2
P 3
Q
1 2 5 3 4
Output
1 2 4 3 5
5
Hint
对于 20%的数据,n<=5,m<=20对于另外 20%的数据,n<=10,m<=100,且无操作 P
对于另外 20%的数据,n<=10,m<=100,且无操作 Q 对于 100%的数据,n<=20,m<=1000
改编自:P3014 [USACO11FEB]牛线Cow Line
今天才知道解决这个问题可以用到康托展开,不,可以说是今天才知道有康托展开这个东西
(一篇很好的讲康托展开的博客:http://blog.csdn.net/acdreamers/article/details/7982067)
康托展开表示的是当前排列在n个不同元素的全排列中的名次。比如213在这3个数所有排列中排第3。
那么,对于n个数的排列,康托展开为:
其中表示第i个元素在未出现的元素中排列第几。
举个简单易懂的例子:
对于排列4213来说,4在4213中排第3,注意从0开始,2在213中排第1,1在13中排第0,3在3中排第0,即:
,这样得到4213在所有排列中排第ans=20
对于这道题,我们就可以利用康拓展开
#include<vector> #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 21000 #define ll long long using namespace std; char ch; bool vis[N]; ll x,s[N],ans,ans1[N],n,m,t,sum,v[N],str[N]; ll read() { ll x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } ll work1(ll x) { memset(ans1,0,sizeof(ans1)); for(int i=1;i<=n;i++) v[i]=i; for(int i=n;i>=1;i--) { t=x/s[i-1]; x%=s[i-1]; t++; sort(v+1,v+1+n); ans1[n-i+1]=v[t]; v[t]=N; } for(int i=1;i<=n;i++) printf("%lld ",ans1[i]); } int work2() { for(int i=1;i<=n;i++) { sum=0; for(int j=i+1;j<=n;j++) if(str[j]<str[i]) sum++; ans+=s[n-i]*sum; } return ans++; } int main() { n=read(),m=read();s[0]=1; for(int i=1;i<=n;i++) s[i]=s[i-1]*i; while(m--) { cin>>ch; if(ch=='P') { x=read(); x--; work1(x); printf("\n"); } else { ans=0; for(int i=1;i<=n;i++) str[i]=read(); work2(); printf("%lld\n",ans); } } }
走楼梯
Description
在你成功地解决了上一个问题之后,xxy 不禁有些气恼,于是她在楼梯上跳来跳去,想要你求出她跳的方案数。..
xxy 站在一个 n 阶楼梯下面,他每次可以往上跳一步或两步,往下跳一步到三步(由于地心引力跳得比较远),而且在往下跳的时候只能踩在往上跳时踩过的格子。
现在 xxy 在楼梯上乱跳,想问她跳到楼梯顶上最后又跳回楼梯下面的方案数 mod 2333333。
注意:xxy 只能一直向上跳,跳到楼梯最上面,然后再往下跳,跳回楼梯最底下。
Input
一个整数 n
Output
方案数 mod 2333333
Example
Input
5
Output
42
Hint
对于 10%的数据,n<=5对于 30%的数据,n<=10
对于 100%的数据,n<=1000000
改编自:U3357 C2-走楼梯
dp(详细思路见:上一篇博文 T2 http://www.cnblogs.com/z360/p/7496500.html)
向下走可以看成向上走
f[i]表示第一次向上走到i,第二次向上也走到i的方案数
如果第二次向上走1步到i,这1步第一次有1种走法
如果第二次向上走2步到i,这2步第一次有2种走法
如果第二次向上走3步到i,这3步第一次有3种走法
如果第二次向上走4步到i,这4步第一次有5种走法
所以状态转移方程:f[i]=f[i-1]+f[i-2]*2+f[i-3]*3+f[i-4]*5
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 11000000 #define mod 2333333 using namespace std; long long n,m,x,y,z,f[5],dp[N]; long long read() { long long x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int main() { n=read();f[0]=1;dp[0]=1; for(int i=1;i<=4;i++) for(int j=1;j<=2;j++) if(i-j>=0) f[i]+=f[i-j]; else break; for(int i=1;i<=n;i++) for(int j=1;j<=4;j++) if(i-j>=0) dp[i]=(dp[i]+dp[i-j]*f[j])%mod; else break; printf("%lld\n",dp[n]); return 0; }
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 11000000 #define mod 2333333 using namespace std; long long n,m,x,y,z,f[4],dp[N]; long long read() { long long x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int main() { n=read();f[0]=1;dp[0]=1; for(int i=1;i<=3;i++) for(int j=1;j<=2;j++) if(i-j>=0) f[i]+=f[i-j]; else break; for(int i=1;i<=n;i++) for(int j=1;j<=3;j++) if(i-j>=0) dp[i]=(dp[i]+dp[i-j]*f[j])%mod; else break; printf("%I64d\n",dp[n]); return 0; }
部落
Description
很久很久以前,有一个岛,岛上有 n 个人,人们经常爆发战争。经 xxy 研究发现,如果两个人住的越近,那么他们之间爆发战争的可能性就越大。Xxy 还发现,如果将某些人按居住地划分为 k 个部落,那么部落内部的人们为了部落的强大便不会发生战争。这就意味着,划分为 k 个部落后,不同的部落的人住的越近,爆发战争的可能性就越大。设 d 为任意两个不同部落的人之间的欧几里得距离,xxy 想知道在爆发战争可能性最小的情况下,最小的 d 是多少。
注:欧几里得距离即平面上两个点的距离
Input
第一行两个整数 n,k。
接下来 n 行,每行两个整数 x,y 表示第 i 个人的坐标
Outpu
最小的 d,保留 2 位小数
Example
| people.in | people.out |
|
|
|
4 | 2 | 1.00 |
0 | 0 |
|
0 | 1 |
|
1 | 1 |
|
1 | 0 |
|
Hint
30%的数据,k<=n<=10
50%的数据,k<=n<=100
100%的数据,k<=n<=1000,0<=x<=100000,0<=y<=10000
这真是道大水题,可是蒟蒻在考试的时候没想出来、、、
使这几个部落划分成m个部落的最小边不就是他最小生成树中要添加的第n-m条边吗????
要将这n各部落全部连接起来需要n-1条边,我们要将它划分成m个部落,也就是说我们要有m个部落不能被连接起来,我们要先将距离小的两个部落先连起来,这就恰好是最小生成树的思想,所以要想到最小生成树也不难。我们将这m个部落连起来恰好是用m-1条边,也就是说我们将除了那m个部落连起来,恰好是要用n-1-(m-1)也就是n-m条边,然后这m部落间的最小的距离就是n-m+1条边的长度了
#include<cmath> #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 1100 using namespace std; double z,ans; int n,m,s,x,y,fx,fy,sum,xx[N],yy[N],fa[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Edge { int x,y; double z; }edge[N*N]; int cmp(Edge a,Edge b) { return a.z<b.z; } int find(int x) { if(fa[x]==x) return x; fa[x]=find(fa[x]); return fa[x]; } int main() { n=read(),m=read(); for(int i=1;i<=n;i++) xx[i]=read(),yy[i]=read(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i!=j) { s++; z=sqrt((double)pow(xx[i]-xx[j],2)+(double)pow(yy[i]-yy[j],2)); edge[s].x=i; edge[s].y=j; edge[s].z=z; } for(int i=1;i<=n;i++) fa[i]=i; sort(edge+1,edge+1+s,cmp); for(int i=1;i<=s;i++) { x=edge[i].x,y=edge[i].y; fx=find(x),fy=find(y); if(fa[fx]==fy) continue; fa[fx]=fy; sum++; if(sum==n-m+1) {ans=edge[i].z; break;} } printf("%.2lf",ans); return 0; }