喵蛋原来写的题解被知乎吞了,所以来牛客发一发
K.硝基甲苯之魇
求满足gcd和异或和相等的区间个数。注意到gcd减少次数有限,所以可以每次去二分查找断点(即gcd变化的位置),然后用map维护区间所有异或前缀和的结果,找到对应的就可以(设原数组为a,异或前缀和数组为b,则对于满足条件的,只需要
即可)。时间复杂度
。
#include <bits/stdc++.h>
using namespace std;
// #define int long long
const int N = 2e5 + 5;
const int LogN = 19;
int t, n;
int brr[N];
int st[N][LogN];
int Log[N];
void Build()
{
// 初始化st表的第0维+求解log
for (int i = 1, now = 2; i <= n; i++)
{
Log[i] = Log[i - 1];
if (now == i)
Log[i]++, now <<= 1;
}
// 倍增求解st表
for (int j = 1, now = 1; j < 19; j++, now <<= 1)
for (int i = 1; i <= n; i++)
st[i][j] = __gcd(st[i][j - 1], st[min(i + now, n)][j - 1]);
}
int Query(int l, int r)
{
int dep = Log[r - l + 1], red = (1 << dep) - 1;
return __gcd(st[l][dep], st[r - red][dep]);
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> t;
while (t--)
{
cin >> n;
long long ans = 0;
map<int, vector<int>> mp;
for (int i = 1; i <= n; i++)
{
cin >> st[i][0];
brr[i] = brr[i - 1] ^ st[i][0];
mp[brr[i]].push_back(i);
}
Build();
for (int i = 1; i <= n; i++)
{
int now = st[i][0], L = i;
while (1)
{
// r即为最后一个满足区间[i,r]的gcd为now的地方
int l = L, r = n;
while (l <= r)
{
int mid = l + r >> 1;
if (Query(i, mid) < now)
r = mid - 1;
else
l = mid + 1;
}
int tar = now ^ brr[i - 1];
ans += lower_bound(mp[tar].begin(), mp[tar].end(), r + 1) - lower_bound(mp[tar].begin(), mp[tar].end(), L);
if ((L = r + 1) > n)
break;
now = __gcd(now, st[L][0]);
}
}
cout << ans - n << '\n';
}
return 0;
}
L.一念神魔之耀
求任意一种操作方案(如果可以的话),每次连续翻转x个或y个使得序列全1。
实质上,x和y可以统一成gcd(x,y),即一次可以翻的最小长度为gcd(x,y)。对于前l-x+1个数,如果是0可以无脑连续翻x个;而对于最后x-1个数,如果还遇到0,则需要借助前面的空间去翻。
虽然代码里没有exgcd,但翻数(最后x-1个)的实质就是扩欧。时间复杂度, 其中
表示凑出gcd的操作的复杂度。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII array<int, 2>
const int inf = 4e18;
const int N = 5e2 + 5;
int t = 1, n, x, y, c;
bool arr[N];
bool op[N][N];
string s;
void Build(int pos)
{
int f = pos <= n - x + 1, now = pos + (f ? 0 : c - 1);
if (f)
{
op[pos][pos + x - 1] ^= 1;
for (int j = 0; j < x; j++)
arr[pos + j] ^= 1;
}
else
{
do
{
op[now - (x - 1)][now] ^= 1;
now -= x - 1;
while (now + y <= pos)
{
op[now][now + (y - 1)] ^= 1;
now += y;
}
} while (now-- != pos);
for (int j = 0; j < c; j++)
arr[pos + j] ^= 1;
}
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> t;
while (t--)
{
cin >> n >> x >> y >> s;
for (int i = 1; i <= n; i++)
{
arr[i] = s[i - 1] - '0';
for (int j = i; j <= n; j++)
op[i][j] = 0;
}
if (x < y)
swap(x, y);
c = __gcd(x, y);
bool valid = 1;
for (int i = 1; i <= n; i++)
if (!arr[i])
{
if (n - i + 1 < c)
{
valid = 0;
break;
}
Build(i);
}
if (valid)
{
vector<PII> g;
for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j++)
if (op[i][j])
g.push_back({i, j});
cout << g.size() << '\n';
for (PII i : g)
cout << i[0] << ' ' << i[1] << '\n';
}
else
cout << "-1\n";
}
return 0;
}
I.井然有序之桠
构造排列,满足
。
首先要注意到排列a给出的数字的排列顺序不重要,实质上都是在求的合法匹配。
①考虑
,这个即为我们构造时需要多构造的量。那么如果
,则只需要将所有数向右移1位即可(构造为
);
②如果让某个数和它自身匹配,则其会提供
的贡献。基于此,我们从大到小地匹配(不从小到大考虑的原因是因为比较小的数中有1,2等简单数,后续调整相对简单)。设
为失配指针(即暂时不配对的最后一个位置),初始
,当目前的
时,即可将
减少
,同时将
与自身匹配后,将
向左移动1位。
③根据上述结论,当失配指针
不再移动后,我们实际上只需要考虑元素
的配对方式。初始时,先按照①的结论配好(即
)。
易知:。考虑现在这些需要匹配的数:
-->1.首先,如果
,那我们就一点都不用动了,现在的就是我们要的结果;
-->2.然后,我们发现,每相邻两个数交换,存在如下性质:
除了第一组交换产生的贡献不确定外,其余都是确定且有规律的。
根据上述的规律很容易构建出几乎所有+
的交换方式(比如要 +
,就交换 [4,5]
位置上的;要 +
,就同时交换 [2,3][4,5]
)。但
是个特例,需要自己手搓一个合法的交换方式(不难,可以稍微思考一下怎么构造)。
时间复杂度。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII array<int, 2>
const int N = 1e5 + 5;
int t = 1, n, k;
int a[N], ans[N];
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> t;
while (t--)
{
cin >> n >> k;
for (int i = 1; i <= n; i++)
cin >> a[i];
if (k < n)
{
cout << "-1\n";
continue;
}
int now = k - n, cur = n;
while (now && (now >= cur - 1)) // 如果退出循环,now的值必定为[now,cur-2]
{
now -= cur - 1;
ans[cur] = cur;
cur--;
}
for (int i = 2; i <= cur; i++) // 接下来需要处理[1,cur]这些数
ans[i] = i - 1;
ans[1] = cur;
// 只要想办法处理好+0~+cur-2(now的可能范围)即可
if (now == 2)
{
ans[2] = 4, ans[3] = 1, ans[4] = 2;
if (cur == 4)
ans[1] = 3;
else
ans[5] = 3;
}
else if (now)
{
if (now % 2 == 0)
swap(ans[2], ans[3]), now--;
swap(ans[now + 1], ans[now + 2]);
}
for (int i = 1; i <= n; i++)
cout << ans[a[i]] << ' ';
cout << '\n';
}
return 0;
}