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 - 循环移位

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

预处理 fi,j 表示若循环移位后 xi 移动到 x0 的位置时,x 数组每一项的二进制第 j 位与下标二进制第 j 位进行异或后的和。

观察到只有 i[0,2j+1) 是有用的,因为 fi,j=fimod2j+1,j

考虑递推 fi1,jfi,j。可以观察到每 2j 位会改变一个,其他位置的值不会变(类似于 0000111110000111,只变了两位)。所以我们每次找到这样的若干位暴力修改即可。

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

复杂度 j=0n2j+12n2j=O(n2n)

点击查看代码
//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 - 最后一块石头的重量

题目等价于求操作后 a 最小值。设 S=a,可以发现每次操作等价于令 S 减掉 2ai,所以求得一个 T=2aixi,(xi{0,1}) 使得 STST 最小即为答案,转换为子集和中位数问题(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

首先有 ab=1[ax][b<x]。所以对于每个 1k<n,令 qi=1[pik]+0[pi<k],求出 q 数组经过 m 次操作后能生成的所有数组中相邻 01 对个数和 + 相邻 10 对个数和,对于所有 k 把和累计起来即为答案。

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

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

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

所以可以做到 O(43nlogm)

点击查看代码
//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 可以作为某天的结尾当且仅当:

  • 对于 0k<m, Sk,0,Sk,1,...,Sk,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) 之间的二进制数可以分成若干块,例子如下:

10010111
0xxxxxxx
1000xxxx
100000xx
1000010x
10000110

其中 x 表示 0,1 均可。

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

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

复杂度 O(log2nn),过不去,使用记忆化即可做到 O(lognn)。为什么呢?因为 r+[0,2max(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 @   KiharaTouma  阅读(17)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起