A
令 表示以
为左端点,最小的合法右端点。那么有
。考虑反证法,若
,那么区间
包含
。所以
也合法,那么不满足
是最小的合法右端点。
于是双指针扫描,用 数组记录每个数字出现次数。时间复杂度
。
#include <bits/stdc++.h>
using namespace std;
long long n, m, s[100100], v[100100], cnt, ans;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> s[i];
for (int l = 1, r = 1; r <= n; r++) {
if (v[s[r]] == 0)
cnt++;
v[s[r]]++;
while (v[s[l]] > 1) v[s[l++]]--;
if (cnt == m)
ans += l;
}
cout << ans << endl; return 0;
}B
表示花费
步跳到
的概率。转移有
,其中
。发现可转移到的点是一个区间,那么用差分后用前缀和优化。最后答案是
。时间复杂度
。
#include <stdio.h>
const int N = 8005;
int a[N], n;
const long long mod = 998244353;
long long f[N], g[N], inv[N], ans;
int main() {
scanf("%d", &n);
inv[1] = 1;
for (int i = 2; i <= n; ++i) inv[i] = (mod - mod / i) * inv[mod % i] % mod;
for (int i = 1; i < n; ++i) scanf("%d", &a[i]);
g[1] = 1;
for (int t = 1, i; t < n; ++t) {
for (i = t; i < n; ++i) {
f[i + 1] += g[i] * inv[a[i]] % mod;
f[i + a[i] + 1] -= g[i] * inv[a[i]] % mod;
}
g[t] = 0;
for (i = t + 1; i <= n; ++i) {
g[i] = (g[i - 1] + f[i]) % mod;
f[i] = 0;
}
ans = (ans + g[n] * g[n]) % mod;
}
printf("%lld\n", ans);
}C
把题中给的关系看成边权建图。那么不合法当且仅当存在一个回路的矢量和不为 。此时需要改的边就是所有这样的回路的交。
否则,能改的边就是图中割边的个数。(因为其他的边肯定在一个回路里,改了就会破坏原有的合法关系)
建立 树后,所有的回路都可以表示为非树边构成的简单环的线性组合。于是枚举非树边,如果构成的简单环矢量和不为
,那么给这条非树边覆盖的树边打标记(树上差分)。如果为
,那这些被覆盖的树边不能更改,打上另一个标记。由于题目保证有解,所以直接这么做即可。时间复杂度
。
#include <bits/stdc++.h>
#define int long long
#define N 500010
using namespace std;
struct node {
int to, id, a, b, c;
} dep[N];
vector<node> e[N];
int fa[N], vis[N], ans[N], s[N];
void dfs(int x, int f) {
vis[x] = 1;
for (auto p : e[x])
if (!vis[p.to]) {
fa[p.to] = p.id;
dep[p.to].id = dep[x].id + 1;
dep[p.to].a = dep[x].a + p.a;
dep[p.to].b = dep[x].b + p.b;
dep[p.to].c = dep[x].c + p.c;
dfs(p.to, x);
s[x] += s[p.to];
}
}
int u[N], v[N], a[N], b[N], c[N];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
int n, m, i;
cin >> n >> m;
for (i = 1; i <= m; i++) {
cin >> u[i] >> v[i] >> a[i] >> b[i] >> c[i];
e[u[i]].push_back({ v[i], i, a[i], b[i], c[i] });
e[v[i]].push_back({ u[i], i, -a[i], -b[i], -c[i] });
}
dfs(1, 0);
for (i = 1; i <= m; i++)
if (abs(dep[u[i]].id - dep[v[i]].id) != 1) {
if (dep[u[i]].id < dep[v[i]].id)
swap(u[i], v[i]);
else
a[i] = -a[i], b[i] = -b[i], c[i] = -c[i];
if (dep[u[i]].a - dep[v[i]].a != a[i] || dep[u[i]].b - dep[v[i]].b != b[i] ||
dep[u[i]].c - dep[v[i]].c != c[i]) {
ans[i] = 1;
s[u[i]]++;
s[v[i]]--;
} else
ans[i] = -1e9, s[u[i]] -= 1e9, s[v[i]] += 1e9;
}
memset(vis, 0, sizeof(vis));
dfs(1, 0);
for (i = 1; i <= n; i++) ans[fa[i]] = s[i];
int res = 0, mx = 0;
for (i = 1; i <= m; i++) mx = max(mx, ans[i]);
for (i = 1; i <= m; i++)
if (ans[i] == mx)
res++;
cout << res << endl;
for (i = 1; i <= m; i++)
if (ans[i] == mx)
cout << i << ' ';
}D
按照出题人所给方法构造():
,前
列构造
次拐弯,后
列构造两次。
,对于每
行,前两行构造
次 ,后两行构造
次。
在前
行和
一致,后面特殊构造。
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 1000 + 5
int n, m, Ans[N][N];
bool flip;
int main() {
scanf("%d%d", &n, &m);
if (n > m)
swap(n, m), flip = true;
if (n == 2) {
Ans[1][1] = 2, Ans[1][2] = 1;
Ans[2][1] = 3, Ans[2][2] = 4;
int a = 2, b = 1;
for (int j = 3; j * 2 <= m; j++) {
Ans[a][j] = j * 2 - 1;
Ans[b][j] = j * 2;
swap(a, b);
}
if (m >= 4) {
for (int j = m / 2 + 1; j <= m; j++) Ans[a][j] = m / 2 + j;
for (int j = m; j > m / 2; j--) Ans[b][j] = 2 * m + m / 2 + 1 - j;
}
} else {
bool flag = false;
if (n % 4 != 0)
n -= 2, flag = true;
int id = 0;
for (int i = n; i; i--) Ans[i][1] = ++id;
for (int i = 1; i <= n; i += 4) {
for (int j = 2; j <= m; j++) Ans[i][j] = ++id;
for (int j = m; j >= 2; j--) Ans[i + 1][j] = ++id;
int a = 2, b = 3;
for (int j = 2; j <= m; j++) {
Ans[i + a][j] = ++id;
Ans[i + b][j] = ++id;
swap(a, b);
}
if (i % 8 == 5) {
reverse(Ans[i] + 2, Ans[i] + m + 1);
reverse(Ans[i + 1] + 2, Ans[i + 1] + m + 1);
reverse(Ans[i + 2] + 2, Ans[i + 2] + m + 1);
reverse(Ans[i + 3] + 2, Ans[i + 3] + m + 1);
}
}
if (flag) {
n += 2;
if (n % 8 == 6) {
for (int i = 1; i <= n - 2; i++)
for (int j = 1; j <= m; j++) Ans[i][j] += 4;
Ans[n - 1][1] = 4, Ans[n - 1][2] = 3;
Ans[n][1] = 1, Ans[n][2] = 2;
int a = n - 1, b = n, id = (n - 2) * m + 4, j = m;
for (int ret = m - 3; ret > 2; j--) {
Ans[a][j] = ++id;
Ans[b][j] = ++id;
swap(a, b);
ret -= (j == m ? 1 : 2);
}
for (int _j = j; _j >= 3; _j--) Ans[a][_j] = ++id;
for (int _j = 3; _j <= j; _j++) Ans[b][_j] = ++id;
} else {
for (int i = 1; i <= n - 2; i++)
for (int j = 1; j <= m; j++) Ans[i][j] += 3;
int id = (n - 2) * m + 3;
Ans[n - 1][1] = 3, Ans[n - 1][2] = ++id;
Ans[n][1] = 2, Ans[n][2] = 1;
int a = n - 1, b = n, j = 3;
for (int ret = m - 2; ret > 2; j++) {
Ans[a][j] = ++id;
Ans[b][j] = ++id;
swap(a, b);
ret -= 2;
}
for (int _j = j; _j <= m; _j++) Ans[a][_j] = ++id;
for (int _j = m; _j >= j; _j--) Ans[b][_j] = ++id;
}
}
}
if (flip) {
for (int i = 1; i <= m; i++)
for (int j = 1; j < i; j++) swap(Ans[i][j], Ans[j][i]);
swap(n, m);
}
puts("Yes");
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) printf("%d%c", Ans[i][j], j == m ? '\n' : ' ');
return 0;
}E
首先有个数最多的方案为 ,这样能有
种。那么不妨先构造一个长度为
的这样的序列,能有
种。接下来把
二进制拆分,考虑构造
。一种方法是在第
个数后面插入比
大的数
,在这个数前面选择的方案有
种。但是长度不够,接下来只需要补全位数满足长度为
即可。但是如果每个
都去补的话,需要的数是
了。而在这里,由于后面也会做类似的操作,我们只需要保证所有插入的
满足递增且都大于
即可。这样前面的就可以利用后面插入的数从而满足长度要求。具体地,对于两个为
的
位
,满足
中间没有其他为
的
。那么在 第
个数后面需要插入
个,也就是第
位往上数
的 个数加上
。
#include <bits/stdc++.h>
using namespace std;
int add[100];
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
int len = 30;
while (!(n >> len)) len--;
int cnt = 0;
for (int i = len - 1; i >= 0; i--) {
if (n >> i & 1) {
add[i] = len - i - cnt;
cnt += add[i];
}
}
printf("%d\n", 2 * len + cnt + 1);
int bas = len * 2;
for (int i = 0; i < len; i++) {
while (add[i] && add[i]--) printf("%d ", ++bas);
printf("%d %d ", i * 2 + 2, i * 2 + 1);
}
printf("%d\n", ++bas);
}
}F
枚举 计算
是
的倍数的子矩阵方案数。然后用莫比乌斯反演求得
恰好为
的方案数。
由于 每个数都有,所以所有的因子总和为
。而求解一个矩阵的全
子矩阵个数,用单调栈即可解决。时间复杂度能做到只遍历为
位置的数。最终复杂度
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1010;
int n, m;
pair<int, int> f[N * N];
int a[N][N], g[N][N], l[N];
ll s[N][N], d[N][N], ans[N * N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
scanf("%d", &a[i][j]);
f[a[i][j]] = make_pair(i, j);
}
for (int i = 1; i <= n * m; i++) {
vector<pair<int, int>> w;
for (int j = 1; j * i <= n * m; j++) {
w.push_back(f[i * j]);
}
sort(w.begin(), w.end());
for (int j = 0; j < w.size(); j++) {
int x = w[j].first, y = w[j].second;
g[x][y] = g[x][y - 1] + 1;
if (g[x - 1][y] == 0) {
l[y] = 0;
d[y][0] = x - 1;
}
++l[y];
d[y][l[y]] = x;
while (l[y] > 1 && g[x][y] <= g[d[y][l[y] - 1]][y]) {
l[y]--;
d[y][l[y]] = d[y][l[y] + 1];
}
s[y][l[y]] = s[y][l[y] - 1] + 1ll * (x - d[y][l[y] - 1]) * g[x][y];
ans[i] += s[y][l[y]];
}
for (int j = 0; j < w.size(); j++) {
l[w[j].second] = 0;
g[w[j].first][w[j].second] = 0;
}
}
for (int i = n * m; i >= 1; i--) {
for (int j = 2; j * i <= n * m; j++) ans[i] -= ans[i * j];
}
ll s = 0;
for (int i = 1; i <= n * m; i++) {
s += 1ll * i * ans[i];
}
printf("%lld\n", s);
}G
由于一个字符串的所有本质不同回文子串只有 。用
或者回文树求出来,然后用哈希统计出现次数。
需要挂一只
。
#include <iostream>
#define maxn 300005
using namespace std;
string s, s1;
int len[maxn], fail[maxn], tr[maxn][30], cnt[maxn], idex = 1, cur;
int get_fail(int x, int i) {
while (i - len[x] - 1 < 0 || s[i] != s[i - len[x] - 1]) {
x = fail[x];
}
return x;
}
void build(int k) {
int llen = s.size();
len[1] = -1, fail[0] = 1, len[0] = 0, fail[1] = 1;
for (int i = 0; i < llen; i++) {
int u = get_fail(cur, i);
if (!tr[u][s[i] - 'a']) {
fail[++idex] = tr[get_fail(fail[u], i)][s[i] - 'a'];
tr[u][s[i] - 'a'] = idex;
len[idex] = len[u] + 2;
}
cur = tr[u][s[i] - 'a'];
if (cnt[cur] == k - 1) {
cnt[cur]++;
}
}
}
int main() {
int k;
cin >> k;
for (int i = 1; i <= k; i++) {
cin >> s;
build(i);
}
long long ans = 0;
for (int i = idex; i > 0; i--) {
if (cnt[i] == k) {
ans++;
}
}
cout << ans << endl;
}I
令 表示前
个数划分成
段从左到右枚举
,考虑单调栈来把以
为右端点的,
相同的左端点
合并成一段区间。
然后一段的由于代价相同,可以直接用 表示第
个区间内的,所有的
(
属于第
个区间)的最小值加上左端点在这里面对应的
值 。
每次弹栈的时候,设这个区间原来的 为
,新的
为
。需要令
加上
,然后再把所有弹出的区间合并成一个区间。并且再维护一个前缀数组
表示
。弹栈的时候更新这个数组。更新
的时候直接取
的值。时间复杂度
。
#include <bits/stdc++.h>
using namespace std;
const int N = 8e3 + 5;
int f[2][N], g[N], mi[N], stk[N], n, a[N], top;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], f[0][i] = 0x3f3f3f3f;
for (int k = 1; k <= n; k++) {
int id = k & 1;
f[id][top = 0] = 0x3f3f3f3f;
for (int i = k; i <= n; i++) {
mi[i] = f[id ^ 1][i - 1];
while (top && a[stk[top]] <= a[i]) {
mi[i] = min(mi[i], mi[stk[top]]);
top--;
}
f[id][i] = min(f[id][stk[top]], mi[i] + a[i]);
stk[++top] = i;
}
cout << f[id][n] << '\n';
}
}J
如果一条边颜色数量大于 ,那么总能选到一个和前后两条边颜色不同的颜色。
剩下的边,每条颜色数小于等于 。于是合并两条路径时候,枚举四条边的颜色得到新的答案。那么对于树上的问题就可以用倍增或者树剖维护了。
考虑基环数,如果路径要经过环,需要枚举从哪边走。一种方法是,把环拎出来,在环上正反做两次倍增维护。时间复杂度 ,常数稍大。
#include <bits/stdc++.h>
#define maxn 200086
using namespace std;
struct Node {
int c[2][2], f[2][2];
};
Node merge(const Node &l, const Node &r) {
Node x;
x.c[0][0] = l.c[0][0], x.c[0][1] = l.c[0][1];
x.c[1][0] = r.c[1][0], x.c[1][1] = r.c[1][1];
memset(x.f, -0x3f, sizeof(x.f));
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
for (int k = 0; k < 2; k++)
for (int o = 0; o < 2; o++) {
x.f[i][o] = max(x.f[i][o], l.f[i][j] + r.f[k][o] - (l.c[1][j] == r.c[0][k]));
}
return x;
}
int n, q;
int a[maxn][2];
bool flag[maxn];
vector<pair<int, int> > v[maxn];
int fa[maxn], id[maxn];
int dep[maxn];
vector<pair<int, int> > w;
void dfs(int i) {
dep[i] = dep[fa[i]] + 1;
for (auto it : v[i]) {
int to = it.first, co = it.second;
if (flag[co])
continue;
flag[co] = true;
if (!dep[to]) {
fa[to] = i, id[to] = co;
dfs(to);
} else {
int x = i;
while (x ^ to) {
w.push_back({ x, id[x] });
x = fa[x];
}
w.push_back({ to, co });
}
}
}
bool vis[maxn];
int rt[maxn], f[maxn][20];
Node g[maxn][20], h[maxn * 2][20];
int lg[maxn];
void init(Node &x, int id) {
x.c[0][0] = x.c[1][0] = a[id][0];
x.c[0][1] = x.c[1][1] = a[id][1];
x.f[0][0] = x.f[1][1] = 1;
x.f[0][1] = x.f[1][0] = -0x3f3f3f3f;
}
void dfs(int i, int x) {
dep[i] = dep[f[i][0]] + 1, rt[i] = x;
for (int j = 1; j < 20; j++) {
f[i][j] = f[f[i][j - 1]][j - 1];
g[i][j] = merge(g[i][j - 1], g[f[i][j - 1]][j - 1]);
}
for (auto it : v[i]) {
int to = it.first, id = it.second;
if (to == f[i][0] || vis[to])
continue;
f[to][0] = i;
init(g[to][0], id);
dfs(to, x);
}
}
int lca(int x, int y) {
if (dep[x] < dep[y])
swap(x, y);
while (dep[x] > dep[y]) x = f[x][lg[dep[x] - dep[y]]];
if (x == y)
return x;
for (int i = 19; ~i; i--)
if (f[x][i] ^ f[y][i])
x = f[x][i], y = f[y][i];
return f[x][0];
}
Node G(int x, int d) {
Node ans;
bool tag = false;
for (int i = 0; i < 20; i++)
if (d & (1 << i)) {
if (!tag)
ans = g[x][i], tag = true;
else
ans = merge(ans, g[x][i]);
x = f[x][i];
}
return ans;
}
Node H(int x, int d) {
Node ans;
bool tag = false;
for (int i = 0; i < 20; i++)
if (d & (1 << i)) {
if (!tag)
ans = h[x][i], tag = true;
else
ans = merge(ans, h[x][i]);
x += 1 << i;
}
return ans;
}
Node rev(Node x) {
swap(x.c[0][0], x.c[1][0]), swap(x.c[0][1], x.c[1][1]);
swap(x.f[0][1], x.f[1][0]);
return x;
}
int cal(Node x) { return max({ x.f[0][0], x.f[0][1], x.f[1][0], x.f[1][1] }); }
int Id[maxn];
int main() {
for (int i = 2; i < maxn; i++) lg[i] = lg[i >> 1] + 1;
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++) {
int x, y;
scanf("%d%d", &x, &y);
v[x].push_back({ y, i }), v[y].push_back({ x, i });
int k;
scanf("%d", &k);
if (k >= 3) {
a[i][0] = a[i][1] = 5e5 + i;
while (k--) scanf("%d", &x);
} else if (k == 2)
scanf("%d%d", &a[i][0], &a[i][1]);
else
scanf("%d", &a[i][0]), a[i][1] = a[i][0];
}
dfs(1);
for (int i = 0; i < w.size(); i++) Id[w[i].first] = i, vis[w[i].first] = true;
for (int i = 0; i < w.size(); i++) dfs(w[i].first, w[i].first);
for (int i = 0; i < w.size(); i++) {
init(h[i][0], w[i].second);
h[i + w.size()][0] = h[i][0];
}
for (int j = 1; j < 20; j++) {
for (int i = 0; i + (1 << (j - 1)) < w.size() * 2; i++) {
h[i][j] = merge(h[i][j - 1], h[i + (1 << (j - 1))][j - 1]);
}
}
while (q--) {
int x, y;
scanf("%d%d", &x, &y);
if (rt[x] == rt[y]) {
Node ans;
if (dep[x] < dep[y])
swap(x, y);
int z = lca(x, y);
if (z == y) {
ans = G(x, dep[x] - dep[y]);
} else {
Node l = G(x, dep[x] - dep[z]), r = rev(G(y, dep[y] - dep[z]));
ans = merge(l, r);
}
printf("%d\n", cal(ans));
} else {
if (Id[rt[x]] > Id[rt[y]])
swap(x, y);
Node l = G(x, dep[x] - dep[rt[x]]), r = rev(G(y, dep[y] - dep[rt[y]]));
Node i = H(Id[rt[x]], Id[rt[y]] - Id[rt[x]]),
j = rev(H(Id[rt[y]], w.size() - (Id[rt[y]] - Id[rt[x]])));
if (x == rt[x] && y == rt[y])
printf("%d\n", max(cal(i), cal(j)));
else if (x == rt[x])
printf("%d\n", max(cal(merge(i, r)), cal(merge(j, r))));
else if (y == rt[y])
printf("%d\n", max(cal(merge(l, i)), cal(merge(l, j))));
else
printf("%d\n", max(cal(merge(l, merge(i, r))), cal(merge(l, merge(j, r)))));
}
}
}K
定义全集 定义集合幂级数
。若存在给定集合
那么
为
。
定义卷积为并卷积,可以用 计算。那么只需要求出
,对于一个
找到最小的
使得
,就是
的度数。
得到这些答案后,再做一次高为后缀 即可。时间复杂度
。
#include <cstdio>
using namespace std;
int n, K, ans[100];
int A[1100000], B[1100000];
void FWT_or(int *f, int type) {
for (int mid = 1; mid < (1 << K); mid <<= 1)
for (int block = mid << 1, j = 0; j < (1 << K); j += block)
for (int i = j; i < j + mid; i++) f[i + mid] = (f[i + mid] + f[i] * type);
}
int main() {
scanf("%d%d", &n, &K);
for (int x, y, z, i = 1; i <= n; i++) {
scanf("%d", &x);
z = 0;
for (int i = 1; i <= x; i++) {
scanf("%d", &y);
z |= 1 << y - 1;
}
A[z] = 1;
}
for (int i = (1 << K) - 1; i > 0; i--)
for (int k = i, j = k & -k; k; k -= j, j = k & -k) A[i ^ j] |= A[i];
FWT_or(A, 1);
B[0] = ans[0] = 1;
for (int j = 1; j <= K; j++) {
FWT_or(B, 1);
for (int i = 0; i < (1 << K); i++) B[i] *= A[i];
FWT_or(B, -1);
for (int i = 0; i < (1 << K); i++)
if (B[i])
B[i] = 1, ans[j]++;
}
for (int i = K; i; i--) ans[i] -= ans[i - 1];
for (int i = 1; i <= K; i++) printf("%d ", ans[i]);
}

全部评论
(1) 回帖