The 2019 ICPC China Nanchang National Invitational and International Silk-Road Programming Contest
Contest Info
[Practice Link](https://www.jisuanke.com/contest/3098?view=challenges)
Solved | A | B | C | D | E | F | G | H | I | J | K |
---|---|---|---|---|---|---|---|---|---|---|---|
9/11 | O | O | - | - | Ø | O | Ø | O | Ø | O | O |
- O 在比赛中通过
- Ø 赛后通过
- ! 尝试了但是失败了
- - 没有尝试
Solutions
A. Attack
题意:
有\(n\)个城市,\(m\)条路可以建造,每条路有边权,现在要使得四对城市之间连通(每对城市连通就行,不用四对相互连通),问建造路的最小代价,如果有共同的路,那么不会重复建造。
思路:
- 如果是四对城市互相连通,那么就是裸的斯坦纳树
- 那么考虑这个问题中不好处理的就是有重复边使得答案更优的情况,怎么避免重复计算贡献?
- 考虑如果在最优答案中有重复边,那么这条重复边所关联的几对城市放在一起做斯坦纳树,不会增加新的边
- 那么就暴力组合一下,更新答案即可。
代码:
view code
#include <bits/stdc++.h>
using namespace std;
#define N 1010
#define pii pair <int, int>
#define fi first
#define se second
int n, m;
int P[4][2];
vector <vector<pii>> G;
map <string, int> mp; int cnt;
int get(string s) {
if (mp.find(s) == mp.end()) {
return mp[s] = ++cnt;
}
return mp[s];
}
struct SteinerTree {
int st[32], dp[32][1 << 8], endSt;
bool vis[32][1 << 8];
queue <int> que;
void init(int n, vector <int> &vec) {
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
memset(dp, -1, sizeof dp);
memset(vis, 0, sizeof vis);
memset(st, 0, sizeof st);
endSt = 1;
for (auto it : vec) {
st[it] = endSt;
endSt <<= 1;
}
for (int i = 1; i <= n; ++i) {
dp[i][st[i]] = 0;
}
}
void update(int &a, int x) {
a = (a > x || a == -1) ? x : a;
}
void SPFA(int state) {
while (!que.empty()) {
int u = que.front(); que.pop();
vis[u][state] = false;
for (auto it : G[u]) {
int v = it.fi, w = it.se, y = st[v] | state;
if (dp[v][y] == -1 || dp[v][y] > dp[u][state] + w) {
dp[v][y] = dp[u][state] + w;
if (y != state || vis[v][state])
continue;
vis[v][state] = true;
que.push(v);
}
}
}
}
int solve() {
for (int j = 1; j < endSt; ++j) {
for (int i = 1; i <= n; ++i) {
if (st[i] && (st[i] & j) == 0) continue;
for (int sub = (j - 1) & j; sub; sub = (sub - 1) & j) {
int x = st[i] | sub, y = st[i] | (j - sub);
if (dp[i][x] != -1 && dp[i][y] != -1) {
update(dp[i][j], dp[i][x] + dp[i][y]);
}
}
if (dp[i][j] != -1) {
que.push(i), vis[i][j] = true;
}
}
SPFA(j);
}
int res = 1e9;
for (int i = 1; i <= n; ++i) if (dp[i][endSt - 1] != -1) {
res = min(res, dp[i][endSt - 1]);
}
return res;
}
}ST;
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while (cin >> n >> m) {
cnt = 0; mp.clear();
G.clear(); G.resize(n + 1);
for (int i = 1; i <= n; ++i) {
string s; cin >> s;
get(s);
}
for (int i = 1, u, v, w; i <= m; ++i) {
string s;
cin >> s; u = get(s);
cin >> s; v = get(s);
cin >> w;
G[u].push_back(pii(v, w));
G[v].push_back(pii(u, w));
}
vector <int> vec;
for (int i = 0; i < 4; ++i) {
string s;
cin >> s; P[i][0] = get(s);
cin >> s; P[i][1] = get(s);
}
int res = 1e9, tmp;
//1 + 1 + 1 + 1
tmp = 0;
for (int i = 0; i < 4; ++i) {
vec.clear();
vec.push_back(P[i][0]);
vec.push_back(P[i][1]);
ST.init(n, vec); tmp += ST.solve();
}
res = min(res, tmp);
// 3 + 1
for (int i = 0; i < 4; ++i) {
tmp = 0;
vec.clear();
vec.push_back(P[i][0]);
vec.push_back(P[i][1]);
ST.init(n, vec); tmp += ST.solve();
vec.clear();
for (int j = 0; j < 4; ++j) {
if (i == j) continue;
vec.push_back(P[j][0]);
vec.push_back(P[j][1]);
}
ST.init(n, vec); tmp += ST.solve();
res = min(res, tmp);
}
// 2 + 2
for (int i = 0; i < 4; ++i) {
for (int j = i + 1; j < 4; ++j) {
tmp = 0;
vec.clear();
vec.push_back(P[i][0]);
vec.push_back(P[i][1]);
vec.push_back(P[j][0]);
vec.push_back(P[j][1]);
ST.init(n, vec); tmp += ST.solve();
vec.clear();
for (int k = 0; k < 4; ++k) if (k != i && k != j) {
vec.push_back(P[k][0]);
vec.push_back(P[k][1]);
}
ST.init(n, vec); tmp += ST.solve();
res = min(res, tmp);
}
}
// 2 + 1 + 1
for (int i = 0; i < 4; ++i) {
for (int j = i + 1; j < 4; ++j) {
tmp = 0;
vec.clear();
vec.push_back(P[i][0]);
vec.push_back(P[i][1]);
vec.push_back(P[j][0]);
vec.push_back(P[j][1]);
ST.init(n, vec); tmp += ST.solve();
for (int k = 0; k < 4; ++k) if (k != i && k != j) {
vec.clear();
vec.push_back(P[k][0]);
vec.push_back(P[k][1]);
ST.init(n, vec); tmp += ST.solve();
}
res = min(res, tmp);
}
}
// 4
vec.clear();
for (int i = 0; i < 4; ++i) {
vec.push_back(P[i][0]);
vec.push_back(P[i][1]);
}
ST.init(n, vec);
res = min(res, ST.solve());
cout << res << "\n";
}
return 0;
}
B. Polynomial
题意:
给出一个多项式的第\(0\)项到第\(n\)项,询问:
思路:
- 考虑一个\(n\)次多项式的前缀和是一个\(n + 1\)次的多项式。
- 插值出第\(n + 1\)项,就可以再插值出前缀和了
代码:
view code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 1100
const ll p = 9999991;
int n, m;
ll f[N], fac[N], inv[N];
ll Inv[10000010];
ll qmod(ll base, ll n) {
ll res = 1;
while (n) {
if (n & 1) {
res = res * base % p;
}
base = base * base % p;
n >>= 1;
}
return res;
}
ll solve(ll *f, int n, int x) {
if (x <= n) return f[x];
int t = (n & 1) ? -1 : 1;
ll res = 0;
ll base = 1;
for (int i = 0; i <= n; ++i) base = base * (x - i) % p;
for (int i = 0; i <= n; ++i, t *= -1) {
res += 1ll * t * f[i] * base % p * inv[n - i] % p * inv[i] % p * Inv[x - i] % p;
res = (res + p) % p;
}
return res;
}
int main() {
fac[0] = 1;
for (int i = 1; i < N; ++i) fac[i] = fac[i - 1] * i % p;
inv[N - 1] = qmod(fac[N - 1], p - 2);
for (int i = N - 1; i >= 1; --i) inv[i - 1] = inv[i] * i % p;
Inv[1] = 1;
for (int i = 2; i < p; ++i) Inv[i] = Inv[p % i] * (p - p / i) % p;
int T; scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
for (int i = 0; i <= n; ++i) scanf("%lld", f + i);
f[n + 1] = solve(f, n, n + 1);
for (int i = 1; i <= n + 1; ++i) f[i] = (f[i] + f[i - 1]) % p;
int l, r;
while (m--) {
scanf("%d%d", &l, &r);
printf("%lld\n", (solve(f, n + 1, r) - solve(f, n + 1, l - 1) + p) % p);
}
}
return 0;
}
E. Interesting Trip
题意:
询问有多少条长度为\(D\)的路径中,满足这条路径上所有点的点权的\(gcd > 1\)。
思路:
我们先统计出所有长度为\(D\)的路径,然后减去点权的\(gcd = 1\)的路径即为答案。
怎么统计点权\(gcd = 1\)的路径?
我们可以考虑莫比乌斯反演,统计\(gcd \;|\; d\)的路径,即将所有点权为\(d\)的倍数的点构成的森林中长度为\(D\)的路径条数。
然后就是经典的容斥,容斥系数是莫比乌斯函数。
考虑到点权在\(10^4\)以下的数的不含有平方因子的因数最多有\(32\)个,也就是说每个点最多会被放到\(32\)个森林中,那么所有森林的总点数大概是\(O(32n)\)的。
那么考虑求定长路径条数可以用基于深度的\(dp\)解决,那么可以做到\(O(n)\)的合并。
所以总复杂度为\(O(32n)\)
代码:
view code
#include <bits/stdc++.h>
using namespace std;
namespace IO {
const int S=(1<<20)+5;
//Input Correlation
char buf[S],*H,*T;
inline char Get() {
if(H==T) T=(H=buf)+fread(buf,1,S,stdin);
if(H==T) return -1;return *H++;
}
inline int read() {
int x=0,fg=1;char c=Get();
while(!isdigit(c)&&c!='-') c=Get();
if(c=='-') fg=-1,c=Get();
while(isdigit(c)) x=x*10+c-'0',c=Get();
return x*fg;
}
}using namespace IO;
typedef long long ll;
const int N = 5e5 + 10;
const int M = 3e4 + 10;
int n, D, a[N], pri[M], check[M], mu[M], fa[N], deep[N], vis[N], md[N], hson[N]; ll res, F[N];
vector<vector<int>> dvec, fac;
struct Edge {int v, nx;}e[N << 1]; int h[N];
inline void addedge(int u, int v) { e[++*h] = {v, h[u]}; h[u] = *h; }
void sieve() {
fac.clear(); fac.resize(M);
for (int i = 1; i < M; ++i)
for (int j = i; j < M; j += i)
fac[j].push_back(i);
*pri = 0;
mu[1] = 1;
for (int i = 2; i < M; ++i) {
if (!check[i]) {
pri[++*pri] = i;
mu[i] = -1;
}
for (int j = 1; j <= *pri; ++j) {
if (i * pri[j] >= M) break;
check[i * pri[j]] = 1;
if (i % pri[j] == 0) break;
else mu[i * pri[j]] = -mu[i];
}
}
}
void pre(int u) {
for (int i = h[u]; i; i = e[i].nx) {
int v = e[i].v;
if (v == fa[u]) continue;
deep[v] = deep[u] + 1;
fa[v] = u;
pre(v);
}
for (auto &it : fac[a[u]])
dvec[it].push_back(u);
}
int tmp[N << 2], *f[N], *id = tmp;
void getdeep(int u) {
md[u] = deep[u];
hson[u] = 0;
for (int i = h[u]; i; i = e[i].nx) {
int v = e[i].v;
deep[v] = deep[u] + 1;
getdeep(v);
if (!hson[u] || md[v] > md[hson[u]]) hson[u] = v;
}
md[u] = md[hson[u]] + 1;
}
void dfs(int u) {
if (hson[u]) {
int v = hson[u];
f[v] = f[u] + 1;
dfs(v);
}
f[u][0] = 1;
if (md[u] > D) {
res += f[u][D];
}
for (int i = h[u]; i; i = e[i].nx) {
int v = e[i].v;
if (v == hson[u]) continue;
f[v] = id; id += md[v] + 1;
dfs(v);
for (int i = 0; i < md[v]; ++i) {
if (md[u] > D - i - 1 && D - i - 1 >= 0) {
res += 1ll * f[u][D - i - 1] * f[v][i];
}
}
for (int i = 0; i < md[v]; ++i)
f[u][i + 1] += f[v][i];
}
}
inline void gao(vector <int> &vec, int x) {
res = 0; h[0] = 0;
for (auto &u : vec) h[u] = 0, vis[u] = x;
for (auto &u : vec) {
if (vis[fa[u]] == x) {
addedge(fa[u], u);
} else {
deep[u] = 0;
getdeep(u);
id = tmp;
f[u] = id;
id += md[u] + 1;
dfs(u);
}
}
}
int main() {
sieve();
int _T; _T = read();
for (int kase = 1; kase <= _T; ++kase) {
printf("Case #%d: ", kase);
n = read(); D = read();
dvec.clear(); dvec.resize(M);
memset(vis, 0, sizeof vis);
memset(h, 0, sizeof h);
for (int i = 1; i <= n; ++i) a[i] = read();
for (int i = 1, u, v; i < n; ++i) {
u = read(); v = read();
addedge(u, v);
addedge(v, u);
}
fa[1] = 0; deep[1] = 0;
pre(1);
for (int i = 1; i <= 30000; ++i) if (mu[i]) {
gao(dvec[i], i);
F[i] = res;
}
res = F[1];
for (int i = 1; i <= 30000; ++i) if (mu[i])
res -= 1ll * mu[i] * F[i];
printf("%lld\n", res * 2);
}
return 0;
}
F. Sequence
题意:
定义:
给出一个序列\(a_i\),要求支持两种操作:
- 将\(a_x\)修改成\(y\)
- 询问\(F(l, r)\)
思路:
我们发现直接去计算这个式子比较不好计算,但是注意到异或的一个性质就是一个数异或偶数次就没了,异或奇数次就是本身。
那么我们不妨去考虑一个\(F(l, r)\)这个过程最终\(a_l, \cdots, a_r\)这每个数最终异或了多少次。
打表发现如下规律:
- \(r - l + 1\)为偶数那么每个数异或次数都是偶数
- 否则,\(l, l + 2, l + 4, \cdots\)这些位置上的数异或次数是奇数,其它位置上的数异或次数为偶数
那么只需要维护两个序列,一个是奇数位置上的前缀和,一个是偶数位置上的前缀和,就可以做了。
代码:
view code
#include <bits/stdc++.h>
using namespace std;
#define N 100010
int n, q, a[N];
struct SEG {
int t[N << 2];
void init() {
memset(t, 0, sizeof t);
}
void update(int id, int l, int r, int pos, int x) {
if (l == r) {
t[id] = x;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid) update(id << 1, l, mid, pos, x);
else update(id << 1 | 1, mid + 1, r, pos, x);
t[id] = t[id << 1] ^ t[id << 1 | 1];
}
int query(int id, int l, int r, int ql, int qr) {
if (l >= ql && r <= qr) {
return t[id];
}
int mid = (l + r) >> 1;
int x = 0;
if (ql <= mid) x ^= query(id << 1, l, mid, ql, qr);
if (qr > mid) x ^= query(id << 1 | 1, mid + 1, r, ql, qr);
return x;
}
}seg[2];
int main() {
int T; scanf("%d", &T);
for (int kase = 1; kase <= T; ++kase) {
printf("Case #%d:\n", kase);
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
seg[0].init(); seg[1].init();
for (int i = 1; i <= n; ++i) {
seg[i % 2].update(1, 1, n, i, a[i]);
}
int op, x, y;
while (q--) {
scanf("%d%d%d", &op, &x, &y);
switch(op) {
case 0 :
seg[x % 2].update(1, 1, n, x, y);
break;
case 1 :
if ((y - x + 1) % 2 == 0) puts("0");
else {
printf("%d\n", seg[x % 2].query(1, 1, n, x, y));
}
break;
}
}
}
return 0;
}
G. Winner
题意:
有一种游戏,三个模式,每个玩家在每个模式中都有一个力量值。现在要进行\(n - 1\)轮游戏,每一轮要挑出两个未被淘汰的人选择一种模式进行决斗,
在那个模式中,力量值小的人会被淘汰,直到最后剩下一个人。
数据保证每种模式中不同玩家的力量值不同。
现在上帝可以选择每一轮游戏参与决斗的两人(未被淘汰的),以及游戏模式,现在询问在上帝的操作下,第\(x\)个人是否可以成为最后留下来的人?
思路:
我们考虑先排个序,然后用\((a, b, c)\)表示要成为最后留下来的人的三种模式的力量的最小值,如果有人有某种大于等于这个三元组中对应的那个,那么就可以用它的其他两个属性去更新这个三元组。
按从大到小的顺序去更新即可。
代码:
view code
#include <bits/stdc++.h>
using namespace std;
#define N 100010
#define pii pair <int, int>
#define fi first
#define se second
int n, q;
struct node {
int a, b, c;
node() {}
node(int a, int b, int c) : a(a), b(b), c(c) {}
bool operator <= (const node &other) const {
return a <= other.a || b <= other.b || c <= other.c;
}
}p[N];
pii a[N], b[N], c[N];
void Min(node &x, node y) {
x.a = min(x.a, y.a);
x.b = min(x.b, y.b);
x.c = min(x.c, y.c);
}
int main() {
while (scanf("%d%d", &n, &q) != EOF) {
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i].fi);
a[i].se = i;
p[i].a = a[i].fi;
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &b[i].fi);
b[i].se = i;
p[i].b = b[i].fi;
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &c[i].fi);
c[i].se = i;
p[i].c = c[i].fi;
}
sort(a + 1, a + 1 + n);
sort(b + 1, b + 1 + n);
sort(c + 1, c + 1 + n);
node T = node(1e9, 1e9, 1e9);
Min(T, p[a[n].se]);
Min(T, p[b[n].se]);
Min(T, p[c[n].se]);
for (int i = n - 1; i >= 1; --i) {
int pos = a[i].se;
if (T <= p[pos]) {
Min(T, p[pos]);
}
pos = b[i].se;
if (T <= p[pos]) {
Min(T, p[pos]);
}
pos = c[i].se;
if (T <= p[pos]) {
Min(T, p[pos]);
}
}
int x;
while (q--) {
scanf("%d", &x);
puts(T <= p[x] ? "YES" : "NO");
}
}
return 0;
}
H. Another Sequence
题意:
给出一个长度为\(n\)的序列\(a_i\)和另一个长度为\(n\)的序列\(b_i\),现在\(c_i\)的生成方式如下:
- \(c_i\)的长度为\(n * n\)
- \(c_{i * (n - 1) + j} = a_i | b_j\)
- 将\(c_i\)升序排序
现在要求支持两种操作: - 将\(c_i\)中\([l, r]\)区间内的数开根
- 询问\(c_x\)的数值。
思路:
- 对于\(c_i\)数组,我们肯定不能直接生成它,但是注意到\(1 \leq a_i, b_i \leq 10^5\),那么\(c_i\)中数的种类不会超过\(2 \cdot 10^5\),那么我们可以用\(FWT_or\)处理出每个数的个数
- 那么对于询问,我们只需要处理出\(x\)位置上的数被开根了多少次,然后找到原本\(x\)位置上的数是什么即可
- 我们考虑\(x\)位置上的数被开根了多少次?即在它前面出现的操作\(1\)中,\(l \leq x\)的区间个数减去\(r < x\)的区间个数
- 考虑怎么找到\(x\)位置上的数是什么,维护一个前缀和,表示前\(i\)个数的出现次数,然后直接二分。
代码:
view code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 200010
int n, q, a[N], b[N];
ll c[N], d[N];
ll H[N << 1]; int tot;
int res[N];
vector <vector<ll>> vec;
struct node {
ll x, y;
node() {}
node (ll x, ll y) : x(x), y(y) {}
}qrr[N];
void FWT(ll *a, int len, int mode) {
for (int i = 1; i < len; i <<= 1) {
for (int p = i << 1, j = 0; j < len; j += p) {
for (int k = 0; k < i; ++k) {
if (mode == 1) a[i + j + k] = (a[j + k] + a[i + j + k]);
else a[i + j + k] = (a[i + j + k] - a[j + k]);
}
}
}
}
struct BIT {
int a[N];
void init() {
memset(a, 0, sizeof a);
}
void update(int x, int v) {
for (; x < N; x += x & -x) {
a[x] += v;
}
}
int query(int x) {
int res = 0;
for (; x > 0; x -= x & -x) {
res += a[x];
}
return res;
}
}bit[2];
int id(ll x) {
return lower_bound(H + 1, H + 1 + tot, x) - H;
}
int main() {
int len = 1;
while (len < 100000) len <<= 1;
vec.clear(); vec.resize(len + 1);
vec[0].push_back(0); vec[1].push_back(1);
for (int i = 2; i <= len; ++i) {
int it = i;
vec[i].push_back(it);
while (it) {
it = floor(sqrt(it));
vec[i].push_back(it);
if (it == 1) break;
}
}
while (scanf("%d", &n) != EOF) {
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
for (int i = 1; i <= n; ++i) scanf("%d", b + i);
memset(c, 0, sizeof c);
memset(d, 0, sizeof d);
for (int i = 1; i <= n; ++i) ++c[a[i]], ++d[b[i]];
FWT(c, len, 1);
FWT(d, len, 1);
for (int i = 0; i < len; ++i) c[i] = c[i] * d[i];
FWT(c, len, -1);
for (int i = 0; i < len; ++i) c[i] += c[i - 1];
scanf("%d", &q);
tot = 0;
ll x, y;
for (int i = 1; i <= q; ++i) {
scanf("%lld%lld", &x, &y);
H[++tot] = x;
H[++tot] = y;
qrr[i] = node(x, y);
res[i] = -1;
}
sort(H + 1, H + 1 + tot);
tot = unique(H + 1, H + 1 + tot) - H - 1;
bit[0].init(); bit[1].init();
for (int i = 1; i <= q; ++i) {
if (qrr[i].x == 0) {
int del = bit[0].query(id(qrr[i].y)) - bit[1].query(id(qrr[i].y) - 1);
int num = lower_bound(c + 1, c + len, qrr[i].y) - c;
del = min(del, (int)vec[num].size() - 1);
res[i] = vec[num][del];
} else {
bit[0].update(id(qrr[i].x), 1);
bit[1].update(id(qrr[i].y), 1);
}
}
for (int i = 1; i <= q; ++i) if (res[i] != -1) printf("%d\n", res[i]);
}
return 0;
}
I. Hamster Sort
题意:
给出一个排列\(p_i\), 给定一种排序操作:
- 选择一个\(k\),令\(p_k = 1\),\(T = 1\)
- 然后遍历序列\(a_i\),寻找\(p_k\)
- 如果找到了,就令\(p_k = p_k + k\),遍历指针往后挪一个位置,然后回到步骤\(2\)
- 如果没有找到,就令\(T = T + 1\),然后遍历指针指向序列的开头,回到步骤\(2\)
现在要支出两种操作:
- 交换\(p_x\)和\(p_y\)
- 给出\(k\),询问上述排序操作的\(T\)是多少
思路:
- 考虑\(k > \sqrt{n}\)的时候,我们可以直接暴力跳,不会跳超过\(\sqrt{n}\)次
- 考虑\(k \leq \sqrt{n}\)的时候,\(k\)的取值只有\(\sqrt{n}\)种,可以直接预处理答案,交换的时候注意一下\(a_x\)和\(a_y\)都是同一个\(k\)的情况的贡献不要多加或者多减
- 预处理答案的时候把\(1\)的贡献剔除出来,最后再加上即可
代码:
view code
#include <bits/stdc++.h>
using namespace std;
#define N 200010
int n, q, S;
int a[N], id[N];
int f[N];
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d", &n); S = min(n - 1, 200);
for (int i = 1; i <= n; ++i) scanf("%d", a + i), id[a[i]] = i;
memset(f, 0, sizeof f);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= S; ++j) {
if (a[i] > 1 && (a[i] - 1) % j == 0) {
if (id[a[i] - j] > i) {
++f[j];
}
}
}
}
scanf("%d", &q);
int op, x, y, k;
while (q--) {
scanf("%d", &op);
if (op == 1) {
scanf("%d%d", &x, &y);
if (x == y) continue;
if (x > y) swap(x, y);
for (int i = 1; i <= S; ++i) {
if ((a[x] - 1) % i == 0) {
int pre = a[x] - i;
if (pre >= 1 && id[pre] <= y && id[pre] > x) {
--f[i];
}
int nx = a[x] + i;
if (nx <= n && id[nx] <= y && id[nx] > x) {
++f[i];
}
}
}
for (int i = 1; i <= S; ++i) {
if ((a[y] - 1) % i == 0) {
int pre = a[y] - i;
if (pre >= 1 && id[pre] > x && id[pre] < y) {
++f[i];
}
int nx = a[y] + i;
if (nx <= n && id[nx] > x && id[nx] < y) {
--f[i];
}
}
}
swap(id[a[x]], id[a[y]]);
swap(a[x], a[y]);
} else {
scanf("%d", &k);
if (k <= S) {
printf("%d\n", f[k] + 1);
} else {
int res = 1;
int it = 1;
while (it + k <= n) {
if (id[it + k] < id[it]) ++res;
it += k;
}
printf("%d\n", res);
}
}
}
}
return 0;
}
J. Prefix
题意:
给出\(n\)个字符串\(s_i\):
- 维护一个字符串集合,把这个字符串加进去。
- 再将所有字符串替换成他们的\(|s_i|\)个前缀。
定义一个字符串的困难度为:
现在询问每个原字符串在那个字符串集合中,有多少字符串是它的前缀,并且困难度比它高?
思路:
先将所有字符串\(Hash\),存入\(map\)。
然后遍历每个字符串的前缀,如果前缀的困难度比它本身高,那么就统计有字符串集合中有多少个这样的前缀。
代码:
view code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100010
#define ull unsigned long long
int n, m, d[30], a[N];
string s[N];
map<ull, int> mp;
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while (cin >> n >> m) {
mp.clear();
for (int i = 0; i < 26; ++i) cin >> d[i];
for (int i = 1; i <= n; ++i) cin >> s[i];
for (int i = 1; i <= n; ++i) {
ull Hash = 0;
ull base = 31;
a[i] = 1;
for (auto it : s[i]) {
Hash += base * it;
++mp[Hash];
base *= 31;
a[i] = 1ll * a[i] * d[it - 'a'] % m;
}
}
for (int i = 1; i <= n; ++i) {
ull Hash = 0;
ull base = 31;
int tmp = 1;
ll res = 0;
for (int j = 0, len = s[i].size(); j < len - 1; ++j) {
Hash += base * s[i][j];
base *= 31;
tmp = 1ll * tmp * d[s[i][j] - 'a'] % m;
if (tmp > a[i]) res += mp[Hash];
}
printf("%lld%c", res, " \n"[i == n]);
}
}
return 0;
}
K. A Good Game
签到题。
代码:
view code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100010
int n, m;
ll v[N];
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%lld", v + i);
v[i] += v[i - 1];
}
ll res = 0;
vector <ll> vec;
for (int i = 1, l, r; i <= m; ++i) {
scanf("%d%d", &l, &r);
vec.push_back(v[r] - v[l - 1]);
}
sort(vec.begin(), vec.end());
for (int i = 0; i < m; ++i) {
res += 1ll * (i + 1) * vec[i];
}
printf("%lld\n", res);
}
return 0;
}