集训模拟赛9
前言
又是学新知识的一天呢……
NO.1 精灵魔法
这个题貌似是一个系列的其中一个,有兴趣可以网上搜一下
题目描述
\(Tristan\) 解决了英灵殿的守卫安排后,便到达了静谧的精灵领地——\(Alfheim\)。由于\(Midgard\) 处在 \(Alfheim\) 和冥界 \(Hel\) 的中间,精灵族领地尚未受到冥界恶灵的侵入。族长 \(Galanodel\) 为了帮助米德加尔特抵御外敌,对邪恶亡灵军团使用了高等魔法,从而使得亡灵军团每个士兵的行进速度变得不一致,从而打乱冥王 \(Hel\)安排的最佳阵型。
由于这个军团离 \(Midgard\)还很远,因此在抵达 \(Midgard\) 之前,对于\(A\),\(B\) 两个亡灵,若 \(A\) 的初始位置在 \(B\) 后面且 \(A\) 的速度比 \(B\) 快,\(A\) 就会冲到 \(B\) 的前面去。现在 \(Galanodel\)想知道,会有多少对亡灵之间出现反超现象?
Input
第一行一个整数 \(n\),表示排成一队的邪恶亡灵军团有多少人。
第二行 \(n\)个整数,\(a_i\),表示邪恶亡灵们在数轴上的初始坐标。数据保证这些坐标全部不同。亡灵军团向数轴正方向前进。
第三行 \(n\)个整数,\(v_i\),表示邪恶亡灵们的行进速度。
Output
一行一个正整数 \(k\),表示反超的个数。
Sample Input
3
1 2 3
2 1 3
Sample Output
1
Hint
对于 \(30\%\)的数据,\(1\le N\le 1000\);
对于\(100\%\)的数据,\(1\le N\le 10^5\)。
所有数据的绝对值均不超过 \(maxlongint\)。
分析
因为亡灵位置靠后速度比靠前的亡灵速度大的话就是能够反超,所以根据这个性质我们就可以想出来利用归并排序来进行求解,当然树状数组和线段树也是可以的,这里先分析一下归并排序的方法:
其实这个题相当与归并排序的一个板子题,需要处理的就是进行一下离散化,然后就可以愉快的递归排序了。我们首先开一个结构体存储位置和速度,然后根据位置进行排序,因为位置靠后的速度比前边大的就是反超,所以就可以转化为求逆序对的个数即可,当然,我们排序的过程就已经进行好了离散化了,只需要用一个记录数组来记录下来排序后的所有速度即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e5+10;
int n;
struct Node{
ll pos,v;
}a[maxn];
ll ans;
ll jl[maxn];
ll b[maxn];
bool cmp(Node a,Node b){
return a.pos<b.pos;
}
void Merge(int l,int mid,int r){
int i=l,j=mid+1,k=0;//左边从l到mid,右边从mid+1到r
while(i<=mid && j<=r){//左右都不为空
if(jl[i]<=jl[j])b[++k] = jl[i++];//左边的小于右边的,另一个数组记录下小的数
else {//左边大于右边
ans+=mid-i+1;//从左边当前位置到mid和右边的全部能组成逆序对
b[++k] = jl[j++];//另一个数组记录小的数
}
}
while(i<=mid){//没有扫完就继续记录
b[++k] = jl[i++];
}
while(j<=r){//同上
b[++k] = jl[j++];
}
for(i=l,k=1;i<=r;++i,++k){//重新记录排好序的数组
jl[i] = b[k];
}
}
void Merge_sort(int l,int r){//归并排序递归
if(l<r){
int mid = (l+r)>>1;
Merge_sort(l,mid);
Merge_sort(mid+1,r);
Merge(l,mid,r);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i].pos);
}
for(int i=1;i<=n;++i){
scanf("%lld",&a[i].v);
}
sort(a+1,a+n+1,cmp);//排序进行离散化
for(int i=1;i<=n;++i){//记录每个点的速度
jl[i] = a[i].v;
}
Merge_sort(1,n);
printf("%lld\n",ans);
return 0;
}
NO.2 最小环
看到题目会想到\(Floyd\),看到数据范围还是算了吧,所以用最短路。
题目描述
她走的悄无声息,消失的无影无踪。
至今我还记得那一段时间,我们一起旅游,一起游遍山水。到了最终的景点,她却悄无声息地消失了,只剩我孤身而返。
现在我还记得,那个旅游区可以表示为一张由\(n\)个节点\(m\)条边组成无向图。我故地重游,却发现自己只想尽快地结束这次旅游。我从景区的出发点(即 \(1\) 号节点)出发,却只想找出最短的一条回路重新回到出发点,并且中途不重复经过任意一条边。
即:我想找出从出发点到出发点的小环。
Input
每个测试点有多组测试数据。
第一行有一个正整数\(T\),\((T\le 10)\),表示数据组数。
接下来对于每组数据,第一行有两个正整数 \(n,m\),\((n\le 10^4,m\le 4×10^4)\) 分别代表图的点数和边数。
接下来有\(m\)行,每行三个整数\(u,v,d\)表示\(u,v\)之间存在一条长度为 \(d,(d\le 10^3)\)的路径。保证不存在重边,自环。
Output
对于每组测试数据,输出题目中所求的最小环的长度。无解输出 \(−1\) 。
Sample Input
2
3 3
1 2 1
2 3 1
3 1 1
4 5
1 2 2
2 3 2
3 4 2
1 4 2
1 3 5
Sample Output
3
8
分析
题目超级狗血……我从网上找的完整版。直接看题:
首先肯定是求最小环了,但是\(Floyd\)又不能用,所以考虑利用最短路。
那么怎么求呢,因为如果有环的话,无向图还不能经过重边,所以我们把出去的那个点的回路断开,也就是置为极大值,然后跑从出去的那个点到\(1\)号节点的最短路,最后统计最小值就行了。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
struct Node{
int v,next,val;
}e[maxn<<1];
int ans = 0x3f3f3f3f;
int head[maxn],dis[maxn],vis[maxn];
int tot=1;
void Add(int x,int y,int z){
e[++tot].v = y;
e[tot].next = head[x];
head[x] = tot;
e[tot].val = z;
}
priority_queue<pair<int,int> >q;
void Dij(int x){//堆优化最短路
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[x] = 0;
q.push(make_pair(0,x));
while(!q.empty()){
int y = q.top().second;
q.pop();
if(vis[y])continue;
vis[y] = 1;
for(int i=head[y];i;i=e[i].next){
int v = e[i].v;
if(dis[v] > dis[y]+e[i].val){
dis[v] = dis[y] + e[i].val;
q.push(make_pair(-dis[v],v));
}
}
}
}
void Init(){
memset(e,0,sizeof(e));
memset(head,0,sizeof(head));
tot=1;ans=0x3f3f3f3f;
}
void Solve(){
int n,m,s;
cin>>n>>m;
for(int i=1;i<=m;++i){//双向建图
int x,y,z;
cin>>x>>y>>z;
Add(x,y,z);
Add(y,x,z);
}
for(int i=head[1];i;i=e[i].next){
int v = e[i].v;
int w = e[i].val;
e[i^1].val = 0x3f3f3f3f;//出点到1的反向边权置为极大,相当与断开
Dij(v);//跑最短路
ans = min(ans,dis[1]+w);//最后加上一段的边权
e[i^1].val = w;//恢复
}
if(ans == 0x3f3f3f3f)printf("-1\n");//ans没变说明没有符合要求的情况
else printf("%d\n",ans);
}
int main(){
int T;
cin>>T;
while(T--){
Init();
Solve();
}
return 0;
}
NO.3 LGTB 与序列
题目描述
\(LGTB\) 有一个长度为 \(N\) 的序列 \(A\),现在他想构造一个新的长度为 \(N\) 的序列 \(B\),使得 \(B\) 中的任意两个数都互质。并且他要使
最小,请输出最小值。
Input
第一行包含一个数 \(N\)代表序列初始长度。
接下来一行包含 \(N\)个数 \(A_1,A_2,…,A_N\),代表序列 \(A\) 。
Output
输出包含一行,代表最小值。
Sample Input
5
1 6 4 2 8
Sample Output
3
Hint
样例解释:\(B=\{1,5,3,2,7\}\),1与任何数都互质。
对于 \(40\%\)的数据, \(1\le N\le 10\)。
对于 \(100\%\)的数据, \(1\le N\le 100,1\le A_i\le 30\)。
分析
看到这范围,可能很少会有人想到状压\(dp\),但是深入思考一下,因为\(A_i\le 30\),而\(B_i\)最小为\(1\),所以我们可以得到\(B_i\)最大为\(58\),因为一旦超过\(58\),差的绝对值肯定还不如\(1\),因为\(1\)是质数,所以最大到\(58\),然后我们找到其中的质数打表打出来,一共是\(16\)个,那么我们就可以用这个作为状态,即含有哪个质因子。
所以我们定义\(f[i][j]\)为前\(i\)个数,使用质因子状态为\(j\)的答案,然后开始愉快的状态转移。
我们首先初始化出来\(1\)到\(58\)含有质因子的状态,存在一个数组里然后枚举质因子和状态,进行状态转移,假设\(j\)为当前状态,\(k\)为枚举\(B_i\)的所有情况,那么状态转移方程就是:
最后枚举每个状态,取最小值。
不要忘了,如果\(N\)大于16的话,后边都取\(1\),还要继续统计。因为要使绝对值最小,所以降序排序,最终的答案就是最小的。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 110;
int a[maxn];
int f[17][1<<17];
int n,prime[17];
int ste[maxn];
void Init(){//打表初始化
prime[0] = 0;
prime[1] = 2;
prime[2] = 3;
prime[3] = 5;
prime[4] = 7;
prime[5] = 11;
prime[6] = 13;
prime[7] = 17;
prime[8] = 19;
prime[9] = 23;
prime[10] = 29;
prime[11] = 31;
prime[12] = 37;
prime[13] = 41;
prime[14] = 43;
prime[15] = 47;
prime[16] = 53;
}
bool cmp(int a,int b){//降序排序
return a>b;
}
int main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
}
sort(a+1,a+n+1,cmp);
Init();
for(int i=1;i<=58;++i){
for(int j=1;j<=16;++j){
if(i<prime[j])break;
else if(i%prime[j] == 0){
ste[i] |= (1<<(j-1));//从1到58每个数的状态预处理
}
}
}
memset(f,0x3f,sizeof(f));
f[0][0] = 0;//f数组初始化
int ms = (1<<16)-1;//总状态
for(int i=1;i<=min(n,16);++i){//枚举质因子
for(int j=0;j<=ms;++j){//枚举状态
for(int k=1;k<=58;++k){//枚举所有Bi的可能
if(!(ste[k]&j)){//上一状态还没有k的质因子
int s = j|ste[k];//使用k的质因子
f[i][s] = min(f[i][s],f[i-1][j]+abs(a[i]-k));//转移
}
}
}
}
int ans = 0x3f3f3f3f;
for(int i=0;i<=ms;++i){//枚举所有状态,统计答案
ans = min(ans,f[min(16,n)][i]);
}
if(n>16){//大于16继续统计
for(int i=17;i<=n;++i){
ans += abs(a[i]-1);
}
}
cout<<ans<<endl;
}
NO.4 步步为零
真就步步为零……
题目描述
你是否听说过这个游戏?游戏者在一张特殊的表格中按照规则跳动,使得跳到的数字经过加号和减号的连接,尽可能的逼近零。表格通常是如图 \(1.1\) 所示的形状,大小由中间一行的方格数 \(N\) 决定(图 \(1.1\) 就是一个 \(N=4\)的例子)。
游戏者通常是从最下面的方格出发,按照如图 \(1.2\)所示的规则在表格中跳动,当游戏者跳到最顶端的方格时,游戏结束。在游戏未结束前,游戏者不允许跳到表格外。
将游戏者跳到的 \(2\times N−1\)个数字依次写下来,在每两个相邻的数字中间加上加号或减号,使得计算结果最接近零。
例如对于图 \(1.1\)
所示的表格,最好的跳动及计算方案是:\(7+8+(−5)+(−2)−5−1−2=0\) 或 \(7+10+(−7)−6+(−3)−3+2=0\) 或 \(7+10+(−5)−10−5+1+2=0\) 或 \(7+10+(−5)+(−2)−5−3−2=0\) 。
Input
输入文件的第一行是 \(N(N\le 50)\)
接下来 \(2\times N−1\) 行给出了表格中每行的每个方格中的数字
第 \(i+1\) 行的第 \(j\) 个数字对应于表格中第 \(i\) 行的第 \(j\)个数字。
文件中第二行的数字表示的是表格顶端的方格中的数字。文件中所有的数字都是整数,同一行相邻的两个数字间用空格符隔开。
Output
输出文件只有一行,是你所求出的最接近零的计算结果的绝对值。
Sample Input
4
2
3 1
-3 5 7
6 10 -2 20
-7 -5 -8
10 8
7
Sample Output
0
Hint
表格中的所有数字大于等于\(−50\),小于等于\(50\)。
分析
这个题其实就是dp,还是个线性的,但是转移非常的费劲和难想。首先看一个简单的图:
要想求从\(1\)到\(3\)的最小绝对值,那么肯定是从\(1\)到\(2\)和\(1\)到\(4\)最后加上或者减去3来得到,我们根据这个进行转移。
需要注意的是,我们需要开一个数组来判断某一阶段能否到达一个值。
因为有卡内存,所以开滚动数组。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 36;
int a[maxn<<1][maxn],n,tot;
bool f[maxn<<1][maxn][6005];//f[i][j][k]表示从最后一行到i行j列能否组成k
bool judge(int x){//判断是否超过最大或最小值
if(x<0 || x>2*tot)return 0;
return 1;
}
int main(){
cin>>n;
for(int i=1;i<=n;++i){
int Max = 0;//求出每行最大值
for(int j=1;j<=i;++j){
cin>>a[i][j];
a[i][j] = abs(a[i][j]);//变为正数好处理
Max = max(Max,a[i][j]);
}
tot += Max;//记录总和
}
for(int i=1;i<n;++i){
int Max = 0;//同上
for(int j=1;j<=n-i;++j){
cin>>a[n+i][j];
a[n+i][j] = abs(a[n+i][j]);
Max = max(Max,a[n+i][j]);
}
tot+=Max;
}
f[2*n-1][1][tot] = 1;//第一个状态为真,为好处理,全部加上tot,那么tot为0,此时0到tot×2为-tot~tot
int now = 0;
for(int i=2*n-1;i>n;--i){//下边的n-1行
for(int j=1;j<=2*n-i;++j){//列
for(int k=0;k<=2*tot;++k){
if(f[i][j][k]){//状态合法
now = k+a[i][j];
if(judge(now)){//当前没超过最大低于最小
f[i-1][j][now] = f[i-1][j+1][now] = 1;
}
now = k-a[i][j];
if(judge(now)){//同上
f[i-1][j][now] = f[i-1][j+1][now] = 1;
}
}
}
}
}
for(int i=n;i>=1;i--){//上边的n行
for(int j=1;j<=i;++j){
for(int k=0;k<=2*tot;++k){
if(f[i][j][k]){
now=k+a[i][j];
if(judge(now))
f[i-1][j][now]=f[i-1][j-1][now]=1;
now=k-a[i][j];
if(judge(now))
f[i-1][j][now]=f[i-1][j-1][now]=1;
}
}
}
}
int ans = 0x3f3f3f3f;
for(int i=0;i<=2*tot;++i){
if(f[0][0][i] || f[0][1][i]){//符合要求就取最小值。
ans = min(ans,abs(i-tot));
}
}
cout<<ans<<endl;
}