2024.05 别急记录

1. POI2015 - Podział naszyjnika

考虑对每个位置附一个随机权值,保证序列中所有等于某个数的位置权值异或和为 \(0\)。则一种划分合法当且仅当两个区间异或和都为 \(0\),相当于找到一个区间 \([L,R]\) 异或和为 \(0\)。于是用 umap 记录前缀异或和即可。第二问把每个相同的前缀异或和放到一个 vector 里双指针即可。注意 \([1,i],[i+1,n]\) 之类的划分可能会被统计两次,解决方法是不统计所有以 \(n\) 结尾的区间。

点击查看代码
//P3587
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 1e6 + 10;
int n, k, a[N], cnt, mx;
ll xom[N], val[N], ans;
unordered_map<ll, int> mp;
vector<int> g[N];

int cl(int x, int y){
	return min(y - x, x + n - y);
}

mt19937 rng(time(0));
uniform_int_distribution<long long>gen(1,0x3f3f3f3f3f3f3f3f);

void solve(){
	srand(unsigned(time(NULL)));
	mp[0] = ++ cnt;
	g[mp[0]].push_back(0);
	scanf("%d%d", &n, &k);
	for(int i = 1; i <= n; ++ i){
		scanf("%d", &a[i]);
		val[i] = gen(rng);
		xom[a[i]] ^= val[i];
	}
	for(int i = 1; i <= n; ++ i){
		val[i] ^= xom[a[i]];
		xom[a[i]] = 0;
		val[i] ^= val[i-1];
		if(!mp[val[i]]){
			mp[val[i]] = ++ cnt;
		}
		g[mp[val[i]]].push_back(i);
	}
	for(int i = 1; i <= cnt; ++ i){
		int x = g[i].size();
		if(mp[val[n]] == i){
			-- x;
		}
		ans += (ll)x * (x-1) / 2;
		int pr = 0;
		for(int j = 1; j < x; ++ j){
			while(pr < j && cl(g[i][pr], g[i][j]) < cl(g[i][pr+1], g[i][j])){
				++ pr;
			}
			mx = max(mx, cl(g[i][pr], g[i][j]));
		}
	}
	printf("%lld %d", ans, n - mx - mx);
}

2. XJTUPC2024 - 循环移位

三种运算做法本质相同,考虑异或的做法。

预处理 \(f_{i,j}\) 表示若循环移位后 \(x_i\) 移动到 \(x_0\) 的位置时,\(x\) 数组每一项的二进制第 \(j\) 位与下标二进制第 \(j\) 位进行异或后的和。

观察到只有 \(i\in[0,2^{j+1})\) 是有用的,因为 \(f_{i,j}=f_{i\bmod 2^{j+1},j}\)

考虑递推 \(f_{i-1,j}\to f_{i,j}\)。可以观察到每 \(2^j\) 位会改变一个,其他位置的值不会变(类似于 \(\texttt{00001111}\to \texttt{10000111}\),只变了两位)。所以我们每次找到这样的若干位暴力修改即可。

求好 \(f\) 数组后就可以暴力枚举 \(x_0\) 的位置然后对于每个位置 \(O(n)\) 求解了。

复杂度 \(\sum_{j=0}^n2^{j+1}\dfrac{2^n}{2^j}=O(n2^n)\)

点击查看代码
//P10524
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = (1 << 20) + 10;
int n, m, a[N];
ll sum[N][20];

