大板子 —— 8

——— 算法技巧 ———

—— 思维 ——
区间GCD
int n, ans, a[maxn], l[maxn], v[maxn];
int main() {
	scanf("%d", &n);
	for(int i=1; i<=n; i++)
		scanf("%d", a+i), v[i] = a[i], l[i] = i;
	for(int i=1; i<=n; i++)
		for(int j=i; j; j=l[j]-1) {
			v[j] = __gcd(v[j], a[i]);	// j ~ i的gcd值 
			while(l[j]>1 && __gcd(a[i], v[l[j]-1])==__gcd(a[i], v[j]))
				l[j] = l[l[j]-1];
			ans = (ans + 1LL * v[j] * (j-l[j]+1)) % mod;
		}
	printf("%d", ans);
}

第k小团

    优先队列暴力枚举每个最小团
    关键在于处理重复的情况

    对于每种情况,只对最后一个 1 1 1 出现的位置之后加点
    也就是新增点要在当前团的最后一个点之后
    时间复杂度: O ( k ⋅ n ⋅ l o g V ⋅ b i t s e t < 100 > ) O(k \cdot n \cdot logV \cdot bitset<100>) O(knlogVbitset<100>)

int n, k, w[maxn]; 
bitset <maxn> mp[maxn], t;
char s[maxn];

struct Node{
	ll v; bitset <maxn> u;
	friend bool operator < (Node A, Node B){
		return A.v > B.v;
	}
} p;
priority_queue <Node> q;

int main() {
	scanf("%d%d", &n, &k);
	for(int i=0; i<n; i++) scanf("%d", w+i);
	for(int i=0; i<n; i++){
		scanf("%s", s);
		int len = strlen(s);
		for(int j=0; j<len; j++)
			if(s[j]=='1') mp[i][j] = 1; 
	}
	q.push({0, t});
	while(q.size()){
		p = q.top(); q.pop(); k--;
		if(k==0) { printf("%lld", p.v); return 0; }
		int pos = 0;
		for(int i=0; i<n; i++) if(p.u[i]) pos = i + 1;
		for(int i=pos; i<n; i++){
			if((mp[i] & p.u) == p.u){
				p.u[i] = 1;
				q.push({p.v + w[i], p.u});
				p.u[i] = 0;
			}
		}
	}
	puts("-1");
}

HDU多校第十场 1008 Coins

    有 n n n 组硬币,每组硬币有两个 a i , b i a_i, b_i ai,bi
    要求拿出 k k k 个硬币最大值为 f ( k ) f(k) f(k)
    限制一组硬币: a i a_i ai 未拿的时候不能拿 b i b_i bi
    求 f ( 1 ) 、 f ( 2 ) 、 . . . f ( k ) f(1)、f(2)、...f(k) f(1)f(2)...f(k)

解题思路:

    因为有 b i b_i bi 的限制,所以局部最优不是全局最优
    但是仔细考虑一下,假设当前拿了 k k k 个硬币,且是最优的
    考虑怎么拿下一个硬币:
     ① : ①: 直接拿当前可以拿的最大值的那个硬币
     ② : ②: 放回拿的第 k k k 个硬币,然后拿当前可以拿的最大的那一组硬币

    经思考发现,没有第三种情况,第三种情况已经包含在上面的最优解里
    所以记录一下各种可以拿的硬币及拿到了第几个
    用优先队列维护一下即可

int T, n, ans[maxn], th2[maxn], th[maxn];
struct node1{
	int x, id, th;
	bool operator < (const node1 &A) const { return x < A.x; }
};
struct node2{
	int x, y, id;
	bool operator < (const node2 &A) const { return x + y < A.x + A.y; }
}; 
priority_queue <node1> q1;
priority_queue <node2> q2;

