2022年xtu程序设计实践3

2022年xtu程序设计实践3

前言:

发现光做题没有用,还是要写题解不断的回顾做题过程,做好总结。

oj动不动就T,是真的拉

最新更新:

2022-5-7 更新第 1 , 8 , 18 , 19 , 20 1,8,18,19,20 1,8,18,19,20 题。

2022-5-9 更新第 14 , 15 , 16 , 17 14,15,16,17 14,15,16,17 题。

2022-5-11 更新第 10 10 10 题。

2022-5-19 更新第 2 2 2 题。

1-逆序数(大数据)(套路题)

老套路了。逆序对 【总结】

  1. 归并排序,合并的时候处理贡献
  2. 树状数组 死活T掉了
  3. 线段树
  4. 排序二叉树 建议是平衡树。。。。

贴一份归并的逆序对,很早之前写的,不怕被查重(狗头保命)

#include <cstdio>

using namespace std;
const int lim = 10003;
int n;
int a[lim],k[lim];
int ans;

int read()
{
	int res = 0;
	char c = getchar();
	while(c > '9' || c < '0') c = getchar();
	while(c >= '0' && c <= '9')
	{
		res = res * 10 + c - 48;
		c = getchar();
	}
	return res;
}

void sort_gb(int l,int r)
{
	if (l == r) return;
	int mid = (l + r) >> 1;
	sort_gb(l,mid); sort_gb(mid+1,r);
	int i = l, j = mid + 1, p = l;
	while(i <= mid || j <= r)
	{
		if (i > mid) k[p++] = a[j++];
		else if (j > r) k[p++] = a[i++], ans += r - mid;
		else
		{
			if (a[i] <= a[j]) k[p++] = a[i++], ans += j - mid - 1;
			else k[p++] = a[j++];
		}
	}
	for (p = l; p <= r; ++p)
		a[p] = k[p];
}

int main()
{
	while(scanf("%d", &n) == 1)
	{
		if (n == 0) break;
		for (int i = 1; i <= n; ++i)
			a[i] = read();
		ans = 0;
		sort_gb(1,n);
		printf("%d\n",ans);
	}
	return 0;
}
2-Shortest Path (一点难)

考虑按顺序经过 { 1 , a 1 , a 2 , a 3 . . a k , n } \{1, a_{1}, a_{2}, a_{3}..a_{k},n \} {1,a1,a2,a3..ak,n} 个点,并且这 k + 2 k + 2 k+2 个点都只能经过一次,那么我们考虑跑 k + 1 k+1 k+1 次最短路,每一次最短路都不能经过这 k + 2 k +2 k+2 个点,除了起点和终点。

比如,我们要顺序经过 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 ,然后保证总路径最短,我们可以分解为: 1 − 2 1-2 12 2 − 3 2-3 23 3 − 4 3-4 34,这三段,跑三次最短路即可。但是要注意的是,在跑最短路的时候是禁止经过 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 这四个点的,当然每一段的起点和终点是可以经过的。我们在做 d i j k s t r a dijkstra dijkstra 的时候,标记一下哪些点不能用即可。

8-Black White Chess (有点难)

经典翻转棋。

前置知识 BFS介绍(图论) - OI Wiki (oi-wiki.org)

我们考虑到询问量巨大,不可能每次都进行搜索,那么就是说,我们需要预先处理出答案,然后输出。

思考过程如下:

  1. 反向思考,只考虑从一个询问的状态 s s s 翻转到全 0 0 0,也就说要是反向操作,就是也可也理解为从全 0 0 0 翻转到询问状态 s s s。那么我们就有了一个对所有状态来讲有共同的源点。我们可以从全 0 0 0 状态出发,进行搜索。这就是一个经典的 b f s bfs bfs,每条边的长度都是 1 1 1
  2. 考虑到翻转到全 1 1 1 的状态,可以再从全 1 1 1 的状态搜索一遍,然后和全 0 0 0 的搜索取小值,如果你可以分析到全 0 0 0 和全 1 1 1 其实是一样的。那么其实搜索一遍就够了。

其实全 0 0 0 的搜索树和全 1 1 1 的搜索树是一样的,只不过每一个节点的值都是取反了而已。

  1. 考虑怎么搜索优雅。我们考虑二进制压缩,即将一个二进制串对应到一个整数的二进制表示,那么每一个整数就可以表示一个搜索树的节点,对于每一个操作,就是异或上一个特定的值。

给代码:

#include <cstdio>

using namespace std;
const int lim = (1 << 16) + 4;
const int Rf = (1 << 16) - 1;
const int Rp[] = {15,15<<4,15<<8,15<<12,4369,4369<<1,4369<<2,4369<<3,
				  51,51<<1,51<<2,51<<4,51<<5,51<<6,51<<8,51<<9,51<<10}; //特点的值,可以手推试试。