int clc(int p, int q, int k, int op){
	p += m;
	if(op == 0){
		return ((p>>k)&1) ^ ((q>>k)&1);
	} else if(op == 1){
		return ((p>>k)&1) & ((q>>k)&1);
	} else {
		return ((p>>k)&1) | ((q>>k)&1);
	}
}
ll calc(int op){
	memset(sum, 0, sizeof(sum));
	for(int j = 0; j < n; ++ j){
		for(int i = 0; i < m; ++ i){
			sum[0][j] += clc(i, a[i], j, op);
		}
		for(int i = 1; i < (1 << j + 1); ++ i){
			sum[i][j] = sum[i-1][j];
			for(int k = (i-1) % (1<<j); k < m; k += (1 << j)){
				sum[i][j] -= clc(k-i+1, a[k], j, op);
				sum[i][j] += clc(k-i, a[k], j, op);
			}
		}
	}
	ll ans = 0;
	for(int i = 0; i < m; ++ i){
		ll now = 0;
		for(int j = 0; j < n; ++ j){
			now += sum[i%(1<<j+1)][j] * (1 << j);
		}
		ans = max(ans, now);
	}
	return ans;
	
}

void solve(){
	scanf("%d", &n);
	m = 1 << n;
	for(int i = 0; i < m; ++ i){
		scanf("%d", &a[i]);
	}
	printf("%lld %lld %lld\n", calc(0), calc(1), calc(2));
}

3. XJTUPC2024 - 最后一块石头的重量

题目等价于求操作后 \(\sum a\) 最小值。设 \(S=\sum a\),可以发现每次操作等价于令 \(S\) 减掉 \(2a_i\),所以求得一个 \(T=\sum 2a_ix_i,(x_i\in\{0,1\})\) 使得 \(S\geq T 且 S-T\) 最小即为答案,转换为子集和中位数问题(AGC020C)。

可以使用随一个排列然后小范围背包过,也可以使用厉害科技

点击查看代码
//P10527
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 1e4 + 10;
int n, x, a[N];
int f[N*2], g[N*2];
 
void solve(){
	scanf("%d", &n);
	int sum = 0, all = 0, ss = 0;
	for(int i = 1; i <= n; ++ i){
		scanf("%d", &a[i]);
		sum += a[i];
		all += a[i];
		x = max(x, a[i]);
	}
	ss = sum;
	sum /= 2;
	int b = 0, w = 0;
	while(w + a[b+1] <= sum){
		++ b;
		w += a[b];
	}
	f[w-sum+x] = b + 1;
	for(int i = b + 1; i <= n; ++ i){
		memcpy(g, f, sizeof(g));
		for(int j = x; j; -- j){
			f[j+a[i]] = max(f[j+a[i]], g[j]);
		}
		for(int j = x + x; j >= x + 1; -- j){
			for(int k = f[j]-1; k >= max(1, g[j]); -- k){
				f[j-a[k]] = max(f[j-a[k]], k);
			} 
		}
	}
	int ans = 0;
	for(int j = x; j; -- j){
		if(f[j]){
			ans = all - sum - j + x;
			break;
		}
	}
	printf("%d\n", ans + ans - ss);
}

4. [ARC176D] Swap Permutation

首先有 \(a-b=\sum1[a\geq x][b<x]\)。所以对于每个 \(1\leq k<n\),令 \(q_i=1[p_i\geq k]+0[p_i<k]\),求出 \(q\) 数组经过 \(m\) 次操作后能生成的所有数组中相邻 \(\texttt{01}\) 对个数和 \(+\) 相邻 \(\texttt{10}\) 对个数和,对于所有 \(k\) 把和累计起来即为答案。

问题转化为给定 \(\texttt{01}\) 数组,求经过 \(m\) 次操作后能生成的所有数组中相邻 \(\texttt{01}\) 对个数和 \(+\) 相邻 \(\texttt{10}\) 对个数和。

考虑一个位置 \((i,i+1)\),它的初始值和经过一次操作后的值总共有 \(16\) 种情况(\(\texttt{00,01,10,11}^2\))。而每一种情况的转移只和数组中 \(\texttt{0,1}\) 的个数有关,可以写成矩阵形式。

对于不同的 \(k\),每次 \(k+1\to k\) 只会修改 \(\texttt{01}\) 数组中一个位置,可以方便维护。

所以可以做到 \(O(4^3n\log m)\)