int main() {
	scanf("%d", &T);
	while(T--){
		scanf("%d", &n);
		while(!q1.empty()) q1.pop();
		while(!q2.empty()) q2.pop();
		for(int i=1, a, b; i<=n; i++){
			scanf("%d%d", &a, &b);
			th2[i] = b, th[i] = 0;
			q1.push({a, i, 0});
			q2.push({a, b, i});
		}
		int f = 0, prex, preid, a, b;
		for(int i=1; i<=n*2; i++){
			while(th[q1.top().id]!=q1.top().th && !q1.empty()) q1.pop();
			while(th[q2.top().id]!=0 && !q2.empty()) q2.pop();
			a = q1.top().x;
			b = (f&&!q2.empty()) ? q2.top().x + q2.top().y - prex : 0;
			if(a <= b){	//	取 2个 
				f = 0; ans[i] = ans[i-1] + b;
				th[q2.top().id] = 2;
				q1.push({prex, preid, --th[preid]});
				q2.pop();
			} else {	//	取 1个 
				f = 1; ans[i] = ans[i-1] + a;
				prex = q1.top().x, preid = q1.top().id;
				q1.pop();
				if(!th[preid]) q1.push({th2[preid], preid, 1});
				++th[preid];
			}
			printf("%d%c", ans[i], " \n"[i==n*2]);
		}
	}
}

2020 wannafly camp day6 H 异或询问

在这里插入图片描述
解题思路:

    如果只考虑 ∑ i = 1 n f ( i ) \sum_{i=1}^n f(i) i=1nf(i),也就是一段连续的区间,预处理前缀即可
    而 ∑ f ( i   x o r   x ) \sum f(i\ xor\ x) f(i xor x),加上了一个异或后,区间就不连续了
    根据异或性质,会分成最多 l o g log log 个连续区间
    从高位到低位考虑,如果 n n n 的第 p o s pos pos 位为 1 1 1,若不考虑 n n n 的低位
    若 x x x 的这一位为 0 0 0 ,则异或后是一段连续的区间: ( 0 , 2 p o s − 1 ) (0, 2^{pos}-1) 0,2pos1
    若 x x x 的这一位为 1 1 1 ,则加上这一位,再计算新的区间

    每一位考虑完之后,要加上这一位的影响,再去考虑低位
    最后要清空影响,其实最后的影响就是 n   x o r   x n\ xor\ x n xor x

int n, q, a[maxn]; ll pre[maxn];
vector <int> v;

ll gao(ll x){
	if(x < a[1]) return 0;
	int pos = upper_bound(v.begin(), v.end(), x) - v.begin() - 1;
	ll ret = pre[pos];
	if(x == v[pos]) return ret;
	ll num = upper_bound(a+1, a+1+n, x) - a - 1;
	ret += 1ll * (x - v[pos]) * num % mod * num % mod;
	return ret % mod;
}

ll query(int l, int r){
	return (gao(r) - gao(l-1) + mod) % mod;
}

ll solve(int n, int x){
	if(n < 0) return 0;
	ll ret = 0; int cur = 0;
	for(int i=30; ~i; i--){
		int u = x >> i & 1;
		if(n >> i & 1){
			ret += query(cur|(u<<i), (cur|(u<<i)) + (1<<i)-1);
			cur |= ((u ^ 1) << i);
		} else cur |= (u << i);
	}
	ret = (ret + query(cur, cur)) % mod;
	return ret;
}

int main() {
	scanf("%d%d", &n, &q);
	for(int i=1; i<=n; i++) scanf("%d", a+i), v.push_back(a[i]);
	sort(a+1, a+1+n);
	sort(v.begin(), v.end());
	v.erase(unique(v.begin(), v.end()), v.end());
	ll cnt = v.size(), num, lnum;
	for(int i=0; i<cnt; i++){
		num = upper_bound(a+1, a+1+n, v[i]) - a - 1;
		if(i == 0) pre[i] = num * num % mod;
		else pre[i] = pre[i-1] + num * num % mod + \
					 (v[i] - v[i-1] - 1) * lnum % mod * lnum % mod;
		pre[i] %= mod; lnum = num;
	}
	while(q--){
		int l, r, x;
		scanf("%d%d%d", &l, &r, &x);
		ll ans = solve(r, x) - solve(l-1, x);
		printf("%lld\n", (ans + mod) % mod);
	}
}

