OI笔记 | 杂类模板

字符串模板先放这,因为现在只学了一个,之后如果还有学的话单独开。

KMP

洛谷 P3375

给出两个字符串 $s_1$ 和 $s_2$,若 $s_1$ 的区间 $[l, r]$ 子串与 $s_2$ 完全相同,则称 $s_2$ 在 $s_1$ 中出现了,其出现位置为 $l$。
现在请你求出 $s_2$ 在 $s_1$ 中所有出现的位置。

定义一个字符串 $s$ 的 border 为 $s$ 的一个非 $s$ 本身的子串 $t$,满足 $t$ 既是 $s$ 的前缀,又是 $s$ 的后缀。
对于 $s_2$,你还需要求出对于其每个前缀 $s’$ 的最长 border $t’$ 的长度。

题解

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e6 + 10;
char a[MAX_N], b[MAX_N];
int p[MAX_N];
inline ll read() {...}
inline void write(ll x) {...}

int main() {
    scanf("%s%s", a + 1, b + 1);
    int la = strlen(a + 1), lb = strlen(b + 1), j = 0;
    for(int i = 2; i <= lb; i++) {
        while(j > 0 && b[i] != b[j + 1])    j = p[j];
        if(b[i] == b[j + 1])    j++;
        p[i] = j;
    }
    j = 0;
    for(int i = 1; i <= la; i++) {
        while(j > 0 && a[i] != b[j + 1])    j = p[j];
        if(a[i] == b[j + 1])    j++;
        if(j == lb) write(i - lb + 1), putchar('\n'), j = p[j];
    }
    for(int i = 1; i <= lb; i++)    write(p[i]), putchar(' ');
    return 0;
}

康托展开

洛谷 P5367

可以用树状数组来维护比 $a_i$ 小的数的数量。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e6 + 10, MOD = 998244353;
ll a[MAX_N], tree[MAX_N], fac[MAX_N], n, ans;
inline ll read() {...}
inline void write(ll x) {...}
inline int lowbit(int x) {return x & -x;}
inline void update(int pos, int k) {for(int i = pos; i <= n; i += lowbit(i))	tree[i] += k;}
inline int query(int pos) {
	int ret = 0;
	for(int i = pos; i >= 1; i -= lowbit(i))	ret += tree[i];
	return ret;
}
int main() {
	n = read();
	fac[0] = 1;
	for(int i = 1; i <= n; i++) {
		a[i] = read();
		update(a[i], 1);
		fac[i] = (fac[i - 1] * i) % MOD;
	}
	for(int i = 1; i <= n; i++) {
		(ans += query(a[i] - 1) * fac[n - i] % MOD) %= MOD;
		update(a[i], -1);
	}
	write(ans + 1);
	return 0;	
} 

模拟退火

我们直接来看一道板子题「JSOI2016」炸弹攻击。

LOJ 2076

洛谷 P5544

游戏地图可以简单认为是一个二维平面。JYY 建造了 $N$ 个建筑,每个建筑都是一个圆,其中第 $i$ 个建筑的圆心位于 $(x_i,y_i)$ 且半径为 $r_i$。地图上一共有 $M$ 个敌人,一个敌人可以近似看成一个平面上的点,其中第 $i$ 个敌人位于 $(p_i,q_i)$。JYY 可以使用一枚可以设置半径的炸弹,可以设置一个不超过 $R$ 的范围,然后选择平面上的一个点引爆,范围内的所有敌人全部消灭。

当然,由于炸弹威力巨大,如果爆炸范围接触到 JYY 的建筑,那么 JYY 的建筑也会受到损伤。(注:如果炸弹的爆炸范围仅接触到了 JYY 建筑的边界,则不会对 JYY 的建筑造成损伤;如果敌人出现在了爆炸范围的边界,则该敌人被消灭)JYY 可以自由控制炸弹的爆炸地点和爆炸半径。作为一个保守的玩家,他希望在保证自己建筑毫发无损的情况下,消灭尽量多的敌人。

