Task 1: 售票机
题目描述:
售票机是一个类似于ATM的设备,它有一个键盘,如下图所示。
键盘分成4行8列。
你在买票的时候,输入要去的目的地即可出票。为了使得输入更快更方便,它有一个功能,在输入的过程中,它会根据你已经输入的字母去匹配可能的目的地,并推测出你要输入的下一个字母,然后键盘只显示那些字母,而其他的字母就会变成”*”。
现在给出所有的目的地和你已经输入的字母,请问现在的键盘会变成什么样?
题目分析:
因为数据范围很小,所以直接暴力就行了。
Code:
#include <cstdio>
#include <cstring>
#define rep(i,x,y) for(int i=x;i<=y;++i)
int n, len[55], L;
char ip[105];
char s[55][105], ans[35];
inline bool Issame(int p){
rep(i,1,L)if(s[p][i] != ip[i]) return 0;
return 1;
}
int main(){
rep(i,1,32) ans[i] = '*';
scanf("%d",&n);
rep(i,1,n) scanf("%s",s[i]+1), len[i] = strlen(s[i]+1);
scanf("%s",ip+1);
L = strlen(ip+1);
rep(i,1,n)if(len[i] >= L+1 && Issame(i))
ans[s[i][L+1]-'A'+4] = s[i][L+1];
rep(i,0,3){
rep(j,1,8) putchar(ans[i*8+j]);
putchar(10);
}
return 0;
}
Task 2: 分钱
题目描述:
两个人在街上捡到了一些钱,这些钱共有N张,他们等了很久也没有等来失主,于是决定把钱平分。但钱可能无法平分。他们先把能够平分的钱尽量先平分了,使得剩下不能平分的钱尽量少。这些不能平分的钱怎么办呢他?他们决定拿去赌场里面赌一把。他们运气太好了,那些不能平分的钱变成了双倍,于是他们就把那个钱分了。现在,请问他们每个人带回家多少钱。
题目分析:
根据题目描述,我们可以发现这其实就是一道经典的DP模型。
这里提供另外一道相同模型的题:http://new.tyvj.cn/p/1114
这里我们可以定义f[i][j]
表示在前i个物品中选择,两个人拿的钱相差为j时,拿的多的那个人有多少钱。
然后的转移就类似01背包。对每个物品来说有拿(先不管是哪个人拿)和不拿两种状态。
对于不拿来说转移很简单:f[i][j] = f[i-1][j]
拿了的话我们就要分类讨论了:
- 是原来就拿得多的人拿了,那么他一定在哪了之后还是拿得多的哪一个,那么在拿之前两个人的差值就是
j-a[i]
。这种情况下,转移方程就是:f[i][j] = f[i-1][j-a[i]]+a[i]
- 是原来拿的少的人拿了,且拿了之后还是少的哪一个,那么在拿之前两个人的差值也是
j-a[i]
。这种情况下,转移方程就是:f[i][j] = f[i-1][j-a[i]]
- 是原来拿得少的人拿了,但拿了之后他就变成拿得多的那个人了,那么之前两个人的差值就是:
a[i]-j
。这种情况下,转移方程就是:f[i][j] = f[i-1][a[i]-j]+j
最后的目标状态就是f[n][0]
然后这道题的答案就是:f[n][0]+(sigma(a[i])-2*f[n][0])
,化简一下就是:sigma(a[i])-f[n][0]
Code:
#include <cstdio>
#define max(a,b) (a>b ? a:b)
int n, a[505];
int f[505][100005];
int main(){
scanf("%d",&n);
int i, j, sum;
for(i=1;i<=n;++i) scanf("%d",a+i);
for(i=1,sum=a[1];i<=n;sum+=a[++i])for(j=0;j<=sum;++j){
if(j+a[i] <= sum) f[i][j] = max(f[i-1][j], f[i-1][j+a[i]]);
if(j >= a[i] && (f[i-1][j-a[i]]||j==a[i])) f[i][j] = max(f[i][j], f[i-1][j-a[i]]+a[i]);
else if(f[i-1][a[i]-j]) f[i][j] = max(f[i][j], f[i-1][a[i]-j]+j);
}
printf("%d\n",sum-f[n][0]);
return 0;
}
Task 3: 押韵
题目描述:
LCS表示最长公共后缀长度。如果两个单词A,B押韵,当且仅当LCS(A,B)>=MAX(A,B)-1。如果一个序列押韵,当且仅当该序列中任意相邻的两个单词押韵。现在,给你一片文章,文章中没有相同的两个单词。请你从该文章中选择任意单词,并任意排列顺序,得到一个尽量长的押韵序列。注意,每个单词只能出现一次。
题目分析:
通过数据范围可以知道,如果写暴力dp是肯定会T的。那么我们就需要对dp进行优化。
这里我们使用的是trie树,然后在树上做dp,这时对于一个字符串,它只能和它的父亲节点、兄弟节点或儿子节点所表示的字符串押韵。因为所有单词的总长度不超过3000000,所以就可以过了。
要注意的是因为所有单词的总长度不超过3000000,所以用dfs来遍历树做dp是会爆栈的,因此这里要用循环来替代dfs。
Code:
#include <map>
#include <cstdio>
#include <cstring>
using std :: map;
const int MAXN = 3000005;
#define max(a,b) (a>b ? a:b)
struct Node{
int fa;
bool flg;
map <char,int> son;
}T[MAXN];
char s[MAXN];
int n, len, i, j, d;
int f[MAXN], g[MAXN];
int u, pcnt, siz[MAXN];
int main(){
scanf("%d",&n);
f[0] = g[0] = 1;
for(i = 1; i <= n; ++ i){
scanf("%s",s+1), len = strlen(s+1);
for(j = len, u = 0; j; u = T[u].son[s[j--]])
if(!T[u].son[s[j]]){
T[u].son[s[j]] = ++pcnt;
T[pcnt].fa = u, f[pcnt] = g[pcnt] = 1;
}
T[u].flg = 1, siz[T[u].fa] ++;
}
for(i = pcnt; i >= 0; -- i)if(T[i].flg){
d = f[i]+siz[i]+T[i].flg-1;
if(d < f[T[i].fa]) g[T[i].fa] = max(g[T[i].fa], d);
else g[T[i].fa] = f[T[i].fa], f[T[i].fa] = d;
}
int ans = 0;
for(i = pcnt; i >= 0; -- i)
ans = max(ans, f[i]+g[i]+siz[i]+T[i].flg-2);
printf("%d\n",ans);
return 0;
}