—— WQS二分 ——

    模型:有 n n n 个物品,选择每一个都会有相应的权值,需要求出强制选 k k k 个物品时的最大/最小权值和

    大致思路:
    限制个数的最大/最小权值和可以用 n 2 n^2 n2 D P DP DP 求出
    根据 D P DP DP 图像,可以画出 x − g ( x ) x - g(x) xg(x) 图像,发现是上凸 / / /下凸包,斜率单调
    设用直线 y = k x + b y = kx + b y=kx+b 去切这个图像,得到 f ( x ) = b = g ( x ) − k x f(x) = b = g(x) - kx f(x)=b=g(x)kx
    使得截距 b b b 最大,即对于每个数的权值 h ( x ) − = k h(x) -= k h(x)=k
    求一遍任意个数情况下的最大 / / /最小 f ( x ) f(x) f(x) O ( n ) O(n) O(n) D P DP DP 可以完成

注意事项:

     ① : ①: 若在答案附近出现斜率相同的情况
    则二分判断结果 n u m < = k num <= k num<=k n u m num num 为尽可能少选的数值
    二分判断结果 n u m > = k num >= k num>=k n u m num num 为尽可能多选的数值
     ② : ②: 权值 h ( x ) − = k h(x) -= k h(x)=k 要根据实际意义转化,有时可以为 h ( x ) + = k h(x) += k h(x)+=k
     ③ : ③: 注意二分的结果是否为目标值 k k k 的截距,最终答案要加上 k ∗ m i d k * mid kmid


    大意:无向带权连通图,每条边为白色或黑色,求一棵最小权的恰好有 n e e d need need 条白色边的生成树

    根据 W Q S WQS WQS 二分,发现这个 x − g ( x ) x - g(x) xg(x) 是一个下凸包
    二分斜率,每条白色边权 − = x -= x =x
    对答案 + k ∗ m i d + k * mid +kmid 即可

int n, m, k, f[maxn], ans, num;
struct edge {
	int s, t, c, col;
} e[maxn], ed[maxn];

bool cmp(const edge A,const edge B) {
	if(B.c == A.c) return A.col > B.col;
	return A.c < B.c;
}

int getf(int x) {
	return x == f[x] ? x : f[x] = getf(f[x]);
}

bool ck(int x) {
	for(int i=1; i<=n; i++) f[i] = i;
	for(int i=1; i<=m; i++) {
		ed[i] = e[i];
		if(!e[i].col) ed[i].c -= x;
	}
	sort(ed+1, ed+1+m, cmp);
	ans = 0, num = 0;
	for(int i=1; i<=m; i++) {
		int fs = getf(ed[i].s), ft = getf(ed[i].t);
		if(fs == ft) continue;
		f[fs] = ft;
		ans += ed[i].c;
		num += !ed[i].col;
	}
	return num <= k;
}

int main() {
	scanf("%d%d%d", &n, &m, &k);
	for(int i=1; i<=m; i++) {
		scanf("%d%d%d%d", &e[i].s, &e[i].t, &e[i].c, &e[i].col);
		e[i].s++, e[i].t++;
	}
	int l = -1e5, r = 1e5, mid;
	while(l <= r) {
		mid = l + r >> 1;
		if(ck(mid)) l = mid + 1;
		else r = mid - 1;
	}
	ck(r);
	printf("%d\n", ans + k * r);
}

—— 增值二分 ——

题目大意:

    给定 a i a_i ai,找到一组 b i b_i bi,满足 0 ≤ b i ≤ a i 0 \le b_i \le a_i 0biai,且 ∑ b i = k \sum{b_i} = k bi=k
    使得 ∑ b i × ( a i − b i 2 ) \sum{b_i \times (a_i - b_i^2)} bi×(aibi2) 最大

解题思路:

    直接对增值进行二分,在上面我们说到使答案减少的值越来越大
    其实等价于使答案增大的值越来越小,观察 f ′ ( x ) = − 3 x 2 + a f'(x) = -3x^2 + a f(x)=3x2+a
     f ′ ( x ) f'(x) f(x) x > 0 x>0 x0 时单调递减,说明 f ( x ) f(x) f(x) x > 0 x>0 x0 时增值越来越小,也就是使答案增大的值越来越小

    当 b i b_i bi 的增值大于二分的 x x x 时,就让它增加,也就减少了增值,这样一定保证答案的合理性
    只是最后要处理一下 ∑ b i ≥ k \sum{b_i} \ge k bik,选取最优的 ∑ b i − k \sum{b_i} - k bik b i b_i bi 使其减少
    时间复杂度: O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