点击查看代码
//AT_arc176_d
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 2e5 + 10;
const int mp[3][3] = {{0,1,4},{2,3,4},{4,4,4}};
int n, m, p[N], pos[N], a[N], cnt[5];
const ll P = 998244353;
ll ans;

struct mat{
	ll a[4][4];
};

mat mul(mat x, mat y){
	mat z;
	memset(z.a, 0, sizeof(z.a));
	for(int k = 0; k < 4; ++ k){
		for(int i = 0; i < 4; ++ i){
			for(int j = 0; j < 4; ++ j){
				z.a[i][j] += x.a[i][k] * y.a[k][j] % P;
				if(z.a[i][j] >= P){
					z.a[i][j] -= P;
				}
			}
		}
	}
	return z;
}
mat qp(mat x, int y){
	mat ans = x;
	-- y;
	while(y){
		if(y & 1){
			ans = mul(ans, x);
		}
		x = mul(x, x);
		y >>= 1;
	}
	return ans;
}

void solve(){
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++ i){
		scanf("%d", &p[i]);
		pos[p[i]] = i;
	}
	a[0] = a[n+1] = 2;
	cnt[mp[0][0]] = n-1;
	for(int i = 1; i <= n; ++ i){//i 1, n-i 0
		int j = pos[i];
		-- cnt[mp[a[j-1]][a[j]]];
		-- cnt[mp[a[j]][a[j+1]]];
		a[j] = 1;
		++ cnt[mp[a[j-1]][a[j]]];
		++ cnt[mp[a[j]][a[j+1]]];
		mat k;
		k.a[0][0] = (ll)n * (n-1) / 2 - 2 * i;
		k.a[0][1] = i;
		k.a[0][2] = i;
		k.a[0][3] = 0;
		k.a[1][0] = n-i-1;
		k.a[1][1] = (ll)n * (n-1) / 2 - (n-1);
		k.a[1][2] = 1;
		k.a[1][3] = i-1;
		k.a[2][0] = n-i-1;
		k.a[2][1] = 1;
		k.a[2][2] = (ll)n * (n-1) / 2 - (n-1);
		k.a[2][3] = i-1;
		k.a[3][0] = 0;
		k.a[3][1] = n-i;
		k.a[3][2] = n-i;
		k.a[3][3] = (ll)n * (n-1) / 2 - 2 * (n-i);
		for(int j = 0; j < 4; ++ j){
			for(int q = 0; q < 4; ++ q){
				k.a[j][q] %= P;
			}
		}
		k = qp(k, m);
		ans += (ll)cnt[0] * (k.a[0][1] + k.a[0][2]) % P;
		ans += (ll)cnt[1] * (k.a[1][1] + k.a[1][2]) % P;
		ans += (ll)cnt[2] * (k.a[2][1] + k.a[2][2]) % P;
		ans += (ll)cnt[3] * (k.a[3][1] + k.a[3][2]) % P;
		ans %= P;
	}
	printf("%lld\n", ans);
}

5. APIO2024 - September

观察到一个位置 \(i\) 可以作为某天的结尾当且仅当:

  • 对于 \(0\leq k < m\), \(S_{k,0},S_{k,1},...,S_{k,i}\) 组成的集合相同。
  • 树去除掉这个集合内的点后仍然连通。

第一个限制很好做,第二个限制可以给每个点打上需要删除的标记,删除一个点时把需要删除的标记设为 \(0\),把未删除儿子的需要删除标记设为 \(1\)。那么满足第二个条件当且仅当所有点的需要删除标记均为 \(0\)

点击查看代码
//qoj8724
#include <bits/stdc++.h>
using namespace std; typedef long long ll;

int val[100010], del[100010], nd[100010];
vector<int> g[100010];

