集训模拟赛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\) 中的任意两个数都互质。并且他要使

\[\sum_{i=1}^{N} {|A_i-B_i|} \]

最小,请输出最小值。

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\)的所有情况,那么状态转移方程就是:

\[f[i][s] = min(f[i][s],f[i-1][j]+abs(a[i]-k)); \]

最后枚举每个状态,取最小值。
不要忘了,如果\(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;
    
}

posted @ 2020-07-06 20:42  Vocanda  阅读(162)  评论(0编辑  收藏  举报