8.4集训
上午
考试
第一题
给你一个初始序列,和两个操作:操作1可以使得\(i\)与\(i+2\)两个数交换位置,操作2是相邻的数可以交换位置
问:这个序列最少用多少次操作2可是使序列单调递增?
你考虑,操作1的性质:使得\(i\)与\(i+2\)两个数交换位置,不就是奇偶性相同的位置吗?而操作2的性质:相邻的数可以交换位置,不就是奇偶性不同的数吗?
把原序列排序之后,对于为排序的序列,我们考虑当前这个数的下标与排完序之后的下标,如果发现他们奇偶性相同,也就是说,可以经过若干次操作1可以变换到那个位置
如果发现奇偶性不同,说明肯定是经过了一次操作1,因此计数器叠加
注意细节:这样统计是真实答案的二倍,因为:假如当前在\(i\),你发现这个数排完序之后在\(j\)(\(i < j\)),且\(i,j\)的奇偶性不同,这时你计数器累加,但是到了\(j\)那个位置,你的计数器还会叠加,也就是说,你的计数器对于同一种情况增加了两次
#include <bits/stdc++.h>
#define LL long long
#define debug
using namespace std;
const int N = 2e5+66;
int n, res;
int a[N], b[N];
inline int thestars() {
cin >> n;
for (int i = 1; i <= n; ++ i) {
cin >> a[i];
b[i] = a[i];
}
sort(b+1, b+n+1);
for (int i = 1; i <= n; ++ i) {
int pos = lower_bound(b+1, b+n+1, a[i]) - b;
if ((pos&1) != (i&1)) ++ res;
}
cout << (res/2);
return 0;
}
int youngore = thestars();
signed main() {;}
第二题
有\(10^8\)条横向道路与竖向道理,相邻的道路之间相差\(100m\),从左到右第\(i\)条纵向街道和从下到上第\(j\)条横向街道之间的交点描述为\((i,j)\),在某些交点上建有以交点为圆心,半径是 \(10\) 米的喷泉,行走时不能穿过喷泉。
问你要从\((x_1,x_2)\)走到\((x_2,y_2)\)最短要走多少?
Sample Input
1 1 6 5
3
3 2
5 3
2 4
Sample Output
891.415926535897938
有\(10^8\)条横向道路与竖向道理,相邻的道路之间相差\(100m\),从左到右第\(i\)条纵向街道和从下到上第\(j\)条横向街道之间的交点描述为\((i,j)\),在某些交点上建有以交点为圆心,半径是 \(10\) 米的喷泉,行走时不能穿过喷泉。
问你要从\((X_1,Y_2)\)走到\((X_2,Y_2)\)最短要走多少?
由于所有的喷泉横纵坐标都相等,所以显然走尽可能多的喷泉可以是路径更短,
对于一个喷泉可以短\(20-\dfrac 1 4* 2\pi r\)
至此问题完成转化:最长上升子序列问题
不妨设\(X_1<X_2,Y_1<Y_2\),仅当\(X_1<x_i<X_2,Y_1<y_i<Y_2\)时候,统计这个点对答案的影响
并把所有点按照横坐标排序,求\(y_i\)以\(Y_1\)为开始,\(Y_2\)终止的最长上升子序列
对于正常的\(dp\)做法,过不去的,需要用\(nlog_n\)的做法,我想,下文会提到的吧
#include <bits/stdc++.h>
#define LL long long
#define debug
using namespace std;
const int N = 3e5+66;
int n, ans, f[N];
int sx, sy, tx, ty;
struct node{int x, y;}a[N];
inline int cmp(node s, node t){
if (s.x == t.x) return s.y > t.y;
if (sy < ty) return s.x < t.x;
return s.x > t.x;
}
inline int thestars() {
cin >> sx >> sy >> tx >> ty >> n;
if (tx < sx) swap(sx, tx), swap(sy, ty);
for (int i = 1; i <= n; ++ i) cin >> a[i].x >> a[i].y;
sort(a+1, a+n+1, cmp);
double mhd = tx - sx + abs(ty - sy);
f[0] = -0x7fffffff;
for (int i = 1; i <= n; ++ i) {
if (a[i].x > max(sx, tx) || a[i].x < min(sx, tx) || a[i].y > max(sy, ty) || a[i].y < min(sy, ty)) continue;
if (a[i].y > f[ans]) f[++ ans] = a[i].y;
else if (a[i].y < f[ans]) {
int pos = lower_bound(f+1, f+ans+1, a[i].y) - f;
f[pos] = a[i].y;
}
}
printf ("%.15lf", 100*mhd-ans*(20-5*M_PI));
return 0;
}
int youngore = thestars();
signed main() {;}
第三题
显然,三分图有以下性质:
- 同一集合内,没有连边
- 不存在一个点向同一个集合连两条边
发现上面的性质好像只涉及到两个集合啊,因此答案呼之欲出:\(calc(A,B)*calc(A,C)*calc(B,C)\)
状态:设\(f[i][j]\)表示一个集合有\(i\)个点,另一个集合有\(j\)个点的时候,炼成的三分图的个数
转移:\(f[i][j] = f[i][j-1]+f[i-1][j-1]*i\)
考虑第二个集合的最后一个点,他可能有什么情况,
第一种,不连边,继承上一次的
第二种,连边,则有\(i\)种连边方式,若练了某条边,则第一个集合对应的点就不能连其他边了,方案数为:\(i*f[i-1][j-1]\)
结果:\(f[a][b]*f[b][c]*f[a][c]\)
#include <bits/stdc++.h>
#define int long long
#define debug
using namespace std;
const int N = 3e3+66, mod = 998244353, M = 2000;
int t, a, b, c;
int f[N][N];
inline int thestars() {
cin >> t;
for (int i = 1; i <= 3000; ++ i) {
f[i][1] = i+1;
for (int j = 2; j <= i; ++ j)
f[i][j] = (f[i][j - 1] + i*f[i - 1][j - 1]%mod)%mod;
}
for (int i = 1; i <= 3000; ++ i)
for (int j = 1; j < i; ++ j)
f[j][i] = f[i][j];
while(t --> 0) {
int a, b, c;
cin >> a >> b >> c;
cout << f[a][b]*f[a][c]%mod*f[b][c]%mod << '\n';
}
return 0;
}
int youngore = thestars();
signed main() {;}
下午
讲dp!!!
我们来通过通过几道例题来体会一下什么是dp
01背包
将n件有着各自体积和价值的物品装入一个容积为m的背包中,求最大的总价值
状态:\(f[i][j]\)表示前\(i\)件物品放入背包为\(j\)的背包中所能获得的最大总价值
转移:\(f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i])\)
结果:\(f[n][m]\)
最长不下降子序列
给出一个长度为n 的序列,求最长的子序列,满足这些元素依次不减
状态:设\(f[i]\)表示以\(i\)为结尾的最长不下降子序列的最长长度
转移:\(f[i] = max(f[j]+1)\)其中\(a[j]<a[i]\)
结果:\(max(f[i])\)
上面是\(O(n^2)\)做法,还有一种\(O(nlogn)\)的做法:
状态:设\(f[i]\)表示长度为\(i\)的的最长不下降子序列的最后一个元素的最小值
举个例子:
有序列\(a[] = {8,2,3,5,7,6,4}\),所以可得:
\(f[0] = 0,f[1] = 2, f[2] = 3, f[3]=5,f[4]=7\)(第一遍扫)
遇到\(6\)考虑更新\(f[4]\),遇到\(4\)考虑更新\(f[3]\)
至于为什么是logn的,因为每次是二分查找(\(lower\)_\(bound\)是log的)
再次考虑\(dp\)的牛逼之处: 需要有易于表示的状态,能够列出的转移,便于计算的结果
要把式子推出来再去写代码
下面有几道一般dp,没什么特点,就是难
渡河问题
状态:\(f[i]\)表示将前\(i\)头牛运到对岸的时间
转移:\(f[i] = min(f[j]+2*M+sum[i-j])\)其中\(sum\)为前缀和
结果:\(f[n]\)
解题
给出贪心的\(Hack\)数据:
50 5
40 10
10 40
10 5
10 3
10 2
状态:\(f[i][j]\)表示处理\(1\)到\(j\)的题,最后一次选择了\(i\)到\(j\)的最短月数
转移:\(f[i][j] = min(f[k][i-1]+1/2)\)其中剩下的钱足够就是\(+1\)否则是\(+2\)
结果:\(min(f[i][p])+1\)
晚上
没时间写了啊,**白某飞主任