int ans[lim];
int q[lim];
#define Min(x,y) ((x)<(y)?(x):(y))

void prime() // 事先bfs搜索出所有的答案。
{
	for (int i = 0; i < lim; ++i) ans[i] = 1e9;
	int l = 1, r = 1,t;
	q[1] = 0; // 队列
	ans[0] = 0;
	while(l <= r)
	{
		for (int i = 0; i < 17; ++i)
		{
			t = q[l] ^ Rp[i];
			if (ans[t] == 1e9)
			{
				ans[t] = ans[q[l]] + 1;
				q[++r] = t; // 新节点,入队
			}
		}
		l++; // 出队
	}
}

int read()
{
	int res = 0;
	char s[18];
	scanf("%s", s);
	for (int i = 0; i < 16; ++i)
		res = (res << 1) + (s[i] == '1' ? 1 : 0);	
	return res;
}

int main()
{
	prime();
	int T,a,p;
	scanf("%d", &T);
	while(T--)
	{
		a = read();
		p = Min(ans[a],ans[a^Rf]);
		printf("%d\n",p < 1e9 ? p : -1);
	}
	return 0;
}
10-Blocks (思维,有点难,线性dp)

参考ericxie解法2

询问很多,我们可以先把所有的情况算出来。

定义 d p dp dp 状态 dp[i][j][k] 为已经精确使用了 i 个积木,排布了 j 列,最后一列的积木数不超过 k 个的情况下的排列方式。

我们考虑怎么转移:

  1. 最后一列不超过 k 个积木,如果是不超过 k-1 的状态,我们可以先从 dp[i][j][k-1] 转移过来。
  2. 如果最后一列恰好是 k 个积木,那么前一列必然不会超过 k-1 个积木,我们就从 dp[i-k][j-1][k-1] 转移过来。
  3. 边界是 dp[0][0][0] = 1
  4. 转移方程:dp[i][j][k] = dp[i][j][k-1] + dp[i-k][j-1][k-1]

考虑的到询问是 n,m,那么答案就是 dp[n][m][n]。由于最后一列,肯定不会超过 n 个积木,所以可以直接采用这个状态,当然最后一列最精确的上界应该是 n − m ∗ ( m − 1 ) 2 n- \frac {m * (m-1)}{2} n2m(m1) 个,即前 m − 1 m-1 m1 列是分别摆放 1 , 2 , 3..... m − 1 1,2,3.....m-1 1,2,3.....m1 个积木。

时间复杂度 O ( n 2 m ) O(n^2m) O(n2m)

d p dp dp 的转移,一般是定义合适的状态,然后按照合理的逻辑,不重不漏的找到前继状态。

多说无益,做题做多了就有感觉了。

14-Splite (一点点难)

线性 d p dp dp ,定义 dp[i][j] 为将前 i 个元素分为 j 部分的最大值。

转移方程 :

// 初始dp全部定义为负无穷
dp[0][0] = 0;
for (i = 1; i <= n; ++i)
{
     for (j = 1; j <= m; ++j)
     {
        for (l = 1; l < i; ++l)
        {
            dp[i][j] = max(dp[i][j], dp[l-1][j-1] + abs(a[i] - a[l]) * (i - l + 1)); 
        }
        dp[i][j] = max(dp[i][j], dp[i-1][j-1] + a[i]); //  注意特判一个元素为一部分的情况
     }
}   
15-消星星 (简单)

四联通的图,相邻的相同的颜色为一个整体,数有多少个块即可。

d f s dfs dfs 遍历一遍即可。

16-Anniversary (难)

首先你得了解 d i j k s t r a dijkstra dijkstra 求单源最短路,如果不会,请跳转到第20题的题解部分。

分析可知,一个校友的路径分为两部分,初始在 v v v 点 ,要从 v v v 点先到 x x x,然后从 x x x 回到 v v v 点。

我们可以构建两幅图。

一幅是原图 G 1 G_1 G1,我们得到 x x x 到所有点的单源最短路 d i s 1 dis_1 dis1dis1[v] 的意义是:xv 的最短路径。

第二幅图是将原图的边全部反向,得到 G 2 G_2 G2,我们再次得到 x x x 到所有点的单源最短路 d i s 2 dis_2 dis2dis2[v]的意义是:v 点到 x 的最短路径。

我们将其对应相加,dis1[v] + dis2[v] 即是 v 先到 x 再回到 v 的最短路径。

17-Train (一点点难)

一个二维的背包问题,我们定义 dp[n][i][j] 为处理完第 n 个货物,已经载重量为 i ,货位数为 j 的情况下的最优价值。

转移即可:

dp[n][i][j] = max(dp[n][i][j], dp[n - 1][i - v][j - s] + v)

参考视频 : 背包九讲专题 bilibili

18-Swap Digits (一般)

暂时没什么高论,感觉暴力就好了。

