CSP2019 练习赛
3 小时完成
(请选手务必仔细阅读本页内容)
一. 题目概况
中文题目名称 | 碎片 | 龙珠 | 仓库 |
英文题目名称 | piece | dragon | warehouse |
可执行文件名 | piece | dragon | warehouse |
输入文件名 | piece.in | dragon.in | warehouse.in |
输出文件名 | piece.out | dragon.out | warehouse.out |
每个测试点时限 | 1 秒 | 1 秒 | 1 秒 |
测试点数目 | 10 | 10 | 10 |
每个测试点分值 | 10 | 10 | 10 |
附加样例文件 | 有 | 有 | 有 |
结果比较方式 | 全文比较 过滤行末空格 及文末回车 |
全文比较 过滤行末空格 及文末回车 |
全文比较 过滤行末空格 及文末回车 |
题目类型 | 传统 | 传统 | 传统 |
二. 提交源程序文件名
对于 pascal 语言 | piece.pas | dragon.pas | warehouse.pas |
对于 C 语言 | piece.c | dragon.c | warehouse.c |
对于 C++语言 | piece.cpp | dragon.cpp | warehouse.cpp |
三. 运行内存限制
内存上限 | 128M | 128M | 256M |
四. 注意事项
1、 文件名(程序名和输入输出文件名)必须使用小写。
2、 C/C++中函数 main()的返回值类型必须是 int,程序正常结束时的返回值必须是 0。
3、 全国统一评测时采用的机器配置为: CPU 1.9GHz,内存 1G,上述时限以此配置为准。各省在自测时可
根据具体配置调整时限。
模拟赛 提高组
1. 碎片
【问题描述】
给你一个由字母构成的字符串 S。每一步操作,我们可以擦除其中一个子序列。 单要求
被擦除的子序列必须是一个回文词。问,要擦除掉整个字符串,最少需要多少步操作?
例如,将“abcba”从“abyczbea”中擦除,就是合法的一步。
【输入】
一行,一个字符串。
【输出】
一个整数。表示最小操作次数。
【输入输出样例】
输入样例 1 | 样例输入 2 | 样例输入 3 |
aa | abb | abyazbya |
样例输出 1 | 样例输出 2 | 样例输出 3 |
1 | 2 | 2 |
【样例说明】无
【数据范围】
对于 100% 的数据: 字符串的长度<=16
1.碎片
状压dp
n<=16,考虑状态压缩
对于集合s,其二进制中,1表示对应位置的字母在集合中,0表示不再
若s中对应字母能构成回文串,则mark[s]=true,否则为false
对于长度为n的字符串,预处理s是否为回文集合
l=0;
r=n-1;
while(l<=r)
{
while(!(s&(1<<l))&&l<=r)l++;
while(!(s&(1<<r))&&r>=l)r--;
if(l>r||l==r)break;
if(a[l]!=a[r])return false;
l++;r--;
}
return true;
动规:
f[i]表示将集合i的元素擦除,最少所需操作步数。
f[i]=min{ f[i^j]+1 } 其中j是i的子集
for(i=0;i<=tot;i++)
for(j=i;j>=0;j=i&j-1)
{
if(mark[j])F[i]=min(F[i],F[i^j]+1);
if(j==0)break;
}
2. 龙珠
【问题描述】
你得到了一个龙珠雷达,它会告诉你龙珠出现的时间和地点。
龙珠雷达的画面是一条水平的数轴,每一个窗口时间,数轴的某些点上会出现同一种
龙珠,每当你获得其中一颗龙珠,其它龙珠就会消失。下一个窗口时间,数轴上又会出现另
一种龙珠。总共有 n 个窗口时间,也就是总共有 n 种龙珠。
假设你会瞬间移动,你从数轴的 x 点移动到 y 点,耗时 0 秒,但是需要耗费|x-y|的体
力。同时,挖出一颗龙珠也需要耗费一定的体力。请问,最少耗费多少体力,就可以收集齐
所有种类的龙珠。
【输入】
第一行,三个整数 n,m,x,表示共有 n 个窗口时间,每个窗口时间会出现 m 个龙珠, x
是一开始你所处的位置。
接下来有两个 n*m 的矩阵。
对于第一个矩阵,坐标为(i,j)的数字表示第 i 个窗口时间,第 j 个龙珠的位置。
对于第二个矩阵,坐标为(i,j)的数字表示第 i 个窗口时间,挖取第 j 个龙珠所需的体力。
【输出】
一个整数,表示所需最小体力。
【输出输出样例】
输入样例 1 | 样例输入 2 |
3 2 5 2 3 4 1 1 3 1 1 1 3 4 2 |
5 3 55 71 88 27 45 6 71 43 36 1 85 12 72 69 7 45 19 4 12 5 17 11 3 13 11 6 15 4 9 5 16 |
样例输出 1 | 样例输出 2 |
8 | 113 |
【样例说明】无
【数据范围】
所有数据均为整数
数轴范围在 0 到 30000
挖一颗龙珠所需体力不超过 30000
结果保证在 int 范围
对于 50%的数据, 1<=n<=50,1<=m<=500。
对于 100%的数据, 1<=n<=50,1<=m<=5000。
2.龙珠
单调队列+DP
设dp[i][j]表示第i批龙珠中取第j个需要花费的最小体力。
dp[i][j] = min{ dp[i-1][k] + abs(pos[i-1][k]-pos[i][j]) } + cost[i][j];
如果枚举k的话总复杂度位m*n*n,会超时。
可以看出若每个状态只由上一层位置在其左边的状态的转移而来的话:
dp[i][j] = min { dp[i-1][k] + pos[i][j] - pos[i-1][k] } + cost[i][j]
= min { dp[i-1][k] - pos[i-1][k] } + pos[i][j] + cost[i][j]
dp[i-1][k]-pos[i-1][k]是个确定的值,就是相当于求位置在pos[i][j]左边的上一层状态中值最小的,可以用个单调队列维护。由右边转移来的类似,再处理一遍右边转移来的取最优。
因为要对同一层的点排序,所以总复杂度是m*n。
3. 仓库
(warehouse.pas/c/cpp)
【问题描述】
某星系有 n 个星球,星球以及星球间的航线形成一棵树。
从星球 a 到星球 b 要花费[dis(a,b) Xor M]秒。(dis(a,b)表示 ab 间的航线长度,
Xor 为位运算中的异或) 。
请计算星球 i(1<=i<=n)到其它所有星球花费的时间之和。
【输入】
第一行包含两个正整数 n, M。
接下来 n-1 行,每行 3 个正整数 a,b,c,表示 a, b 之间的航线长度为 c。
【输出】
n 行,每行一个整数,表示星球 i 到其它所有星球花费的时间之和。 保证答案不超过
2*10^9
【输出输出样例】
输入样例 1 | 样例输入 2 |
4 0 1 2 1 1 3 2 1 4 3 |
6 3 1 2 2 1 3 1 2 4 3 2 5 1 5 6 2 |
样例输出 1 | 样例输出 2 |
6 8 10 12 |
15 3 19 23 17 17 |
【数据范围】
N M
6 | 0 |
100 | 5 |
2000 | 9 |
50000 | 0 |
50000 | 0 |
50000 | 1 |
50000 | 6 |
100000 | 10 |
100000 | 13 |
100000 | 15 |
树形DP
算法1:
不会写函数的小伙伴们,我们只需要写个floyd,就有10分啦!
算法2:
在算法1的基础上,我们对每条边处理一下xor,就有20分啦!
算法3:
简单的树形DP,或者你会nlogn的dij,处理完每个点到其它点的最短路后再加上xor,那么这样就有30分啦!
算法4:
第4、5个点无需xor,那么我们树形DP扫一个节点与其它所有节点的路径长度之和,可以合并信息,最终均摊O(1),50分到手。
算法5:
第6个点xor 1,那么我们树形DP到一个点时记录有多少个0,多少个1,然后每当一条路径到2,那部分就再记录一个值,60分到手。
算法6:
如果你第6个点都过了,却没有满分,笨死啦!
一样的嘛,就是原来的“0”、“1”、大于等于2变成了0~16么~~
我们先回忆一下,如果异或值为0(就是没有异或)的情况应该怎么dp:
假如v是u的儿子,他们之间边长为w,
size[u]表示u为根的子树中节点的个数
tot1[i]表示i子树内所有节点到i的距离和,
tot2[i]表示所有非i子树内的节点到i的距离和。那么有:
tot1[u] = sigma{ tot1[v] } + ( size[u] - 1 ) * w = sigma{ tot1[v] + size[v] * w }
tot2[v] = tot2[u] + ( n - size[v] ) * w
直接dfs计算。
那么如果有异或的话,应该怎么搞呢?
我们发现虽然n挺大、c挺大,但是异或的值M很小,最大只有15。所以这么小的异或值可以来搞些什么呢?
我们知道对每两点间的dis,只有mod 16的部分会被异或影响,
那么我们用树dp求每个点到其他所有点的dis值的mod 16后1~16的各有多少个 和 >16部分(/ 16)的值.
但我们发现m很小,最多也就2^4-1,也就是1111
异或只会影响后4位
于是设f[i][j]为i到其它点,后4位状态为j的路径条数
显然列出:
f[u][0]=1
f[u][(j+w)%16]+=f[v][j]
这还只算了子树
f[v][(j+w)%16]+=(f[u][j]-f[v][((j-w)%16+16)%16])
解释一下,u这个点的所有f[u][j]除去v的方案再加入v,等于把v的方案
从子树补到整颗树