对于 $100\%$ 的数据,满足 $0 \le N \le 10, 0 \lt M \le 10^3 , 1 \le R, r_i \le 2\times 10^4 ,\lvert p_i\rvert ,\lvert q_i\rvert,\lvert x_i\rvert ,\lvert y_i\rvert \le 2\times 10^4$。

题解

一道很好的模拟退火题。

首先是初温的设置,不能设置得太大。由于我们使用 nx = ansx + t * (rand() * 2 - RAND_MAX) 来生成新解,如果初温太大,容易偏移到没有任何敌人的地方去,所以综合考虑数据范围并多次调参,我们把它设为 7e3

降温的设置个人感觉影响没有很大,这里设为玄学的 0.996633。末温同理,这里设为 1e-10

答案的计算,我们先找到最大的不与建筑接触的 $r$,然后统计有多少个敌人在当前的圆内。

这样还不是很稳,这是因为在平面上的敌人可能是很稀疏的,测试发现模拟退火途中有大量的答案 $cnt=0$。第一篇题解告诉了我们一种方式,即在返回函数中加入因素:离最近的敌人还差多远。设 $m=\min\limits_{i=1}^M (d_i-r)$,其中 $d_i$ 为当前坐标到第 $i$ 个敌人的距离, $r$ 为当前选择的半径,则模拟退火的能量 $e=-k_1\cdot m+k_2\cdot cnt$,其中 $k_1,k_2$ 为常数,这里设为 $13.14$ 和 $5.20$(一些玄学数字)。这样如果距离越远,答案越劣;摧毁的敌人越多,答案越优。

再加上卡时,这题出解的概率就很高了。事实上,在洛谷上调完参后我直接交到 LOJ 上也能一遍过,可见比较稳定。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 35;
const double BEGIN = 7e3, DOWN = 0.996633, MIN_T = 1e-10;
int n, m, R, ans, sumx, sumy, nans;
double ansx, ansy, anse;
inline bool check() {return (double)clock() / CLOCKS_PER_SEC > 0.97;}
struct bd {int x, y, r;} bds[22];
struct enm {int x, y;} e[1010];

inline ll read() {...}
char buf[50];
inline void write(ll x) {...}

double dis(double x, double y, double xx, double yy) {return sqrt((x - xx) * (x - xx) + (y - yy) * (y - yy));}
double cal(double x, double y) {
	double r = R, mn = 1e18;
	nans = 0;
	for(int i = 1; i <= n; i++)
		r = min(r, dis(x, y, bds[i].x, bds[i].y) - bds[i].r);
	for(int i = 1; i <= m; i++) {
		double d = dis(x, y, e[i].x, e[i].y); 
		if(d <= r)	nans++;
		mn = min(mn, d - r);
	}
	return -max(0.0, mn) * 13.14 + nans * 5.20;
}
void SA() {
	double t = BEGIN, nx = 0, ny = 0;
	while(t > MIN_T) {
		if(check())	return;
		nx = ansx + t * (rand() * 2 - RAND_MAX), ny = ansy + t * (rand() * 2 - RAND_MAX);
		double ne = cal(nx, ny);
		ans = max(ans, nans);
		int delta = ne - anse;
		if(delta > 0 || exp(delta / t) * RAND_MAX > rand())	ansx = nx, ansy = ny, anse = ne;
		t *= DOWN;
	}
}

int main() { 
	srand(19260817);
	n = read(), m = read(), R = read();
	for(int i = 1; i <= n; i++)
		bds[i].x = read(), bds[i].y = read(), bds[i].r = read();
	for(int i = 1; i <= m; i++)
		e[i].x = read(), e[i].y = read(), sumx += e[i].x, sumy += e[i].y;
	ansx = (double)sumx / m, ansy = (double)sumy / m;
	anse = cal(ansx, ansy);
	ans = nans;
	while(!check())
		SA(), srand(rand());
	write(ans);
	return 0;
}

离线算法

LINK

Joy © 2023 Powered by Jekyll and Theme by solid

今日诗词API Valine 评论管理 Github