前言
这次考试整体来言还是不错的,但是还是存在一些问题,就第三题而言考试时并没有深入思考,然后暴力还挂了。
试题
【道路修建】(Road.pas/c/cpp Time:1.2s Memory:256M)
【问题描述】
现在在 LJY 星球上有 N 个城市。LJY 为了使各个国家的经济发展,决定在
各个国家之间建设双向道路使得国家之间连通。但是 LJY 很吝啬,只愿意修建
恰好 n–1 条双向道路。每条道路的修建都要付出一定的费用,这个费用等于道
路长度乘以道路两端的国家个数之差的绝对值。例如,在下图中,虚线所示道路
两端分别有 2 个、4 个国家,如果该道路长度为 1,则费用为 1×|2 – 4|=2。图
中圆圈里的数字表示国家的编号。
由于国家的数量十分庞大,道路的建造方案有很多种,同时每种方案的修建
费用难以用人工计算,LJY 决定找人设计一个软件,对于给定的建造方案,计算
出所需要的费用。请你帮助 LJY 设计一个这样的软件。
【输入】
输入文件名为 Road.in。
输入第一行为一个正整数 N,代表总的点数的个数。
下接 N-1 行,每行三个正整数 ai、bi、ci,代表有一条长度为 ci 的双向道路
连接 ai 和 bi 两个国家之间。
【输出】
输出文件名为 Road.txt。
输出一行一个正整数,为修建所有道路的总费用。
【输入输出样例】
Road.in
6
1 2 1
1 3 1
1 4 2
6 3 1
5 2 1
Road.txt
20
【数据范围】
对于 40%的数据,
N≤1000
。
对于 70%的数据,
N≤100000
。
对于 100%的数据,
N≤1000000
。
不允许开开关。
【题解】
原题竟然是NOI2011,但是这道题真的很水
我们可以直接以某个点为根节点,然后将对这个树进行一次搜索遍历。那么显然我们可以算出每个以每个点为根节点的子树包含的点数,简单的记为
Si
。
那么,连接第I个点的树边连接城市之差就是
|N−SI∗2|
,记住,这里的点不包括根节点。
那么进行一次简单的统计即可。
但是本题直接dfs搜索会爆掉栈空间,但是测评时全机房决定还是开大栈空间(似乎大部分人打的dfs,我当时怕爆栈就打了bfs(虽然有点慢))。
【代码】
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;
const int maxn = 2000000+20;
int point[maxn],nxt[maxn],g[maxn],val[maxn];
int p[maxn],f[maxn],fx[maxn],s[maxn];
bool v[maxn];
int n,tot;
inline int read() {
int in=0,f=1;
char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())
if(ch=='-')
f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())
in=in*10+ch-'0';
return in*f;
}
inline void add_edge(int x,int y,int z) {
point[++tot]=y;nxt[tot]=g[x];g[x]=tot;val[tot]=z;
}
int main() {
freopen("road.in","r",stdin);
freopen("road.out","w",stdout);
n=read();
for(int i=1;i<n;i++) {
int x=read(),y=read(),z=read();
add_edge(x,y,z);add_edge(y,x,z);
}
p[1]=1;v[1]=true;
for(int i=0,j=1;i<=j;i++) {
for(int k=g[p[i]];k!=0;k=nxt[k]) {
if(v[point[k]]==false) {
v[point[k]]=true;fx[point[k]]=val[k];
f[point[k]]=p[i];p[++j]=point[k];
}
}
}
for(int i=n;i>=1;i--) ++s[p[i]],s[f[p[i]]]+=s[p[i]];
LL ans=0;
for(int i=1;i<=n;i++) {
ans+=((LL)fx[i])*(abs((LL)n-((s[i])<<1)));
}
printf("%lld\n",ans);
return 0;
}
【迷宫巡回】(maze.pas/c/cpp Time:1s Memory:256M)
【问题描述】
现在有一个 N*M 的迷宫,LJY 处在第一行第一列这个位置,也就是起点上,迷宫的补
给点在(N,M)。这个迷宫中的每个格子都有一个激情度,也就是说,LJY 走到这个格子
便可以获得这个格子上的激情度。但是,走过一遍的格子便没有激情度了。所以,LJY 为了
获得最大的激情度,便不希望走到同一个格子上,除了起点。首先,LJY 会从起点走到补给
点,此时,LJY 只能向下或者向右运动到相邻的格子。到了补给点之后,LJY 又从补给点开
始,向上或者向左运动到相邻的格子,一直到起点。当然,万一 LJY 走到了迷宫之外,他
就挂定了,所以他绝对不会走到迷宫之外的。现在 LJY 想知道,自己巡回一遍迷宫之后,
能获得的最大的激情度有多少?
【输入】
输入文件名为 maze.in。
输入一行两个正整数 N 和 M,代表迷宫的行数和列数。
下接一个 N 行 M 列的矩阵,其中第 I 行第 J 列的数代表游历这个格子的激情度。
【输出】
输出文件名为 maze.txt。
输出一行一个正整数,代表 LJY 巡回一遍迷宫能获得的最大的激情度。
【输入输出样例】
maze.in
3 3
0 3 9
2 8 5
5 7 0
maze.txt
34
【数据范围】
对于 30%的数据,
1≤N,M≤10
。
对于 100%的数据,
1≤N,M≤50
。
保证每个格子的激情度均为不大于 100 的非负整数。
【题解】
刚开始打了个暴力,把题目大概意思搞清楚了。
然后发现人都只能向下和向右走。
所以我们可以用dp的思想,用一个三维数组
F[i][j][k]
代表这两个人走到了第I条斜线时,第一个人在第J列,第二个人在第K列时能获得的最大激情度。
【代码】
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;
const int size = 50+5;
int f[size<<1][size][size],w[size][size];
int n,m;
inline int read() {
int in=0,f=1;
char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())
if(ch=='-')
f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())
in=in*10+ch-'0';
return in*f;
}
namespace my_dp{
inline int cal(int x,int y) {
x-=y;
if (x<=0||x>n) return -1;
return w[x][y];
}
inline void work() {
f[3][1][2]=w[1][1]+w[1][2]+w[2][1];
for(int i=3;i<=m+n;i++) {
for(int x=1;x<=m;x++) {
for(int y=x+1;y<=m;y++) {
// dp_start-------------------------
int sta=cal(i,x),stb=cal(i,y);
if(sta<0 or stb<0) continue;
// dp_1-------------------------
sta=cal(i+1,x);stb=cal(i+1,y);
if(sta>=0 and stb>=0 and sta+stb+f[i][x][y]>f[i+1][x][y])
f[i+1][x][y]=f[i][x][y]+sta+stb;
// dp_2-------------------------
sta=cal(i+1,x);stb=cal(i+1,y+1);
if(sta>=0 and stb>=0 and sta+stb+f[i][x][y]>f[i+1][x][y+1])
f[i+1][x][y+1]=f[i][x][y]+sta+stb;
// dp_3-------------------------
sta=cal(i+1,x+1);stb=cal(i+1,y);
if(sta>=0 and stb>=0 and sta+stb+f[i][x][y]>f[i+1][x+1][y])
f[i+1][x+1][y]=f[i][x][y]+sta+stb;
// dp_4-------------------------
sta=cal(i+1,x+1);stb=cal(i+1,y+1);
if(sta>=0 and stb>=0 and sta+stb+f[i][x][y]>f[i+1][x+1][y+1])
f[i+1][x+1][y+1]=f[i][x][y]+sta+stb;
// dp_end-------------------------
}
}
}
printf("%d\n",f[n+m-1][m-1][m]+w[n][m]);
}
}
int main() {
freopen("maze.in","r",stdin);
freopen("maze.out","w",stdout);
n=read();m=read();
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
w[i][j]=read();
}
}
//my_bfs::print();
my_dp::work();
return 0;
}
【单词矩阵】(twofive.pas/c/cpp Time:1s Memory:256M)
【问题描述】
LJY开始研究英语的单词。对于包含字母A到Y各一次的单词S,将其从上到下从左到右写
在一个5*5的矩阵中,如单词ADJPTBEKQUCGLRVFINSWHMOXY写出来如下:
合法的
A D J P T
A D J P T
B E K Q U
B E G Q U
C G L R V
不合法的
C K L R V
F I N S W
F I N S W
H M O X Y
H M O X Y
若该矩阵满足每一行每一列的字母都是字典序递增的则满足LJY对优美的要求,如上述
单词就是优美的,而ADJPTBEGQUCKLRVFINSWHMOXY则不是(第二列不满足要求)。
LJY将所有优美的单词按字典序列出,从小到大编号1,2,……
请你完成以下两种任务:
1. 给定一个优美的单词,求其编号。
2. 给定一个编号,求对应的优美的单词。
【输入】
输入文件名为twofive.in。
输入第一行一个字母,W表示任务1,N表示任务2。
若是任务1,第二行是一个优美的单词,否则第二行是一个正整数,表示某个优美的单
词的编号,保证该数不超过优美的单词的总数。
【输出】
输出文件名为twofive.txt。
输出仅一行,若是任务1,输出对应编号,否则输出对应的优美的单词
【输入输出样例】
twofive.in
W
ABCDEFGHIJKLMNOPQRSUTVWXY
twofive.txt
2
twofive.in
N
20
twofive.txt
ABCDEFGHIJKLMNOPQSUWRTVXY
【数据范围】
保证数据合法。保证数据有梯度。
【题解】
(来源于网络)
以下叙述中,“单词”均指合法单词。
举个例子说明:若为单词转编码,如求单词ACF……的编码,则设一累加器,先累加以AB开头的单词的个数,再累加以ACB开头的单词的个数(这个数为0,但若已知6个字母的位置,B拐到了第2行,则可能不为0),再累加以ACD开头的单词的个数,再累加以ACE开头的单词的个数……最后加1即得答案。若为编码转单词,如求第n个单词,同样设一累加器s,先累加以AB开头的单词的个数,若
s≥n
了,说明第二个字母就是B,否则继续累加以AC开头的单词的个数……直到
s≥n
,这样第二个字母就确定了。将最后一次累加的数减去,用类似的方法确定第三、第四……个字母,直至结束。
现在的问题是:如何求出以某给定序列开头的单词的个数?这个问题是用记忆化搜索解决的。用
f[a,b,c,d,e](5>=a>=b>=c>=d>=e>=0)
表示把前
a+b+c+d+e
个字母填入第1行的前a个格,第2行的前b个格……第5行的前e个格,且已经确定位置的字母各就各位时可能的单词数,那么
f[0,0,0,0,0]
就表示以给定序列开头的单词数。下面以求以AC开头的单词数为例说明递归求f数组的方法:
第一层递归安置字母A。因其位置已固定,故
f[0,0,0,0,0]=f[1,0,0,0,0]
,进入第二层递归计算
f[1,0,0,0,0]
。
第二层递归安置字母B。B的位置尚未固定,于是枚举所有合法位置(合法位置指左、上方均已填有字母的位置,认为第0行与第0列均已填满。此例中为12、21),分别进入第三层递归计算
f[2,0,0,0,0]
(这个值等于0,下面会讨论其原因)与
f[1,1,0,0,0]
。
f[1,0,0,0,0]
即等于这二者之和。
第三层递归安置字母C。这层递归的过程与第一层递归类似。更深层递归的过程与第二层递归类似。若在某一次递归中,需要计算的f值已经算出,则不必再递归下去,直接退出即可。
【代码】
#include<cstdio>
const char alphabet[]="ABCDEFGHIJKLMNOPQRSTUVWXY";
using namespace std;
int f[6][6][6][6][6],row[6],xpos[30],ypos[30];
int state[26][50][6];
char task,s[30],ans[30];
int snum[26];
int a,b,c,d,e,ANSWER=1;
inline int DP()
{
f[5][5][5][5][5]=1;
for (int sum=24;sum>=0;sum--)
for (int nows=1;nows<=snum[sum];nows++)
{
row[1]=state[sum][nows][1];
row[2]=state[sum][nows][2];
row[3]=state[sum][nows][3];
row[4]=state[sum][nows][4];
row[5]=state[sum][nows][5];
row[0]=5;
a=row[1];b=row[2];c=row[3];d=row[4];e=row[5];
f[a][b][c][d][e]=0;
if (!xpos[sum])
{
if (a<5) f[a][b][c][d][e]+=f[a+1][b][c][d][e];
if (b<a) f[a][b][c][d][e]+=f[a][b+1][c][d][e];
if (c<b) f[a][b][c][d][e]+=f[a][b][c+1][d][e];
if (d<c) f[a][b][c][d][e]+=f[a][b][c][d+1][e];
if (e<d) f[a][b][c][d][e]+=f[a][b][c][d][e+1];
}
else
{
if (row[xpos[sum]]<row[xpos[sum]-1]&&row[xpos[sum]]+1==ypos[sum])
{
++row[xpos[sum]];
f[a][b][c][d][e]+=f[row[1]][row[2]][row[3]][row[4]][row[5]];
}
}
}
return f[0][0][0][0][0];
}
inline void init()
{
for (int i1=0;i1<6;i1++)
for (int i2=0;i2<=i1;i2++)
for (int i3=0;i3<=i2;i3++)
for (int i4=0;i4<=i3;i4++)
for (int i5=0;i5<=i4;i5++)
{
int sum=i1+i2+i3+i4+i5;
++snum[sum];
state[sum][snum[sum]][1]=i1;
state[sum][snum[sum]][2]=i2;
state[sum][snum[sum]][3]=i3;
state[sum][snum[sum]][4]=i4;
state[sum][snum[sum]][5]=i5;
}
}
void num2word()
{
for (int i=0;i<25;i++)
{
xpos[i]=0;ypos[i]=0;
}
int n;
scanf("%d",&n);
for (int i=1;i<6;i++)
for (int j=1;j<6;j++)
for (int ch=0;ch<25;ch++)
if (!xpos[ch])
{
xpos[ch]=i;ypos[ch]=j;
int temp=DP();
if (n>temp) n-=temp;
else break;
xpos[ch]=0;ypos[ch]=0;
}
for (int i=0;i<25;i++)
ans[(xpos[i]-1)*5+ypos[i]-1]=alphabet[i];
printf("%s\n",&ans);
}
int word2num()
{
char tmp;
scanf("%c",&tmp);
for (int i=1;i<6;i++)
for (int j=1;j<6;j++)
{
scanf("%c",&tmp);
int nowch=tmp-65;
for (int ch=0;ch<nowch;ch++)
if (!xpos[ch])
{
xpos[ch]=i;ypos[ch]=j;
ANSWER+=DP();
xpos[ch]=0;ypos[ch]=0;
}
xpos[nowch]=i;ypos[nowch]=j;
}
return ANSWER;
}
int main()
{
freopen("twofive.in","r",stdin);
freopen("twofive.out","w",stdout);
init();
scanf("%c",&task);
if (task=='N') num2word();
else printf("%d\n",word2num());
return 0;
}
总结
这次考试题目除了最后一题外整体还是不太难的,最后一题的确是一道好题,而且让我对dp的思想似乎有了一种新的突破,但是还需继续努力。