int n; ll k, a[maxn], b[maxn];
struct node{
	ll num; int id;
	bool operator < (const node &A) { return num > A.num; }
} p[maxn];

bool ck(ll x){
	ll all = 0, l, r, mid;
	for(int i=1; i<=n; i++){
		l = 0, r = a[i], mid;
		while(l <= r){
			mid = l + r >> 1;
			ll tmp1 = mid * (a[i] - mid * mid);
			ll tmp2 = (mid - 1) * (a[i] - (mid - 1) * (mid - 1));
			if(tmp1 - tmp2 >= x) l = mid + 1;
			else r = mid - 1;
		}
		b[i] = r; all += b[i];
	}
	return all >= k;
}
void gao(){
	ll all = 0, cnt = 0;
	for(int i=1; i<=n; i++) {
		all += b[i];
		if(b[i] == 0) continue;
		p[++cnt].id = i;
		ll tmp1 = 1ll * b[i] * (a[i] - b[i] * b[i]);
		ll tmp2 = 1ll * (b[i] - 1) * (a[i] - 1ll * (b[i] - 1) * (b[i] - 1));
		p[cnt].num = tmp2 - tmp1;
	}
	all -= k; if(all == 0) return;
	sort(p+1, p+1+cnt);
	for(int i=1; all; i++, all--) b[p[i].id]--;
}

signed main() {
	scanf("%d%lld", &n, &k);
	for(int i=1; i<=n; i++) scanf("%lld", a+i);
	ll l = -4e18, r = 4e18, mid;
	while(l <= r){
		mid = l + r >> 1;
		if(ck(mid)) l = mid + 1;
		else r = mid - 1;
	}
	ck(r); gao();
	for(int i=1; i<=n; i++) printf("%d ", b[i]);
}

—— 01分数规划 ——

给定 n n n 个二元组 ( v a l u e i , c o s t i ) (value_i,cost_i) (valueicosti),分别表示价值和代价
x i ∈ { 0 , 1 } x_i∈\{0, 1\} xi{0,1},表示第 i i i 个二元组是否选取
要求最大(小)化下式:
r = ∑ v a l u e i × x i ∑ c o s t i × x i r = \frac{ \sum{value_i} \times x_i } { \sum{cost_i} \times x_i } r=costi×xivaluei×xi


二分 m i d mid mid ,若最优值 ≥ m i d : ≥ mid: mid:
∑ v a l u e i × x i ∑ c o s t i × x i ≥ m i d \frac{ \sum{value_i} \times x_i } { \sum{cost_i} \times x_i } ≥ mid costi×xivaluei×ximid
∑ ( v a l u e i − m i d ∗ c o s t i ) × x i ≥ 0 \sum{(value_i - mid * cost_i)} \times x_i ≥ 0 (valueimidcosti)×xi0
也就是找到一组 { x i } \{x_i\} {xi} 使得 ∑ ( v a l u e i − m i d ∗ c o s t i ) \sum{(value_i - mid * cost_i)} (valueimidcosti) 最大
若找到的最大值 ≥ 0 ≥ 0 0,也就是 最优值大于 m i d mid mid,反之就是小于 m i d mid mid


选取 n − k n-k nk 个二元组使得 ∑ a i ∑ b i \frac{ \sum{a_i} } { \sum{b_i} } biai 最大

int n, k, a[maxn], b[maxn]; double c[maxn];
bool ck(double x){
	for(int i=1; i<=n; i++) c[i] = a[i] - x * b[i];
	sort(c+1, c+1+n);
	double res = 0;
	for(int i=k+1; i<=n; i++) res += c[i];
	return res >= eps;
}

signed main() {
	while(scanf("%d%d", &n, &k) && n){
		for(int i=1; i<=n; i++) scanf("%d", a+i);
		for(int i=1; i<=n; i++) scanf("%d", b+i);
		double l = 0, r = 1e5, mid;
		while((r - l) >= eps){
			mid = (l + r) / 2;
			if(ck(mid)) l = mid + eps;
			else r = mid - eps;
		}
		printf("%.0f\n", r * 100);
	}
}