int solve(int N, int M, std::vector<int> F,
std::vector<std::vector<int>> S){
	int cnt = 0, ndc = 0;
	ll sum = 0;
	int n = N, m = M;
	for(int i = 1; i < n; ++ i){
		g[F[i]].push_back(i);
	}
	for(int i = 0; i < n - 1; ++ i){
		for(int j = 0; j < m; ++ j){
			sum -= abs(val[S[j][i]]);
			if(!j){
				int p = S[j][i];
				val[p] += m-1;
				if(nd[p]){
					nd[p] = 0;
					-- ndc;
				}
				del[p] = 1;
				for(int q : g[p]){
					if(!del[q]){
						nd[q] = 1;
						++ ndc;
					}
				}
			} else {
				-- val[S[j][i]];
			}
			sum += abs(val[S[j][i]]);
			if(sum == 0 && ndc == 0){
				++ cnt;
			}
		}
	}
	for(int i = 0; i <= n; ++ i){
		vector<int> ().swap(g[i]);
		val[i] = nd[i] = del[i] = 0;
	}
	return cnt;
}

//int main(){
//	printf("%d\n", solve(3, 1, {-1, 0, 0}, {{1, 2}}));
//	printf("%d\n", solve(5, 2, {-1, 0, 0, 1, 1}, {{1, 2, 3, 4}, {4, 1, 2, 3}}));
//	return 0;
//}

6. LuoguP3791 - 普通数学题

\(N=n+1\),则 \([0,N)\) 之间的二进制数可以分成若干块,例子如下:

\(\texttt{10010111}\)
\(\texttt{0xxxxxxx}\)
\(\texttt{1000xxxx}\)
\(\texttt{100000xx}\)
\(\texttt{1000010x}\)
\(\texttt{10000110}\)

其中 \(\texttt{x}\) 表示 \(\texttt{0,1}\) 均可。

那么设一个块为 \(x+[0,2^y)\),枚举 \(n,m\) 分成的块 \(p+[0,2^i),q+[0,2^j)\),设 \(r\)\(p\operatorname{xor}q\operatorname{xor}x\) 的最高 \(\max(i,j)\) 位(低位全部设为 \(0\)),那么这两块与 \(x\) 两两异或和为 \(r+[0,2^{\max(i,j)})\) 重复 \(2^{\min(i,j)}\) 遍。

问题转化为给定区间求约数个数和,转化为给定前缀求约数个数和,数论分块即可。

复杂度 \(O(\log^2n\sqrt n)\),过不去,使用记忆化即可做到 \(O(\log n\sqrt n)\)。为什么呢?因为 \(r+[0,2^{\max(i,j)})\) 的区间左右端点分别只和 \(i,j\) 其中一个相关。

点击查看代码
//P3791
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const ll P = 998244353;
ll n, m, x, ans;
map<ll, ll> mp;

ll cw(ll v){
	if(v <= 0 || mp[v]){
		return mp[v];
	}
	ll ans = 0;
	for(ll i = 1, j = 0; i <= v; i = j + 1){
		j = v / (v / i);
		ans += v / i * (j - i + 1);
	}
	return mp[v] = ans;
}
ll cq(ll v, int p){
	return (cw(v + (1ll<<p) - 1) - cw(v - 1) + P) % P;
}

void solve(){
	cin >> n >> m >> x;
	++ n;
	++ m;
	for(int i = 0; i <= 40; ++ i){
		if(n & (1ll << i)){
			for(int j = 0; j <= 40; ++ j){
				if(m & (1ll << j)){
					ll t = x ^ n ^ m;
					int r = max(i, j);
					t ^= (1ll << i) ^ (1ll << j);
					t = (t | ((1ll<<r)-1)) - (1ll<<r) + 1;
					ans += cq(t, r) * (1ll << min(i, j)) % P;
					ans %= P;
				}
			}
		}
	}
	printf("%lld\n", ans);
}

posted @ 2024-05-05 11:05  KiharaTouma  阅读(15)  评论(0编辑  收藏  举报