多校A层冲刺NOIP2024模拟赛25
非常好的模拟赛,使我的大脑旋转。
终于不垫底了。
T1
有一张 \(n\) 个点的无向图,初始没有边。
给定 \(m\) 次操作,每次操作给出集合 \(S\) 和 \(T\) ,并将满足以下条件的边 \((x,y)\) 存在状态取反:
- \(1\le x<y\le n\)
- \(x\in S,y\in T\) 或 \(x\in T,y\in S\)
求 \(m\) 次操作后图中的边数。
\(n \le 10000,m\le 64\)
出题人给了个什么 __builtin_popcount
的提示,感觉没用,好像还误导我了。不知道为啥直觉上觉得 bool
开一亿会爆,发现空间很小就直接切了。
做法就是对于每个点维护周围的直接连边,bitset
模拟即可。
复杂度 \(O(\frac{nm}{w})\)。
#include <bits/stdc++.h>
using namespace std;
using ubt = long long;
#define vec vector
#define eb emplace_back
#define bg begin
#define mkp make_pair
#define fi first
#define se second
const int inf = 1e9;
const int maxN = 1e4 + 3;
int n, m;
bitset<maxN> b[maxN];
vec<int> S, T, U;
bitset<maxN> s, t, u;
int main() {
ifstream cin("a.in");
ofstream cout("a.out");
cin >> n >> m;
for (int p = 1; p <= m; p++) {
S.clear(), T.clear(), U.clear();
s.reset(), t.reset(), u.reset();
for (int i = 0; i < n; i++) {
char c;
cin >> c;
if (c == '0') continue;
if (c == '1') S.eb(i), s.set(i);
if (c == '2') T.eb(i), t.set(i);
if (c == '3') U.eb(i), u.set(i);
}
for (int i : S)
b[i] ^= t ^ u;
for (int i : T)
b[i] ^= s ^ u;
for (int i : U)
b[i] ^= u ^ s ^ t;
// cerr << "T: " << t << '\n';
// cerr << "S: " << s << '\n';
// for (int i = 0; i < n; i++)
// cerr << i + 1 << ": " << b[i] << '\n';
}
int ans = 0;
for (int i = 0; i < n; i++)
b[i].reset(i), ans += b[i].count();
cout << ans / 2 << '\n';
}
昨天刚做皇后游戏,今天直接考贪心了,感觉很厉害。赛时过了。
给定长为 \(n\) 的序列 \(a_1,a_2,\dots,a_n\) 和 \(m\) 次操作,每次操作有参数 \((t,x,y)\):
- \(t=1\) 表示修改 \(a_x^′=y\)
- \(t=2\) 表示修改 \(a_x^′=a_x+y\)
- \(t=3\) 表示修改 \(a_x^′=a_x\times y\)
从中选择至多 \(k\) 个不同的操作并以任意顺序执行,最大化最终的 \(\prod _{i=1}^n a_i\),答案对 \(1e9+7\) 取模。
发现所有操作都应该是赋值在前,加法随后,乘法最后。赋值只有最大的有用,可以把赋值看成加上 \(y-a_i\),因为加法具有交换律,所以正确。
现在只有加法和乘法了。因为求的是 \(\prod\limits_i (a_i+\sum\limits_j x_j)\times \prod\limits_j y_j\),\(x\) 为第 \(i\) 个数的加法操作,\(y\) 是乘法操作。所以所有的乘法操作都是在外面的,乘法具有交换律,所以同一个数、不同数乘法和乘法之间的优先级都是大的优先。就可以把乘法拿出来单独考虑了。
对于同一个数的加法操作显然是大的先。
问题现在在于不同数的加法操作优先级,还有加法和乘法操作之间的优先级。
因为你刚做完皇后游戏,所以考虑类似临项交换的方法。
\(i\) 的一个加法,比 \(j\) 的优:
\(i\) 的一个加法,比乘法优:
把所有加法操作扔到优先队列里,每次看最优的加法操作比不比最劣的乘法操作优,贪心的选就行了。
PS:被 hack 了,不过不是大问题,实现不太精细,有个地方爆 long long
了。
PS:阿巴阿巴阿巴,卡了个逆元,感觉没意义,就不改了。 直接改完了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using ubt = long long;
#define vec vector
#define eb emplace_back
#define bg begin
#define mkp make_pair
#define fi first
#define se second
const int inf = 1e9;
const int maxN = 1e5 + 7, mod = 1e9 + 7;
int ksm(int a, int b = mod - 2) {
a %= mod;
int r = 1;
while (b) {
(b & 1) && (r = r * a % mod);
a = a * a % mod;
b >>= 1;
}
return r;
}
int n, m;
struct node {
int v;
vec<int> add;
int co;
friend bool operator < (const node &A, const node &B) {
auto a = A.add.back(), b = B.add.back();
return 1. * a * B.v < 1. * b * A.v;
}
} a[maxN];
int mul[maxN], kp[maxN], ml;
priority_queue<node> Q;
signed main() {
ifstream cin("b.in");
ofstream cout("b.out");
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i].v;
for (int i = 1; i <= m; i++) {
int t, x, y;
cin >> t >> x >> y;
if (t == 1)
if (y > a[x].co)
a[x].co = y;
if (t == 2)
a[x].add.eb(y);
if (t == 3)
mul[++ml] = y;
}
sort(mul + 1, mul + ml + 1, greater<int> ());
mul[0] = 1;
for (int i = 0; i <= ml; i++) kp[i] = mul[i];
for (int i = 1; i <= ml; i++)
mul[i] = mul[i - 1] * mul[i] % mod;
for (int i = 1; i <= n; i++) {
a[i].add.eb(-1);
if (a[i].co > a[i].v)
a[i].add.eb(a[i].co - a[i].v);
sort(a[i].add.bg(), a[i].add.end());
}
for (int i = 1; i <= n; i++)
Q.emplace(a[i]);
int cnt = 0;
int ans = 1;
auto calc = [&](int p, int tot) {
int num = tot - p;
if (num > ml) num = ml;
if (num < 0) num = 0;
return mul[num];
};
int CNT = 0;
for (int i = 1; i <= n; i++)
(ans *= a[i].v) %= mod;
for (int k = 0; k <= m; k++) {
while (cnt < k) {
auto t = Q.top();
auto ad = t.add.back();
if (ad == -1) break;
if (cnt + 1 + ml <= k || t.v * (kp[k - cnt] - 1) < ad) {
Q.pop();
cnt++;
if (t.v % mod == 0) CNT--;
else ans = ans * ksm(t.v) % mod;
if ((t.v + ad) % mod == 0) CNT++;
else ans = ans * (t.v + ad) % mod;
t.v += ad;
t.add.pop_back();
Q.emplace(t);
} else break;
}
if (CNT) {
cout << "0 ";
continue;
}
auto res = ans * calc(cnt, k) % mod;
cout << res << ' ';
}
cout << '\n';
}
给定长为 \(n\) 的字符串 \(s\)(下标从 \(1\) 开始),其仅包含前 \(k\) 种小写字母,共 \(m\) 种操作,为以下两种之一:
\(∀i\in [l,r]\) 修改 \(s_i\) 为其后 \(c\) 个字母(这里认为第 \(k\) 个小写字母的后一个字母为 \(a\))
对于前 \(k\) 种小写字母的一个排列 \(t\),在 \(s[l,r]\) 中插入若干字符使得其为若干个 \(t\) 拼接的结果
求最小的 \(\frac{∣s^′∣}{k}\)(\(s^′\) 为插入字符后的字符串),注意并不真的在 \(s\) 中插入。
赛后看到线段树就瞬间明白了。
试了一下各种引用,拿了最优解。
感觉挺智慧。将每个字符按 \(t\) 中位置编号,即求其极长递增段的个数,也即相邻两个字符逆序的个数。
#include <bits/stdc++.h>
using namespace std;
#define vec vector
using Ar = array<array<int, 10>, 10>;
const int maxN = 2e5 + 7;
int n, m, k;
string s;
#define id(x) \
((x) >= k ? (x) - k : (x))
int tg[maxN * 2];
struct dat {
int l, r;
Ar f;
friend dat operator + (const dat &A, const dat &B) {
dat res;
res.l = A.l, res.r = B.r;
for (int i = 0; i < k; i++)
for (int j = 0; j < k; j++)
res.f[i][j] = A.f[i][j] + B.f[i][j];
res.f[A.r][B.l]++;
return res;
}
void operator += (const int &T) {
Ar tmp;
for (int i = 0; i < k; i++)
for (int j = 0; j < k; j++)
tmp[id(i + T)][id(j + T)] = f[i][j];
f = tmp;
l = id(l + T), r = id(r + T);
}
} t[maxN * 2];
#define ls (mid << 1)
#define rs (mid << 1 | 1)
void build(int l, int r, int p) {
if (l == r) {
t[p].l = t[p].r = s[l] - 'a';
return;
}
int mid = (l + r) >> 1;
build(l, mid, ls), build(mid + 1, r, rs);
t[p] = t[ls] + t[rs];
}
inline void make(const int &p, int &lz) {
t[p] += lz;
tg[p] = id(tg[p] + lz);
}
inline void down(int &p, int &mid) {
if (!tg[p]) return;
make(ls, tg[p]);
make(rs, tg[p]);
tg[p] = 0;
}
void change(int &L, int &R, int &v, int p, int l, int r) {
if (L <= l && r <= R) return make(p, v);
int mid = (l + r) >> 1;
down(p, mid);
if (L <= mid)
change(L, R, v, ls, l, mid);
if (R > mid)
change(L, R, v, rs, mid + 1, r);
t[p] = t[ls] + t[rs];
}
dat ask(int &L, int &R, int p, int l, int r) {
if (L <= l && r <= R) return t[p];
int mid = (l + r) >> 1;
down(p, mid);
if (R <= mid)
return ask(L, R, ls, l, mid);
if (L > mid)
return ask(L, R, rs, mid + 1, r);
return ask(L, R, ls, l, mid) + ask(L, R, rs, mid + 1, r);
}
int main() {
ifstream cin("d.in");
ofstream cout("d.out");
cin >> n >> m >> k;
cin >> s;
s = '!' + s;
build(1, n, 1);
while (m--) {
int op, l, r;
cin >> op >> l >> r;
if (op == 1) {
int c;
cin >> c;
change(l, r, c, 1, 1, n);
}
if (op == 2) {
string g;
cin >> g;
auto &&res = ask(l, r, 1, 1, n);
int ans = 1;
for (int i = 0; i < k; i++)
for (int j = i, x = g[i] - 'a'; j < k; j++) {
int y = g[j] - 'a';
ans += res.f[y][x];
}
cout << ans << '\n';
}
}
}
快乐打球,肘击颜旭。
over.