整体二分

整体二分:对所有的询问一起进行二分
满足前提:
① : ①: 单组询问可以二分
② : ②: 对于修改具有高效的数据结构维护
③ : ③: 贡献满足交换律,结合律,具有可加性
④ : ④: 可以离线
⑤ : ⑤: 具有类似决策单调性

大致过程:
每次对询问 ( x , y ) (x, y) (x,y) 并且答案在 ( l , r ) (l, r) (l,r) 进行处理
处理结束后,答案在 m i d mid mid 之前的询问放在左边
答案在 m i d mid mid 之后的询问减掉 ( l , m i d ) (l,mid) (l,mid) 的贡献,放在右边
递归处理
时间复杂度: O ( q × l o g ( r − l ) × 修 改 ) O(q \times log(r - l) \times 修改) O(q×log(rl)×)


大意:区间加,询问每个点达到指定数目的最少次数
题解:注意树状数组进行区间修改

int n, m, k, ans[maxn];
int L[maxn], R[maxn], A[maxn];
ll d[maxn];
vector <int> g[maxn];
struct node{
	int nd, id;
} a[maxn], ta[maxn];

inline void add(int x, ll v){
	for(int i=x; i<=m<<1; i+=(i&-i))
		d[i] += v;
}
inline ll sum(int x){
	ll ret = 0;
	for(int i=x; i; i-=(i&-i))
		ret += d[i];
	return ret;
}

void solve(int l, int r, int x, int y){
	if(l > r || x > y) return; 
	if(l == r){
		for(int i=x; i<=y; i++) ans[a[i].id] = l;
		return ;
	}
	int mid = l + r >> 1, tl = 0, tr = n;
	for(int i=l; i<=mid; i++) add(L[i], A[i]), add(R[i]+1, -A[i]);
	for(int i=x; i<=y; i++){
		ll tmp = 0;
		for(auto j : g[a[i].id]) {
			tmp += sum(j) + sum(j+m);
			if(tmp >= a[i].nd) break;
		}
		if(tmp >= a[i].nd) ta[++tl] = a[i];
		else ta[++tr] = a[i], ta[tr].nd -= tmp;
	}
	for(int i=l; i<=mid; i++) add(L[i], -A[i]), add(R[i]+1, A[i]);
	for(int i=1; i<=tl; i++) a[x+i-1] = ta[i];
	for(int i=n+1; i<=tr; i++) a[x+tl+i-n-1] = ta[i];
	if(tl) solve(l, mid, x, x+tl-1);
	if(tr>n) solve(mid+1, r, x+tl, y);
}

int main() {
	scanf("%d%d", &n, &m);
	for(int i=1, x; i<=m; i++){
		scanf("%d", &x);
		g[x].push_back(i);
	}
	for(int i=1; i<=n; i++){
		scanf("%d", &a[i].nd);
		a[i].id = i;
	}
	scanf("%d", &k);
	for(int i=1; i<=k; i++){
		scanf("%d%d%d", L+i, R+i, A+i);
		if(R[i] < L[i]) R[i] += m;
	}
	solve(1, k+1, 1, n);
	for(int i=1; i<=n; i++)
		if(ans[i] == k+1) puts("NIE");
		else printf("%d\n", ans[i]);
}

2020 wannafly camp day3 F 社团管理

题目大意:

     n n n 个数字,分为 k k k
    使每段内相同数字的对数 的和最小

解题思路:

     d p [ i ] [ k ] dp[i][k] dp[i][k] 为以 i i i 为止,分为 k k k 段的最小值
    发现随着 i i i 的增大,满足决策单调性
    设区间 ( x , y ) (x, y) (x,y) 的答案位置在 ( l , r ) (l, r) (l,r)
    则可以进行类似整体二分的转移
    对于修改,可以用莫队处理,复杂度均摊

