2019.10.05
第1题: 拯救
时间限制 1ms 空间限制 128MB
题目描述
正义之士被恶魔抓了,被关在小黑屋里,无法继续他的正义事业,你决定去拯救他。
关正义之士的小黑屋迅速被你打开,可是正义之士却被恶魔用一把锁给锁住了。这把锁包含了N个小锁。只有打开前K-2个锁,且锁上第K-1个锁,才能改变第K个锁的状态(打开或锁上该锁),第1个锁可以任意改变状态,当第1 个锁锁上时第2个锁就可以改变状态。
为了知道你到底是要留下来开锁,还是“走为上”,你需要知道到底需要多少次操作才能开锁(打开或锁上一把锁算一次操作,只有当N 个小锁都被打开进才算开了锁)。
输入格式
输入文件save.in 第一行为一个N(小锁的个数,1<=N<=1000)。
第二行为n 个整数a1,a2,…,an(每个都是0 或者1),中间用单个空格隔开。如果是ai=1,表示第i 个锁是锁着的,反之表示该锁已被打开。
输出格式
输出文件save.out 包括一个数,表示最少要操作的次数。
【输入样例】
4
1 0 1 0
【输出样例】
6
数据规模与提示
样例说明
1010->1110->0110->0100->1100->1000->0000
对于40%的数据,有N<=30
对于100%的数据,有N<=1000;
分析
没有什么好说的,只能递推。
首先发现,把000…000(共n个0)变为000…001(n-1个0,1个1)需要的步数和把000…001(n-1个0,1个1000…000(共n个)变为把000…000(共n个0)所需要的步数是一样的,假设 f [ n ] f[n] f[n]代表这个步数,比方说从一堆零变到一堆零带一个1,我们需要把: 000 ⋯ 000 → f [ n − 1 ] 000 ⋯ 010 → f [ n − 1 ] 000 ⋯ 011 → 1 000 ⋯ 001 000\cdots000\xrightarrow{f[n-1]} 000\cdots010\xrightarrow{f[n-1]}000\cdots011\xrightarrow{1}000\cdots001 000⋯000f[n−1]000⋯010f[n−1]000⋯0111000⋯001,看了看这个图示,数学功底无限好的你们一眼就看出来了 f [ n ] = 2 ∗ f [ n − 1 ] + 1 f[n]=2*f[n-1]+1 f[n]=2∗f[n−1]+1。又因为你们的数学功底无限好,你们迅速的知道了 f [ n ] = 2 n − 1 f[n]=2^n-1 f[n]=2n−1。
知道这个有什么用呢?
我们现在就可以实现对一串零和一串零带一个1的转换了。
设
g
[
n
]
g[n]
g[n]代表把题目给出的数列的前n位变为一串零所需的步数,
h
[
n
]
h[n]
h[n]代表变为一串零带一个1所需的步数。
于是数学功底无限好的你们就迅速秒出来了:
若
a
n
=
0
,
则
g
[
n
]
=
g
[
n
−
1
]
,
h
[
n
]
=
h
[
n
−
1
]
+
1
+
2
n
−
1
若
a
i
=
1
,
则
g
[
n
]
=
h
[
n
−
1
]
+
1
+
2
n
−
1
,
h
[
n
]
=
g
[
n
−
1
]
若a_n=0,则g[n]=g[n-1],h[n]=h[n-1]+1+2^n-1 \\ 若a_i=1,则g[n]=h[n-1]+1+2^n-1,h[n]=g[n-1]
若an=0,则g[n]=g[n−1],h[n]=h[n−1]+1+2n−1若ai=1,则g[n]=h[n−1]+1+2n−1,h[n]=g[n−1]
我比较差,所以我需要解释一下,
我们当前的状态是:
n
↓
0000000000000
?
0
\quad\quad\quad\quad\quad\quad\quad ~n\\ \quad\quad\quad\quad\quad\quad\quad~↓\\ 0000000000000?0
n ↓0000000000000?0
的话,你想求
g
[
n
]
g[n]
g[n]的话,只需要
?
=
0
?=0
?=0即可,而这步数正是
g
[
n
−
1
]
g[n-1]
g[n−1],并不需要额外的步数;你想求
h
[
n
]
h[n]
h[n]的话,就是说你想让第
n
n
n位上的
0
0
0变为1,首先你就得让
?
=
1
?=1
?=1,这正是
h
[
n
−
1
]
h[n-1]
h[n−1],其次,你需要额外的一步来变成
000000000000011
000000000000011
000000000000011,然后为了让第
n
−
1
n-1
n−1的
1
1
1变为
0
0
0,你就需要
f
[
n
−
1
]
f[n-1]
f[n−1]步,这样你就得到了
h
[
n
]
:
000000000000001
h[n]:000000000000001
h[n]:000000000000001。
如果第
n
n
n位是1的话,可以类似的讨论。
那么答案就是
g
[
n
]
g[n]
g[n],你还得高精度。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
inline void Read(int &p)
{
p=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
}
const int MAXN=5024;
int n,now,arr[MAXN],zero[2][MAXN]={{1},{1}},one[2][MAXN]={{1},{1}},two[MAXN]={1,1};
int main()
{
freopen("save.in","r",stdin);
freopen("save.out","w",stdout);
Read(n);
for(int i=1;i<=n;i++) Read(arr[i]);
if(arr[1]==0) one[now][1]=1;
else zero[now][1]=1;
for(int t=2;t<=n;t++)
{
for(int i=1;i<=two[0];i++) two[i]*=2;
for(int i=1;i<two[0];i++) two[i+1]+=two[i]/10,two[i]%=10;
while(two[two[0]]>=10) two[two[0]+1]+=two[two[0]]/10,two[two[0]++]%=10;
now^=1;
memset(zero[now],0,sizeof zero[now]);
memset(one[now],0,sizeof one[now]);
if(arr[t])
{
for(int i=0;i<=zero[now^1][0];i++) one[now][i]=zero[now^1][i];
zero[now][0]=max(one[now^1][0],two[0]);
for(int i=1;i<=zero[now][0];i++) zero[now][i]=one[now^1][i]+two[i];
for(int i=1;i<zero[now][0];i++) zero[now][i+1]+=zero[now][i]/10,zero[now][i]%=10;
while(zero[now][zero[now][0]]>=10) zero[now][zero[now][0]+1]+=zero[now][zero[now][0]]/10,zero[now][zero[now][0]]%=10,zero[now][0]++;
}
else
{
for(int i=0;i<=zero[now^1][0];i++) zero[now][i]=zero[now^1][i];
one[now][0]=max(one[now^1][0],two[0]);
for(int i=1;i<=one[now][0];i++) one[now][i]=one[now^1][i]+two[i];
for(int i=1;i<one[now][0];i++) one[now][i+1]+=one[now][i]/10,one[now][i]%=10;
while(one[now][one[now][0]]>=10) one[now][one[now][0]+1]+=one[now][one[now][0]]/10,one[now][one[now][0]]%=10,one[now][0]++;
}
}
for(int i=zero[now][0];i>=1;i--) printf("%d",zero[now][i]); puts("");
}
第2题: 魔法物品
时间限制 1ms 空间限制 128MB
题目描述
有两种类型的物品:普通物品和魔法物品。普通物品没有魔法属性而魔法物品拥有一些魔法属性。每种普通物品有一个价值P,但每种魔法物品有两种价值:鉴定前的价值P1 和鉴定后的价值P2(当然,P2 总是大于P1)。
为了鉴定一个魔法物品,你需要购买一个鉴定卷轴,用它来鉴定魔法物品。鉴定完一件魔法物品以后,鉴定卷轴便会消失。每个鉴定将会消耗Pi 元钱,如果没有足够的钱,你将无法购买任何鉴定卷轴。
现在,你正在一个集市中,同时拥有很多物品。你知道每件物品的价值并且想要出售全部物品。那么,你最多能够获得多少钱呢?
你可以假定:
• 开始的时候你没有钱。
• 所有的魔法物品都还没有被鉴定。
• 只要你有足够的钱,你可以购买任意多的鉴定卷轴。
输入格式
输入magic.in
第一行有两个整数N 和Pi (0 < Pi <= 5000),表示你拥有的物品数和一个鉴定卷轴价格。
接下来N 行,每行给出一件物品的价格。
对于每件普通物品,那一行仅有一个整数P (0 < P <= 10000)。
对于每件魔法物品,那一行将会有两个整数P1 和P2 (0 < P1 < P2 <= 10000)
输出格式
输出magic.out
一个整数表示你最多能够获得多少钱。
数据规模与提示
数据规模
对于30%的数据N <= 50
对于100%的数据N <= 1000
分析
Pi居然是个定值。我还以为每个东西的鉴定费用都不一样呢。
首先很显然的,那些非魔法物品和那些鉴定了之后会亏本的“劣质”魔法物品(
p
2
≤
p
1
+
p
i
p_2\leq p_1+p_i
p2≤p1+pi)都卖掉。
此时你手上就有了很多资本,你可以拿去买卷轴鉴定并出售剩下的“优质”魔法物品(
p
2
>
p
1
+
p
i
p_2\gt p_1+p_i
p2>p1+pi)的物品,因为它们是优质的,所以你肯定能把买卷轴的钱挣回来(还有多的钱),于是你又买卷轴鉴定……。就这样利滚利,本题结束了。
可是神明很少眷顾。
如果你没有那么多普通物品和劣质魔法物品,你把它们卖了以后,你得到了
h
a
s
has
has元的资本,但
h
a
s
<
p
i
has\lt p_i
has<pi,你并不能愉快地利滚利,你必须忍心卖掉一些优质魔法物品来赚取资本。
卖哪些呢?
贪心似乎不能解决问题。
于是考虑dp背包。
设
d
p
[
s
]
dp[s]
dp[s]代表现在你其中你卖掉了某些优质魔法物品,赚取了
s
s
s的资本时,你的最小亏损。
那么现在我们考虑是否卖掉第
i
i
i件物品,在什么时候卖,就有
d
p
[
s
]
=
m
i
n
{
d
p
[
s
−
p
1
[
j
]
]
+
p
2
[
j
]
−
p
1
[
i
]
−
p
i
}
dp[s]=min\{dp[s-p1[j]]+p2[j]-p1[i]-pi\}
dp[s]=min{dp[s−p1[j]]+p2[j]−p1[i]−pi},所以
a
n
s
=
m
a
x
{
h
a
s
+
∑
p
2
−
d
p
[
s
]
−
p
i
∗
c
n
t
}
ans=max\{has+\sum p_2-dp[s]-p_i*cnt \}
ans=max{has+∑p2−dp[s]−pi∗cnt},cnt代表优质魔法物品的总数,然后枚举s。
什么意思,或者说,怎么想到的?
我们知道,如果我们的资本够的话,我们得到的钱就是
h
a
s
+
∑
p
2
−
p
i
∗
c
n
t
has+\sum p_2-p_i*cnt
has+∑p2−pi∗cnt,然而我们有一个物品以
p
1
p_1
p1的价格卖了出去,而不是
p
2
−
p
i
p_2-p_i
p2−pi,所以上文说的亏损就是
(
p
2
−
p
i
)
−
p
1
(p_2-p_i)-p_1
(p2−pi)−p1,你的dp这亚子定义的话,算答案就方便一些。
还有一些细节相信数学功底顶好的你们能秒杀。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
inline bool Read(int &p)
{
p=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
if(c==' ') return 1;
return 0;
}
const int MAXN=1024,MAXV=10002030;
bool c;
int n,p,u,v,all,ans,cnt,goes,p1[MAXN],p2[MAXN],dp[MAXV];
int main()
{
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
Read(n),Read(p);
memset(dp,0x3f,sizeof dp),dp[0]=0;
for(int i=1;i<=n;i++)
{
c=Read(u);
if(!c) goes+=u;
else
{
Read(v);
if(v-p<=u) goes+=u;
else p1[++cnt]=u,p2[cnt]=v,all+=v;
}
}
if(goes>=p) return printf("%d\n",goes+all-p*cnt),0;
for(int i=1;i<=cnt;i++)
for(int j=p-goes+p1[i];j>=p1[i];j--)
{
dp[j]=min(dp[j],dp[j-p1[i]]+p2[i]-p1[i]-p);
if(j>=p-goes && dp[j]!=0x3f3f3f3f)
ans=max(ans,goes+all-dp[j]-p*cnt);
}
if(!ans) {for(int i=1;i<=cnt;i++) ans+=p1[i]; ans+=goes;}
printf("%d\n",ans);
}
第3题: 字典
时间限制 1ms 空间限制 128MB
题目描述
小M 发现了1 个由未知字母表组成的单词列表。当然,这些单词都是按照该字母表的字典顺序排列的。
写一个程序来根据出现过的字母找出唯一的字母表,或断言不存在这样字母表,或存在多个这样的字母表。
输入格式
输入:
第一行包含1 个正整数N(N<=100),表示单词的个数。
接下来的N 行包含发现的单词列表,每行一个单词。每个单词最多包含10 个小写字母。
输出格式
输出:只有1 行,按照字母表顺序输出所有的字母。如果不存在这样的字母表,则输出‘!’,如果
存在多个这样的字母表,则输出‘?’。
数据规模与提示
分析
容易想到暴力枚举每对字符串,找到它们第一处不同的字母,然后从较小的字符串的那个字母到较大的子串的那个字母连一条边代表小于关系。
由于这是拓扑排序板子题所以我采用了另一种方法。
我们用floyd或者其他的东西跑出来完整的联通矩阵,这好像不能叫做邻接矩阵吧,总是如果 [ i ] [ j ] = 1 [i][j]=1 [i][j]=1,代表我们能从 i i i到 j j j, i < j i<j i<j。
于是我们再次枚举点对,如果
[
i
]
[
j
]
=
[
j
]
[
i
]
=
1
[i][j]=[j][i]=1
[i][j]=[j][i]=1,那么就发生矛盾,输出!;如果
[
i
]
[
j
]
=
[
j
]
[
i
]
=
0
[i][j]=[j][i]=0
[i][j]=[j][i]=0,我们就无法确定它们的大小关系,输出?。
否则就有解,我们再次枚举点对,统计每个点
i
i
i,它能到达几个点,就说明它比几个字母小,我们就知道了它的字典序了。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,all;
char s[150][15],ans[150];
bool show[30],lower[30][30];
int main()
{
freopen("dictionary.in","r",stdin);
freopen("dictionary.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
for(int i=1;i<=n;i++)
{
int li=strlen(s[i]+1);
for(int j=1;j<=li;j++) show[s[i][j]-'a']=1;
for(int j=i+1;j<=n;j++)
{
int lj=strlen(s[j]+1);
for(int k=1;k<=li && k<=lj;k++)
if(s[i][k]!=s[j][k]) {lower[s[i][k]-'a'][s[j][k]-'a']=1; break;}
}
}
for(int i=0;i<26;i++) all+=show[i];
for(int k=0;k<26;k++)
for(int i=0;i<26;i++)
for(int j=0;j<26;j++)
if(show[i] && show[j] && show[k])
lower[i][j]|=lower[i][k] && lower[k][j];
for(int i=0;i<26;i++)
for(int j=0;j<26;j++)
if(show[i] && show[j] && i!=j)
{
if(!lower[i][j] && !lower[j][i]) return puts("?"),0;
if(lower[i][j] && lower[j][i]) return puts("!"),0;
}
for(int i=0;i<26;i++)
if(show[i])
{
int cnt=0;
for(int j=0;j<26;j++) cnt+=(show[j] && lower[i][j] && i!=j);
ans[all-cnt]=i+'a';
}
puts(ans+1);
}