总结:
T1 升降梯上(updown),考场 AC
T2 重叠的图像(Frame Up),读错题了,惨爆 44pts
T3 小奇回地球(earth),hzwer 大佬的神仙题,完全不会做,但找不到 OJ 能测...
T4 道路(road),最小生成树板子。然而刚开始没看出来,浪费了很长时间。好在最后 AC 了
T1 升降梯上(updown)
题目描述
有一个 (N) 层的塔,升降梯在每层都有一个停靠点。手柄有 (M) 个控制槽,第 (i) 个控制槽旁边标着一个数 (C_i),满足 (C_1<C_2<C_3<...<C_M)。如果 (C_i>0),表示手柄扳动到该槽时,电梯将上升 (C_i) 层;如果 (C_i<0),表示手柄扳动到该槽时,电梯将下降 (-C_i) 层:并且一定存在一个 (C_i=0),手柄最初就位于此槽中。注意升降梯只能在 (1-N) 层之间移动
电梯每移动 (1) 层,需要花费 (2) 秒钟时间,而手柄从一个控制槽扳到相邻的槽,需要花费 (1) 秒钟时间。探险队员现在在 (1) 层,并且想尽快到达 (N) 层,他们想知道 (1) 层到 (N) 层至少需要多长时间?
输入格式
第一行两个正整数 (N), (M)
第二行 (M) 个整数 (C_1, C_2, C_3, ..., C_m)
输出格式
输出一个整数表示答案,即至少需要多长时间。若不可能到达输出 (-1)
数据范围
对于 (30\%) 的数据,满足 (1 leq N leq 10, space 2 leq M leq 5)
对于 (100\%) 的数据,满足 (1 leq N leq 1000, space 2 leq M leq 20, space -N<C_1<C_2<...<C_M<N)
Solution
由一个普通的 (mathrm{dijkstra}) 稍加更改得到。因为手柄的位置也应该关心,所以记 (mathrm{vis}) 时也应该标记当前手柄位置。那么入队条件变成了更新最短路径或手柄位置未标记。相应的,当前的步数也应该入队。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <cmath>
#define LL long long
using namespace std;
const int N = 233333;
LL n, m, st = 0, c[N], dis[N], vis[N], use[N][30];
struct node //dijkstra 板子重载运算符,按照 dis 从小到大排序
{
LL x, dis, now;
friend bool operator < (node a, node b)
{
return a.dis > b.dis;
}
};
LL dijkstra()
{
priority_queue <node> q;
memset(use, 0, sizeof(use));
memset(dis, 0x7f7f7f, sizeof(dis));
memset(vis, 0, sizeof(vis));
q.push((node) { 1, 0, st });
dis[1] = 0, use[1][st] = 1;
while(!q.empty())
{
node tmp = q.top();
q.pop();
if(vis[tmp.x]) continue;
vis[tmp.x] = 1;
for(int i = 1; i <= m; i++)
{
if(c[i] == 0) continue;
int len = abs(tmp.now - i) + (abs(c[i])) * 2, nxt = tmp.x + c[i]; //计算代价
if(nxt > n || nxt < 1) continue; //判断越界
if(len + tmp.dis < dis[nxt] || !use[nxt][i])
{
dis[nxt] = min(dis[nxt], len + tmp.dis);
use[nxt][i] = 1;
if(!vis[nxt]) q.push((node) { nxt, len + tmp.dis, i });
}
}
}
if(dis[n] == dis[0]) return -1;
return dis[n];
}
int main()
{
freopen("updown.in", "r", stdin);
freopen("updown.out", "w", stdout);
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= m; i++)
{
scanf("%lld", &c[i]);
if(c[i] == 0) st = i;
}
printf("%lld
", dijkstra());
fclose(stdin);
fclose(stdout);
return 0;
}
T2 重叠的图像(frame)
题面都在上面的链接里了...
Solution
和容易想到暴枚 (26!),很明显会 TLE(但也能拿很多分啊喂)
考虑加一些限制条件,形如 (A) 的图像在 (B) 的图像上方。这东西有什么用呢?如果满足 (A) 在 (B) 上面就从 (B) 连一条边到 (A),那么从下往上放图像的过程中,只有放了 (B) 才能放 (A).
是不是很熟悉?这就是拓扑排序的过程!每次从队列里取出一个元素,这个元素是不确定的,这就造就了此题的多解。然而麻烦就麻烦再这里。因为多解,所以需要 (mathrm{dfs}) 暴力枚举所有可能情况,然后发现 (mathrm{stl}) 又不好用了,只能手写队列拓扑。细节贼多,都在代码里。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2333;
char a[N][N];
int n, m, Num = 0, tot = 0, res = 0;
int vis[N], lx[N], rx[N], ly[N], ry[N], e[N][N];
int q[N], du[N], use[N];
string ans[2333333]; //答案数组一定要开足够大!
void dfs(int num, int stp)
{
if(stp == Num)
{
res++;
ans[res] = "";
for(int i = 1; i <= stp; i++) ans[res] += (char)(use[i]);
return ;
}
//这里不能对 q 排序,要不然 dfs 回溯就乱套了
for(int i = 1; i <= num; i++) //取第 i 个元素
{
int x = q[i], cnt = num - 1;
for(int j = i; j < num; j++) q[j] = q[j + 1];
for(int j = 'A'; j <= 'Z'; j++)
{
if(vis[j] && e[x][j])
{
du[j]--;
if(du[j] == 0) q[++cnt] = j;
}
}
use[stp + 1] = x;
dfs(cnt, stp + 1);
//回溯
for(int j = num; j > i; j--) q[j] = q[j - 1];
q[i] = x;
for(int j = 'A'; j <= 'Z'; j++)
if(vis[j] && e[x][j]) du[j]++;
}
}
int main()
{
scanf("%d%d", &n, &m);
memset(e, 0, sizeof(e));
memset(lx, 0x3f, sizeof(lx));
memset(ly, 0x3f, sizeof(ly));
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
cin >> a[i][j];
if(a[i][j] >= 'A' && a[i][j] <= 'Z')
{
if(!vis[a[i][j]])
{
vis[a[i][j]] = 1;
Num++;
}
//标记每个字母对应的矩阵
lx[a[i][j]] = min(lx[a[i][j]], i);
ly[a[i][j]] = min(ly[a[i][j]], j);
rx[a[i][j]] = max(rx[a[i][j]], i);
ry[a[i][j]] = max(ry[a[i][j]], j);
}
}
for(int i = 'A'; i <= 'Z'; i++) //添加限制条件
{
if(!vis[i]) continue;
for(int j = ly[i]; j <= ry[i]; j++)
{
char ch1 = a[lx[i]][j], ch2 = a[rx[i]][j];
if(vis[ch1] && ch1 != i) e[i][ch1] = 1;
if(vis[ch2] && ch2 != i) e[i][ch2] = 1;
}
for(int j = lx[i]; j <= rx[i]; j++)
{
char ch1 = a[j][ly[i]], ch2 = a[j][ry[i]];
if(vis[ch1] && ch1 != i) e[i][ch1] = 1;
if(vis[ch2] && ch2 != i) e[i][ch2] = 1;
}
}
memset(du, 0, sizeof(du));
for(int i = 'A'; i <= 'Z'; i++)
{
if(!vis[i]) continue;
for(int j = 'A'; j <= 'Z'; j++)
{
if(!vis[j]) continue;
if(e[i][j] == 1) du[j]++;
}
}
for(int i = 'A'; i <= 'Z'; i++)
{
if(!vis[i]) continue;
if(du[i] == 0) //入队要全!否则可能搜不出解
{
tot++;
q[tot] = i;
}
}
dfs(tot, 0);
sort(ans + 1, ans + res + 1); //按照字典序从小到大输出
for(int i = 1; i <= res; i++) cout << ans[i] << endl;
return 0;
}
T3 小奇回地球(earth)
啊这题完全不会呢...放上大佬 (mathrm{hzwer}) 的题解
考场代码也放不了了,因为考场上根本就没写代码 /dk
T4 道路(raod)/ 安慰奶牛
题目描述
(mathrm{John}) 变得非常懒,他不想再继续维护供奶牛之间供通行的道路。
道路被用来连接 (N(5 leq N leq 10000)) 个牧场,牧场被连续地编号为 (1..N. space)每一个牧场都是一个奶牛的家。
(mathrm{FJ}) 计划除去 (P(N-1 leq P leq 100,000)) 条道路中尽可能多的道路, 但是还要保持牧场之间的连通性。你首先要决定那些道路是需要保留的 (N-1) 条道路。第 (j) 条双向道路连接了牧场 (S_j) 和 (E_j(1 leq S_j leq N, space 1 leq E_j leq N, space S_j eq E_j)),而且走完它需要 (L_j (0 leq L_j leq 1,000)) 的时间。没有两个牧场是被一条以上的道路所连接。
奶牛们非常伤心,因为她们的交通系统被削减了. 你需要到每一个奶牛的住处去安慰她们. 每次 你到达第i个牧场的时候(即使你已经到过),你必须花去 (C_i (1 leq C_i leq 1000)) 的时间和奶牛交谈。你每个晚上都会在同一个牧场(这是供你选择的)过夜,直到奶牛们都从悲伤中缓过神来。在早上起来和晚上回去睡觉的时候,你都需要和在你睡觉的牧场的奶牛交谈一次。这样你才能完成你的交谈任务。
假设 (mathrm{Farmer John}) 采纳了你的建议,请计算出使所有奶牛都被安慰的最少时间。
输入格式
第 (1) 行,用空格隔开的两个整数 (N) 和 (P)
第 (2-N+1) 行,第 (i+1) 行包含了 (1) 个整数:(C_i)
第 (N+2-N+P+1) 行,第 (N+j+1) 行包含用空格隔开的三个整数: (S_j, space E_j, space L_j)
输出格式
共 (1) 行,一个整数,所需要的总时间(包含和在你所在的牧场的奶牛的两次谈话时间)
Solution
才开始以为是个最小生成树 + 树形dp,写完发现被 (mathrm{Hack}) 了。仔细一看原来是个最小生成树板子题...这题真是人均切啊,不得不 (mathrm{orz}) 各位巨佬 %%%
除根节点外,每个点被访问的次数 (=) 它儿子的个数 (+1). 根节点还会被额外访问 (1) 次。
稍微画几个图比较一下,可以得出每条边只会经过两次,即进子树一次,出子树一次。
综上,一条权值为 (w) 的边(设其连接的两个点编号为 (a, space b))对答案的贡献就是 (w imes 2 + C_a + C_b). 直接以贡献为权值最小生成树即可。
最后还要选一个根节点加上,显然选花费最小的。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;
const int N = 2333333;
struct edge { LL a, b, c; } e[N];
LL n, m, Sum = 0, cnt = 0, ans = 1e18, c[N], f[N], head[N];
bool cmp(edge a, edge b) { return a.c < b.c; }
LL find(LL x) { return f[x] == x ? x : f[x] = find(f[x]); }
int main()
{
freopen("road.in", "r", stdin);
freopen("road.out", "w", stdout);
scanf("%lld%lld", &n, &m);
memset(head, 0, sizeof(head));
for(int i = 1; i <= n; i++) scanf("%lld", &c[i]), f[i] = i;
for(int i = 1; i <= m; i++)
{
scanf("%lld%lld%lld", &e[i].a, &e[i].b, &e[i].c);
e[i].c = e[i].c * 2 + c[e[i].a] + c[e[i].b]; //根据分析修改权值
}
sort(e + 1, e + m + 1, cmp); //最小生成树板子
for(int i = 1; i <= m; i++)
{
LL fa = find(e[i].a), fb = find(e[i].b);
if(fa != fb) f[fa] = fb, Sum += e[i].c;
}
//找一个权值最小的点加上
for(int i = 1; i <= n; i++) ans = min(ans, c[i]);
printf("%lld", ans + Sum);
fclose(stdin);
fclose(stdout);
return 0;
}