其他 / Others(ACM-Template 2.0)

语言

C++11

初始代码

// #pragma GCC optimize(3)
#include <bits/stdc++.h>
#define repeat(i, a, b) for (int i = (a), ib = (b); i < ib; i++)
#define repeat_back(i, a, b) for (int i = (b) - 1, ib = (a);i >= ib; i--)
#define mst(a, x) memset(a, x, sizeof(a))
#define fi first
#define se second
// #define int ll
using namespace std;
namespace start {
	typedef long long ll; const int inf = INT_MAX >> 1; const ll INF = INT64_MAX >> 1;
	typedef double lf; const lf pi = acos(-1);
	typedef pair<int, int> pii;
	mt19937 rnd(chrono::high_resolution_clock::now().time_since_epoch().count());
		ll read() { ll x; if (scanf("%lld", &x) != 1) exit(0); return x; } // will detect EOF
		ll readfast() {
			ll x = 0, tag = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') tag = -1;
			for (; isdigit(c); c = getchar()) x = x * 10 + c - 48; // ungetc(c, stdin);
			return x * tag;
		}
		template <typename T> void write(T x) {
			if (x < 0) x = -x, putchar('-');
			if (x >= 10) write(x / 10);
			putchar(x % 10 + 48);
		}
		void print(ll x, bool e = 0) { printf("%lld%c", x, " \n"[e]); }
		lf readf() { lf x; if (scanf("%lf",&x) != 1) exit(0); return x; } // will detect EOF
		void printlf(lf x, bool e = 0) { printf("%.12f%c", x, " \n"[e]); }
		template <typename T> vector<T> &operator<<(vector<T> &a, const T &b) { a.push_back(b); return a; }
		template <typename T> T sqr(const T &x) { return x*x; }
		// int cansel_sync = (ios::sync_with_stdio(0), cin.tie(0), 0);
		constexpr int bitlog(ll x) { return __lg(x); } // floor(log2(x))
	const int N = 200010;
	const lf eps = 1e-9;
	const int mod = (1 ? 1000000007 : 998244353);
		int mul(int a, int b, int m = mod) { return 1ll * a * b % m; }
		int qpow(int a, ll b,int m = mod) {
			int ans = 1;
			for (; b; a = mul(a, a, m), b >>= 1) if (b & 1)
				ans = mul(ans, a, m);
			return ans;
		}
} using namespace start;
void Solve() {
}
signed main() {
	// freopen("data.txt", "r", stdin);
	int T = 1; // T = read();
	repeat (ca, 1, T + 1) {
		Solve();
	}
	return 0;
}

放在本地的内容(比如可以放进 bits/stdc++.h)(修改头文件后需要编译一下)

template<typename A,typename B>
std::ostream &operator<<(std::ostream &o,const std::pair<A,B> &x){
	return o<<'('<<x.first<<','<<x.second<<')';
}
#define qwq [&]{cerr<<"qwq"<<endl;}()
#define orz(x) [&]{cerr<<#x": "<<x<<endl;}()
#define orzarr(a,n) [&]{cerr<<#a": "; repeat(__,0,n)cerr<<(a)[__]<<" "; cerr<<endl;}()
#define orzeach(a) [&]{cerr<<#a": "; for(auto __:a)cerr<<__<<" "; cerr<<endl;}()
#define pause [&]{system("pause");}()

如果没有万能头

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<vector>
#include<array>
#include<list>
#include<queue>
#include<string>
#include<set>
#include<map>
// #include<chrono>
// #include<unordered_set>
// #include<unordered_map>

其他定义

#pragma GCC optimize(2) // (3),("Ofast")
#define lll __int128
#define inline __inline __attribute__((always_inline))
// struct name{bool operator()(const type &x,const type &y){return func(x,y);}}
#define sortunique(a) [&]{sort(a.begin(),a.end()); a.erase(unique(a.begin(),a.end()),a.end());}()
#define gets(s) (scanf("%[^\n]",s)+1)
template<typename T> T sqr(const T &x){return x*x;}
typedef long double llf;
#define endl "\n"

容器

平板电视红黑树

  • 其实就是一个支持排名的 set/map,改造成 multiset/multimap 需要加个时间戳
  • 第二个参数 null_type 表示这是个 set,如果想要 map 可以换成其他类型
  • 第四个参数可以改成 splay_tree_tag(然后就不是红黑树了)
  • 第五个参数表示支持排名(tree_policy.hpp 只找到这一个类)
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
using namespace __gnu_pbds;
tree<pii,null_type,less<pii>,rb_tree_tag,tree_order_statistics_node_update> t; // 定义
t.insert({x,i+1}); // ----------------- 插入 x,用时间戳 i+1 标注(因为不是 multiset)
t.erase(t.lower_bound({x,0})); // ----- 删除 x(删除单个元素)
t.order_of_key({x,0})+1; // ----------- x 的排名(小于 x 的元素个数 + 1)
t.find_by_order(x-1)->first; // ------- 排名为 x 的元素(第 x 小的数)
prev(t.lower_bound({x,0}))->first; // - x 的前驱(小于x且最大)
t.lower_bound({x+1,0})->first; // ----- x 的后继(大于x且最小)
t.join(t2); // ------------------------ 将 t2 并入 t, t2 清空,前提是取值范围不相交
t.split(v,t2); // --------------------- 小于等于 v 的元素属于 t,其余的属于 t2

平板电视优先队列

  • pairing_heap_tag 配对堆,应该是可并堆里最快的
  • thin_heap_tag 斐波那契堆
  • std::priority_queue 不合并就很快
#include<ext/pb_ds/priority_queue.hpp>
using namespace __gnu_pbds;
__gnu_pbds::priority_queue<int,less<int>,pairing_heap_tag> h; // 大根堆
h.push(x); h.top(); h.pop();
h.join(h2); // 将h2并入h,h2清空

rope

  • 可能是可分裂平衡树
#include <ext/rope>
using namespace __gnu_cxx;
rope<int> r; // 块状链表
r.push_back(n);
r.insert(pos,n); // 插入一个元素
r.erase(pos,len); // 区间删除
r.copy(pos,len,x); // 区间赋值到x
r.replace(pos,x); // 相当于r[pos]=x;
r.substr(pos,len); // 这是啥不会用
r[pos] // 只能访问不能修改
r.clear();
rope<int> *his[N]; his[0]=new rope<int>(); his[i]=new rope<int>(*his[i-1]); // 据说O(1)拷贝,一行可持久化

其他语法

STL

a=move(b); // 容器移动(a赋值为b,b清空)
priority_queue<int>(begin,end) // O(n)建堆
a.find(key) // set,map查找,没找到返回a.end()
a.lower_bound(key) // set,map限制最小值
a.insert(b.begin(),b.end()); // set,map合并(时间复杂度极高)
a.emplace_hint(it,b); // 把插入b,如果位置恰好为it就会很快
complex<lf> c; complex<lf> c(1,2);// 复数
c.real(),c.imag() // 实部、虚部
bitset<32> b; // 声明一个32位的bitset
b[n]; b[n]=1; // 访问和修改
b.none(); // 返回是否为空
b.count(); // 返回1的个数
b.to_ullong(); b.to_string(); // 转换
b._Find_first(); // 最低位 1 的下标
b._Find_next(pos); // 位置严格大于 pos 的最低位 1 的下标(不存在就返回 size())

unordered 容器手写 hash 函数

struct myhash{
	typedef unsigned long long ull;
	ull f(ull x)const{
		x+=0x321354564536; // 乱敲
		x=(x^(x>>30))*0x3212132123; // 乱敲
		return x^(x>>31);
	}
	ull operator()(pii x)const{
		static ull t=chrono::steady_clock::now().time_since_epoch().count();
		return f(x.fi+t)^f(x.se+t*2);
	}
};
unordered_set<pii,myhash> a;

cmath

fmod(x) // 浮点取模
tgamma(x) // 计算Γ(x)
atan2(x,y) // 计算坐标(x,y)的极角
hypot(x,y) // 计算sqrt(x^2+y^2)

scanf字符串正则化