10位数,暴力枚举排列方式(最多也就 9 ! 9! 9! 种排列方式), d f s dfs dfs 枚举每一位是多少,同时用一个 vis[] 数组记录某一位有没有被用过。在 d f s dfs dfs 的过程中记录一下交换了已经多少次。然后比较是否是小于等于 K K K 次就行。大概用了 4 4 4 秒左右。

讲一下怎么在 d f s dfs dfs 的过程中记录已经交换的次数。这是个套路,假设从左到右,当前是放置第 i i i 位的数字,假设放的是原数字第 j j j 位的数字,那么交换的次数就要加上原数字中 1 1 1 j − 1 j-1 j1 位中还没有被用过的数字次数。

举个例子:

假设原数字是 1234567 1234567 1234567,当前处理到第 3 3 3 位,目前已经得到的数字是 1763 1763 1763,假设在第四位放置的是 5 5 5,那么在原数字中 5 5 5 之前没有用过的位置的数字有 { 2 , 4 } \{ 2,4 \} {2,4} 两个,那么交换的次数就要加 2 2 2注意我们找的是没有用过的位置的数字,而不是数字本身,因为一个数中可能有重复的数位。

最后用 s e t set set 去重就好了。

这个题应该就是 d f s dfs dfs 全排列的一个改版吧。DFS(搜索) - OI Wiki (oi-wiki.org)

给几个多余的测试样例:

1221 3
ans : 6

776432 3
ans : 34

123456789 20 // 大样例
ans : 251802

123456789 3 // 大样例
ans : 155

123456789 1000 // 9的全排列都可以,这组不符合输入规则,但是可以测试全排列对不对
362880
   
1 3
ans : 1

0 1   // 特判
ans : 1

1111 2
ans : 1
19-Lost Digits (中等难)

其实是个标准的数位 d p dp dp

尽量用思想的方式描述。

首先的话要明白几个同余等式:
a ≡ p 0 m o d    7 a ∗ 10 + d ≡ p 1 m o d    7 = > p 1 ≡ p 0 ∗ 10 + d m o d      7 \begin{aligned} a & \equiv p_0 \mod 7 \\ a * 10 + d & \equiv p_1 \mod 7 \\ => p_1 & \equiv p_0 * 10 + d \mod \ 7 \end{aligned} aa10+d=>p1p0mod7p1mod7p010+dmod 7
a a a 是一个十进制数, d d d 是单个数位(0-9)。

然后我们考虑处理到第 i i i 位 (数值是 字符串 s 0 . . s i s_0..s_i s0..si 组成的数),余数是 p p p 的时候有多少个满足条件的数。假设是有 dp[i][p] 个数。

那么如果 i i i 的下一位是 p 1 p_1 p1 ,那么dp[i][p] 的贡献可以转移到 dp[i+1][(p*10 + p) % p]

我们只需要枚举下一位是什么即可。转移这个状态即可。

由于不能有前导0,我们需要特判这个情况,同时只有一位的时候,是可以为0的。。。

没有特判,找教练要数据,被骂了

拓展阅读: 浅学数位dp

20-path (比较难)

要求的是所有的城市到最近的救援站的最小路径和。

一句话概括,类似于多源 b f s bfs bfs 的多源最短路算法,套一层 d i j k s t r a dijkstra dijkstra 即可。

我们简要的来说一下思考流程:

  1. 要是所有的边长是 1 1 1 ,我们应该怎么做?

显然,我们跑一遍多源 b f s bfs bfs 即可。建一个队列,先将所有的救援站入队,定于距离 dis[u] = 0 ,然后跑 b f s bfs bfs,遍历到一个节点 v,如果不在队列中,那么直接赋值 dis[v] = dis[u] + 1。由于 b f s bfs bfs 可以保证每个点只访问一次,那么就对于每一个点一定是最短路。如果你了解单源最短路,那么理解这个应该很简单 BFS介绍(图论) - OI Wiki (oi-wiki.org)

  1. 要是只有一个救援站

如果只有一个救援站,只需要求这一个救援站到所有点的最短距离即可,具体参考 d i j k s t r a dijkstra dijkstra 算法

最短路 dijkstra- OI Wiki (oi-wiki.org)

  1. 多源最短路?

将两个问题组合起来,我们发现十分的契合。我们完全可以用多源 b f s bfs bfs 的思想套一层 d i j k s t r a dijkstra dijkstra 算法,具体的做法就是,在 d i j dij dij 第一次入队的过程中,将所有的救援站入队,并将 dis[u] = 0 ,然后跑普通的 d i j dij dij 即可。

	priority_queue<pair<int,int> > q; // dijkstra 优先队列
 memset(dis, 0x3f, sizeof(dis)); // 初始将每个点的距离定义为无穷大
 while (k--) // k个救援站
 {
     x = read(); // 救援站编号
     dis[x] = 0; // 赋初值
     q.push({0,x}); // 初始点入队
 }
  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值