做题记录 4.0
Merging Towers
来源:CF1380E, 2300
仔细推一推性质,发现最优策略就是从小到大将 $\mathrm{i}$ 移到 $\mathrm{i+1}$ 上.
然后可以把连续段缩成一个连通块,那么连通块个数减一就是答案了.
所以这个问题就转化成每次合并两个 $\mathrm{vector}$, 同时要求合并连通块.
这里用 $\mathrm{set}$ 维护的连通块,然后进行的启发式合并,复杂度为 $\mathrm{O(n \log^2 n)}$.
#include <bits/stdc++.h>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n, m, p[N], L[N], R[N];
int find(int x) {
return p[x] == x ? x : p[x] = find(p[x]);
}
int merge(int x, int y) {
x = find(x), y = find(y);
if(x == y) return 0;
L[x] = min(L[x], L[y]);
R[x] = max(R[x], R[y]);
p[y] = x;
return 1;
}
void init() {
for(int i = 1; i < N ; ++ i) {
p[i] = i, L[i] = R[i] = i;
}
}
int idx[N], ans;
set<int>G[N];
int main() {
// setIO("input");
init();
scanf("%d%d", &n, &m);
ans = n;
// n 个数字, m 个种类.
for(int i = 1; i <= m ; ++ i) idx[i] = i;
for(int i = 1; i <= n ; ++ i) {
int x;
scanf("%d", &x);
G[x].insert(i);
}
for(int i = 1; i <= m ; ++ i) {
set<int>::iterator it = G[i].begin();
int prev = (*it);
for(it ++ ; it != G[i].end(); it ++ ) {
int now = (*it);
if(now == prev + 1) {
ans -= merge(prev, now);
}
prev = now;
}
}
printf("%d\n", ans - 1);
for(int i = 1; i < m ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
int px = idx[x], py = idx[y];
// merge py and px
if(G[px].size() > G[py].size()) {
swap(px, py);
}
// px 合并到 py 里面,并把 idx[x] 定位 py
set<int>::iterator it = G[px].begin();
for(; it != G[px].end(); it ++ ) {
int now = (*it);
int root = find(now);
if(L[root] == now) {
// 没和 now - 1 合并呢.
set<int>::iterator i2 = G[py].find(now - 1);
if(i2 != G[py].end()) {
ans -= merge(root, now - 1);
}
}
if(R[root] == now) {
set<int>::iterator i3 = G[py].find(now + 1);
if(i3 != G[py].end()) {
ans -= merge(root, now + 1);
}
}
}
for(it = G[px].begin(); it != G[px].end(); it ++ ) {
int now = (*it);
G[py].insert(now);
}
G[px].clear();
idx[x] = py;
printf("%d\n", ans - 1);
}
return 0;
}
Xenon's Attack on the Gangs
来源:CF1292C, 2300
仔细分析发现 $\mathrm{mex}$ 有意义的话必须要包含零边.
然后一定是 $0$ 到 $\mathrm{K}$ 连续分布在一跳链上.
在这条链上,分布情况就是以 0 为中心,从小到大向两边扩展.
那么满足这个性质就可以进行 $\mathrm{DP}$ 了.
令 $\mathrm{f[x][y]}$ 表示端点分别为 $\mathrm{x, y}$ 的情况然后每次考虑新加入边的贡献即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define N 3004
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
vector<int>G[N];
int n, fa[15][N], dep[N], size[N];
void dfs(int x, int ff) {
fa[0][x] = ff, dep[x] = dep[ff] + 1;
for(int i = 1; i < 13; ++ i) {
fa[i][x] = fa[i - 1][fa[i - 1][x]];
}
size[x] = 1;
for(int i = 0; i < G[x].size() ; ++ i) {
int v = G[x][i];
if(v == ff)
continue;
dfs(v, x);
size[x] += size[v];
}
}
int get_lca(int x, int y) {
if(dep[x] != dep[y]) {
if(dep[y] < dep[x])
swap(x, y);
for(int i = 12; i >= 0; -- i)
if(dep[fa[i][y]] >= dep[x])
y = fa[i][y];
}
if(x == y)
return x;
for(int i = 12; i >= 0; -- i) {
if(fa[i][x] != fa[i][y]) {
x = fa[i][x], y = fa[i][y];
}
}
return fa[0][x];
}
int get_dis(int x, int y) {
return dep[x] + dep[y] - 2 * dep[get_lca(x, y)];
}
// (x, y) 的路径上距离 x 最近的点.
int get(int x, int y) {
int lca = get_lca(x, y);
if(lca != x) return fa[0][x];
else {
// 这里 y 的父亲是 x
for(int i = 12; i >= 0; -- i) {
if(dep[fa[i][y]] > dep[x])
y = fa[i][y];
}
return y;
}
}
struct data {
int x, y;
data(int x = 0, int y = 0):x(x), y(y){}
}e[5000000];
int hd[N], to[5000009], nex[5000009], idx[5000009];
ll f[N][N];
int main() {
// setIO("input");
scanf("%d",&n);
for(int i = 1; i < n ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
G[x].pb(y);
G[y].pb(x);
}
// 建树
// 这里开始预处理.
dfs(1, 0);
int cnt = 0;
for(int i = 1; i <= n ; ++ i) {
for(int j = i + 1; j <= n ; ++ j) {
int now = get_dis(i, j);
nex[++ cnt] = hd[now], hd[now] = cnt, e[cnt] = data(i, j);
}
}
ll ans = 0;
for(int i = 1; i < n ; ++ i) {
for(int j = hd[i]; j ; j = nex[j]){
data cur = e[j];
int x = cur.x;
int y = cur.y;
if(i == 1) {
if(fa[0][x] == y)
swap(x, y);
f[x][y] = f[y][x] = 1ll * size[y] * (n - size[y]);
}
else {
int px = get(x, y);
int py = get(y, x);
ll p1, p2;
if(px == fa[0][x]) p1 = size[x];
else p1 = n - size[px];
if(py == fa[0][y]) p2 = size[y];
else p2 = n - size[py];
f[x][y] = f[y][x] = 1ll * max(f[px][y], f[x][py]) + 1ll * p1 * p2;
}
ans = max(ans, f[x][y]);
}
}
printf("%lld", ans);
return 0;
}
Boxes And Balls
来源:CF884D, 2300
理论最优是 3 个合并,但是偶数的情况会剩下两个.
那么就特判,如果是偶数就提前合并最小的两个,然后再三个一起合并.
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n;
priority_queue<ll, vector<ll>, greater<ll> >q;
int main() {
// setIO("input");
scanf("%d", &n);
for(int i = 1; i <= n ; ++ i) {
int x;
scanf("%d", &x);
q.push(1ll * x);
}
ll ans = 0;
if(n % 2 == 0) {
ll x, y;
x = q.top(); q.pop();
y = q.top(); q.pop();
ans += x + y;
q.push(x + y);
}
while(q.size() >= 3) {
ll x, y, z;
x = q.top(); q.pop();
y = q.top(); q.pop();
z = q.top(); q.pop();
ans += x + y + z;
q.push(x + y + z);
}
printf("%lld", ans);
return 0;
}
Removing Leaves
来源:CF1385F, 2300
显然只要有 $\mathrm{k}$ 个叶子就要尽量删除,那么用堆贪心维护叶子最多的即可.
模拟的时候要精细一点,对于每个点开一个叶子 $\mathrm{set}$ 和总相邻节点的 $\mathrm{set}$.
要及时删除和更新.
#include <bits/stdc++.h>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n , K, deg[N];
set<int>leaf[N], G[N];
struct data {
int x, y;
data(int x = 0, int y = 0):x(x), y(y) {}
bool operator < (const data b) const {
return b.y == y ? b.x < x : b.y < y;
}
};
set<data>q;
void add(int x, int y) {
++ deg[x], ++ deg[y];
G[x].insert(y);
G[y].insert(x);
}
void solve() {
if(scanf("%d%d", &n, &K) == EOF)
exit(0);
for(int i = 1; i < n ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y);
}
if(K == 1) {
printf("%d\n", n - 1);
}
else {
for(int i = 1; i <= n ; ++ i) {
if(deg[i] == 1) {
int v = *G[i].begin();
// v 是唯一临近的.
leaf[v].insert(i);
}
}
for(int i = 1; i <= n ; ++ i) {
q.insert(data(i, leaf[i].size()));
}
int ans = 0;
while(!q.empty()) {
data e = *q.begin(); q.erase(*q.begin());
// printf("%d %d\n", e.x, e.y);
if(e.y < K) break;
++ ans;
for(int i = 1; i <= K ; ++ i) {
int now = *leaf[e.x].begin();
G[e.x].erase(now);
leaf[e.x].erase(now);
}
if(e.y > K) {
q.insert(data(e.x, e.y - K));
}
// 删除完毕.
if(G[e.x].size() == 1 && leaf[e.x].size() == 0) {
int now = *G[e.x].begin();
q.erase(data(now, leaf[now].size()));
leaf[now].insert(e.x);
q.insert(data(now, leaf[now].size()));
}
}
q.clear();
printf("%d\n", ans);
}
for(int i = 1; i <= n ; ++ i) {
G[i].clear();
leaf[i].clear();
deg[i] = 0;
}
}
int main() {
// setIO("input");
int T;
scanf("%d", &T);
while(T -- ) solve();
return 0;
}
Pattern Matching
来源:CF1476E, 2300
首先,模式串是互不相同的且匹配串最多可以与 16 个模式串所配对.
那么就可以暴力连边进行拓扑排序,然后按照拓扑序输出结果即可.
这里偷懒,直接用的 $\mathrm{string+map}$, 常数可能会大一些.
#include <cstdio>
#include <vector>
#include <string>
#include <set>
#include <map>
#include <queue>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n, m ,K, tar[N], deg[N];
char p[N][5], str[N][5];
map<string, int>mp;
vector<int>G[N];
void add(int u, int v) {
G[u].pb(v);
++ deg[v];
// 入度 ++
}
queue<int>q;
vector<int>fin;
int main() {
// setIO("input");
scanf("%d%d%d", &n, &m, &K);
for(int i = 1; i <= n ; ++ i) {
scanf("%s", p[i] + 1);
string s;
for(int j = 1; j <= K ; ++ j)
s += p[i][j];
mp[s] = i;
}
for(int i = 1; i <= m ; ++ i) {
scanf("%s", str[i] + 1);
scanf("%d", &tar[i]);
vector<int>v;
int fl = 0;
for(int j = 0; j < (1 << K) ; ++ j) {
string s;
for(int o = 0; o < K ; ++ o) {
if(j & (1 << o)) {
s += str[i][o + 1];
}
else {
s += '_';
}
}
if(mp[s]) {
v.pb(mp[s]);
if(mp[s] == tar[i]) fl = 1;
}
}
if(fl == 0) {
printf("NO");
return 0;
}
for(int j = 0; j < v.size() ; ++ j) {
if(v[j] == tar[i])
continue;
add(tar[i], v[j]);
}
}
for(int i = 1; i <= n ; ++ i) {
if(!deg[i]) {
q.push(i);
fin.pb(i);
}
}
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = 0; i < G[u].size() ; ++ i){
int v = G[u][i];
-- deg[v];
if(deg[v] == 0) {
q.push(v);
fin.pb(v);
}
}
}
if(fin.size() < n) {
printf("NO");
}
else {
printf("YES\n");
for(int i = 0; i < n ; ++ i)
printf("%d ", fin[i]);
}
return 0;
}
Prefix Product Sequence
来源:CF487C, 2300
大于 4 的合数不合法,只需考虑质数的情况.
理想的构造方式是 $\mathrm{\frac{i}{i-1}}$, 这样的话在模意义下正好是一个排列.
验证发现不会出现数字相同的情况,直接输出即可.
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n ;
int qpow(int x, int y, int mod) {
int tmp = 1;
for(; y; y >>= 1, x = (ll)x * x % mod)
if(y & 1) tmp = (ll)tmp * x % mod;
return tmp;
}
int get_inv(int x, int mod) {
return qpow(x, mod - 2, mod);
}
int main() {
// setIO("input");
scanf("%d", &n);
if(n == 4) {
printf("YES\n");
printf("1\n3\n2\n4");
return 0;
}
for(int i = 2; i * i <= n ; ++ i)
if(n % i == 0) {
printf("NO");
return 0;
}
printf("YES\n");
printf("1\n");
for(int i = 2; i < n ; ++ i) {
printf("%d\n", (ll)i * get_inv(i - 1, n) % n);
}
if(n != 1) printf("%d", n);
return 0;
}
Pig and Palindromes
来源:CF570E, 2300
这道题的话最直观是列状态 $\mathrm{f[x1][y1][x2][y2]}$ 但是多了一个 $\mathrm{n}$.
但是我们发现如果知道步数的话通过横坐标可以求得纵坐标,这样就能降低复杂度了.
由于空间消耗很大,所以要用到滚动数组.
#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 504
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int mod = (int)1e9 + 7;
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, -1, 0, 1};
int n , m;
char str[N][N];
int f[2][N][N];
int check(int i, int j) {
if(i >= 1 && i <= m && j >= 1 && j <= n)
return 1;
return 0;
}
int main() {
// setIO("input");
scanf("%d%d", &n, &m);
for(int i = 1; i <= n ; ++ i) {
scanf("%s", str[i] + 1);
}
int lim = (n + m - 1 + 1) / 2;
int now = 0;
f[now][1][m] = (str[1][1] == str[n][m]);
// lim steps in total.
for(int k = 1; k < lim ; ++ k) {
memset(f[now ^ 1], 0, sizeof(f[now ^ 1]));
for(int i = 1; i <= m ; ++ i) {
for(int j = i; j <= m ; ++ j) {
if(!f[now][i][j]) continue;
int y[2];
y[0] = k + 1 - i;
y[1] = m - j + 1 - k + n;
for(int d1 = 0; d1 < 4; ++ d1) {
for(int d2 = 0; d2 < 4; ++ d2) {
int px = i + dx[d1];
int py = y[0] + dy[d1];
int qx = j + dx[d2];
int qy = y[1] + dy[d2];
if(check(px, py) == 0 || check(qx, qy) == 0)
continue;
if(px >= i && py >= y[0] && qx <= j && qy <= y[1]) {
if(str[py][px] == str[qy][qx]) {
(f[now ^ 1][px][qx] += f[now][i][j]) %= mod;
}
}
}
}
}
}
now ^= 1;
}
int ans = 0;
if((n + m - 1) & 1) {
for(int i = 1; i <= m ; ++ i)
ans = (ll)(ans + f[now][i][i]) % mod;
}
else {
for(int i = 1; i <= m ; ++ i) {
ans = (ll)(ans + f[now][i][i]) % mod;
ans = (ll)(ans + f[now][i][i + 1]) % mod;
}
}
printf("%d", ans);
return 0;
}
Scalar Queries
来源:CF1167F, 2300
可以对每个数字拆开考虑贡献,然后一个数字的贡献就是在每个区间中的排名.
根据常见的套路,这个可以转化成多少个小于等于这个数字的个数.
然后就可以依次计算该位置左面的贡献和右面的贡献以及本身的贡献.
用树状数组统计答案即可.
#include <bits/stdc++.h>
#define N 500009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int mod = (int)1e9 + 7;
struct BIT {
int C[N];
int lowbit(int x) {
return x & (-x);
}
void upd(int x, int v) {
while(x < N) {
C[x] = (ll)(C[x] + v) % mod;
x += lowbit(x);
}
}
int query(int x) {
int tmp = 0;
while(x) {
tmp = (ll)(tmp + C[x]) % mod;
x -= lowbit(x);
}
return tmp;
}
int calc(int l, int r) {
return (ll)(query(r) - query(l - 1) + mod) % mod;
}
}T[2];
int n, a[N];
struct data {
int pos, v;
}q[N];
bool cmp(data i, data j) {
return i.v < j.v;
}
ll calc(int x) {
return 1ll * x * (x - 1) / 2;
}
int ADD(int x, int v) {
return (ll)(x + v) % mod;
}
int main() {
// setIO("input");
scanf("%d", &n);
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &a[i]);
q[i].pos = i;
q[i].v = a[i];
}
sort(q + 1, q + 1 + n, cmp);
int ans = 0;
for(int i = 1; i <= n ; ++ i) {
int p = q[i].pos;
int pl = T[0].calc(1, p);
int pr = T[1].calc(p, n);
ll to = (1 + calc(n) - calc(p - 1) - calc(n - p)) % mod;
int tot = ADD((ll)pl * (n - p + 1) % mod, (ll)pr * p % mod);
tot = ADD(tot, (int)to);
ans = ADD(ans, (ll)tot * q[i].v % mod);
T[0].upd(p, p);
T[1].upd(p, n - p + 1);
}
printf("%d", ans);
return 0;
}
Beautiful Pairs of Numbers
来源:CF403D, 2300
这道题的话可以看做先用 $\mathrm{k}$ 段凑成一个长度为 $\mathrm{l}$ 的段,然后其余插空.
插空的话就是隔板法,然后前面就是一个二维 $\mathrm{DP}$, 提前预处理即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 2009
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int mod = (int)1e9 + 7;
int f[104][N], inv[N], fac[N], g[104][N];
int qpow(int x, int y) {
int tmp = 1;
for(; y; y >>= 1, x = (ll)x * x % mod)
if(y & 1) tmp = (ll)tmp * x % mod;
return tmp;
}
int get_inv(int x) {
return qpow(x, mod - 2);
}
int C(int x, int y) {
return (ll)fac[x] * inv[y] % mod * inv[x - y] % mod;
}
void init() {
f[0][0] = 1;
for(int k = 1; k <= 1000; ++ k) {
for(int i = 50; i >= 1 ; -- i) {
for(int j = k; j <= 1000 ; ++ j) {
f[i][j] = (ll)(f[i][j] + f[i - 1][j - k]) % mod;
}
}
}
inv[0] = fac[0] = 1;
for(int i = 1; i < N ; ++ i) fac[i] = (ll)fac[i - 1] * i % mod;
inv[N - 1] = get_inv(fac[N - 1]);
for(int i = N - 2; i >= 1; -- i) {
inv[i] = (ll)inv[i + 1] * (i + 1) % mod;
}
for(int k = 1; k <= 50; ++ k) {
for(int n = 1; n <= 1000; ++ n) {
for(int j = 1; j <= n ; ++ j) {
int tmp = (ll)fac[k] * f[k][j] % mod * C(n - j + k, n - j) % mod;
tmp %= mod;
g[k][n] = (ll)(g[k][n] + tmp) % mod;
}
}
}
}
void solve() {
int n , k;
scanf("%d%d", &n, &k);
if(k >= 50) printf("0\n");
else printf("%d\n", g[k][n]);
}
int main() {
// setIO("input");
init();
int T;
scanf("%d", &T);
while(T -- ) solve();
return 0;
}
Daniel and Spring Cleaning
来源:CF1245F, 2300
这道题可以转化成有多少个 $\mathrm{a}$ 与 $\mathrm{b}$ 为 0.
然后这个东西可以拆分成若干个前缀和的形式,省去了下界.
在进行数位 $\mathrm{DP}$ 的时候,同时进行 $\mathrm{x,y}$ 两维,枚举当前选什么.
最后记忆化搜索即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
ll dp[100][2][2];
int v1[100], v2[100], l1, l2;
ll dfs(int x, int d1, int d2) {
// 现在要加入 x, 现在要加入 y.
if(dp[x][d1][d2] != -1) {
return dp[x][d1][d2];
}
if(x == 0)
return 1;
ll s = 0;
int lim1 = d1 ? v1[x] : 1;
int lim2 = d2 ? v2[x] : 1;
for(int i = 0; i <= lim1; ++ i) {
for(int j = 0; j <= lim2; ++ j) {
// 同时加入啊
if(i + j == 2) continue;
// 与为 0.
s += dfs(x - 1, d1 && (i == lim1), d2 && (j == lim2));
}
}
return dp[x][d1][d2] = s;
}
ll calc(int l, int r) {
if(l < 0 || r < 0)
return 0;
// 计算 l and r
memset(dp, -1, sizeof(dp));
l1 = l2 = 0;
memset(v2, 0, sizeof(v2));
memset(v1, 0, sizeof(v1));
int i = l, j = r;
while(i) v1[++ l1] = (i & 1), i >>= 1;
while(j) v2[++ l2] = (j & 1), j >>= 1;
// 从 l1 到 1
// printf("%d\n", max(l1, l2));
return dfs(max(l1, l2), 1, 1);
}
void solve() {
int l, r;
scanf("%d%d", &l, &r);
printf("%lld\n", calc(r, r) - 2 * calc(l - 1, r) + calc(l - 1, l - 1));
}
int main() {
// setIO("input");
int T;
scanf("%d", &T);
while(T -- ) solve();
return 0;
}
Double Profiles
来源:CF154C, 2300
可以把每个点所连的点哈希起来,然后放到 $\mathrm{map}$ 里面判断.
特别地,对于相连点就挨个枚举,然后刨掉相邻点看哈希值是否相等即可.
#include <bits/stdc++.h>
#define N 1000009
#define ll long long
#define mp make_pair
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int base1 = 233;
const int base2 = 233233;
const int mod1 = (int)1e9 + 7;
const int mod2 = 998244353;
int n, m;
int s1[N], s2[N];
void init() {
s1[0] = s2[0] = 1;
for(int i = 1; i < N ; ++ i) {
s1[i] = (ll)s1[i - 1] * base1 % mod1;
s2[i] = (ll)s2[i - 1] * base2 % mod2;
}
}
pair<int, int>a[N];
map<pair<int, int>, int>Ha;
vector<int>G[N];
int DEC(int x, int y, int mo) {
return (ll)(x - y + mo) % mo;
}
int ADD(int x, int y, int mo) {
return (ll)(x + y) % mo;
}
int main() {
// setIO("input");
scanf("%d%d", &n, &m);
init();
for(int i = 1; i <= m ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
(a[x].first += s1[y]) %= mod1;
(a[x].second += s2[y]) %= mod2;
(a[y].first += s1[x]) %= mod1;
(a[y].second += s2[x]) %= mod2;
if(x > y) swap(x, y);
G[x].pb(y);
}
for(int i = 1; i <= n ; ++ i) {
Ha[a[i]] ++ ;
}
ll ans = 0;
for(map<pair<int,int>, int>::iterator it = Ha.begin(); it != Ha.end(); it ++ ) {
ll q = (*it).second;
ans += q * (q - 1) / 2;
}
for(int i = 1; i <= n ; ++ i) {
for(int j = 0; j < G[i].size() ; ++ j) {
int v = G[i][j];
if(DEC(a[i].first, s1[v], mod1) == DEC(a[v].first, s1[i], mod1) && DEC(a[i].second, s2[v], mod2) == DEC(a[v].second, s2[i], mod2))
++ ans;
}
}
printf("%I64d", ans);
return 0;
}
Anton and School - 2
来源:CF785D, 2300
这道题正常做是 $\mathrm{O(n^2)}$ 的,然后注意到可以用范德蒙德恒等式来做.
这里注意组合数有 $\mathrm{C(n, i)=C(n,n-i)}$ 这个性质,如果下标之和并不是定值可以考虑翻转一下.
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define N 400009
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int mod = (int)1e9 + 7;
int fac[N], inv[N];
char str[N];
int n ;
int qpow(int x, int y) {
int tmp = 1;
for(; y ; y >>= 1, x = (ll)x * x % mod)
if(y & 1) tmp = (ll)tmp * x % mod;
return tmp;
}
int get_inv(int x) {
return qpow(x, mod - 2);
}
void init() {
fac[0] = inv[0] = 1;
for(int i = 1; i < N ; ++ i) {
fac[i] = (ll)fac[i - 1] * i % mod;
}
inv[N - 1] = get_inv(fac[N - 1]);
for(int i = N - 2; i >= 1; -- i) {
inv[i] = (ll)inv[i + 1] * (i + 1) % mod;
}
}
int C(int x, int y) {
return (ll)fac[x] * inv[y] % mod * inv[x - y] % mod;
}
int L[N], R[N];
int main() {
// setIO("input");
init();
scanf("%s", str + 1);
n = strlen(str + 1);
for(int i = 1; i <= n ; ++ i) {
L[i] = L[i - 1] + (str[i] == '(');
}
for(int i = n; i >= 1 ; -- i) {
R[i] = R[i + 1] + (str[i] == ')');
}
int ans = 0;
for(int i = 1; i <= n ; ++ i) {
if(str[i] == '(') {
// i and i + 1
if(L[i] && R[i + 1]) {
(ans += C(L[i] + R[i + 1] - 1, L[i])) %= mod;
}
}
}
printf("%d", ans);
return 0;
}
Devu and Flowers
来源:CF451E, 2300
如果没有数量限制就是经典的组合问题,有数量限制就考虑容斥一下.
不合法的情况就是强制几个选 $\mathrm{f[i] + 1}$ 个,然后剩余的随便选.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 22
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const ll mod = (ll)1e9 + 7;
int n ;
ll s, f[N], sum[1 << N], F[N];
int size[1 << N];
ll qpow(ll x, ll y) {
ll tmp = 1;
for(; y; y >>= 1, x = x * x % mod)
if(y & 1) tmp = tmp * x % mod;
return tmp;
}
ll get_inv(ll x) {
return qpow(x, mod - 2);
}
ll calc(ll x) {
ll tp = 1;
for(ll i = x + 1; i <= x + n - 1; ++ i)
tp = i % mod * tp % mod;
ll tq = 1;
for(ll i = 1; i <= n - 1; ++ i)
tq = i % mod * tq % mod;
return tp * get_inv(tq) % mod;
}
int main() {
// setIO("input");
scanf("%d%lld", &n, &s);
for(int i = 0; i < n ; ++ i)
scanf("%lld", &f[i]);
for(int i = 1; i < (1 << n) ; ++ i) {
for(int j = 0; j < n ; ++ j) {
if((1 << j) & i) {
sum[i] = sum[i - (1 << j)] + f[j];
size[i] = size[i - (1 << j)] + 1;
break;
}
}
}
F[0] = calc(s);
for(int i = 1; i < (1 << n) ; ++ i) {
if(sum[i] + size[i] > s) continue;
F[size[i]] = (F[size[i]] + calc(s - sum[i] - size[i])) % mod;
}
ll ans = 0;
for(int i = 0; i <= n ; ++ i) {
ll d = (i & 1) ? (mod - 1) : 1;
(ans += d * F[i] % mod) %= mod;
}
printf("%lld", ans);
return 0;
}
Enchanted Artifact
来源:CF1282D, 2300
交互题.
不妨第一次询问一个 $\mathrm{a}$ 来确定长度.
之后可以询问 $\mathrm{aaaaabaaaaa}$ 这种 1 个 $\mathrm{b}$ 的串来确定第 $\mathrm{i}$ 位的字母.
然后在开头特判一下是否是全 $\mathrm{b}$ 的序列即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int ask(int len, int pos) {
for(int i = 1; i <= len ; ++ i) {
if(i == pos) printf("b");
else printf("a");
}
printf("\n");
// printf("\n");
fflush(stdout);
int an ;
scanf("%d", &an);
if(an == 0)
exit(0);
return an ;
}
int vis[N];
int main() {
// setIO("input");
int l = ask(1, 0);
if(l == 300) {
for(int i = 1; i <= 300; ++ i)
printf("b");
printf("\n");
fflush(stdout);
scanf("%d", &l);
}
else {
// l + 1 的试试
int a1 = ask(l + 1, 0);
if(a1 == l + 1) {
// 全 b
for(int i = 1; i <= l ; ++ i) printf("b");
printf("\n");
fflush(stdout);
scanf("%d", &l);
}
else {
// a1 个 b
int now = 0;
for(int i = 1; i <= l ; ++ i) {
int cur = ask(l + 1, i);
if(cur < a1) ++ now, vis[i] = 1;
}
if(now < a1) vis[l + 1] = 1;
for(int i = 1; i <= l + 1; ++ i) {
if(vis[i]) printf("b");
else printf("a");
}
printf("\n");
fflush(stdout);
scanf("%d", &now);
}
}
return 0;
}
Hemose in ICPC ?
来源:CF1592D, 2300
肯定要和某种二分结合,每次肯定是要放进去一个连续的点集.
如果对欧拉序了解的话可知道欧拉序中相邻两点就代表着一跳树边,所以用欧拉序二分即可.
#include <bits/stdc++.h>
#define N 2009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n , arr[N], tot, vis[N];
vector<int>G[N];
int query(vector<int>g) {
printf("? %d ", g.size());
for(int i = 0; i < g.size() ; ++ i) {
printf("%d ", g[i]);
}
printf("\n");
printf("\n");
fflush(stdout);
int an;
scanf("%d", &an);
return an ;
}
void report(int x, int y) {
printf("! %d %d\n", x, y);
fflush(stdout);
}
void dfs(int x, int ff) {
arr[++ tot] = x;
for(int i = 0; i < G[x].size() ; ++ i) {
int v = G[x][i];
if(v == ff) continue;
dfs(v, x);
arr[++ tot] = x;
}
}
int ask(int l, int r) {
memset(vis, 0, sizeof(vis));
vector<int>f;
for(int i = l; i <= r; ++ i) {
if(!vis[arr[i]]) {
vis[arr[i]] = 1;
f.pb(arr[i]);
}
}
return query(f);
}
int main() {
// setIO("input");
scanf("%d", &n);
for(int i = 1; i < n ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
G[x].pb(y);
G[y].pb(x);
}
dfs(1, 0);
int ans = ask(1, tot), l = 2, r = tot, fi = 0;
while(l < r) {
int mid = (l + r) >> 1;
if(ask(1, mid) == ans)
r = mid;
else l = mid + 1;
}
// l is key
report(arr[l - 1], arr[l]);
return 0;
}
Drazil and Park
来源:CF515E, 2300
可以分成两种情况:顺时针走和逆时针走.
对于顺时针走的情况是一个 $\mathrm{x[i]+y[j]}$ 的情况,这个可以用线段树来维护
对于逆时针的情况直接查询前缀最大值即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define ls now << 1
#define rs now << 1 | 1
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const ll inf = 10000000000000000ll;
int n , m ;
ll d[N], h[N], sum[N], pre[N], suf[N];
struct data {
ll x, y, an;
data() { y = -inf, x = -inf, an = -inf; }
data operator+(const data b) const {
data c;
c.an = max(max(an, b.an), x + b.y);
c.x = max(x, b.x), c.y = max(y, b.y);
return c;
}
}s[N << 2];
void build(int l, int r, int now) {
if(l == r) {
s[now].x = 2 * h[l] - sum[l];
s[now].y = 2 * h[l] + sum[l];
s[now].an = -inf;
return ;
}
int mid = (l + r) >> 1;
build(l, mid, ls), build(mid + 1, r, rs);
s[now] = s[ls] + s[rs];
}
data query(int l, int r, int now, int L, int R) {
if(l >= L && r <= R)
return s[now];
int mid = (l + r) >> 1;
if(L <= mid && R > mid)
return query(l, mid, ls, L, R) + query(mid + 1, r, rs, L, R);
else if(L <= mid)
return query(l, mid, ls, L, R);
else return query(mid + 1, r, rs, L, R);
}
ll find(int l, int r) {
if(l >= r)
return -inf;
else
return query(1, n, 1, l, r).an;
}
int main() {
// setIO("input");
scanf("%d%d", &n, &m);
for(int i = 1; i <= n ; ++ i)
scanf("%lld", &d[i]);
for(int i = 1; i <= n ; ++ i)
scanf("%lld", &h[i]);
for(int i = 2; i <= n ; ++ i) {
sum[i] = sum[i - 1] + d[i - 1];
}
build(1, n, 1);
// 这里构建完毕了.
for(int i = 1; i <= n ; ++ i) {
pre[i] = max(pre[i - 1], sum[i] + 2 * h[i]);
}
for(int i = n; i >= 1; -- i) {
suf[i] = max(suf[i + 1], sum[n] - sum[i] + d[n] + 2 * h[i]);
}
for(int i = 1; i <= m ; ++ i) {
int a, b;
scanf("%d%d", &a, &b);
ll ans = 0;
if(a <= b) {
ans = max(ans, max(find(1, a - 1), find(b + 1, n)));
if(a != 1 && b != n)
ans = max(ans, pre[a - 1] + suf[b + 1]);
}
else {
ans = max(ans, find(b + 1, a - 1));
}
printf("%lld\n", ans);
}
return 0;
}
Mike and Foam
来源:CF547C, 2300
考虑每次加入一个数的时候算多少个与其不互质的,然后容斥一下.
算不互质的就是一个容斥,容斥系数其实就是莫比乌斯函数,这里就直接爆搜了.
由于一个数字本质不同的质数最多就 7 个,所以时间复杂度完全够用.
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
#define N 200009
#define M 500009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
vector<int>G[N], h[M];
int n , Q, cur[N], a[N], vis[M], cnt[M], fin ;
void dfs(int i, int o, int num) {
// 当前直接统计.
int p = abs(num), d = num > 0 ? 1 : -1;
fin += d * cnt[p];
// printf("%d %d\n", p, cnt[p]);
for(int j = i + 1; j < G[o].size() ; ++ j) {
dfs(j, o, - num * G[o][j]);
}
}
int calc(int x) {
// x 和其他数字的总和.
fin = 0;
for(int i = 0; i < G[x].size() ; ++ i)
dfs(i, x , G[x][i]);
return fin ;
}
void add(int i, int o, int num, int d) {
cnt[num] += d;
// printf("%d %d\n", num, d);
for(int j = i + 1; j < G[o].size() ; ++ j)
add(j, o, num * G[o][j], d);
}
void upd(int x, int d) {
// printf("%d %d %d\n", x, G[x].size(), d);
for(int i = 0; i < G[x].size() ; ++ i)
add(i, x , G[x][i], d);
}
int main() {
// setIO("input");
scanf("%d%d", &n, &Q);
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &a[i]);
h[a[i]].pb(i);
}
for(int i = 2; i < M ; ++ i) {
if(vis[i]) continue;
for(int j = i; j < M ; j += i) {
vis[j] = 1;
for(int k = 0; k < h[j].size() ; ++ k)
G[h[j][k]].pb(i);
}
vis[i] = 0;
}
// 处理完了,到时候可以进行分解.
int tot = 0;
ll ans = 0;
for(int i = 1, x; i <= Q; ++ i) {
scanf("%d", &x);
if(!cur[x]) {
cur[x] = 1;
ans += tot - calc(x);
upd(x, 1);
++ tot;
}
else {
cur[x] = 0;
upd(x, -1);
-- tot;
ans -= (tot - calc(x));
}
printf("%lld\n", ans);
}
return 0;
}
Edges in MST
来源:CF160D, 2300
先随便拿出一个最小生成树出来.
显然,对于非树边,可以看是否能替换树边分为 2, 3 两类.
对于每一个树边,如果存在非树边可以替换该树边,则为 $\mathrm{2}$, 否则为 $\mathrm{1}$.
用树上差分加线段树合并可以方便地解决该问题.
#include <cstdio>
#include <vector>
#include <set>
#include <map>
#include <cstring>
#include <algorithm>
#define N 200009
#define M 1000000
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
struct UFS {
int p[N];
void init() {
for(int i = 0; i < N ; ++ i) p[i] = i;
}
int find(int x) {
return p[x] == x ? x : p[x] = find(p[x]);
}
int merge(int x, int y) {
x = find(x), y = find(y);
if(x == y)
return 0;
p[x] = y;
return 1;
}
}con;
struct SGT {
struct data {
int ls, rs, sum;
}s[N * 50];
int tot ;
int update(int x, int l, int r, int p, int v) {
int now = ++ tot;
s[now] = s[x];
s[now].sum += v;
if(l == r)
return now;
int mid = (l + r) >> 1;
if(p <= mid)
s[now].ls = update(s[x].ls, l, mid, p, v);
else
s[now].rs = update(s[x].rs, mid + 1, r, p, v);
return now;
}
int merge(int l, int r, int x, int y) {
if(!x || !y)
return x + y;
if(l == r) {
s[x].sum += s[y].sum;
return x;
}
int mid = (l + r) >> 1;
s[x].ls = merge(l, mid, s[x].ls, s[y].ls);
s[x].rs = merge(mid + 1, r, s[x].rs, s[y].rs);
return x;
}
int query(int x, int l, int r, int p) {
if(l == r) return s[x].sum;
int mid = (l + r) >> 1;
if(p <= mid)
return query(s[x].ls, l, mid, p);
else
return query(s[x].rs, mid + 1, r, p);
}
}T;
int n, m ;
struct Edge {
int u, v, c, id;
Edge(int u = 0, int v = 0, int c = 0, int id = 0):u(u), v(v), c(c), id(id) {}
}e[N];
bool cmp(Edge i, Edge j) { return i.c < j.c; }
struct E {
int v, c, id;
E(int v = 0, int c = 0, int id = 0):v(v), c(c), id(id) {}
};
vector<E>G[N];
int fa[19][N], mi[19][N], rt[N], ans[N], idx[N], vis[N], dep[N], val[N];
void dfs(int x, int ff) {
dep[x] = dep[ff] + 1;
for(int i = 0; i < G[x].size() ; ++ i) {
E to = G[x][i];
if(to.v == ff) continue;
int v = to.v;
val[v] = to.c;
idx[v] = to.id;
fa[0][v] = x, mi[0][v] = val[v];
// printf("%d %d\n", v, val[v]);
for(int j = 1; j < 19 ; ++ j) {
fa[j][v] = fa[j - 1][fa[j - 1][v]];
mi[j][v] = max(mi[j - 1][v], mi[j - 1][fa[j - 1][v]]);
}
dfs(v, x);
}
}
int get_lca(int x, int y) {
if(dep[x] > dep[y]) swap(x, y);
if(dep[x] != dep[y]) {
for(int i = 18; i >= 0; -- i) {
if(dep[fa[i][y]] >= dep[x])
y = fa[i][y];
}
}
if(x == y) return x;
for(int i = 18; i >= 0; -- i) {
if(fa[i][x] != fa[i][y]) {
x = fa[i][x], y = fa[i][y];
}
}
return fa[0][x];
}
int qmin(int x, int y) {
int lca = get_lca(x, y), an = 0;
for(int i = 18; i >= 0; -- i) {
if(dep[fa[i][x]] >= dep[lca])
an = max(an, mi[i][x]), x = fa[i][x];
if(dep[fa[i][y]] >= dep[lca])
an = max(an, mi[i][y]), y = fa[i][y];
}
return an;
}
void dfs2(int x, int ff) {
for(int i = 0; i < G[x].size() ; ++ i) {
E to = G[x][i];
if(to.v == ff) continue;
dfs2(to.v, x);
rt[x] = T.merge(1, M, rt[x], rt[to.v]);
}
int o = T.query(rt[x], 1, M, val[x]);
if(vis[idx[x]]) {
if(o == 0)
ans[idx[x]] = 1;
else
ans[idx[x]] = 2;
}
}
int main() {
// setIO("input");
scanf("%d%d", &n, &m);
for(int i = 1; i <= m ; ++ i) {
scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].c);
e[i].id = i;
}
sort(e + 1, e + 1 + m, cmp);
con.init();
for(int i = 1; i <= m ; ++ i) {
if(con.merge(e[i].u, e[i].v)) {
G[e[i].u].pb(E(e[i].v, e[i].c, e[i].id));
G[e[i].v].pb(E(e[i].u, e[i].c, e[i].id));
vis[e[i].id] = 1;
}
}
// 要开始建树了.
dfs(1, 0);
for(int i = 1; i <= m ; ++ i) {
if(vis[e[i].id] == 0) {
// printf("%d\n", e[i].id);
// 不在生成树里面.
int mi = qmin(e[i].u, e[i].v);
// printf("%d %d %d\n", e[i].u, e[i].v, mi);
if(mi != e[i].c)
ans[e[i].id] = 3;
else {
ans[e[i].id] = 2;
// 这个就是可以替换的边.
int lca = get_lca(e[i].u, e[i].v);
rt[e[i].u] = T.update(rt[e[i].u], 1, M, e[i].c, 1);
rt[e[i].v] = T.update(rt[e[i].v], 1, M, e[i].c, 1);
rt[lca] = T.update(rt[lca], 1, M, e[i].c, -2);
}
}
}
dfs2(1, 0);
for(int i = 1; i <= m ; ++ i) {
if(ans[i] == 3) printf("none");
if(ans[i] == 2) printf("at least one");
if(ans[i] == 1) printf("any");
printf("\n");
}
return 0;
}
Yet Another Segments Subset
来源:CF1399F, 2300
区间 $\mathrm{DP}$,难点在于求 $\mathrm{c[i]}$, 即第 $\mathrm{i}$ 条线段最多包含多少条其他线段.
然后求这个的话可以把所有线段按照长度从小到大排序,然后每次 $\mathrm{O(n)}$ 拿出 $\mathrm{i}$ 内的线段进行一遍 $\mathrm{DP}$.
由于内部的线段长度更小,所以 $\mathrm{c[j]}$ 是可以直接拿来用的.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 6009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n, A[N << 1], cnt, dp[N], f[N];
struct seg {
int l, r, len , id ;
seg(int l = 0, int r = 0, int len = 0, int id = 0):l(l), r(r), len(len) , id(id) {}
}a[N], b[N];
vector<seg>se[N];
bool cmp1(seg i, seg j) { return i.len < j.len; }
bool cmp2(seg i, seg j) { return i.r < j.r; }
void calc(int x) {
// a[x] 这条线段.
for(int i = 1; i <= n ; ++ i) {
if(a[x].id != b[i].id && b[i].l >= a[x].l && b[i].r <= a[x].r) {
se[b[i].r].pb(b[i]);
}
}
for(int i = a[x].l; i <= a[x].r; ++ i) {
f[i] = max(f[i], f[i - 1]);
for(int j = 0; j < se[i].size() ; ++ j) {
f[i] = max(f[i], dp[se[i][j].id] + f[se[i][j].l - 1]);
}
}
dp[a[x].id] = 1 + f[a[x].r];
for(int i = a[x].l; i <= a[x].r; ++ i) f[i] = 0;
for(int i = a[x].l; i <= a[x].r; ++ i) se[i].clear();
}
void solve() {
scanf("%d", &n);
cnt = 0 ;
for(int i = 1; i <= n ; ++ i) {
scanf("%d%d", &a[i].l, &a[i].r);
A[++ cnt] = a[i].l;
A[++ cnt] = a[i].r;
a[i].len = a[i].r - a[i].l + 1;
a[i].id = i;
}
++ n ;
a[n].l = 1, a[n].r = (int)2e5 + 5;
a[n].len = a[n].r - a[n].l + 1;
a[n].id = n ;
A[++ cnt] = a[n].l;
A[++ cnt] = a[n].r;
sort(A + 1, A + 1 + cnt);
cnt = unique(A + 1, A + 1 + cnt) - A - 1;
for(int i = 1; i <= n ; ++ i) {
a[i].l = lower_bound(A + 1, A + 1 + cnt, a[i].l) - A;
a[i].r = lower_bound(A + 1, A + 1 + cnt, a[i].r) - A;
}
// 按照长度依次处理.
sort(a + 1, a + 1 + n, cmp1);
for(int i = 1; i <= n ; ++ i)
b[i] = a[i];
sort(b + 1, b + 1 + n, cmp2);
for(int i = 1; i <= n ; ++ i) {
calc(i);
}
printf("%d\n", dp[a[n].id] - 1);
for(int i = 0; i <= n ; ++ i) dp[i] = 0;
}
int main() {
// setIO("input");
int T;
scanf("%d", &T);
while(T -- ) solve();
return 0;
}
Isomorphic Strings
来源:CF985F, 2300
这道题强调字母之间的相对位置,不妨对每种字母开一个哈希表,然后判断一下即可.
为了防止出题人卡哈希,这里用了两个 $\mathrm{base}$ 和两个模数.
#include <cstdio>
#include <vector>
#include <set>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
char str[N];
const int mod1 = 998244353;
const int mod2 = (int)1e9 + 7;
const int base1 = 233;
const int base2 = 233233;
int b1[N], b2[N];
struct HASH {
int s1[N], s2[N];
}ha[26];
void init() {
b1[0] = b2[0] = 1;
for(int i = 1; i < N ; ++ i) {
b1[i] = (ll)b1[i - 1] * base1 % mod1;
b2[i] = (ll)b2[i - 1] * base2 % mod2;
}
}
int ax[2][30], ay[2][30];
int main() {
// setIO("input");
int n , m ;
scanf("%d%d", &n, &m);
init();
scanf("%s", str + 1);
// 字符串输入进来.
for(int i = 1; i <= n ; ++ i) {
int c = str[i] - 'a';
for(int j = 0; j < 26 ; ++ j) {
ha[j].s1[i] = ha[j].s1[i - 1];
ha[j].s2[i] = ha[j].s2[i - 1];
}
(ha[c].s1[i] += b1[i]) %= mod1;
(ha[c].s2[i] += b2[i]) %= mod2;
}
// 加密好了.
for(int i = 1; i <= m ; ++ i) {
int x, y, len ;
scanf("%d%d%d", &x, &y, &len);
if(x > y) swap(x, y);
for(int j = 1; j <= 26; ++ j) {
ax[0][j] = (ll)(ha[j - 1].s1[x + len - 1] - ha[j - 1].s1[x - 1] + mod1) % mod1 * b1[y - x] % mod1;
ax[1][j] = (ll)(ha[j - 1].s2[x + len - 1] - ha[j - 1].s2[x - 1] + mod2) % mod2 * b2[y - x] % mod2;
ay[0][j] = (ll)(ha[j - 1].s1[y + len - 1] - ha[j - 1].s1[y - 1] + mod1) % mod1;
ay[1][j] = (ll)(ha[j - 1].s2[y + len - 1] - ha[j - 1].s2[y - 1] + mod2) % mod2;
}
sort(ax[0] + 1, ax[0] + 1 + 26);
sort(ax[1] + 1, ax[1] + 1 + 26);
sort(ay[0] + 1, ay[0] + 1 + 26);
sort(ay[1] + 1, ay[1] + 1 + 26);
int flag = 0;
for(int j = 1; j <= 26; ++ j) {
// printf("%d %d\n", ax[0][j], ay[0][j]);
if((ax[0][j] != ay[0][j]) || (ax[1][j] != ay[1][j])) {
flag = 1;
break;
}
}
if(flag) printf("NO\n");
else printf("YES\n");
}
return 0;
}
Envy
来源:CF891C, 2300
对于一个图来说,最小生成树所构成的边的种类数是固定的.
就是说,对于边权为 $\mathrm{v}$ 的边在一个树中有 $\mathrm{c}$ 个,则任意必有 $\mathrm{c}$ 个.
然后对于一个生成树中同一权值的边,连通性也同样是必须相同的.
那么在这道题中就可以对于询问离线,每次讲 $\mathrm{[1, i-1]}$ 的边连出来,然后看 $\mathrm{i}$ 的询问边是否有环即可.
由于需要连边并撤销,所以直接上可撤销并查集即可.
p.s. 这道题用 $\mathrm{LCT}$ 做也是经典问题,即每次暴力查看一条边是否能加入,能加入则加,不能加则不合法.
#include <cstdio>
#include <vector>
#include <set>
#include <cstring>
#include <algorithm>
#define N 1000009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
struct UFS {
int p[N], top , size[N];
struct OP {
int x, y;
// x 连向 y
OP(int x = 0, int y = 0):x(x), y(y){}
}sta[N];
void init() {
top = 0;
for(int i = 1; i < N ; ++ i) {
size[i] = 1, p[i] = i;
}
}
int find(int x) {
return p[x] == x ? x : find(p[x]);
}
int merge(int x, int y) {
x = find(x), y = find(y);
if(x == y) return 0;
if(size[x] > size[y]) swap(x, y);
// size[y] > size[x]
size[y] += size[x], p[x] = y;
sta[++ top] = OP(x, y);
return 1;
}
void undo(int h) {
// (h, top] 撤销掉.
while(top > h) {
OP cur = sta[top];
size[cur.y] -= size[cur.x];
p[cur.x] = cur.x;
-- top;
}
}
}T;
int n , m , Q, ans[N];
struct Edge {
int u, v, c;
Edge(int u = 0, int v = 0, int c = 0):u(u), v(v), c(c){}
}e[N];
bool cmp(Edge i, Edge j) { return i.c < j.c; }
struct query {
int u, v, c, id;
query(int u = 0, int v = 0, int c = 0, int id = 0):u(u), v(v), c(c), id(id){}
};
vector<query>G[N];
bool cmp2(query i, query j) { return i.id < j.id; }
int main() {
// setIO("input");
scanf("%d%d", &n, &m);
int mx = 0;
for(int i = 1; i <= m ; ++ i) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
e[i] = Edge(x, y, z);
mx = max(mx, z);
}
scanf("%d", &Q);
for(int i = 1, k; i <= Q; ++ i) {
scanf("%d", &k);
for(int j = 1; j <= k ; ++ j) {
int x;
scanf("%d", &x);
// printf("%d %d\n", x, e[x].c);
G[e[x].c].pb(query(e[x].u, e[x].v, e[x].c, i));
}
}
sort(e + 1, e + 1 + m, cmp);
T.init();
memset(ans, -1, sizeof(ans));
for(int i = 1, j = 1; i <= mx; ++ i) {
// 先解决
int o = T.top;
sort(G[i].begin(), G[i].end(), cmp2);
if(G[i].size()) {
int d = G[i][0].id;
for(int j = 0; j < G[i].size() ; ++ j) {
if(G[i][j].id != d) {
T.undo(o);
} // 换询问了.
if(!T.merge(G[i][j].u, G[i][j].v)) {
ans[G[i][j].id] = 0;
}
d = G[i][j].id;
}
}
T.undo(o);
for( ; j <= m && e[j].c <= i; ++ j) {
T.merge(e[j].u, e[j].v);
}
// 把新的边加进来.
}
for(int i = 1; i <= Q; ++ i) {
printf("%s\n", ans[i] == 0 ? "NO" : "YES");
}
return 0;
}
Centroids
来源:CF708C, 2300
对于重心来说,显然合法,然后非重心来说要从最大的子树选择最大的且不超过 $\mathrm{n/2}$ 的子块删下来.
求最大的 $\mathrm{n/2}$ 的子块用换根 $\mathrm{DP}$ 求解即可,这里开了一个前缀/后缀的最大值数组,会方便很多.
#include <cstdio>
#include <set>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 400009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
vector<int>G[N];
int n , size[N], son[N], f[N], g[N], ans[N], mk[N];
void dfs(int x, int ff) {
size[x] = 1;
for(int i = 0; i < G[x].size() ; ++ i) {
int v = G[x][i];
if(v == ff) continue;
dfs(v, x);
if(size[v] <= n / 2)
f[x] = max(f[x], size[v]);
else
f[x] = max(f[x], f[v]);
size[x] += size[v];
mk[x] = max(mk[x], size[v]);
if(size[v] > size[son[x]]) son[x] = v;
}
mk[x] = max(mk[x], n - size[x]);
if(n - size[x] > size[son[x]]) son[x] = ff;
}
void dfs2(int x, int ff) {
vector<int>pre, suf;
for(int i = 0; i < G[x].size() ; ++ i) {
int v = G[x][i];
if(v != ff) {
if(size[v] <= n / 2)
pre.pb(size[v]), suf.pb(size[v]);
else
pre.pb(f[v]), suf.pb(f[v]);
}
}
for(int i = 1; i < pre.size() ; ++ i)
pre[i] = max(pre[i - 1], pre[i]);
for(int i = suf.size() - 2; i >= 0; -- i)
suf[i] = max(suf[i], suf[i + 1]);
int now = 0;
for(int i = 0; i < G[x].size() ; ++ i) {
int v = G[x][i];
if(v == ff) continue;
if(now > 0)
g[v] = max(g[v], pre[now - 1]);
if(now < suf.size() - 1)
g[v] = max(g[v], suf[now + 1]);
if(n - size[v] <= n / 2)
g[v] = max(g[v], n - size[v]);
else
g[v] = max(g[v], g[x]);
dfs2(v, x), ++ now;
}
}
int main() {
// setIO("input");
scanf("%d", &n);
for(int i = 1; i < n ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
G[x].pb(y);
G[y].pb(x);
}
dfs(1, 0);
dfs2(1, 0);
for(int i = 1; i <= n ; ++ i) {
if(mk[i] <= n / 2) printf("1 ");
else {
if(n - size[i] > n / 2) {
// 外面是不合法的.
if(n - size[i] - g[i] <= n / 2)
printf("1 ");
else printf("0 ");
}
else {
int v = son[i];
if(size[v] - f[v] <= n / 2)
printf("1 ");
else printf("0 ");
}
}
}
return 0;
}
Count Pairs
来源:CF1188B, 2300
在等式两侧分别乘上一个差,左边就能凑成平方差的形式.
然后最后可以把和 $\mathrm{i,j}$ 相关的分别移到两侧,用 $\mathrm{map}$ 来统计即可.
#include <bits/stdc++.h>
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
ll mod, K;
ll calc(ll x) {
return (x * x % mod * x % mod * x % mod - K * x % mod + mod) % mod;
}
int n;
map<ll, int>mp;
int main() {
// setIO("input");
scanf("%d%lld%lld", &n, &mod, &K);
ll ans = 0;
for(int i = 1; i <= n ; ++ i) {
ll x;
scanf("%lld", &x);
ll p = calc(x);
ans += mp[p];
mp[p] = mp[p] + 1;
}
printf("%lld", ans);
return 0;
}
A Simple Task
来源:CF558E, 2300
对于每一个字母的位置开一个线段树,然后排序的话就是两个区间赋值操作.
全程都用线段树维护,由于空间十分充裕所以该做法是正确的.
#include <bits/stdc++.h>
#define N 100009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n , Q;
char str[N];
struct Seg {
#define ls now << 1
#define rs now << 1 | 1
int sum[N << 2], lazy[N << 2];
void mark(int l, int r, int now, int v) {
sum[now] = (r - l + 1) * v;
lazy[now] = v;
}
void pushdown(int l, int r, int now) {
if(lazy[now] != -1) {
int mid = (l + r) >> 1;
mark(l, mid, ls, lazy[now]);
mark(mid + 1, r, rs, lazy[now]);
lazy[now] = -1;
}
}
void build(int l, int r, int now) {
sum[now] = 0, lazy[now] = -1;
if(l == r) return ;
int mid = (l + r) >> 1;
build(l, mid, ls), build(mid + 1, r, rs);
}
void pushup(int now) {
sum[now] = sum[ls] + sum[rs];
}
void update(int l, int r, int now, int L, int R, int v) {
if(l >= L && r <= R) {
mark(l, r, now, v);
return ;
}
int mid = (l + r) >> 1;
pushdown(l, r, now);
if(L <= mid) update(l, mid, ls, L, R, v);
if(R > mid) update(mid + 1, r, rs, L, R, v);
pushup(now);
}
void modify(int l, int r, int now, int p, int v) {
if(l == r) {
sum[now] = v;
return ;
}
int mid = (l + r) >> 1;
if(p <= mid) modify(l, mid, ls, p, v);
else modify(mid + 1, r, rs, p, v);
pushup(now);
}
int query(int l, int r, int now, int L, int R) {
if(l >= L && r <= R) {
return sum[now];
}
pushdown(l, r, now);
int mid = (l + r) >> 1, re = 0;
if(L <= mid) re += query(l, mid, ls, L, R);
if(R > mid) re += query(mid + 1, r, rs, L, R);
return re;
}
#undef ls
#undef rs
}T[26];
int main() {
// setIO("input");
scanf("%d%d", &n, &Q);
for(int i = 0; i < 26 ; ++ i) {
T[i].build(1, n, 1);
}
scanf("%s", str + 1);
for(int i = 1; i <= n ; ++ i) {
int c = str[i] - 'a';
T[c].modify(1, n, 1, i, 1);
// 修改为 1.
}
for(int i = 1; i <= Q; ++ i) {
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
if(k == 1) {
// 这个是升序.
int pre = 0;
for(int j = 0; j < 26; ++ j) {
int cur = T[j].query(1, n, 1, l, r);
if(!cur) continue;
T[j].update(1, n, 1, l, r, 0);
T[j].update(1, n, 1, l + pre, l + pre + cur - 1, 1);
pre += cur;
}
}
else {
int pre = 0;
for(int j = 25; j >= 0; -- j) {
int cur = T[j].query(1, n, 1, l, r);
if(!cur) continue;
T[j].update(1, n, 1, l, r, 0);
T[j].update(1, n, 1, l + pre, l + pre + cur - 1, 1);
pre += cur;
}
}
}
for(int i = 1; i <= n ; ++ i) {
for(int j = 0; j < 26; ++ j) {
if(T[j].query(1, n, 1, i, i)) {
printf("%c", 'a' + j);
}
}
}
return 0;
}
Another Filling the Grid
来源:CF1228E, 2300
二项式反演:只要满足钦定的情况把恰好的情况算了组合数次就可以使用.
这道题中钦定几行/几列不选,然后用二项式反演计算恰好的情况即可.
#include <cstdio>
#include <cmath>
#include <set>
#include <cstring>
#include <algorithm>
#define N 303
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int mod = (int)1e9 + 7;
int n , K , fac[N], inv[N];
int qpow(int x, int y) {
int tmp = 1;
for( ; y ; y >>= 1, x = (ll)x * x % mod) {
if(y & 1) tmp = (ll)tmp * x % mod;
}
return tmp ;
}
int get_inv(int x) {
return qpow(x, mod - 2);
}
void init() {
fac[0] = inv[0] = 1;
for(int i = 1; i < N ; ++ i) {
fac[i] = (ll)fac[i - 1] * i % mod;
inv[i] = get_inv(fac[i]);
}
}
int C(int x, int y) {
return (ll)fac[x] * inv[y] % mod * inv[x - y] % mod;
}
int F(int x, int y) {
return (ll)C(n, x) * C(n, y) % mod * qpow(K - 1, n * n - (n - x) * (n - y)) % mod * qpow(K, (n - x) * (n - y)) % mod;
}
int main() {
// setIO("input");
scanf("%d%d", &n, &K);
init();
if(K == 1) printf("1");
else {
int ans = 0;
for(int i = 0; i <= n ; ++ i) {
for(int j = 0; j <= n ; ++ j) {
int d = (i + j) & 1 ? (mod - 1) : 1;
(ans += (ll)d * F(i, j) % mod) %= mod;
}
}
printf("%d", ans);
}
return 0;
}
Road Improvement
来源:CF543D, 2300
这道题是一个换根 $\mathrm{DP}$, 但是无法求逆元.
当逆元不存在的情况,这种问题的套路就是用前缀/后缀积来替代逆元.
那么这道题中只需对每个点的所有儿子维护一个前后缀的积,然后转移即可.
#include <cstdio>
#include <set>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int mod = (int)1e9 + 7;
int qpow(int x, int y) {
int tmp = 1;
for( ; y ; y >>= 1, x = (ll)x * x % mod)
if(y & 1) tmp = (ll)tmp * x % mod ;
return tmp ;
}
int get_inv(int x) {
return qpow(x, mod - 2);
}
vector<int>F[N];
int fa[N], f[N], g[N], n , G[N] ;
void dfs(int x, int ff) {
int tmp = 1;
for(int i = 0; i < F[x].size() ; ++ i) {
int v = F[x][i];
if(v == ff) continue;
dfs(v, x);
tmp = (ll)tmp * (ll)(1 + g[v]) % mod;
}
g[x] = tmp ;
}
int ans[N];
void dfs2(int x, int ff) {
ans[x] = 1;
vector<int>pre, suf;
for(int i = 0; i < F[x].size() ; ++ i) {
int v = F[x][i];
ans[x] = (ll)ans[x] * (g[v] + 1) % mod;
if(v != ff) {
pre.pb(g[v] + 1);
suf.pb(g[v] + 1);
}
}
for(int i = 1; i < pre.size() ; ++ i)
pre[i] = (ll)pre[i - 1] * pre[i] % mod ;
for(int i = suf.size() - 2; i >= 0; -- i)
suf[i] = (ll)suf[i + 1] * suf[i] % mod ;
int now = 0;
for(int i = 0; i < F[x].size() ; ++ i) {
int v = F[x][i];
if(v == ff) continue;
// 更新 g[x]
g[x] = g[ff] + 1;
if(now > 0) g[x] = (ll)g[x] * pre[now - 1] % mod;
if(now < suf.size() - 1)
g[x] = (ll)g[x] * suf[now + 1] % mod;
dfs2(v, x), ++ now;
}
}
int main() {
// setIO("input");
scanf("%d", &n);
for(int i = 2; i <= n ; ++ i) {
scanf("%d", &fa[i]);
F[fa[i]].pb(i);
F[i].pb(fa[i]);
}
dfs(1, 0);
dfs2(1, 0);
for(int i = 1; i <= n ; ++ i) printf("%d ", ans[i]);
return 0;
}
Pairwise Modulo
来源:CF1553F, 2300
把式子拆开化成两个部分,然后前半部分可以调和级数在乘上 $\mathrm{O(\log n)} $ 处理.
后一半部分的话是 $\mathrm{a[k + 1]/a[i] = t}$ 这种形式,不妨考虑每一个 $\mathrm{a[i]}$ 对于后面的贡献:
即枚举那个 $\mathrm{t}$, 然后给 $\mathrm{a[i] \times t}$ 加上 $\mathrm{a[i]}$ 的贡献即可.
#include <bits/stdc++.h>
#define N 400009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n , a[N];
ll sum[N];
struct BIT {
ll C[N];
int lowbit(int x) { return x & (-x); }
void upd(int x, int v) {
while(x < N) {
C[x] += v, x += lowbit(x);
}
}
ll query(int x) {
ll tmp = 0;
while(x > 0) {
tmp += C[x], x -= lowbit(x);
}
return tmp;
}
ll calc(int l, int r) {
if(l > r) return 0ll;
return query(r) - query(l - 1);
}
}T1, T2;
int main() {
// asetIO("input");
scanf("%d", &n);
int mx = 0 ;
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &a[i]);
mx = max(mx, a[i]);
sum[i] = sum[i - 1] + a[i];
}
ll ans = 0;
for(int i = 1; i <= n ; ++ i) {
ll d1 = sum[i - 1];
for(int j = 1; j * a[i] <= mx; ++ j) {
// a[p] is [j * a[i], (j + 1) * a[i])
d1 -= 1ll * j * a[i] * T1.calc(j * a[i], min(mx, (j + 1) * a[i] - 1));
}
T1.upd(a[i], 1);
ll d2 = 1ll * (i - 1) * a[i];
d2 -= T2.calc(1, a[i]);
for(int j = 1; a[i] * j <= mx ; ++ j)
T2.upd(a[i] * j, a[i]);
ans += d1 + d2;
printf("%lld ", ans);
}
return 0;
}
Array GCD
来源:CF623B, 2300
有一个非常关键的信息:因为删除的是连续段,所以 $\mathrm{a[1]}$ 和 $\mathrm{a[n]}$ 并不可能都删除掉.
那么就可以枚举每一种可能的质因子然后进行 $\mathrm{DP}$ 了,设计 $\mathrm{DP}$ 状态为 $\mathrm{f[0][x],f[1][x],f[2][x]}$.
分别表示没有删除,正在删除,删除完毕三个状态,然后转移即可.
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 1000009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n , A, B, a[N];
vector<int>g;
void divide(int x) {
for(int i = 2; i * i <= x ; ++ i) {
if(x % i == 0) {
g.pb(i);
while(x % i == 0) x /= i;
}
}
if(x > 1) g.pb(x);
}
ll F[3][N];
ll solve(int prime) {
// F[0][x] : 并没有删除
// F[1][x] : 正在删除
// F[2][x] : 删除完毕了
memset(F, 0x3f3f3f, sizeof(F));
F[0][0] = 0;
for(int i = 1; i <= n ; ++ i) {
int ty = 0;
if(a[i] % prime == 0) ty = 0;
else if((a[i] - 1) % prime == 0 || (a[i] + 1) % prime == 0) ty = 1;
else ty = 2;
if(ty == 0) {
F[0][i] = F[0][i - 1];
F[2][i] = min(F[2][i - 1], F[1][i - 1]);
}
if(ty == 1) {
// 这个是 + 1 / -1
F[0][i] = F[0][i - 1] + 1ll * B;
F[2][i] = min(F[2][i - 1], F[1][i - 1]) + 1ll * B;
}
F[1][i] = min(F[0][i - 1], F[1][i - 1]) + 1ll * A;
}
return min(min(F[0][n], F[1][n]), F[2][n]);
}
int main() {
// setIO("input");
scanf("%d%d%d", &n, &A, &B);
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &a[i]);
}
divide(a[1]), divide(a[n]);
divide(a[1] - 1), divide(a[1] + 1);
divide(a[n] - 1), divide(a[n] + 1);
ll ans = 100000000000000000ll;
for(int i = 0 ; i < g.size() ; ++ i) {
ans = min(ans, solve(g[i]));
}
printf("%lld", ans);
return 0;
}
Optimal Insertion
来源:CF1601C, 2300
考虑 $\mathrm{b}$ 序列只有 1 个数字怎么做:直接贪心塞到 $\mathrm{a}$ 中的最佳位置.
然后有多个数字时答案的下界就是把每个数字都塞到各自的最优位置.
但问题是我们无法预知 $\mathrm{b}$ 中内部的数字会产生多少逆序对.
但是我们发现随着数字递增,最佳位置不可能递减,所以把每个数字独立开来各自求最佳位置即可.
#include <bits/stdc++.h>
#define N 2000009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
struct BIT {
int C[N << 1];
int lowbit(int x) { return x & (-x); }
void upd(int x, int v) {
while(x < N) C[x] += v, x += lowbit(x);
}
void clr(int x) {
while(x < N) C[x] = 0, x += lowbit(x);
}
int query(int x) {
int tmp = 0;
while(x > 0) tmp += C[x], x -= lowbit(x);
return tmp;
}
}T;
struct Segment_Tree {
#define ls now << 1
#define rs now << 1 | 1
int minv[N << 2], sum[N << 2];
void build(int l, int r, int now) {
minv[now] = N, sum[now] = 0;
if(l == r) return ;
int mid = (l + r) >> 1;
build(l, mid, ls), build(mid + 1, r, rs);
}
void pushup(int now) {
sum[now] = sum[ls] + sum[rs];
minv[now] = min(minv[ls], sum[ls] + minv[rs]);
}
void update(int l, int r, int now, int p, int v) {
if(l == r) {
sum[now] += v;
minv[now] = min(0, sum[now]);
return ;
}
int mid = (l + r) >> 1;
if(p <= mid) update(l, mid, ls, p, v);
else update(mid + 1, r, rs, p, v);
pushup(now);
}
#undef ls
#undef rs
}se;
struct data {
int p, v;
data(int p = 0, int v = 0):p(p), v(v){}
}q[N];
bool cmp(data i, data j) {
return i.v < j.v;
}
int n , m , a[N], b[N], A[N];
void solve() {
scanf("%d%d", &n, &m);
ll ans = 0;
int tot = 0;
for(int i = 1; i <= n ; ++ i) scanf("%d", &a[i]), A[++ tot] = a[i];
for(int i = 1; i <= m ; ++ i) scanf("%d", &b[i]), A[++ tot] = b[i];
sort(A + 1, A + 1 + tot);
for(int i = 1; i <= n ; ++ i) a[i] = lower_bound(A + 1, A + 1 + tot, a[i]) - A ;
for(int i = 1; i <= m ; ++ i) b[i] = lower_bound(A + 1, A + 1 + tot, b[i]) - A ;
for(int i = 1; i <= n ; ++ i) {
ans += 1ll * i - 1ll - 1ll * T.query(a[i]);
T.upd(a[i], 1);
}
sort(b + 1, b + 1 + m);
for(int i = 1; i <= m ; ++ i)
ans += T.query(b[i] - 1);
se.build(1, n + 1, 1);
for(int i = 1; i <= n ; ++ i) {
q[i].p = i, q[i].v = a[i];
}
sort(q + 1, q + 1 + n, cmp);
// 等于的,不变
// 小于的,- 1
// 大于的,+ 1
for(int i = 1; i <= n + 1; ++ i)
se.update(1, n + 1, 1, i, 1);
for(int i = 1, j = 1; i <= m ; ++ i) {
if(b[i] != b[i - 1]) {
for(int k = j - 1; k >= 1; -- k) {
if(q[k].v == b[i - 1])
se.update(1, n + 1, 1, q[k].p, -1);
else break;
}
while(j <= n && q[j].v < b[i]) {
se.update(1, n + 1, 1, q[j].p, -2);
++ j;
}
while(j <= n && q[j].v == b[i]) {
se.update(1, n + 1, 1, q[j].p, -1);
++ j;
}
}
// -1 和 不变.
ans += 1ll * se.minv[1];
}
printf("%lld\n", ans);
for(int i = 1; i <= n ; ++ i) T.clr(a[i]);
}
int main() {
// setIO("input");
int Q;
scanf("%d", &Q);
while(Q -- ) solve();
return 0;
}
Company
来源:CF1062E, 2300
显然对于一堆点的 $\mathrm{lca}$ 来说只需看 $\mathrm{dfs}$ 序最小和最大的 $\mathrm{lca}$ 就行.
那么删除的时候也肯定删除最小/最大的点,然后就写两个 $\mathrm{ST}$ 表和倍增求 $\mathrm{lca}$ 即可.
#include <bits/stdc++.h>
#define N 100009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
vector<int>G[N];
int n , Q, Lg[N], dep[N], fa[N][18], mi[N][18], ma[N][18], dfn[N], scc;
void dfs(int x) {
dfn[x] = ++ scc, dep[x] = dep[fa[x][0]] + 1;
for(int i = 0; i < G[x].size() ; ++ i)
dfs(G[x][i]);
}
void init() {
for(int i = 2; i < N ; ++ i) Lg[i] = Lg[i >> 1] + 1;
for(int i = 1; i <= n ; ++ i) mi[i][0] = ma[i][0] = i;
for(int j = 1; (1 << j) <= n ; ++ j) {
for(int i = 1; i + ( 1 << j) - 1 <= n ; ++ i) {
if(dfn[mi[i][j - 1]] < dfn[mi[i + (1 << (j - 1))][j - 1]])
mi[i][j] = mi[i][j - 1];
else mi[i][j] = mi[i + (1 << (j - 1))][j - 1];
if(dfn[ma[i][j - 1]] > dfn[ma[i + (1 << (j - 1))][j - 1]])
ma[i][j] = ma[i][j - 1];
else ma[i][j] = ma[i + (1 << (j - 1))][j - 1];
}
}
// printf("%d %d\n", mi[4][1], mi[5][2]);
}
int qmin(int l, int r) {
int p = Lg[r - l + 1];
if(dfn[mi[l][p]] < dfn[mi[r - (1 << p) + 1][p]])
return mi[l][p];
else return mi[r - (1 << p) + 1][p];
}
int qmax(int l, int r) {
int p = Lg[r - l + 1];
if(dfn[ma[l][p]] > dfn[ma[r - (1 << p) + 1][p]])
return ma[l][p];
else return ma[r - (1 << p) + 1][p];
}
int get_lca(int x, int y) {
if(dep[x] > dep[y]) swap(x, y);
if(dep[x] != dep[y]) {
for(int i = 17; i >= 0; -- i)
if(dep[fa[y][i]] >= dep[x]) y = fa[y][i];
}
if(x == y) return x;
for(int i = 17; i >= 0; -- i)
if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int calc(int l, int r) {
// printf("%d %d\n", l, r);
int p1 = qmin(l, r);
int p2 = qmax(l, r);
return get_lca(p1, p2);
}
int check(int l, int r, int det) {
// [l, det - 1] and [det + 1, r]
if(det > l && det < r)
return dep[get_lca(calc(l, det - 1), calc(det + 1, r))];
else if(det == l) return dep[calc(det + 1, r)];
else return dep[calc(l, det - 1)];
}
int main() {
// setIO("input");
scanf("%d%d", &n, &Q);
for(int i = 2; i <= n ; ++ i) {
scanf("%d", &fa[i][0]);
G[fa[i][0]].pb(i);
}
for(int j = 1; j < 18 ; ++ j)
for(int i = 1; i <= n ; ++ i)
fa[i][j] = fa[fa[i][j - 1]][j - 1];
dfs(1);
init();
// 预处理完毕了.
//
for(int i = 1; i <= Q; ++ i) {
int l, r;
scanf("%d%d", &l, &r);
int pmin = qmin(l, r);
int pmax = qmax(l, r);
int p0 = check(l, r, pmin), p1 = check(l, r, pmax);
// printf("%d %d\n", pmax, pmin);
if(p0 > p1) {
printf("%d %d\n", pmin, p0 - 1);
}
else {
printf("%d %d\n", pmax, p1 - 1);
}
}
return 0;
}
Pursuit For Artifacts
来源:CF652E, 2300
降智题.
开始一直在想 $\mathrm{DFS}$ 树和返祖边那一套理论,然后就想不出来了.
但是直接按照点双的思路思考的话就简单了:因为不能重复经过边,所以显然可以点双缩点.
那么一个点双内存在 $\mathrm{1}$ 边就把这个团标为 1,然后点双之间的边就正常连.
最后只需要看点双树上 $\mathrm{a,b}$ 两点之间是否有 1 边/ 1 点即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
#include <map>
#include <vector>
#define N 300009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
stack<int>S;
map<int, int>mp[N];
struct Edge {
int v, c;
Edge(int v = 0, int c = 0):v(v),c(c){}
};
vector<Edge>G[N];
int n , m, cnt, id[N], ti[N], low[N], dfn[N], scc;
int vis[N], hd[N], to[N << 1], nex[N << 1], val[N << 1], edges;
void add(int u, int v, int c) {
nex[++ edges] = hd[u];
hd[u] = edges, to[edges] = v, val[edges] = c;
}
void tarjan(int x, int ff) {
vis[x] = 1;
S.push(x);
low[x] = dfn[x] = ++ scc;
for(int i = hd[x]; i ; i = nex[i]) {
int v = to[i];
if(v == ff) continue;
if(!vis[v]) {
tarjan(v, x);
low[x] = min(low[x], low[v]);
}
else if(vis[v] != -1) {
low[x] = min(low[x], dfn[v]);
}
}
if(low[x] == dfn[x]) {
++ cnt;
for( ; ; ) {
int u = S.top(); S.pop();
vis[u] = -1, id[u] = cnt;
if(u == x) break;
}
}
}
int fa[N], fin[N], dep[N];
void dfs(int x, int ff) {
fa[x] = ff, dep[x] = dep[ff] + 1;
for(int i = 0; i < G[x].size() ; ++ i) {
Edge e = G[x][i];
if(e.v == ff) continue;
fin[e.v] = e.c;
dfs(e.v, x);
}
}
int solve(int A, int B) {
A = id[A], B = id[B];
int flag = 0;
if(dep[A] > dep[B]) swap(A, B);
while(dep[B] > dep[A]) {
if(ti[B] || fin[B]) return 1;
B = fa[B];
}
if(B == A) return ti[A];
while(A != B) {
if(ti[A]) return 1;
if(ti[B]) return 1;
if(fin[A]) return 1;
if(fin[B]) return 1;
A = fa[A], B = fa[B];
}
return ti[A];
}
int main() {
// setIO("input");
scanf("%d%d", &n, &m);
edges = 1;
for(int i = 1; i <= m ; ++ i) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z), add(y, x, z);
}
tarjan(1, 0);
for(int i = 1; i <= n ; ++ i) {
for(int j = hd[i]; j ; j = nex[j]) {
int v = to[j];
if(id[i] == id[v] && val[j] == 1)
ti[id[i]] = 1;
}
}
for(int i = 1; i <= n ; ++ i) {
for(int j = hd[i] ; j ; j = nex[j]) {
int v = to[j];
if(id[i] == id[v] ) continue;
if(!mp[id[i]][id[v]]) {
mp[id[i]][id[v]] = mp[id[v]][id[i]] = 1;
G[id[i]].pb(Edge(id[v], val[j]));
G[id[v]].pb(Edge(id[i], val[j]));
}
}
}
dfs(1, 0);
int A, B;
scanf("%d%d", &A, &B);
printf("%s", solve(A, B) ? "YES" : "NO");
return 0;
}
Product Oriented Recurrence
来源:CF1182E, 2300
直接考虑递推式时是 $\mathrm{\sum f(y) + b}$ 的形式,然后 $\mathrm{b}$ 不好处理.
但是好在 $\mathrm{b}$ 是关于 $\mathrm{i}$ 线性的,所以在进行矩阵乘法的时候把后面的常数一起维护即可.
#include <bits/stdc++.h>
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int mod = (int)1e9 + 6;
ll f[5], n, c0;
struct M {
int len, C[6][6];
M(int t = 0) {
len = t;
memset(C, 0, sizeof(C));
}
void I() {
for(int i = 1; i <= len ; ++ i) C[i][i] = 1;
}
M operator*(const M b) const {
M c(len);
for(int k = 1; k <= len ; ++ k) {
for(int i = 1; i <= len ; ++ i) {
for(int j = 1; j <= len ; ++ j) {
(c.C[i][j] += (ll)C[i][k] * b.C[k][j] % mod) %= mod;
}
}
}
return c;
}
};
M operator^(M a, ll b) {
M tmp(a.len);
tmp.I();
while(b) {
if(b & 1) tmp = tmp * a;
b >>= 1, a = a * a;
}
return tmp;
}
ll qpow(ll x, ll y) {
ll tmp = 1;
while(y) {
if(y & 1) tmp = (ll)tmp * x % (mod + 1);
y >>= 1, x = (ll)x * x % (mod + 1);
}
return tmp ;
}
int main() {
// setIO("input");
scanf("%lld%lld%lld%lld%lld", &n, &f[1], &f[2], &f[3], &c0);
M c(5);
for(int i = 1; i <= 5; ++ i)
c.C[i][1] = 1;
c.C[1][2] = c.C[2][3] = c.C[4][4] = c.C[5][4] = c.C[5][5] = 1;
c = c ^ (n - 3);
M F(3);
F.C[1][1] = F.C[2][1] = F.C[3][1] = F.C[1][2] = F.C[2][3] = 1;
F = F ^ (n - 3);
ll pc = qpow(c0, (ll)2 * c.C[5][1] % mod);
ll p1 = qpow(f[1], F.C[3][1]);
ll p2 = qpow(f[2], F.C[2][1]);
ll p3 = qpow(f[3], F.C[1][1]);
printf("%lld", (ll)pc * p1 % (mod + 1) * p2 % (mod + 1) * p3 % (mod + 1));
return 0;
}
Dogeforces
来源:CF1494D, 2300
写了一个分治算法.
对于一个叶子节点来说,可以知道这个叶子节点到根的路径.
然后对于其他的叶子节点,可以知道与选定叶子节点 $\mathrm{lca}$ 处的值.
那么就根据 $\mathrm{lca}$ 处的值对叶子节点分类,然后进行分治即可.
有一些细节需要注意一下.
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
#define N 504
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
set<int>se;
set<int>::iterator it;
int leaf, val[N][N], cnt, fa[N * N], fin[N * N];
vector<int>tmp;
void solve(vector<int>g, int ff) {
int p = g[0], rt = 0, pr = g[0];
fin[rt = p] = val[p][p];
se.clear();
for(int i = 1; i < g.size() ; ++ i) {
int q = g[i];
se.insert(val[p][q]);
}
vector<int>h;
for(it = se.begin(); it != se.end() ; it ++ ) {
int now = *it;
if(now == fin[ff]) {
fa[pr] = ff, pr = rt = ff;
}
else {
rt = ++ cnt;
fa[pr] = cnt, fin[cnt] = now, pr = cnt;
}
h.pb(now);
}
pr = g[0];
for(int i = 0; i < h.size() ; ++ i) {
vector<int>tp;
tp.clear();
for(int j = 1; j < g.size() ; ++ j) {
if(val[p][g[j]] == h[i]) {
tp.pb(g[j]);
}
}
solve(tp, fa[pr]), pr = fa[pr];
}
if(rt != ff) fa[rt] = ff;
}
int main() {
// setIO("input");
scanf("%d", &leaf);
for(int i = 1; i <= leaf; ++ i)
for(int j = 1; j <= leaf; ++ j) scanf("%d", &val[i][j]);
cnt = leaf;
for(int i = 1; i <= leaf; ++ i)
tmp.pb(i);
solve(tmp, 0);
printf("%d\n", cnt);
for(int i = 1; i <= cnt; ++ i) printf("%d ", fin[i]);
printf("\n");
for(int i = 1; i <= cnt; ++ i) if(!fa[i]) printf("%d\n", i);
for(int i = 1; i <= cnt; ++ i) {
if(fa[i]) printf("%d %d\n", i, fa[i]);
}
return 0;
}
Shovels Shop
来源:CF1154F, 2100
肯定会买最便宜的 $\mathrm{K}$ 个,那么直接令 $\mathrm{f[x]}$ 表示购买 $\mathrm{x}$ 个的花费.
然后 $\mathrm{O(k^2)}$ 转移即可.
#include <bits/stdc++.h>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
ll suf[N], f[N];
int n , m, K, a[N], x[N], y[N];
int main() {
// setIO("input");
scanf("%d%d%d", &n, &m, &K);
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= m ; ++ i) {
scanf("%d%d", &x[i], &y[i]);
y[i] = x[i] - y[i];
}
sort(a + 1, a + 1 + n);
for(int i = 1; i <= K ; ++ i)
suf[i] = suf[i - 1] + a[i];
f[0] = 0;
for(int i = 1; i <= K ; ++ i) {
f[i] = f[i - 1] + a[i];
for(int j = 1; j <= m ; ++ j) {
if(x[j] <= i) f[i] = min(f[i], f[i - x[j]] + suf[i] - suf[i - y[j]]);
}
}
printf("%lld", f[K]);
return 0;
}
Increase Sequence
来源:CF466D, 2100
一道差分题.
令 $\mathrm{a[i]' = h - a[i]}$ 即与 $\mathrm{h}$ 相差多少,然后我们想通过对这个序列 $\mathrm{-1}$ 使之变成全 0.
然后这种问题就可以用差分数组来解决了,即每次可以将一个位置 +1, 一个位置 -1 且次数仅为 1 次.
然后每次 $\mathrm{+1}$ 就是一个与前面 $\mathrm{-1}$ 的匹配问题,求解即可.
#include <bits/stdc++.h>
#define ll long long
#define N 2003
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n, h, a[N], d[N];
const int mod = (int)1e9 + 7;
int main() {
// setIO("input");
scanf("%d%d", &n, &h);
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &a[i]);
a[i] = h - a[i];
if(a[i] < 0) {
printf("0");
return 0;
}
}
for(int i = 1; i <= n ; ++ i)
d[i] = a[i] - a[i - 1];
ll ans = 1;
int det = 0;
for(int i = 1; i <= n ; ++ i) {
if(abs(d[i]) > 1) {
printf("0");
return 0;
}
if(d[i] == 1) {
// 加一个 -1
det -= 1;
}
if(d[i] == -1) {
// 加一个 +1
if(det >= 0) {
printf("0");
return 0;
}
ans = (ll)ans * abs(det) % mod;
det += 1;
}
if(d[i] == 0) {
if(det < 0) {
// +1, -1
(ans += (ll)ans % mod * abs(det) % mod) %= mod;
}
}
}
if(det == 0 || det == -1) printf("%lld", ans);
else printf("0");
return 0;
}
Pencils and Boxes
来源:CF985E, 2100
把所有数字从小到大排序,那么每次一定是连续的一段放到一起.
这样的话就可以从左到右进行 $\mathrm{DP}$ 了,每次枚举最靠右选几个.
然后这个可选择的数量也是一段区间,直接用前缀数组判断这个区间内有无合法状态即可.
#include <bits/stdc++.h>
#define N 500009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r", stdin)
using namespace std;
int n, K, D, a[N], sum[N], dp[N];
int S(int l, int r) {
if(l == 0) return sum[r];
else return sum[r] - sum[l - 1];
}
int main() {
// setIO("input");
scanf("%d%d%d", &n, &K, &D);
for(int i = 1; i <= n; ++ i) {
scanf("%d", &a[i]);
}
sort(a + 1, a + 1 + n);
dp[0] = sum[0] = 1;
for(int i = 1; i <= n ; ++ i) {
int R = i - K + 1, L = lower_bound(a + 1, a + 1 + n, a[i] - D) - a;
// L: 第一个大于等于的.
// 则 [L, R] 是当前段的左端点可行处.
if(L <= R) {
dp[i] = S(L - 1, R - 1) ? 1 : 0;
}
sum[i] = sum[i - 1] + dp[i];
}
printf("%s", dp[n] ? "YES" : "NO");
return 0;
}
Anton and Tree
来源:CF734E, 2100
把颜色相同的连通块缩点,然后考虑如何进行染色.
对于一跳链来说,答案就是从链的中间开始染色,每次会向两侧拓展一次.
树的情况答案肯定不会小于链的情况,也肯定不会小于直径的情况.
直接从直径的中心开始拓展即可取到答案的下界,所以在缩点的树上求一个树的直径即可.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int ans;
int col[N], n, p[N], f[N], se[N];
vector<int>G[N], V[N];
int find(int x) {
return p[x] == x ? x : p[x] = find(p[x]);
}
void init() {
for(int i = 0; i < N ; ++ i) p[i] = i;
}
void merge(int x, int y) {
x = find(x), y = find(y);
if(x == y) return ;
p[x] = y;
}
void dfs(int x, int ff) {
if(ff && col[x] != col[ff]) {
V[find(ff)].pb(find(x));
}
for(int i = 0; i < G[x].size() ; ++ i) {
if(G[x][i] != ff) dfs(G[x][i], x);
}
}
void dfs2(int x) {
f[x] = 1;
for(int i = 0; i < V[x].size() ; ++ i) {
int v = V[x][i];
dfs2(v);
ans = max(ans, f[x] + f[v]);
f[x] = max(f[x], f[v] + 1);
}
}
int main() {
// setIO("input");
init();
scanf("%d", &n);
for(int i = 1; i <= n ; ++ i) scanf("%d", &col[i]);
for(int i = 1; i < n ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
G[x].pb(y);
G[y].pb(x);
if(col[x] == col[y]) {
merge(x, y);
}
}
dfs(1, 0);
dfs2(find(1));
printf("%d\n", ans / 2);
return 0;
}
OpenStreetMap
来源:CF1195E, 2100
二维 $\mathrm{RMQ}$ 来做,不妨先对每一个列求一次,然后放到一个二维数组里.
最后再对行求一遍 $\mathrm{RMQ}$ 即可.
#include <bits/stdc++.h>
#define N 3004
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int Lg[N];
ll ans;
int n, m, x0, Y0, mod, A, B, g[N * N], mi[N][N], f[N][20];
void init() {
for(int i = 1; i <= n * m ; ++ i) {
g[i] = (ll)((ll)g[i - 1] * x0 % mod + Y0) % mod;
}
Lg[1] = 0;
for(int i = 2; i < N ; ++ i) Lg[i] = Lg[i >> 1] + 1;
}
// 空间并不够大.
int Query(int l, int r) {
int p = Lg[r - l + 1];
return min(f[l][p], f[r - (1 << p) + 1][p]);
}
void solve(int c) {
// c 为指定的列.
for(int i = 1; i <= n ; ++ i) f[i][0] = g[(i - 1) * m + c - 1];
for(int j = 1; (1 << j) <= n ; ++ j) {
for(int i = 1; i + (1 << j) - 1 <= n ; ++ i) {
f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
for(int i = 1; i + A - 1 <= n ; ++ i) {
mi[i][c] = Query(i, i + A - 1);
}
}
void calc(int r) {
// 第 r 行.
for(int i = 1; i <= m ; ++ i) f[i][0] = mi[r][i];
for(int j = 1; (1 << j) <= m ; ++ j) {
for(int i = 1; i + (1 << j) - 1 <= m ; ++ i) {
f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
for(int i = B; i <= m ; ++ i) {
// printf("%d %lld\n", Query(i - B + 1, i), ans);
ans += Query(i - B + 1, i);
}
}
int main() {
// setIO("input");
scanf("%d%d%d%d", &n, &m, &A, &B);
scanf("%d%d%d%d", &g[0], &x0, &Y0, &mod);
init();
for(int i = 1; i <= m ; ++ i) solve(i);
// 把每一列的都处理完毕.
for(int i = 1; i + A - 1 <= n ; ++ i) {
calc(i);
}
printf("%lld", ans);
return 0;
}
Mishka and Interesting sum
来源:CF703D, 2100
区间异或和就是出现次数奇数次的数的异或和.
那么偶数次就是所有不同的数异或上奇数次,求不同的数字就用离线树状数组即可.
#include <bits/stdc++.h>
#define N 1000009
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
map<int, int>lst;
int C[N], n, a[N], m ;
struct Query {
int l, r, id;
}q[N];
bool cmp(Query i, Query j) {
return i.r < j.r;
}
int lowbit(int x) {
return x & (-x);
}
void upd(int x, int v) {
while(x < N) {
C[x] ^= v;
x += lowbit(x);
}
}
int query(int x) {
int tmp = 0;
while(x > 0) {
tmp ^= C[x];
x -= lowbit(x);
}
return tmp;
}
int ans[N], pre[N];
int main() {
// setIO("input");
scanf("%d", &n);
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &a[i]);
pre[i] = pre[i - 1] ^ a[i];
}
scanf("%d", &m);
for(int i = 1; i <= m ; ++ i) {
scanf("%d%d", &q[i].l, &q[i].r);
q[i].id = i;
}
sort(q + 1, q + 1 + m, cmp);
for(int i = 1, j = 1; i <= m ; ++ i) {
while(j <= q[i].r) {
if(lst[a[j]]) {
upd(lst[a[j]], a[j]);
}
lst[a[j]] = j;
upd(j, a[j]);
++ j;
}
ans[q[i].id] = pre[q[i].r] ^ pre[q[i].l - 1] ^ query(q[i].r) ^ query(q[i].l - 1);
}
for(int i = 1; i <= m ; ++ i) {
printf("%d\n", ans[i]);
}
return 0;
}
Duff in the Army
来源:CF587C, 2200
直接上树链剖分维护每个区间内前 10 小的编号即可.
#include <bits/stdc++.h>
#define N 100009
#define ll long long
#define pb push_back
#define ls now << 1
#define rs now << 1 | 1
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
vector<int>G[N], ID[N];
int n , m, Q, dfn[N], bu[N], fa[N], dep[N], top[N], size[N], son[N], scc;
struct data {
vector<int>id;
data() { id.clear(); }
data operator+(const data b) const {
data c;
for(int i = 0; i < id.size() ; ++ i) c.id.pb(id[i]);
for(int i = 0; i < b.id.size() ; ++ i) c.id.pb(b.id[i]);
sort(c.id.begin(), c.id.end());
while(c.id.size() > 10) c.id.pop_back();
return c;
}
}s[N << 2];
void dfs1(int x, int ff) {
size[x] = 1, fa[x] = ff, dep[x] = dep[ff] + 1;
for(int i = 0; i < G[x].size() ; ++ i) {
int v = G[x][i];
if(v == ff) continue;
dfs1(v, x);
size[x] += size[v];
if(size[v] > size[son[x]]) son[x] = v;
}
}
void dfs2(int x, int tp) {
top[x] = tp;
dfn[x] = ++ scc, bu[dfn[x]] = x;
if(son[x]) dfs2(son[x],tp);
for(int i = 0; i < G[x].size() ; ++ i) {
int v = G[x][i];
if(v == fa[x] || v == son[x]) continue;
dfs2(v, v);
}
}
void update(int l, int r, int now, int p, data g) {
if(l == r) {
s[now] = s[now] + g;
return ;
}
int mid = (l + r) >> 1;
if(p <= mid) update(l, mid, ls, p, g);
else update(mid + 1, r, rs, p, g);
s[now] = s[ls] + s[rs];
}
data query(int l, int r, int now, int L, int R) {
if(l >= L && r <= R) return s[now];
int mid = (l + r) >> 1;
if(L <= mid && R > mid) return query(l, mid, ls, L, R) + query(mid + 1, r, rs, L, R);
else if(L <= mid) return query(l, mid, ls, L, R);
else return query(mid + 1, r, rs, L, R);
}
void solve(int x, int y, int z) {
data fin;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
// 每次跳 x
fin = fin + query(1, n, 1, dfn[top[x]], dfn[x]);
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
fin = fin + query(1, n, 1, dfn[x], dfn[y]);
z = min(z, (int)fin.id.size());
printf("%d ", z);
sort(fin.id.begin(), fin.id.end());
for(int i = 0; i < z; ++ i) printf("%d ", fin.id[i]);
printf("\n");
}
int main() {
//setIO("input");
scanf("%d%d%d", &n, &m, &Q);
for(int i = 1; i < n ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
G[x].pb(y);
G[y].pb(x);
}
dfs1(1, 0);
dfs2(1, 1);
for(int i = 1; i <= m ; ++ i) {
int p;
data c;
scanf("%d", &p),c.id.pb(i);
update(1, n, 1, dfn[p], c);
}
for(int i = 1; i <= Q; ++ i) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
solve(x, y, z);
}
return 0;
}
Magic Gems
来源:CF1117D, 2100
直接做用组合数不太能做,不妨考虑递推.
令 $\mathrm{f[i]}$ 表示凑满 $\mathrm{i}$ 个的方案数,然后考虑最后一个如何选择.
1. 最后一个只贡献 1
2. 最后一个贡献 $\mathrm{m}$
然后这个递推就可以用矩阵乘法来做了.
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int mod = (int)1e9 + 7;
ll n;
int m;
struct M {
int len, C[102][102];
M(int tp = 0) {
len = tp;
memset(C, 0, sizeof(C));
}
void I() {
for(int i = 1; i <= len ; ++ i)
C[i][i] = 1;
}
M operator*(const M b) const {
M c;
c.len = len;
for(int k = 1; k <= len ; ++ k) {
for(int i = 1; i <= len ; ++ i) {
for(int j = 1; j <= len ; ++ j) {
(c.C[i][j] += (ll)C[i][k] * b.C[k][j] % mod) %= mod;
}
}
}
return c;
}
};
M operator^(M a, ll b) {
M c;
c.len = a.len;
c.I();
while(b) {
if(b & 1) c = c * a;
b >>= 1, a = a * a;
}
return c;
}
int main() {
// setIO("input");
scanf("%lld%d", &n, &m);
M tp;
tp.len = m;
for(int i = 1; i < m ; ++ i)
tp.C[i + 1][i] = 1;
tp.C[1][m] = tp.C[m][m] = 1;
M ans = tp ^ n;
ll fin = 0;
for(int i = 1; i <= m ; ++ i)
fin = (ll)(fin + ans.C[i][1]) % mod;
printf("%lld", fin);
return 0;
}
Array Partition
来源:CF1454F, 2100
可以枚举前缀,然后合法的后缀是一段区间,然后二分找到最小值等于前缀的中间段即可.
#include <bits/stdc++.h>
#define N 200009
#define pb push_back
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
set<int>se[N];
int L[N], R[N];
int a[N], A[N], mi[N][20], ma[N][20], Lg[N], n;
void init() {
Lg[1] = 0;
for(int i = 2; i <= n ; ++ i) Lg[i] = Lg[i >> 1] + 1;
for(int i = 1; i <= n ; ++ i) mi[i][0] = ma[i][0] = a[i];
for(int j = 1; (1 << j) <= n ; ++ j) {
for(int i = 1; i + (1 << j) - 1 <= n ; ++ i) {
mi[i][j] = min(mi[i][j - 1], mi[i + (1 << (j - 1))][j - 1]);
ma[i][j] = max(ma[i][j - 1], ma[i + (1 << (j - 1))][j - 1]);
}
}
}
int qmin(int l, int r) {
int p = Lg[r - l + 1];
return min(mi[l][p], mi[r - (1 << p) + 1][p]);
}
int qmax(int l, int r) {
int p = Lg[r - l + 1];
return max(ma[l][p], ma[r - (1 << p) + 1][p]);
}
void solve() {
scanf("%d", &n);
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &a[i]);
A[i] = a[i];
}
sort(A + 1, A + 1 + n);
for(int i = 1; i <= n ; ++ i) {
a[i] = lower_bound(A + 1, A + 1 + n, a[i]) - A;
}
init();
for(int i = n, v = 0; i >= 1 ; -- i) {
if(a[i] > v) {
R[a[i]] = i, v = a[i];
}
L[v] = i;
}
int ok = 0;
for(int i = 1, v = 0; i <= n ; ++ i) {
v = max(v, a[i]);
// printf("%d %d\n", L[v], R[v]);
// 前缀 v 搞好了.
if(!L[v] || !R[v]) continue;
if(R[v] > i + 1) {
// [i + 1, R[v])
int l = max(i + 1, L[v] - 1), r = R[v] - 1, ans = 0;
// printf("%d %d\n", l, r);
if(l > r) continue;
while(l <= r) {
int mid = (l + r) >> 1;
if(qmin(i + 1, mid) <= v) {
ans = mid, r = mid - 1;
}
else l = mid + 1;
}
if(!ans) continue;
if(qmin(i + 1, ans) == v) {
printf("YES\n");
printf("%d %d %d\n", i, ans - (i + 1) + 1, n - ans);
ok = 1;
break;
}
}
}
if(!ok) printf("NO\n");
for(int i = 0; i <= n + 2; ++ i)
L[i] = R[i] = 0;
}
int main() {
// ssetIO("input");
int T;
scanf("%d", &T);
while(T -- ) solve();
return 0;
}

浙公网安备 33010602011771号