CCF-NOIP-2018 提高组(复赛) 模拟试题(三)
T1 取球游戏
问题描述
现有\(N\)个小球,依次编号为\(1\)到\(N\),这些小球除了编号以外没有任何区别。从这\(N\)个小球中取出\(M\)个,请问有多少种取球方案使得在取出的\(M\)个小球中,编号最小的小球的编号为\(K\)。
考虑到方案数可能很大,请输出方案数对\(1e9+7\)去模的值。
输入格式
输入数据只有一行,包含三个整数\(N,M,K\)。
输出格式
一个整数,表示取法总数对\(1e9+7\)取模的值。
样例
样例输入1 |
---|
4 2 2 |
样例输出1 |
---|
2 |
样例输入2 |
---|
888 222 555 |
样例输出2 |
---|
424089030 |
题解
板子题,卢卡斯定理。这里给卢卡斯定理的板子写法。
#include<bits/stdc++.h>
#define p 1000000007
#define maxn 10000005
using namespace std;
long long E[maxn];
inline void init(){
E[0]=1;
for(register int i=1;i<maxn;i++)E[i]=E[i-1]*i%p;
}
inline long long inv(long long a,long long m){
if(a==1)return a;
return inv(m%a,m)*(m-m/a)%m;
}
inline long long lucas(long long n,long long m){
long long ans=1;
while(n&&m){
long long a=n%p;
long long b=m%p;
if(a<b)return 0;
ans=ans*E[a]%p*inv(E[a-b]*E[b]%p,p)%p;
n/=p;
m/=p;
}
return ans;
}
long long n,m,k;
int main(){
init();
cin>>n>>m>>k;
n-=k;m-=1;
printf("%lld",lucas(n,m));
return 0;
}
T2 维修机器人
问题描述
土豪贾老师拥有\(n\)个机器人。这\(n\)个机器人排成一行,第\(i\)个机器人的身高
为\(h_i\)。贾老师发现这些机器人的身高参差不齐,看起来十分不美观,于是决定对
它们的身高进行修改。
贾老师希望修改后的机器人队伍身高值单调,形式化地说,满足下面两个条
件之一的机器人队伍是合格的队伍 。
- \(h_1\le h_2\le ... \le h_{n-1} \le h_n\)
- $h_1 \ge h_2\ge...\ge h_{n-1} \ge h_n $
增加第\(i\)个机器人的身高,需要的费用为\(m_1\),减小第\(i\)个机器人的身高,需
要的费用为\(m_2\)。注意,费用与是否增加和是否减小有关,与具体增加或减小的
数值无关。对于一个身高为\(5\)的机器人,把它的身高增加到\(6\)和增加到\(100\)所需
要的费用都为\(m_1\)。
贾老师希望你能帮他计算出,为了得到合格的机器人队伍,所需要花费的最
小费用是多少。 由于某些特殊的原因, 我们保证这\(n\)个机器人不同的身高不会超
过\(1,000\)个。
输入格式
输入文件共包含两行。
第一行共包括三个正整数,分别为\(n,m_1,m_2\),含义如上文所述。
第二行包含\(n\)个整数,依次表示每个机器人的身高\(h_i\)。
输出格式
共一行,包含一个整数,表示贾老师所需修理费用的最小值。
样例
样例输入1 |
---|
5 2 3 |
1 2 3 5 4 |
样例输出1 |
---|
2 |
样例输入2 |
---|
15 5 7 |
10 10 10 10 10 9 2 8 7 6 1000 5 3 4 1 |
样例输出2 |
---|
17 |
数据规模与规定
题解
首先考\(m_1=m_2\)的情况,因为升高或降低一个机器人的高度所需要的花费一样,所以我们只需要设法用最小的步数将整个序列变为单调递增或单调递减即可。此时我们可以选择求整个序列的最长不上升与最长不下降序列子序列,用\([n-max(up_{size},down_{size})]*m_1\)即可得到答案
代码如下(75)
#include<bits/stdc++.h>
#define maxn 50005
#define inf 0x3f3f3f3f
using namespace std;
inline char get(){
static char buf[30],*p1=buf,*p2=buf;
return p1==p2 && (p2=(p1=buf)+fread(buf,1,30,stdin),p1==p2)?EOF:*p1++;
}
inline long long read(){
register char c=get();register long long f=1,_=0;
while(c>'9' || c<'0')f=(c=='-')?-1:1,c=get();
while(c<='9' && c>='0')_=(_<<3)+(_<<1)+(c^48),c=get();
return _*f;
}
bool cmp(long long a,long long b){
return a>b;
}//重载upper_bound
long long n,m1,m2;
long long a[maxn],f[maxn],g[maxn];
long long ans1,ans2;
long long cas[maxn];
int main(){
//freopen("robot.in","r",stdin);
//freopen("robot.out","w",stdout);
n=read();m1=read();m2=read();
for(register long long i=1;i<=n;i++)a[i]=read();
if(m1==m2){
for(register long long i=0;i<maxn;i++)f[i]=0,g[i]=0x3f3f3f3f;
g[0]=0;
for(register long long i=1;i<=n;i++){
f[i]=upper_bound(g+1,g+1+ans1,a[i])-g;
g[f[i]]=a[i];
ans1=max(ans1,f[i]);
}
//cout<<ans1<<endl;
for(register long long i=0;i<maxn;i++)f[i]=0,g[i]=0x3f3f3f3f;
g[0]=0;
for(register long long i=1;i<=n;i++){
f[i]=upper_bound(g+1,g+1+ans2,a[i],cmp)-g;
g[f[i]]=a[i];
ans2=max(ans2,f[i]);
}
//cout<<ans2<<endl;
printf("%lld",(n-max(ans1,ans2))*m1);
return 0;
}
return 0;
}
注意到题目的关键条件,不同的\(h_i\)取值不超过$T=1000 $个。考虑将
读入数据先离散成\(1——1000\)的整数,接下来用动态规划解决。设\(dp[i][j]\)表示将前 i 个机
器人改为单调不减,第\(i\)个机器人的身高变为\(j\)所需的最小费用。则\(dp[i][j]=min(dp[i-
1][k])+change(a[i],j)(1≤k≤j)\)。其中\(change(x,y)\)表示把一个机器人的身高从\(x\)改为\(y\)所需的
费用,这个值只能是\(m_1\)或者0或者\(m_2\) 。
按上述做法时间复杂度为\(O(nT^2 )。\)注意到对于当前的决策位置,\(k\)的可取值越来越
多, 因此可以再同时维护$ min(dp[i-1][k])$, 这样转移代价变成 \(O(1)\), 时间复杂度变为\(O(nT)\),
可以获得\(100\)分。
#include <bits/stdc++.h>
#define maxn 50005
#define maxt 1005
#define maxl 1000005
#define inf 0x3f3f3f3f
using namespace std;
int n,m1,m2;
int p[maxn];
int t[maxt];
bool vis[maxl];
int level[maxl];
int cnt;
int now,pre;
int minv;
int dp[2][maxt];
int ans=inf;
void init(){
scanf("%d %d %d",&n,&m1,&m2);
for (int i=1;i<=n;i++){
scanf("%d",&p[i]);
if (!vis[p[i]]) t[++cnt]=p[i];
vis[p[i]]=true;
}
sort(t+1,t+cnt+1);
for (int i=1;i<=cnt;i++) level[t[i]]=i;
for (int i=1;i<=n;i++) p[i]=lev el[p[i]];
}
inline int getdp(){
now=1;
for (int i=1;i<=cnt;i++){
if (i>p[1])dp[now][i]=m1;
else if (i<p[1])dp[now][i]=m2;
else dp[now][i]=0;
}
for (int i=2;i<=n;i++){
now=1-now;pre=1-now;
minv=dp[now][1]=dp[pre][1];
for (int j=2;j<=cnt;j++){
minv=min(minv,dp[pre][j]);
dp[now][j]=minv;
}
for (int j=1;j<p[i];j++)dp[now][j]+=m2;
for (int j=p[i]+1;j<=cnt;j++)dp[now][j]+=m1;
}
int res=inf;
for (int i=1;i<=cnt;i++)res=min(res,dp[now][i]);
return res;
}
void solve(){
ans=getdp();
reverse(p+1,p+n+1);
ans=min(ans,getdp());
printf("%d\n",ans);
}
int main(){
freopen ("robot.in","r",stdin);
freopen ("robot.out","w",stdout);
init();
solve();
return 0;
}
T3 下标
问题描述
Bella 同学在学习 C++的时候,有一天意外地把\(a[i]\)写成了\(i[a]\),发现程
序居然还能正常地编译和运行!(如果你现在做题做累了,不妨拿出半分钟时间
试试看这是不是真的! )
通过进一步的实验,Bella认为,对于任意的两个合法的表达式\(A\)和\(B\),表
达式\(A[B]\)与\(B[A]\)是等价的。
并且,等价是具有传递性的。例如,\(a[b[c]]\)和\(c[b][a]\)是等价的,因为这
两个表达式都和\(a[c[b]]\)等价。
现在给你一些合法的表达式,其中只会出现小写字母与方括号。你需要对每
个表达式进行若干次这样的等价变换,得到一个字典序尽可能小的表达式。
更正式地,所有可能出现的表达式恰好能由如下上下文无关文法从符号
Expr生成:
而每次的等价变换,是将一个形如\(Expr1[Expr2]\)的式子变为
\(Expr2[Expr1]\),并且要求\(Expr1\)与\(Expr2\)都能由\(Expr\)生成。
输入格式
输入文件的第一行只包含一个正整数\(T\),表示该输入文件的数据个数。
接下来\(T\)行,每行一个字符串,表示一个合法的表达式。
输出格式
对于每个输入数据,输出一行,表示所能得到的字典序最小的表达式。
样例
样例输入 |
---|
aaa[bbb] |
a[b[abbb]] |
b[a[azzz]] |
x[a][b[a]] |
样例输出 |
---|
aaa[bbb] |
a[abbb[b]] |
a[azzz][b] |
a[b]a[x]] |
题解
将输入字符串的括号进行匹配,找到每个右括号对应的左括号。记
匹配后的字符串为\(S=aaa[A][B]\),其中\(A\)和\(B\)都为\(expr,B\)右侧的括号为整个字符串
的结尾,\(B\)左侧的括号是与最后的右括号匹配的左括号。定义过程\(solve(S)\)表示寻找
字符串 S 的最小字典序表示,首先递归调用\(solve(aaa[A])与solve(B)\)然后再比较
\(aaa[A][B]\)与\(B[aaa[A]]\)的字典序, 将\(S\)变为这两者中较小的一个。时间复杂度\(O(n^2)\),
期望得分 80 分。在 80 分做法的基础上,使用链表控制整个字符串,记录每个字符串
的后继元素,这可以使得等价变换的时间复杂度变为\(O(1)\)即可。
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;
const int maxn = 100010;
int sta[maxn], len, top, mp[maxn];
string s;
string work(int l, int r){
string res = "";
if(s[r] != ']'){
for(int i = l; i <= r; i++){
res += s[i];
}
return res;
}
string s1 = work(l, mp[r] - 1);
string s2 = work(mp[r] + 1, r - 1);
if(s1 > s2){
swap(s1, s2);
}
res += s1;
res += '[';
res += s2;
res += ']';
return res;
}
int main(){
int t;
scanf("%d", &t);
while(t--){
cin >> s;
len = s.size();
for(int i = len - 1; i >= 0; i--){
if(s[i] == ']'){
sta[top++] = i;
}
if(s[i] == '['){
mp[sta[--top]] = i;
}
}
cout << work(0, len - 1) << endl;;
}
return 0;
}