int n, k, a[maxn], nl = 1, nr;
ll dp[maxn][25], cnt[maxn], sum;
inline void add(int x){
	x = a[x]; if(cnt[x]) sum -= cnt[x] * (cnt[x] - 1) / 2;
	cnt[x]++; f(cnt[x]) sum += cnt[x] * (cnt[x] - 1) / 2;
}

inline void del(int x){
	x = a[x]; if(cnt[x]) sum -= cnt[x] * (cnt[x] - 1) / 2;
	cnt[x]--; if(cnt[x]) sum += cnt[x] * (cnt[x] - 1) / 2;
}

inline void gao(int ql, int qr){
	while(ql < nl) add(--nl);
	while(ql > nl) del(nl++);
	while(qr < nr) del(nr--);
	while(qr > nr) add(++nr);
}

void solve(int l, int r, int x, int y, int k){
	if(l > r || x > y) return;
	int mid = x + y >> 1, pos;
	ll mi = 1e18;
	for(int i=l; i<=min(mid, r); i++){
		gao(i, mid);
		ll tmp = dp[i-1][k-1] + sum;
		if(tmp < mi){
			dp[mid][k] = mi = tmp;
			pos = i;
		}
	}
	solve(l, pos, x, mid-1, k);
	solve(pos, r, mid+1, y, k);
}

int main() {
	scanf("%d%d", &n, &k);
	for(int i=1; i<=n; i++) scanf("%d", a+i);
	for(int i=1; i<=n; i++) 
		gao(1, i), dp[i][0] = sum;
	for(int i=1; i<k; i++)
		solve(1, n, 1, n, i);
	printf("%lld\n", dp[n][k-1]);
}

—— RMQ ——
int dp_max[maxn][20], dp_min[maxn][20];
void ST(int n, int d[]) {
	for (int i=1; i<=n; i++) {
		dp_max[i][0] = d[i]; dp_min[i][0] = d[i];
	}
	for (int j=1; (1<<j) <= n; j++) {
		for (int i=1; i+(1<<j)-1 <= n; i++) {
			dp_max[i][j] = max(dp_max[i][j-1], dp_max[i + (1<<(j-1))][j-1]);
			dp_min[i][j] = min(dp_min[i][j-1], dp_min[i + (1<<(j-1))][j-1]);
		}
	}
}
int RMQ_max(int l, int r) {
	int k = log(r - l + 1.0) / log(2.0);
	return max(dp_max[l][k], dp_max[r - (1<<k)+1][k]);
}
int RMQ_min(int l, int r) {
	int k = log(r - l + 1.0) / log(2.0); 
	return min(dp_min[l][k], dp_min[r - (1<<k)+1][k]);
}

—— 启发式分治 ——

    给定 n n n 个数,求满足某种条件的点对数目或最大权值,而这个最大权值与点对 ( a , b ) (a,b) (a,b) 的区间 [ a , b ] [a,b] [a,b] 的区间最大 / / /最小值有关。
    那么这时就可以考虑分治,对于区间 [ L , R ] [L,R] [L,R],找到最小 / / /大值所在位置
    然后处理横跨最小/大值所在位置的点对,再递归处理子区间

    对于一个区间,找到最大/最小值的位置 m i d mid mid 可以用 R M Q RMQ RMQ 预处理
    然后枚举 [ l , m i d ] [l, mid] [l,mid] [ m i d , r ] [mid, r] [mid,r] 的较小区间,查询处理出答案即可


大意:一个好的区间定义为 m a x ( a l , a l + 1 , . . . , a r ) − ( r − l + 1 ) < = k max(a_l, a_{l+1},...,a_r) - (r - l + 1) <= k max(al,al+1,...,ar)(rl+1)<=k
           并且区间内所有数互不相同,问有多少个好的区间?
题解:根据最大值进行启发式分治, R M Q RMQ RMQ 预处理区间最大值位置
            L [ i ] L[i] L[i] 为以 a [ i ] a[i] a[i] 往左满足数字互不相同的的最远位置, R [ i ] R[i] R[i] 为往右,用双指针 O ( n ) O(n) O(n) 预处理即可