scanf("%ns",str); // 读入n个字符
scanf("%[a-z]",str); // 遇到非小写字母停止
scanf("%[^0-9]",str); // 遇到数字停止,^表示非
scanf("%*[a-z]"); // 也是遇到非小写字母停止,只不过不读入字符串

神奇特性

  • 在64位编译器(我的编译器)中set每个元素需要额外32字节内存。
  • 命名空间rel_ops:之后只定义小于就能用其他所有次序关系符号
  • raw strings:R"(abc\n)" 相当于 "abc\\n"
  • 定义数字开头的变量:type operator ""_name(type number){/*...*/}(之后 1_name 即把1带入上述函数中,参数类型只能是ull,llf,char,(const char *,size_t)
  • 高级宏:__VA_ARGS__是参数列表(对应...),__LINE__ 是当前行数,__FUNCTION__是当前函数名,__COUNTER__是宏展开次数-1
  • 位域:struct{int a:3;}; 表示struct里a占3 bit,可以节省空间
  • %n:scanf,printf 中 %n 将读入/输出的字符个数写入变量

Java

import java.util.*;
import java.math.BigInteger;
import java.math.BigDecimal;
public class Main{
	static Scanner sc=new Scanner(System.in);
	public static void main(String[] args){
	}
}
import java.util.*;
import java.io.*;
import java.math.BigInteger;
import java.math.BigDecimal;
public class Main{
	static Scanner cin=new Scanner(System.in);
	static PrintStream cout=System.out;
	public static void main(String[] args){
	}
}
  • 编译运行 java Main.java
  • 编译 javac Main.java // 生成 Main.class
  • 运行 java Main

数据类型

int // 4字节有符号
long // 8字节有符号
double,boolean,char,String
final double PI=3.14; // final 类似 c++ 的 const
var n=1; // var 类似 c++ 的 auto
long 型常量结尾加 L,如 1L

数组

int[] arr=new int[100]; // 数组(可以是变量)
int[][] arr=new int[10][10]; // 二维数组
arr.length; // 数组长度,没有括号
Arrays.binarySearch(arr,l,r,x) // 在 arr[[l,r-1]] 中二分查找 x,若存在返回位置,不存在返回 -lowerbound-1
Arrays.sort(arr,l,r); Arrays.sort(arr); // 对 arr[[l,r-1]] 排序
Arrays.fill(arr,l,r,x); Arrays.fill(arr,x); // 填充 arr[[l,r-1]] 为x

极其麻烦的结构体 compareTo 重载

public class Main{
public static class node implements Comparable<node>{
	int x;
	public node(){}
	public int compareTo(node b){
		return x-b.x;
	}
}
static Scanner sc;
public static void main(String[] args){
	sc=new Scanner(System.in);
	int n=sc.nextInt();
	node[] a=new node[n];
	for(int i=0;i<n;i++){
		a[i]=new node();
		a[i].x=sc.nextInt();
	}
	Arrays.sort(a);
	for(node i:a)
		System.out.print(i.x+" ");
	System.out.println();
}
}

输出

System.out.print(x);
System.out.println();
System.out.println(x);
System.out.printf("%.2f\n",d); // 格式化

输入

import java.util.Scanner;
Scanner sc=new Scanner(System.in); // 初始化
String s=sc.nextline(); // 读一行字符串
int n=sc.nextInt(); // 读整数
double d=sc.nextDouble(); // 读实数
sc.hasNext() // 是否读完

String

s1.equals(s2) // 返回是否相等
s1.compareTo(s2) // s1>s2 返回 1,s1<s2 返回 -1,s1==s2 返回 0
s1.contains(s2) // 返回是否包含子串 s2
s1.indexOf(s2,begin=0) // 查找子串位置
s1.substring(l,r) // 返回子串 [l,r-1]
s1.charAt(x) // 类似 c++ 的 s1[x]
s1.length() // 返回长度
s1+s2 // 返回连接结果
String.format("%d",n) // 返回格式化结果

StringBuffer / StringBuilder

StringBuffer s1=new StringBuffer();
StringBuffer s1=new StringBuffer("A");
s1.append("A"); // 类似 c++ 的 s1+="A";
s1.reverse(); // 反转字符串
s1.replace(l,r,"A"); // 将子串 [l,r-1] 替换为 "A" (delete + insert)
s1.charAt(x); // 类似 c++ 的 s1[x]
s1.setCharAt(x,c); // 类似 c++ 的 s1[x]=c;

Math

// 不用 import 就能用下列函数
Math.{sqrt,sin,atan,abs,max,min,pow,exp,log,PI,E}

Random

import java.util.Random;
Random rnd=new Random(); // 已经把时间戳作为了种子
rnd.nextInt();
rnd.nextInt(n); // [0,n)

BigInteger

import java.math.BigInteger;
BigInteger n;
new BigInteger("0"); // String 转换为 BigInteger
BigInteger.valueOf(I) // int/long 转换为 BigInteger
BigInteger.ZERO/ONE/TEN // 0 / 1 / 10
sc.nextBigInteger() // 读入,括号里可以写进制
BigInteger[] arr=new BigInteger[10];
n1.intValue/longValue() // 转换为 int/long(太大直接丢失高位,不报异常,报异常可以后面加 Exact)
n1.add/subtract/multiply(n2) // 加 减 乘
n1.divide/mod(n2) // 整除 取模
n1.compareTo(n2) // n1>n2 返回 1,n1<n2 返回 -1,n1==n2 返回 0
n1.equals(n2) // 判断相等
n1.abs() n1.pow(I) n1.gcd(n2) n1.max/min(n2) // int I
n1.modPow(n2,n3) n1.modInverse(n2) // 快速幂 逆元(gcd(n1,n2)!=1)
n1.toString(I) // 返回 I 进制字符串
n1.and/or/xor/shiftLeft/shiftRight(n2) // 位运算
n1.testBit(I) n1.setBit/clearBit/flipBit(I) // 取二进制第 I 位,操作第 I 位
n1.bitCount() // 二进制 1 的个数
// 运算时 n2 一定要转换成 BigInteger

BigDecimal

import java.math.BigDecimal;
n1.divide(n2,2,BigDecimal.ROUND_HALF_UP) // 保留 2 位(四舍五入)
// 貌似没有 sqrt 等操作,都得自己实现 qwq

Python

读入(EOF停止)

def Solve():
	a,b=map(int,input().split())
	print(a+b)
while True:
	try:
		Solve()
	except EOFError:
		break

读入(T组数据)

def Solve():
	a,b=map(int,input().split())
	print(a+b)
T=int(input())
for ca in range(1,T+1):
	Solve()

输出(不回车)

print(x,end="")

常量

None # 空值
True False # 布尔值

字符串str

eval(s); # 表达式求值
ord(c); chr(c); # 字符和编码的转换
int("123"); str(123); # 数字与字符串的转换

列表list

a=[]; a.append(x); a.pop(); a[x]; # 最后一个不存在会报错
len(a); # 返回 size
a.sort(); # 排序
a.sort(reverse=True); # 降序排序
a.sort(key=str); # 按照指定函数转换后比较

字典dict

- a={}; a={x:y}; a[x]=y; a[x]; # 最后一个不存在会报错
- a.get(x); # 不存在返回 None
- a.pop(x); # 不存在会报错

集合set

a=set(); a.add(x); a.remove(x); # 最后一个不存在会报错
a&b; a|b; a-b; a^b; # 集合的交、并、差、对称差

高级操作

enumerate(<list>) # 返回 list 的每个元素变成 (下标, 元素) 后的生成器
zip(<list>, <list>) # 返回两个 list 对应元素组成二元组的生成器

常规算法

算法基础

  • STL自带算法
fill(begin,end,element); // 填充
fill_n(begin,n,element); // 填充
iota(begin,end,t); // 递增填充(赋值为t,t+1,t+2,...)
copy(a_begin,a_end,b_begin); // 复制(注意会复制到b里)
reverse(begin,end); // 翻转
nth_element(begin,begin+k,end); // 将第k+1小置于位置k,平均O(n)
binary_search(begin,end,key,[less]) // 返回是否存在
upper_bound(begin,end,key,[less]) // 返回限制最小值地址
lower_bound(begin,end,key,[less]) // 返回严格限制最小值地址
merge(a_begin,a_end,b_begin,b_end,c_begin,[less]); // 归并a和b(结果存c)
inplace_merge(begin,begin+k,end); // 归并(原地保存)
next_permutation(begin,end); prev_permutation(begin,end); // 允许多重集,返回不到底,使用方法 do{/*...*/}while(next_permutation(begin,end));
min_element(begin,end); max_element(begin,end); // 返回最值的指针
for_each(begin,end,work); // 每个元素进行work操作
auto it=back_inserter(a); // it=x表示往a.push_back(x)
  • 进制转换
strtol(str,0,base),strtoll // 返回字符数组的base进制数
stol(s,0,base),stoll // 返回字符串的base进制数
sscanf,sprintf // 十进制
to_string(n) // 十进制
  • 随机数
#include<random>
mt19937 rnd(time(0));
// 巨佬定义:mt19937 rnd(chrono::high_resolution_clock::now().time_since_epoch().count());
cout<<rnd()<<endl; // 范围是unsigned int
rnd.max() // 返回最大值
lf rndf(){return rnd()*1.0/rnd.max();}
lf rndf2(){return rndf()*2-1;}
  • 手写二分/三分
while(l<=r){
	int mid=(l+r)/2; // l+(r-l)/2
	if(ok(mid))l=m+1; else r=m-1; // 小的值容易ok
}
// 此时r是ok的右边界
while(l<r){
	int x=(l+r)/2,y=x+1; // l+(r-l)/2
	if(work(x)<work(y))l=x+1; else r=y-1; // 最大值
}
// 此时l和r均为极值点
#define f(x) (-x*x+23*x)
const lf ph=(sqrt(5)-1)/2; // 0.618
lf dfs(lf l,lf x,lf r,lf fx){
	if(abs(l-r)<1e-9)return x;
	lf y=l+ph*(r-l),fy=f(y);
	if(fx<fy)return dfs(x,y,r,fy);
	else return dfs(y,x,l,fx);
}
lf search(lf l,lf r){
	lf x=r-ph*(r-l);
	return dfs(l,x,r,f(x));
}
  • 次大值
int m1=-inf,m2=-inf;
repeat(i,0,n){m1=max(m1,a[i]); if(m1>m2)swap(m1,m2);}
// m1即次大值
  • 朴素 mex
int mex(const vector<int> &v){
	static int vis[N],dcnt=0; dcnt++;
	for(auto i:v)vis[i]=dcnt;
	for(int i=0;;i++)if(vis[i]!=dcnt)return i;
}

离散化

  • 从小到大标号并赋值,\(O(n\log n)\)是个好东西
void disc(int a[],int n){
	vector<int> b(a,a+n);
	sort(b.begin(),b.end());
	b.erase(unique(b.begin(),b.end()),b.end());
	repeat(i,0,n)
		a[i]=lower_bound(b.begin(),b.end(),a[i])-b.begin(); // 从0开始编号
}
void disc(int a[],int n,int d){ // 把距离>d的拉近到d
	vector<int> b(a,a+n);
	sort(b.begin(),b.end());
	b.erase(unique(b.begin(),b.end()),b.end());
	vector<int> c(b.size()); c[0]=0; // 从0开始编号
	repeat(i,1,b.size())
		c[i]=c[i-1]+min(d,b[i]-b[i-1]);
	repeat(i,0,n)
		a[i]=c[lower_bound(b.begin(),b.end(),a[i])-b.begin()];
}
struct Disc{ // 离散化后a[]的值互不相同,但是a[i]与a[j]∈[d.pre[a[i]],d.nxt[a[i]]]在离散化前是相同的
	int b[N],pre[N],nxt[N];
	void init(int a[],int n){
		copy(a,a+n,b); sort(b,b+n);
		pre[0]=0;
		repeat(i,1,n){
			if(b[i]==b[i-1])pre[i]=pre[i-1];
			else pre[i]=i;
		}
		nxt[n-1]=n-1;
		repeat_back(i,0,n-1){
			if(b[i]==b[i+1])nxt[i]=nxt[i+1];
			else nxt[i]=i;
		}
		repeat(i,0,n){
			a[i]=lower_bound(b,b+n,a[i])-b;
			b[a[i]]--;
		}
	}
}d;

01 分数规划

  • n 个物品,都有两个属性 \(a_i\)\(b_i\),任意取 k 个物品使它们的 \(\dfrac {\sum a_j}{\sum b_j}\) 最大
  • 解:二分答案
  • m 是否满足条件即判断 \(\dfrac {\sum a_j}{\sum b_j}\ge m\),即 \(\sum(a_j-mb_j)\ge 0\)
  • 因此计算 \(c_i=a_i-mb_i\) ,取前 k 个最大值看它们之和是否 \(\ge 0\)
  • 如果限制条件是 \(\sum b_j\ge W\),则将 \(b_j\) 看成体积,\(a_j-mb_j\) 看成价值,转换为背包dp
int n,k; lf a[N],b[N],c[N];
bool check(lf mid){
	repeat(i,0,n)c[i]=a[i]-mid*b[i];
	nth_element(c,c+k,c+n,greater<lf>());
	lf sum=0; repeat(i,0,k)sum+=c[i];
	return sum>=0;
}
lf solve(){
	lf l=0,r=1;
	while(r-l>1e-9){
		lf mid=(l+r)/2;
		if(check(mid))l=mid; else r=mid;
	}
	return l;
}

任务规划

Livshits-Kladov 定理

  • 给出 n 个任务,第 i 个任务花费 \(t_i\) 时间,该任务开始之前等待 t 时间的代价是 \(f_i(t)\) 个数,求一个任务排列方式,最小化代价 \(\sum\limits_{i=1}^n f_j(\sum\limits_{j=1}^{i-1}t_i)\)
  • Livshits-Kladov定理:当 \(f_i(t)\) 是一次函数 / 指数函数 / 相同的单增函数时,最优解可以用排序计算
  • 一次函数:\(f_i(t)=c_it+d_i\),按 \(\dfrac {c_i}{t_i}\) 升序排列
  • 指数函数:\(f_i(t)=c_ia^t+d_i\),按 \(\dfrac{1-a^{t_i}}{c_i}\) 升序排列
  • 相同的单增函数:按 \(t_i\) 升序排序

Johnson 规则

  • n 个任务和两个机器,每个任务必须先在 A 上做 \(a_i\) 分钟再在 B 上做 \(b_i\) 分钟,求最小时间
sort(p,p+n,[](int x,int y){
	int xx=a[x]>b[x],yy=a[y]>b[y];
	if(xx!=yy)return xx<yy;
	if(xx==0)return a[x]<a[y];
	else return b[x]>b[y];
});

分治

逆序数 using 二维偏序

  • \(O(n\log n)\)
void merge(int l,int r){
	static int t[N];
	if(r-l<=1)return;
	int mid=l+(r-l)/2;
	merge(l,mid);
	merge(mid,r);
	int p=l,q=mid,s=l;
	while(s<r){
		if(p>=mid || (q<r && a[p]>a[q])){
			t[s++]=a[q++];
			ans+=mid-p; // here
		}
		else
			t[s++]=a[p++];
	}
	for(int i=l;i<r;++i)a[i]=t[i];
}

反悔贪心

  • 例:第 i 天要么获得 \(A_i\) 元要么获得 \(B_i\) 元和 1 积分,\(A_i\ge 0\)\(B_i\) 可能 \(<0\),问获得积分最大值
  • 能拿 B 就拿 B,不然就把之前某个 B 换成 A 然后拿这次的 B(如果更优的话)
priority_queue<int> q;
int n=read(),now=0,ans=0;
repeat(i,0,n){
	int A=read(),B=read();
	if(now+B>=0){
		q.push(A-B);
		ans++;
		now+=B;
	}
	else if(!q.empty() && now+q.top()+B>=0 && q.top()>A-B){
		now+=q.top()+B;
		q.pop();
		q.push(A-B);
	}
	else now+=A;
}

线段树分治

  • 支持添删边的离线操作
  • 对时间建立线段树,每个结点开一个vector。对一条边,添加到删除的时间区间,插入到线段树中。最后对线段树 DFS 一遍统计答案,向下走即添边,向上走即撤销,用可撤销数据结构维护
  • 复杂度为 \(O(n\log n)\) 乘以可撤销数据结构复杂度
  • 如果元素对询问的贡献可以分开计算,那么只要对线段树每个节点进行统计即可,不需要可撤销数据结构
struct seg{
	vector<pii> a;
	int l,r; seg *lc,*rc;
	void init(int,int);
	void update(int x,int y,pii xx){
		if(x>r || y<l)return;
		if(x<=l && y>=r){a.push_back(xx); return;}
		lc->update(x,y,xx);
		rc->update(x,y,xx);
	}
	void dfs(){
		for(auto i:a)d.join(i.fi,i.se);
		if(l==r)ans[l]=now;
		if(l!=r){lc->dfs(); rc->dfs();}
		repeat(i,0,a.size())d.undo();
	}
}tr[N*2],*pl;
void seg::init(int _l,int _r){
	l=_l,r=_r;
	if(l==r){return;}
	int m=(l+r)>>1;
	lc=++pl; lc->init(l,m);
	rc=++pl; rc->init(m+1,r);
}
void init(int l,int r){
	pl=tr; tr->init(l,r);
}

最大空矩阵 using 悬线法

  • 求01矩阵中全是0的最大连续子矩阵(面积最大)\(O(nm)\)
  • 此处障碍物是正方形。如果障碍只是一些整点,答案从 \(ab\) 变为 \((a+1)(b+1)\)
int n,m,a[N][N],l[N][N],r[N][N],u[N][N];
int getlm(){
	int ans=0;
	repeat(i,0,n)
	repeat(k,0,m)
		l[i][k]=r[i][k]=u[i][k]=(a[i][k]==0);
	repeat(i,0,n){
		repeat(k,1,m)
		if(a[i][k]==0)
			l[i][k]=l[i][k-1]+1; // 可以向左延伸几格
		repeat_back(k,0,m-1)
		if(a[i][k]==0)
			r[i][k]=r[i][k+1]+1; // 可以向右延伸几格
		repeat(k,0,m)
		if(a[i][k]==0){
			if(i!=0 && a[i-1][k]==0){
				u[i][k]=u[i-1][k]+1; // 可以向上延伸几格
				l[i][k]=min(l[i][k],l[i-1][k]);
				r[i][k]=min(r[i][k],r[i-1][k]); // 如果向上延伸u格,lr对应的修改
			}
			ans=max(ans,(l[i][k]+r[i][k]-1)*u[i][k]);
		}
	}
	return ans;
}

搜索

舞蹈链 / DLX

精确覆盖

  • 在 01 矩阵中找到某些行,它们两两不相交,且它们的并等于全集
  • xy 编号从 1 开始!\(O(\exp)\),结点数 \(<5000\)
int n,m;
vector<int> rec; // dance后存所有选中的行的编号
struct DLX{
	#define rep(i,i0,a) for(int i=a[i0];i!=i0;i=a[i])
	int u[N],d[N],l[N],r[N],x[N],y[N]; // N=10010
	int sz[N],h[N];
	int top;
	void init(){
		top=m;
		repeat(i,0,m+1){
			sz[i]=0; u[i]=d[i]=i;
			l[i]=i-1; r[i]=i+1;
		}
		l[0]=m; r[m]=0;
		repeat(i,0,n+1)h[i]=-1;
		rec.clear();
	}
	void add(int x0,int y0){
		top++; sz[y0]++;
		x[top]=x0; y[top]=y0;
		u[top]=u[y0]; d[top]=y0;
		u[d[top]]=d[u[top]]=top;
		if(h[x0]<0)
			h[x0]=l[top]=r[top]=top;
		else{
			l[top]=h[x0]; r[top]=r[h[x0]];
			l[r[h[x0]]]=top; r[h[x0]]=top;
		}
	}
	void remove(int c){
		l[r[c]]=l[c]; r[l[c]]=r[c];
		rep(i,c,d)rep(j,i,r){
			u[d[j]]=u[j]; d[u[j]]=d[j];
			sz[y[j]]--;
		}
	}
	void resume(int c){
		rep(i,c,d)rep(j,i,r){
			u[d[j]]=d[u[j]]=j;
			sz[y[j]]++;
		}
		l[r[c]]=r[l[c]]=c;
	}
	bool dance(int dep=1){ // 返回是否可行
		if(r[0]==0)return 1;
		int c=r[0];
		rep(i,0,r)if(sz[c]>sz[i])c=i;
		remove(c);
		rep(i,c,d){
			rep(j,i,r)remove(y[j]);
			if(dance(dep+1)){rec.push_back(x[i]); return 1;}
			rep(j,i,l)resume(y[j]);
		}
		resume(c);
		return 0;
	}
}dlx;
  • 解数独,一般数独有 \(k=9\)
int k,sqrtk; 
int f(int x,int y,int co){return (x*k+y)*k+co;}
int ord(char c){return c=='.'?0:isdigit(c)?c-'0':c-'A'+10;}
char chr(int x){return x<10?x+'0':x-10+'A';}
int a[16][16];
void Solve(){
	sqrtk=read(); k=sqrtk*sqrtk;
	n=k*k*k,m=k*k*4; dlx.init();
	repeat(x,0,k){
		static char str[20];
		scanf("%s",str);
		repeat(y,0,k){
			a[x][y]=ord(str[y]);
			int l,r; if(a[x][y])l=r=a[x][y]; else l=1,r=k;
			repeat(co,l,r+1){
				dlx.add(f(x,y,co),x*k+y+1);
				dlx.add(f(x,y,co),x*k+co+k*k);
				dlx.add(f(x,y,co),y*k+co+k*k*2);
				dlx.add(f(x,y,co),(x/sqrtk*sqrtk+y/sqrtk)*k+co+k*k*3);
			}
		}
	}
	dlx.dance();
	repeat(i,0,rec.size()){
		int s=rec[i]-1,x=s/(k*k),y=s/k%k,co=s%k+1;
		a[x][y]=co;
	}
	repeat(i,0,k){
		repeat(j,0,k)printf("%c",chr(a[i][j]));
		puts("");
	}
}

重复覆盖

  • 在01矩阵中找到最少的行,它们的并等于全集
  • xy编号还是从 1 开始!\(O(\exp)\),结点数可能 \(<3000\)
struct DLX{
	#define rep(i,d,s) for(node* i=s->d;i!=s;i=i->d)
	struct node{
		node *l,*r,*u,*d;
		int x,y;
	};
	static const int M=2e5;
	node pool[M],*h[M],*R[M],*pl;
	int sz[M],vis[M],ans,clk;
	void init(int n,int m){ // 行和列
		clk=0; ans=inf; pl=pool; ++m;
		repeat(i,0,max(n,m)+1)
			R[i]=sz[i]=0,vis[i]=-1;
		repeat(i,0,m)
			h[i]=new(pl++)node;
		repeat(i,0,m){
			h[i]->l=h[(i+m-1)%m];
			h[i]->r=h[(i+1)%m];
			h[i]->u=h[i]->d=h[i];
			h[i]->y=i;
		}
	}
	void link(int x,int y){
		sz[y]++;
		auto p=new(pl++)node;
		p->x=x; p->y=y;
		p->u=h[y]->u; p->d=h[y];
		p->d->u=p->u->d=p;
		if(!R[x])R[x]=p->l=p->r=p;
		else{
			p->l=R[x]; p->r=R[x]->r;
			p->l->r=p->r->l=p;
		}
	}
	void remove(node* p){
		rep(i,d,p)i->l->r=i->r,i->r->l=i->l;
	}
	void resume(node* p){
		rep(i,u,p)i->l->r=i->r->l=i;
	}
	int eval(){
		++clk; int ret=0;
		rep(i,r,h[0])
		if(vis[i->y]!=clk){
			++ret;
			vis[i->y]=clk;
			rep(j,d,i)rep(k,r,j)vis[k->y]=clk;
		}
		return ret;
	}
	void dfs(int d){
		if(h[0]->r==h[0]){ans=min(ans,d); return;}
		if(eval()+d>=ans)return;
		node* c; int m=inf;
		rep(i,r,h[0])
			if(sz[i->y]<m){m=sz[i->y]; c=i;}
		rep(i,d,c){
			remove(i); rep(j,r,i)remove(j);
			dfs(d+1);
			rep(j,l,i)resume(j); resume(i);
		}
	}
	int solve(){ // 返回最优解
		ans=inf; dfs(0); return ans;
	}
}dlx;

启发式算法

A-star

  • 定义 \(g(v)\) 是 s 到 v 的实际代价,\(h(v)\) 是 v 到 t 的估计代价
  • 定义估价函数 \(f(v)=g(v)+h(v)\)
  • 每次从堆里取出 \(f(v)\) 最小的点进行更新
  • 如果满足 \(h(v_1)+w(v_2,v_1)\ge h(v_2)\) (存疑)则不需要重复更新同一点,可以用set标记,已标记的不入堆

模拟退火

  • 以当前状态 X 为中心,半径为温度 T 的圆(或球)内选一个新状态 Y
  • 计算 \(D=E(Y)-E(X)\) 新状态势能减去当前状态势能
  • 如果 \(D<0\) 则状态转移(势能 E 越小越优)
  • 否则状态转移的概率是 \(\exp(-\dfrac{KD}{T})\)(Metropolis接受准则,学不会
  • 最后温度乘以降温系数,返回第一步
  • 需要调 3 个参数:初始温度,终止温度,降温系数(?)
  • 注意点:
    • 让运行时间在TLE边缘试探
    • 多跑几次退火
    • 多交几次(注意风险)
    • 可以先不用某准则,输出中间过程后再调参
lf rndf(){return rnd()*1.0/rnd.max();}
vec rndvec(){return vec(rndf()*2-1,rndf()*2-1);}
// lf E(vec); // 计算势能
struct state{ // 表示一个状态
	vec v; lf e; // 位置和势能
	state(vec v=vec()):v(v),e(E(v)){}
	operator lf(){return e;}
};
state getstate(){
	state X; lf T=1000;
	auto work=[&](){
		state Y=X.v+rndvec()*T;
		if(Y<X /*|| rndf()<exp(-K*(Y.e-X.e)/T)*/){X=Y; return 1;}
		return 0;
	};
	while(T>1e-9){
		if(work()){work(); work(); T*=1.1;}
		T*=0.99992;
	}
	return X;
}

void solve(){
	state X;
	repeat(i,0,6){
		state Y=getstate();
		if(X>Y)X=Y;
	}
	printf("%.10f\n",lf(X));
}

动态规划

多重背包

  • 二进制版,\(O(nV\log num)\),V 是总容量
int n,V; ll dp[N];
void push(int val,int v,int c){ // 处理物品(价值=val,体积=v,个数=c)
	for(int b=1;c;c-=b,b=min(b*2,c)){
		ll dv=b*v,dval=b*val;
		repeat_back(j,dv,V+1)
			dp[j]=max(dp[j],dp[j-dv]+dval);
	}
}
// 初始化fill(dp,dp+V+1,0),结果是dp[V]
  • 单调队列版,\(O(nV)\),V 是总容量
int n,V; ll dp[N];
void push(int val,int v,int c){ // 处理物品(价值=val,体积=v,个数=c)
	static deque< pair<int,ll> > q; // 单调队列,fi是位置,se是价值
	if(v==0){
		repeat(i,0,V+1)dp[i]+=val*c;
		return;
	}
	c=min(c,V/v);
	repeat(d,0,v){
		q.clear();
		repeat(j,0,(V-d)/v+1){
			ll t=dp[d+j*v]-j*val;
			while(!q.empty() && t>=q.back().se)
				q.pop_back();
			q.push_back({j,t});
			while(q.front().fi<j-c)
				q.pop_front();
			dp[d+j*v]=max(dp[d+j*v],q.front().se+j*val);
		}
	}
}
// 初始化fill(dp,dp+V+1,0),结果是dp[V]

最长不降子序列 / LIS

  • 二分查找优化,\(O(n\log n)\)
fill(dp,dp+n+1,inf);
repeat(i,0,n)
	*lower_bound(dp,dp+n,a[i])=a[i];
return lower_bound(dp,dp+n,inf)-dp;
  • 最大权不下降子序列

  • 即把 \(\langle x,v\rangle\) 拆成 v 个 x 后求一遍 LIS

void insert(map<ll,ll> &mp,vector<pii> &push,vector<pii> &pop){ // pii = <x,v>
	for(auto i:push){
		int key=i.fi,val=i.se;
		auto r=mp.lower_bound(key);
		if(r->fi==key)
			r->se+=val,++r;
		else
			mp.emplace_hint(r,key,val);
		auto s=r; int sum=0;
		while(sum+s->se<=val){
			pop.push_back(*s);
			sum+=s->se;
			++s;
		}
		if(s->fi!=INF)pop.push_back({s->fi,val-sum});
		s->se-=val-sum;
		mp.erase(r,s);
	}
	push.clear();
}
// init: mp.clear(),mp[INF]=INF;
// query: INF-mp[i].rbegin()->se

数位 dp

  • 记忆化搜索,DFS的参数lim表示是否被限制,lz表示当前位的前一位是不是前导零
  • 复杂度等于状态数
  • 如果每个方案贡献不是1,dp可能要变成struct数组(cnt,sum,....)
ll dp[20][*][2],bit[20]; // 这个[2]表示lz状态,如果lz被使用了的话就需要记录
ll dfs(int pos,ll *,bool lim=1,bool lz=1){
	if(pos==-1)return *; // 返回该状态是否符合要求(0或1)
	ll &x=dp[pos][*];
	if(!lim && x!=-1)return x;
	ll ans=0;
	int maxi=lim?bit[pos]:9;
	repeat(i,0,maxi+1){
		...// 状态转移
		if(lz && i==0)...// 可能要用lz,其他地方都不用
		ans+=dfs(pos-1,*,
			lim && i==maxi,
			lz && i==0);
	}
	if(!lim)x=ans; // 不限制的时候才做存储
	return ans;
}
ll solve(ll n){
	int len=0;
	while(n)bit[len++]=n%10,n/=10;
	return dfs(len-1,*);
}
signed main(){
	mst(dp,-1); // 在很多时候dp值可以反复使用
	ll t=read();
	while(t--){
		ll l=read(),r=read();
		printf("%lld\n",solve(r)-solve(l-1));
	}
	return 0;
}

换根dp

  • 两次 DFS
  • 第一次求所有点所在子树的答案 \(dp_v\),此时 \(dp_{rt}\)\(rt\) 的最终答案
  • 第二次将根转移来算其他点的最终答案,回溯时复原即可
void dfs1(int x,int fa=-1){
	for(auto p:a[x])
	if(p!=fa){
		dfs1(p,x);
		dp[x]+=op(dp[p]);
	}
}
void dfs2(int x,int fa=-1){
	ans[x]=dp[x];
	for(auto p:a[x])
	if(p!=fa){
		dp[x]-=op(dp[p]);
		dp[p]+=op(dp[x]);
		dfs2(p,x);
		dp[p]-=op(dp[x]);
		dp[x]+=op(dp[p]);
	}
}

斜率优化

  • 例:HDOJ3507
  • \(dp_i=\min\limits_{j=0}^{i-1}[dp_j+(s_i-s_j)^2+M]\)
  • 考虑 \(k<j<i\),j 决策优于 \(k\Leftrightarrow dp_j+(s_i-s_j)^2<dp_k+(s_i-s_k)^2\)
  • 一通操作后 \(\dfrac{s_j^2+dp_j-s_k^2-dp_k}{2(s_j-s_k)}<s_i\),即寻找点集 \(\{(2s_j,s_j^2+dp_j)\}\) 的下凸包中斜率刚好 \(>s_i\) 的线段的左端点,作为决策点

四边形优化

  • \(dp(l,r)=\min\limits_{k=l}^{r-1}[dp(l,k)+dp(k+1,r)]+w(l,r)\)
  • 其中 \(w(l,r)\) 满足
    • 区间包含单调性:任意 \(l \le l' \le r' \le r\)\(w(l',r')\le w(l,r)\)
    • 四边形不等式:任意 \(a \le b \le c \le d\)\(w(a,c)+w(b,d)\le w(a,d)+w(b,c)\)(若等号恒成立则满足四边形恒等式)
  • 决策单调性:令 \(m(l,r)\) 为最优决策点(满足 \(dp(l,r)=dp(l,m)+dp(m+1,r)+w(l,r)\)),则有 \(m(l,r-1) \le m(l,r) \le m(l+1,r)\),遍历这个区间可以优化至 \(O(n^2)\)
repeat(i,0,n)dp[i][i]=0,m[i][i]=i;
repeat(len,2,n+1)
for(int l=0,r=len-1;r<n;l++,r++){
	dp[l][r]=inf;
	repeat(k,m[l][r-1],min(m[l+1][r]+1,r))
	if(dp[l][r]>dp[l][k]+dp[k+1][r]+w(l,r)){
		dp[l][r]=dp[l][k]+dp[k+1][r]+w(l,r);
		m[l][r]=k;
	}
}
  • \(dp(i)=\min\limits_{k=1}^{i-1}w(k,i)\)\(w(l,r)\) 满足四边形不等式
  • 决策单调性:令 \(m_i\) 为最优决策点(满足 \(dp(i)=w(m,i)\)),则 \(m_{i-1}\le m_i\),因此可以分治优化成 \(O(n\log n)\)

哈希 / Hash

字符串哈希

  • 如果不需要区间信息,可以调用 hash<string>()(s) 获得 ull 范围的 hash 值。
  • 碰撞概率:单哈希 \(10^6\) 次比较大约有 \(\dfrac 1 {1000}\) 概率碰撞。
  • 更新生日悖论:n 个字符串,两个串 Hash 值相同概率 \(P\approx 1-(1-\tfrac{1}{mod})^{\tfrac{n(n-1)}{2}}\)
  • 支持查询子串 hash 值,编号从 0 开始,初始化 \(O(n)\),子串查询 \(O(1)\)
template<typename string>
struct Hash{
	template<int b,int mod,int x=101>
	struct hs{
		vector<int> a,p;
		hs(const string &s=""){
			a={0},p={1};
			for(auto c:s){
				a.push_back((1ll*a.back()*b+(c^x))%mod);
				p.push_back(1ll*p.back()*b%mod);
			}
		}
		ll q(int l,int r){
			return (a[r+1]-1ll*a[l]*p[r-l+1]%mod+mod)%mod;
		}
		ll q2(int l,int r){ // 循环字符串
			if(l<=r)return q(l,r); 
			return (a[r+1]+q(l,a.size()-2)*p[r+1])%mod;
		}
	};
	hs<257,1000000007> h1;
	hs<257,2147483647> h2;
	Hash(const string &s):h1(s),h2(s){}
	pii query(int l,int r){
		return {h1.q(l,r),h2.q(l,r)};
	}
	pii query2(int l,int r){ // 循环字符串
		return {h1.q2(l,r),h2.q2(l,r)};
	}
};

质因数哈希

int fac(int n,int c,int mod,const function<int(int)> &f){
	int p=c*c%mod,ans=0;
	for(int i=2;i*i<=n;i++){
		int cnt=0;
		while(n%i==0)n/=i,cnt++;
		ans=(ans+p*f(cnt))%mod;
		p=p*c%mod;
	}
	if(n>1)ans=(ans+qpow(c,n,mod)*f(1))%mod;
	return ans;
}
// 例:匹配乘积为x^k(x任意)的两个数
pii hash1(int n){
	return pii(
		fac(n,101,2147483647,[](int x){return x%k;}),
		fac(n,103,1000000007,[](int x){return x%k;})
	);
}
pii hash2(int n){
	return pii(
		fac(n,101,2147483647,[](int x){return (k-x%k)%k;}),
		fac(n,103,1000000007,[](int x){return (k-x%k)%k;})
	);
}

字符串

  • 我字符串是最菜的
  • 寻找模式串p在文本串t中的所有出现

字符串函数

前缀函数 using kmp

  • \(p[x]\) 表示满足 s.substr(0,k)==s.substr(x-k,k)\(x\not=k\) 的 k 的最大值,\(p[0]=0\)
  • 线性复杂度
int p[N];
void kmp(const string &s) {
	p[0] = 0; int k = 0;
	repeat (i, 1, s.length()){
		while (k > 0 && s[i] != s[k]) k = p[k - 1];
		if (s[i] == s[k]) k++;
		p[i] = k;
	}
}
void solve(string s1,string s2){ // 模拟s1.find(s2)
	kmp(s2+'#'+s1);
	repeat(i,s2.size()+1,s.size())
	if(p[i]==(int)s2.size())
		ans.push_back(i-2*s2.size()); // 编号从0开始的左端点
}
struct KMP{
	string s;
	vector<int> p;
	int get(int k,char c){
		while(k>0 && c!=s[k])k=p[k-1];
		if(c==s[k])k++;
		return k;
	}
	void init(const string &_s){
		p.assign(1,0); int k=0; s=_s+'#';
		repeat(i,1,s.size())p.push_back(k=get(k,s[i]));
	}
	int size(){return s.size()-1;}
};

z函数 using exkmp

  • \(z[x]\) 表示满足 s.substr(0,k)==s.substr(x,k) 的 k 的最大值,\(z[0]=0\)
  • 线性复杂度
int z[N];
void exkmp(const string &s){ // 求s的z函数
	fill(z,z+s.size(),0); int l=0,r=0;
	repeat(i,1,s.size()){
		if(i<=r)z[i]=min(r-i+1,z[i-l]);
		while(i+z[i]<(int)s.size() && s[z[i]]==s[i+z[i]])z[i]++;
		if(i+z[i]-1>r)l=i,r=i+z[i]-1;
	}
}

马拉车 / Manacher

  • 预处理为 "#*A*A*A*A*A*"
  • 线性复杂度
int len[N*2]; char s[N*2]; // 两倍内存
int manacher(char s1[]){ // s1可以是s
	int n=strlen(s1)*2+1;
	repeat_back(i,0,n)s[i+1]=(i%2==0?'*':s1[i/2]);
	n++; s[0]='#'; s[n++]=0;
	len[0]=0;
	int mx=0,id=0,ans=0;
	repeat(i,1,n-1){
		if(i<mx)len[i]=min(mx-i,len[2*id-i]);
		else len[i]=1;
		while(s[i-len[i]]==s[i+len[i]])len[i]++;
		if(len[i]+i>mx)mx=len[i]+i,id=i;
		ans=max(ans,len[i]-1); // 最长回文串长度
	}
	return ans;
}

最小表示法

  • 求 s 重复无数次的字符串最小后缀的左端点
  • 线性复杂度
int minstr(const string &s){
	int k=0,i=0,j=1,n=s.size();
	while(max(k,max(i,j))<n){
		if(s[(i+k)%n]==s[(j+k)%n])k++;
		else{
			s[(i+k)%n]>s[(j+k)%n]?i+=k+1:j+=k+1;
			if(i==j)i++;
			k=0;
		}
	}
	return min(i,j);
}

后缀数组 / SA

  • \(sa[i]\) 表示所有后缀中第 i 小的后缀是 s.substr(sa[i],-1)
  • \(rk[i]\) 表示所有后缀中 s.substr(i,-1) 是第 \(rk[i]\)
  • 编号从 1 开始!\(O(n\log n)\)
int sa[N],rk[N]; // sa,rk即结果
void get_sa(const string &S){
	static int pre[N*2],id[N],px[N],cnt[N];
	int n=S.length(),m=256;
	const char *const s=S.c_str()-1; // 为了编号从1开始
	for(int i=1;i<=n;i++)cnt[rk[i]=s[i]]++;
	for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>=1;i--)sa[cnt[rk[i]]--]=i;
	for(int w=1;w<n;w<<=1){
		int t=0;
		for(int i=n;i>n-w;i--)id[++t]=i;
		for(int i=1;i<=n;i++)
			if(sa[i]>w)id[++t]=sa[i]-w;
		mst(cnt,0);
		for(int i=1;i<=n;i++)cnt[px[i]=rk[id[i]]]++;
		for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
		for(int i=n;i>=1;i--)sa[cnt[px[i]]--]=id[i];
		memcpy(pre,rk,sizeof(rk));
		int p=0;
		static auto pp=[&](int x){return pii(pre[x],pre[x+w]);};
		for(int i=1;i<=n;i++)
			rk[sa[i]]=pp(sa[i])==pp(sa[i-1])?p:++p;
		m=p; // 优化计数排序值域
	}
}
  • sa可以在同一文本串中在线多次查找模式串(二分查找)

height数组

  • 定义 \(lcp(i,j)=\) 后缀 i 和后缀 j 的最长公共前缀长度
  • 定义 \(height[i]=lcp(sa[i],sa[i-1])\)\(height[1]=0\)
  • \(height[rk[i]]\ge height[rk[i-1]]-1\)
  • 编号从 1 开始,\(O(n)\)
for(int i=1,k=0;i<=n;i++){
	if(k)k--;
	while(s[i+k]==s[sa[rk[i]-1]+k])k++;
	ht[rk[i]]=k;
}
  • 不相等的子串个数为 \(\dfrac{n(n+1)}{2}-\sum\limits_{i=2}^{n}height[i]\)

自动机

字典树 / Trie

  • 线性复杂度
struct trie{
	struct node{
		int to[26],cnt;
		int &operator[](int n){return to[n];}
	}a[N];
	int t;
	void init(){t=0; a[t++]=node();}
	void insert(const char s[]){
		int k=0;
		for(int i=0;s[i];i++){
			int c=s[i]-'a'; // small letter
			if(!a[k][c])a[k][c]=t,a[t++]=node();
			k=a[k][c];
			// a[k].son++; // 子树大小
		}
		a[k].cnt++;
	}
	int query(const char s[]){
		int k=0;
		for(int i=0;s[i];i++){
			int c=s[i]-'a'; // small letter
			if(!a[k][c])return 0;
			k=a[k][c];
		}
		return a[k].cnt;
	}
}t;
  • 最小异或生成树,01字典树
  • 归并,\(O(n\log^2n)\)
struct trie{
	static const int B=30;
	struct node{
		int to[2];
		int &operator[](int n){return to[n];}
	}a[N];
	int t;
	void init(){t=0; a[t++]=node();}
	void insert(ll s){
		int k=0;
		repeat_back(i,0,B){
			int c=(s>>i)&1;
			if(!a[k][c])a[k][c]=t,a[t++]=node();
			k=a[k][c];
		}
	}
	ll query(ll s){ // the min value in {s^t | t in trie}
		int k=0; ll ans=0;
		repeat_back(i,0,B){
			int c=(s>>i)&1;
			if(!a[k][c])c^=1,ans^=1ll<<i;
			k=a[k][c];
		}
		return ans;
	}
}t;
int a[N],n; ll ans;
void merge(int l,int r,ll s){
	if(s==0 || l>=r-1)return;
	int m=lower_bound(a+l,a+r,s)-a;
	if(l<m && m<r){
		t.init(); ll mn=INF;
		repeat(i,l,m)t.insert(a[i]);
		repeat(i,m,r)mn=min(mn,t.query(a[i]));
		ans+=mn;
	}
	repeat(i,m,r)a[i]^=s;
	merge(l,m,s>>1); merge(m,r,s>>1);
}
void solve(){
	sort(a,a+n);
	merge(0,n,1ll<<31);
}
  • 可持久化字典树,查询与 s 异或后的序列的区间最大值
struct trie{
	static const int B=30;
	struct node{
		int to[2]; int lst;
		int &operator[](int n){return to[n];}
	}a[N];
	int t;
	int clone(int k){a[t]=a[k]; return t++;}
	int init(){t=1; return ins(0,0,0);}
	int ins(int rt,ll s,int lst){
		int k=rt=clone(rt);
		repeat_back(i,0,B){
			int c=(s>>i)&1;
			a[k][c]=clone(a[k][c]);
			k=a[k][c];
			a[k].lst=max(a[k].lst,lst);
		}
		return rt;
	}
	ll q(int rt,ll s,int lst){ // the max value in {s^t | t in trie}
		int k=rt; ll ans=0;
		repeat_back(i,0,B){
			int c=(s>>i)&1; c^=1,ans^=1ll<<i;
			if(!a[k][c] || a[a[k][c]].lst<lst)c^=1,ans^=1ll<<i;
			k=a[k][c];
		}
		return ans;
	}
}tr;
int h[N],top;
int query(int l,int r,int s){
	return tr.q(h[r],s,l);
}
void push_back(int s){
	top++; h[top]=tr.ins(h[top-1],s,top);
}
void init(){
	top=0; h[0]=tr.init();
}

AC自动机

  • 先构建字典树,再构建fail树和字典图
  • 线性复杂度
struct AC{
	static const int sigma=26,c0='a'; // 小写字母
	struct node{
		int to[sigma],fail,trie_cnt,cnt;
		int &operator[](int x){return to[x];}
		// vector<int> p; // 指向模式串集合
	}a[N];
	int t;
	vector<int> q; // 存了BFS序
	void init(){t=0; a[t++]=node(); q.clear();}
	void insert(const char s[]/*,int ptr*/){ // 将模式串插入字典树
		int k=0;
		for(int i=0;s[i];i++){
			int c=s[i]-c0;
			if(!a[k][c])a[k][c]=t,a[t++]=node();
			k=a[k][c];
		}
		a[k].trie_cnt++;
		// a[k].p.push_back(ptr);
	}
	void build(){ // 构建fail树,将字典树扩展为图
		int tail=0;
		repeat(i,0,sigma)
		if(a[0][i])
			q.push_back(a[0][i]);
		while(tail!=(int)q.size()){
			int k=q[tail++];
			repeat(i,0,sigma)
			if(a[k][i])
				a[a[k][i]].fail=a[a[k].fail][i],q.push_back(a[k][i]);
			else
				a[k][i]=a[a[k].fail][i];
		}
	}
	void query(const char s[]){ // 记录文本串中模式串出现次数
		int k=0;
		for(int i=0;s[i];i++){
			int c=s[i]-c0;
			k=a[k][c];
			a[k].cnt++; // fail树上差分
			// for(int kk=k;kk!=0;kk=a[kk].fail)
			// for(auto p:a[kk].p)
				// ans[p]++; // 代替下面的反向遍历,我也不知道什么时候用
		}
		repeat_back(i,0,q.size()){ // 反向遍历BFS序
			a[a[q[i]].fail].cnt+=a[q[i]].cnt; // 差分求和
			// for(auto p:a[q[i]].p)ans[p]=a[q[i]].cnt; // 反馈答案
		}
	}
}ac;

后缀自动机 / SAM

  • 给定字符串中所有子串对应了SAM中从源点出发的一条路径
  • SAM是一个DAG,至多有 \(2n-1\) 个结点和 \(3n-4\) 条边
  • 每个结点表示一个endpos等价类,对应子串长度区间 \([a[a[i].fa].len+1,a[i].len]\)
  • 构造 \(O(n)\),编号从 1 开始(\(a[1]\) 表示源点)
struct SAM{
	static const int sigma=26,c0='a'; // 小写字母
	struct node{
		int to[sigma],fa,len;
		int &operator[](int x){return to[x];}
	}a[N*2];
	int last,tot;
	void init(){last=tot=1;}
	int add(){a[++tot]=node(); return tot;}
	void push(int c){
		c-=c0;
		int x=last,nx=last=add();
		a[nx].len=a[x].len+1;
		for(;x && a[x][c]==0;x=a[x].fa)a[x][c]=nx;
		if(x==0)a[nx].fa=1;
		else{
			int p=a[x][c];
			if(a[p].len==a[x].len+1)a[nx].fa=p;
			else{
				int np=add();
				a[np]=a[p]; a[np].len=a[x].len+1;
				a[p].fa=a[nx].fa=np;
				for(;x && a[x][c]==p;x=a[x].fa)a[x][c]=np;
			}
		}
	}
}sam;
// 构造:for(auto i:s)sam.push(i);

杂项

位运算

位运算操作

  • 中点向下取整 (x+y)/2: (x & y) + ((x ^ y) >> 1)
  • 中点向上取整 (x+y+1)/2: (x | y) - ((x ^ y) >> 1)
    • 一般来说用 x + (y - x >> 1)
  • abs(n): (n ^ (n >> 31)) - (n >> 31)
  • max(a,b): b & ((a - b) >> 31) | a & (~(a - b) >> 31)
  • min(a,b): a & ((a - b) >> 31) | b & (~(a - b) >> 31)
template<typename T> T getbit(T x,int pos){return x>>pos&1;}
template<typename T> void setbit(T &x,int pos,bool z){z?x|=T(1)<<pos:x&=~(T(1)<<pos);}
  • 位运算函数(不需要头文件)
__builtin_ctz(x),__builtin_ctzll(x) // 返回x后导0的个数,x是0则返回32 or 64
__builtin_clz(x),__builtin_clzll(x) // 返回x前导0的个数,x是0则返回32 or 64
__builtin_popcount(x),__builtin_popcountll(x) // 返回x中1的个数
__builtin_parity(x),__builtin_parityll(x) // 返回x中1的个数是否为奇数

枚举二进制子集

  • 枚举二进制数m的非空子集
for(int s=m;s;s=(s-1)&m){
	work(s);
}
  • 枚举n个元素的大小为k的二进制子集(要保证k不等于0)
int s=(1<<k)-1;
while(s<(1<<n)){
	work(s);
	int x=s&-s,y=s+x;
	s=((s&~y)/x>>1)|y; // 这里有一个位反~
}

浮点数

浮点数操作

const lf eps=1e-11;
if(abs(x)<eps)x=0; // 输出浮点数的预处理

浮点数常量

float 1e38, 有效数字6
double 1e308, 有效数字15
long double 1e4932, 有效数字18

常数优化 / 卡常

估计函数用时

  • clock() 可以获取时刻,单位毫秒,运行函数前后的时间之差即为用时。
  • 一些巨佬测出来的结论:
- 整数加减:1(个 CPU 周期,下同)
- 整数位运算:1
- 整数乘法:2
- 整数除法:21
- 浮点加减:3
- 浮点除法:35
- 浮点开根:60

指令集优化

#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")

快读快写

ll readfast(){
	ll x=0,tag=1; char c=getchar();
	for(;!isdigit(c);c=getchar())if(c=='-')tag=-1;
	for(; isdigit(c);c=getchar())x=x*10+c-48;
	// ungetc(c,stdin);
	return x*tag;
}
template<typename T>
void write(T x){ // slow
	if(x<0)x=-x,putchar('-');
	if(x>=10)write(x/10);
	putchar(x%10^48);
}
char getc(){ // 代替 getchar,用了这个就不能用其他读入函数如 scanf
	static char now[1<<21],*S,*T;
	if(T==S){T=(S=now)+fread(now,1,1<<21,stdin); if(T==S)return EOF;}
	return *S++;
}

STL 手写内存分配器

static char space[10000000],*sp=space;
template<typename T>
struct allc:allocator<T>{
	allc(){}
	template<typename U>
	allc(const allc<U> &a){}
	template<typename U>
	allc<T>& operator=(const allc<U> &a){return *this;}
	template<typename U>
	struct rebind{typedef allc<U> other;};
	T* allocate(size_t n){
		T *res=(T*)sp;
		sp+=n*sizeof(T);
		return res;
	}
	void deallocate(T* p,size_t n){}
};
vector< int,allc<int> > a;

其他优化

int mul(int a,int b,int m=mod){ // 汇编,a * b % mod
	int ret;
	__asm__ __volatile__ (
		"\tmull %%ebx\n\tdivl %%ecx\n"
		: "=d"(ret)
		: "a"(a),"b"(b),"c"(m)
	);
	return ret;
}
int bitrev(int x){ // 反转二进制 x
	int z;
	#define op(i) \
		z = ~0u / (1 << i | 1); \
		x = (x & z) << i | (x >> i & z)
	op(1); op(2); op(4); op(8); op(16);
	#undef op
	return x;
}
int kthbit(int x,int k){ // 从低到高第 k 个 1 的位置
	int ans=0,z;
	#define op(b) \
		z=__builtin_popcount(x & ((1 << b) - 1)); \
		if(z<k)k-=z,ans|=b,x>>=b;
	op(16); op(8); op(4); op(2); op(1);
	#undef op
	return ans;
}

查表法反转二进制位

u32 rev_tbl[65537];
void bitrevinit() {
	rev_tbl[0] = 0;
	for (int i=1; i<65536; ++i)
	rev_tbl[i] = (rev_tbl[i>>1]>>1) | ((i&1)<<15);
}
u32 bitrev(u32 x) {
	return (rev_tbl[x & 65535] << 16) | rev_tbl[x >> 16];
}
// (都是听说的)
1ll*a 比 (ll)a 快
取模:x%mod 优化为 x<mod?x:x%mod
减少浮点除法:a/b+c/d 优化为 (a*d+b*c)/(b*d)
精度足够时用ll代替浮点类型
多路并行运算,如 (a+b)+(c+d) 比 ((a+b)+c)+d 快
加上inline,以及强制内联__inline __attribute__((always_inline))
多重for循环时,修改for的顺序保证内存连续访问
多使用局部变量
排序后依次比较可以减少分支预测失败率

卡时暴力

  • 在 TLE 的边缘试探。
while(clock()<0.9*CLOCKS_PER_SEC){
	// 反复更新最优解
}

对拍 / bat

#include<bits/stdc++.h>
using namespace std;
int main(){
	for(int i=0;;i++){
		if(i%10==0)cerr<<i<<endl;
		system("gen.exe > test.in");
		system("test1.exe < test.in > a.out");
		system("test2.exe < test.in > b.out");
		if(system("fc a.out b.out")){ // linux: diff
			system("pause");
			return 0;
		}
	}
}

备选

#include<bits/stdc++.h>
using namespace std;
ifstream a,b;
int main(){
	for(int i=0;;i++){
		if(i%10==0)cerr<<i<<endl;
		system("datamaker.exe > data.txt");
		system("A.exe < data.txt > a.out");
		system("B.exe < data.txt > b.out");
		a.open("a.out");
		b.open("b.out");
		while(a.good() || b.good()){
			if(a.get()!=b.get()){
				system("pause");
				return 0;
			}
		}
		a.close(),b.close();
	}
}

战术分析

交题前给爷好好考虑:

  • 数据范围。
  • 初始化,并测试一下。
  • 考虑极端数据!!
  • 试一试 ctrl+Z / ctrl+D 能否结束。

坑点

  • ll t; 1<<t 返回int,必须是 1ll<<t
  • int x; x<<y 的 y 会先对 32 取模。
  • operator< 的比较内容要写完整。
  • 无向图输入时每条边要给两个值赋值 g[x][y]=g[x][y]=1
  • 建模的转换函数的宏定义一定要加括号,或者写成函数。
  • islower() 等函数返回值不一定是 0 或 1。
  • 多用相空间角度思考问题。
  • 内存比想象的要大一些(有时候 1e7 可以塞下)。
  • 字符串 find() 最好与 string::npos 比较,或者 (signed)s.find(c)==-1
  • 多年来多项式常数大的原因找到了,加后取模改成 int D(int x){return x>=mod?x-mod:x;}(ll 改 int 以及两个分支改一个),减法改成 D(x-y+mod);mod 等等也改成 int 类型。
posted @ 2021-07-07 01:11  axiomofchoice  阅读(212)  评论(0编辑  收藏  举报