int T, n, k, a[maxn];
int L[maxn], R[maxn], vis[maxn];
ll ans;
void solve(int l, int r) {
	if(l > r) return;
	int mid = RMQ_max(l, r);
	if(mid - l <= r - mid) {
		for(int i=l; i<=mid; i++) {
			int r1 = max(mid, a[mid] - k + i - 1);
			int r2 = min(r, R[i]);
			if(r1 <= r2) ans += r2 - r1 + 1;
		}
	} else {
		for(int i=mid; i<=r; i++){
			int l2 = min(mid, -a[mid] + k + i + 1);
			int l1 = max(l, L[i]);
			if(l1 <= l2) ans += l2 - l1 + 1;
		}
	}
	solve(l, mid - 1), solve(mid + 1, r);
}

int main() {
	scanf("%d", &T);
	while(T--) {
		scanf("%d%d", &n, &k);
		for(int i=1; i<=n; i++) scanf("%d", a+i);
		vis[a[1]] = 1;
		for(int i=1, p=2; i<=n; i++) {
			while(p<=n && !vis[a[p]]) vis[a[p++]] = 1;
			vis[a[i]] = 0, R[i] = p - 1;
		}
		vis[a[n]] = 1;
		for(int i=n, p=n-1; i; i--) {
			while(p && !vis[a[p]]) vis[a[p--]] = 1;
			vis[a[i]] = 0, L[i] = p + 1;
		}
		ans = 0; ST(n, a); solve(1, n);
		printf("%lld\n", ans);
	}
}

—— 莫队 ——

1、奇偶性排序 + 快速读入输出

int a[maxn], cnt[maxn], belong[maxn];
int n, m, size, bnum, now, ans[maxn];
struct query {
	int l, r, id;
} q[maxn];

inline int cmp(query a, query b) {
	return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] : \
	       ((belong[a.l] & 1) ? a.r < b.r : a.r > b.r);
}

inline void add(int pos) {
    if(!cnt[a[pos]]) ++now;
    ++cnt[a[pos]];
}
inline void del(int pos) {
    --cnt[a[pos]];
    if(!cnt[a[pos]]) --now;
}

int main() {
	read(n);
	size = sqrt(n);
	bnum = ceil(1.0 * n / size);
	for(int i=1; i<=bnum; ++i)
		for(int j=(i-1)*size+1; j<=i*size; ++j) 
			belong[j] = i;
	for(int i=1; i<=n; ++i) read(a[i]);
	read(m);
	for(int i=1; i<=m; ++i) {
		read(q[i].l), read(q[i].r);
		q[i].id = i;
	}
	sort(q+1, q+m+1, cmp);
	
	int l = 1, r = 0;
	for(int i=1; i<=m; ++i) {
		int ql = q[i].l, qr = q[i].r;
		while(l < ql) del(l++);
		while(l > ql) add(--l);
		while(r < qr) add(++r);
		while(r > qr) del(r--);
		ans[q[i].id] = now;
	}
	for(int i=1; i<=m; ++i)
		print(ans[i]), putchar('\n');
}

2、带修改莫队

int a[maxn], cnt[maxc], ans[maxn], belong[maxn];
int cntq, cntc, n, m, size, bnum, now;

struct query {
	int l, r, time, id;
} q[maxn];
struct modify {
	int pos, color, last;
} c[maxn];

inline int cmp(query a, query b) {
	return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] :\
	 ((belong[a.r] ^ belong[b.r]) ? belong[a.r] < belong[b.r] : a.time < b.time);
}

inline void add(const int x){
	if(++cnt[x] == 1) ++now;
}

inline void del(int x){
	if(--cnt[x] == 0) --now;
}

int main() {
	n = read(), m = read();
	size = pow(n, 2.0 / 3.0);
	bnum = ceil((double)n / size);
	for(int i=1; i<=bnum; ++i)
		for(int j=(i-1)*size+1; j<=i*size; ++j) 
			belong[j] = i;
	for(int i=1; i<=n; ++i)
		a[i] = read();
	for(int i=1; i<=m; ++i) {
		char opt[100];
		scanf("%s", opt);
		if(opt[0] == 'Q') {
			q[++cntq].l = read();
			q[cntq].r = read();
			q[cntq].time = cntc;
			q[cntq].id = cntq;
		} else if(opt[0] == 'R') {
			c[++cntc].pos = read();
			c[cntc].color = read();
		}
	}
	sort(q+1, q+cntq+1, cmp);
	int l = 1, r = 0, time = 0; now = 0;
	for(int i = 1; i <= cntq; ++i) {
		int ql = q[i].l, qr = q[i].r, qt = q[i].time;
		while(l < ql) del(a[l++]);
		while(l > ql) add(a[--l]);
		while(r < qr) add(a[++r]);
		while(r > qr) del(a[r--]);
		while(time < qt) {
			++time;
			if(ql <= c[time].pos && c[time].pos <= qr) 
				del(a[c[time].pos]), add(c[time].color);
			swap(a[c[time].pos], c[time].color);
		}
		while(time > qt) {
			if(ql <= c[time].pos && c[time].pos <= qr) 
				del(a[c[time].pos]), add(c[time].color);
			swap(a[c[time].pos], c[time].color);
			--time;
		}
		ans[q[i].id] = now;
	}
	
	for(int i = 1; i <= cntq; ++i) printf("%d\n", ans[i]);
}

—— 模拟退火 ——

如何生成新解

  • 坐标系内:随机生成一个点,或者生成一个向量
  • 序列问题: r a n d o m _ s h u f f l e random\_shuffle random_shuffle 或者随机交换两个数
  • 网格问题:可以看做二维序列,每次交换两个格子即可

注意事项

这里的 新解与最优解的差为 Δ E \Delta E ΔE,对应代码里的 d e l del del
如果新解更优,则直接更新,否则以一定概率接受它,概率为 exp(-del/T)*RAND_MAX > rand()
要注意这里的 d e l del del 前有没有负号要看能不能跑出正解,括号内的必须是负值
若接受这个不优解,不能更新答案,而是在这个不优解的影响下继续降温
while(1.0*clock()/CLOCKS_PER_SEC < 0.85) sa(); 可用来极限卡时间


给定一个要满足的椭球的方程 a x 2 + b y 2 + c z 2 + d y z + e x z + f x y = 1 ax^2+by^2+cz^2+dyz+exz+fxy=1 ax2+by2+cz2+dyz+exz+fxy=1
求球面上一个点到原点 ( 0 , 0 , 0 ) (0,0,0) (0,0,0) 的距离最小

则可以随机一个点 ( x , y ) (x,y) (x,y),然后求出 z z z,即可开始退火

const double eps = 1e-15;
double a, b, c, d, e, f;
double ansx, ansy, ansz, ans;

double cal(double x, double y, double z){
	return sqrt(x*x + y*y + z*z);
}

double calz(double x, double y){
	double A = c;
	double B = d * y + e * x;
	double C = a * x * x + b * y * y + f * x * y - 1.0;
	double dlt = B * B - 4.0 * A * C;
	if(dlt < 0) return 1e15;
	double x1 = (-B + sqrt(dlt)) / (2.0 * A);
	double x2 = (-B - sqrt(dlt)) / (2.0 * A);
	if(cal(x, y, x1) < cal(x, y, x2)) return x1;
	return x2;
}

void sa() {
	double T = 66666, nowx = ansx, nowy = ansy, DT = 0.99;
	while(T > eps) {
		double tx = nowx + (rand()*2-RAND_MAX) * T;
		double ty = nowy + (rand()*2-RAND_MAX) * T;
		double tz = calz(tx, ty);
		if(tz == 1e15) {T = T * DT; continue;}
		double now = cal(tx, ty, tz);
		double del = now - ans;
		if(del < 0) {
			ans = now;
			ansx = tx, ansy = ty;
			nowx = tx, nowy = ty;
		} else if(exp(-del/T)*RAND_MAX > rand()) {
			nowx = tx, nowy = ty;
		}
		T = T * DT;
	}
}

signed main() {
	srand(time(0));
	while(~scanf("%lf%lf%lf%lf%lf%lf", &a, &b, &c, &d, &e, &f)) {
		ansx = ansy = 0; ans = 1e15;
		for(int i=1; i<5; i++) sa();
		printf("%.5f\n", ans);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值