QOJ1813 排列的乐趣
原题链接:排列的乐趣
分析
我初步的思路是在两端询问 2,中间依次询问 1,然后差分约束建图,尝试跑出来一个唯一的序列。
但是,如何确定一个唯一确定的序列呢?
昨天晚上想了想,我可能会一个 n l o g n nlogn nlogn 次询问 1 的,就是,从中间选一个数,进行询问,把大于的扔到一边,小于的扔到一边,然后每一边分别递归下去......别急......我思考一下......如何从中位数知道剩下两个分别的大小呢?
抓住了什么......
诶,从某些角度而言,我通过 1 操作无法确定的只有最大值和最小值,那么,这个 2 是不是就是为此准备的呢?那么,我需要使用 n + 2 n+2 n+2 次 1 询问和 2 2 2 次 2 询问得知最大值和最小值的位置,然后,再使用 n − 2 n-2 n−2 次操作把每个非最大值、最小值的位置询问出来,就做完了。这个,由于这个是中位数,是排序的,所以说,我们根本没必要去思考最大值有多个,最小值有多个的情况。也不可能有多个,这个是一个排列......
那么,我们先进性 n − 2 n-2 n−2 次询问,然后依次每连续 3 3 3 个询问一次。然后,我们得出中位数的最大值和最小值,则最大值和最小值一定在这两个区间内。对吗?应该有满足这个的吧......然后,我们可以询问两端的大小关系,得到最大值和最小值的确切位置,然后,我们对于整个序列跑一遍 1 操作......就做完了?好像真的是对的......
这个是错误的,对于排列:
cpp
2 3 4 1 9 5 6 7 8
就可以轻松卡掉......但是,题目真的保证了这个东西唯一确定吗?
天啊,这个是真的......有点厉害了......就是说,我人为的选择哨兵,然后,不断进行更新,似乎就可以得到了!
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 60005;
int n;
int ans[N];
bool vis[N];
int query1(int i, int j, int k){
int res;
cout << "? 1 " << i << " " << j << " " << k << endl;
cin >> res;
return res;
}
int query2(int i, int j){
int res;
cout << "? 2 " << i << " " << j << endl;
cin >> res;
return res;
}
void answer(){
cout << "! ";
for (int i = 1; i <= n; i++){
cout << ans[i] << " ";
}
cout << endl;
}
signed main(){
cin >> n;
int tmp1 = query1(1, 2, 3);
int tmp2 = query1(1, 2, 4);
int tmp3 = query1(1, 3, 4);
int mn_pos, mmn_pos, mmx_pos, mx_pos;
if (tmp1 == tmp2){
if (tmp1 < tmp3){
mn_pos = 1;
mmn_pos = 2;
mmx_pos = 3;
mx_pos = 4;
}
else{
mn_pos = 3;
mmn_pos = 4;
mmx_pos = 1;
mx_pos = 2;
}
}
else if (tmp1 == tmp3){
if (tmp1 < tmp2){
mn_pos = 1;
mmn_pos = 3;
mmx_pos = 2;
mx_pos = 4;
}
else{
mn_pos = 2;
mmn_pos = 4;
mmx_pos = 1;
mx_pos = 3;
}
}
else{
if (tmp1 < tmp2){
mn_pos = 2;
mmn_pos = 3;
mmx_pos = 1;
mx_pos = 4;
}
else{
mn_pos = 1;
mmn_pos = 4;
mmx_pos = 2;
mx_pos = 3;
}
}
int cur_mmn = min({tmp1, tmp2, tmp3});
int cur_mmx = max({tmp1, tmp2, tmp3});
for (int i = 5; i <= n; i++){
int tmp = query1(mn_pos, mmx_pos, i);
if (tmp < cur_mmn){
ans[mmn_pos] = cur_mmn;
mmn_pos = i;
cur_mmn = tmp;
}
else if (tmp == cur_mmn){
ans[mn_pos] = cur_mmn;
mn_pos = i;
cur_mmn = query1(mn_pos, mmn_pos, mmx_pos);
}
else if (tmp < cur_mmx){
ans[i] = tmp;
}
else if (tmp == cur_mmx){
ans[mmx_pos] = cur_mmx;
mmx_pos = i;
cur_mmx = query1(mmn_pos, mmx_pos, mx_pos);
}
else{
ans[mx_pos] = cur_mmx;
mx_pos = i;
cur_mmx = tmp;
}
}
if (query2(mn_pos, mmn_pos) == mmn_pos)
swap(mn_pos, mmn_pos);
if (query2(mmx_pos, mx_pos) == mx_pos)
swap(mmx_pos, mx_pos);
ans[mmn_pos] = cur_mmn;
ans[mmx_pos] = cur_mmx;
for (int i = 1; i <= n; i++){
vis[ans[i]] = true;
}
for (int i = 1; i <= n; i++){
if (!vis[i]){
if (!ans[mn_pos]){
ans[mn_pos] = i;
}
else{
ans[mx_pos] = i;
}
}
}
answer();
return 0;
}
LGP9907 COCI 2023/2024 #1 Mostovi
原题链接:COCI 2023/2024 #1 Mostovi
分析
难道说把边看成点,把点看成边进行重构,然后,新图上的一些"关键点"就是原图上的重要的边......这个并不好抽象啊,感觉做不了......
那这个能评黑吗?
思考暴力怎么打......就是说,这个我会一个 O ( m n l o g n ) O(mnlogn) O(mnlogn) 的枚举边 + + + 并查集的做法。这个可以获得 30 p t s 30pts 30pts......试一下吧。好的,非常完美啊......
这个暴力显然对于正解并没有什么启发性......
上了一节生物课,xy 送了我一本高等数学期末卷子。里面有一道题出处是"(山东某 985 高校)",这和直接说出来有什么区别?
开一个题解吧......
对吧,这种删边你最好找一棵树做。
那么,我们对于图,你就找一棵 dfs 树呗,
不不不,这个有点过于太难了,我决定先放弃。
LGP17279 『__OI R1』Little Circle
分析
四连通块是指在一个格子或像素区域中,每个格子通过上下左右四个方向相连形成的连通区域。
就是要保证联通是吗?
我们发现,对于 d = 1 d=1 d=1,我们想要构造尽可能多的类似下面的结构:

对于 d = 2 d=2 d=2,我们想要构造尽可能多的类似下面的结构:

对于 d = 3 d=3 d=3,我们想要构造尽可能多的类似下面的结构:

对于 d = 4 d=4 d=4,我们想要构造尽可能多的类似下面的结构:

那么,这个似乎找个规律直接就可以算了......
D D D 老师发力了......

需要勘误的是,在 d = 1 d=1 d=1 的情况下:
- n = 1 n=1 n=1,输出 0 0 0
- n = 2 n=2 n=2 或 n = 3 n=3 n=3,输出 2 2 2
- n ≡ 0 ( m o d 4 ) , n ≥ 4 n \equiv 0 \pmod{4},n\ge 4 n≡0(mod4),n≥4,输出 ⌊ n 4 ⌋ × 2 + 1 \left\lfloor\dfrac{n}{4}\right\rfloor\times 2+1 ⌊4n⌋×2+1
- n ≢ 0 ( m o d 4 ) , n ≥ 4 n \not\equiv 0 \pmod{4},n\ge 4 n≡0(mod4),n≥4,输出 ⌊ n 4 ⌋ × 2 + 2 \left\lfloor\dfrac{n}{4}\right\rfloor\times 2+2 ⌊4n⌋×2+2
这也是一个精度问题。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
int T;
int n, d;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
for (int cs = 1; cs <= T; cs++){
cin >> n >> d;
if (d == 1){
if (n == 1){
cout << 0 << "\n";
}
else if (n == 2 || n == 3){
cout << 2 << "\n";
}
else if (n % 4 == 0){
cout << n / 4 * 2 + 1 << "\n";
}
else{
cout << n / 4 * 2 + 2 << "\n";
}
}
else if (d == 2){
if (n <= 2){
cout << 0 << "\n";
}
else if (n == 3){
cout << 1 << "\n";
}
else if (n == 4){
cout << 4 << "\n";
}
else if (n == 5){
cout << 3 << "\n";
}
else if (n == 6){
cout << 4 << "\n";
}
else if (n == 9){
cout << 7 << "\n";
}
else{
cout << n - n % 2 << "\n";
}
}
else if (d == 3){
if (n <= 3){
cout << 0 << "\n";
}
else if (n == 4){
cout << 1 << "\n";
}
else{
cout << n - 4 << "\n";
}
}
else if (d == 4){
if (n <= 4){
cout << 0 << "\n";
}
else{
int r = ((-2.0 + sqrt(4.0 - 4.0 * 2.0 * (1.0 - n))) / 4.0);
int m = n - 2 * r * r - 2 * r - 1;
cout << 2 * r * r - 2 * r + 1 + max(0ll, m - m / (r + 1) - 1) << "\n";
}
}
}
}
AT_abc348_g ABC348G Max (Sum - Max)
分析
这个看着挺有意思的,但是我不会......可能我会 O ( n 2 ) O(n^2) O(n2) 甚至更大的做法?就是我们可能需要一个 DP,甚至是反悔贪心呢?总之这个是一个决策......但是,我没看过决策单调性啊😭
这无疑是恐怖的😭
差点以为这题要高精度。
那估计就是 DP 的优化?看看题解吧。
找 D D D 老师终于是理解了。
首先呢,我们先以 B B B 为关键字排序,那么,对于一个 k k k,如果钦定某个 B i B_i Bi 为所选取的最大值,那么,就要从 j ∈ 1 , i j\in1,i j∈1,i 中选取前 k − 1 k-1 k−1 小的 A j A_j Aj,可以用主席树维护这个东西。
然后呢,我们发现,如果每次都枚举 k k k 是超时的,我们发现,随着 k k k 的增加,我们所选择的 B i B_i Bi 是不降的。那么,可以进行分治。我们设 k ∈ l , r , i ∈ L , R k\inl,r,i\inL,R k∈l,r,i∈L,R,然后,在 L , R L,R L,R 内暴力枚举最适合的位置,即可继续递归下去(这样可以把每次最优位置下传)。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200005;
const int VL = -1000000000;
const int VR = 1000000000;
int n;
struct node{
int a, b;
}inp[N];
bool cmp(node tmp1, node tmp2){
return tmp1.b < tmp2.b;
}
int rt[N], tot;
struct tree{
int ls, rs;
int cnt, val;
}tr[N << 6];
int ans[N];
struct president_segtree{
void pushup(int p){
tr[p].cnt = tr[tr[p].ls].cnt + tr[tr[p].rs].cnt;
tr[p].val = tr[tr[p].ls].val + tr[tr[p].rs].val;
}
void modify(int pre, int &p, int l, int r, int val){
if (!p)
p = ++tot;
if (l == r){
tr[p].val = tr[pre].val + val;
tr[p].cnt = tr[pre].cnt + 1;
return ;
}
int mid = (l + r) >> 1;
if (val <= mid){
tr[p].rs = tr[pre].rs;
modify(tr[pre].ls, tr[p].ls, l, mid, val);
}
else{
tr[p].ls = tr[pre].ls;
modify(tr[pre].rs, tr[p].rs, mid + 1, r, val);
}
pushup(p);
}
int query(int p, int l, int r, int k){
if (!k)
return 0;
if (l == r){
return k * l;
}
int mid = (l + r) >> 1;
if (k <= tr[tr[p].rs].cnt)
return query(tr[p].rs, mid + 1, r, k);
else
return tr[tr[p].rs].val + query(tr[p].ls, l, mid, k - tr[tr[p].rs].cnt);
}
}T;
void solve(int l, int r, int L, int R){
if (l > r)
return ;
int mid = (l + r) >> 1;
int pos = max(L, mid);
ans[mid] = 0xc0c0c0c0c0c0c0c0;
for (int i = max(L, mid); i <= R; i++){
int tmp = T.query(rt[i], VL, VR, mid);
if (tmp - inp[i].b > ans[mid]){
ans[mid] = tmp - inp[i].b;
pos = i;
}
}
solve(l, mid - 1, L, pos);
solve(mid + 1, r, pos, R);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++){
cin >> inp[i].a >> inp[i].b;
}
sort(inp + 1, inp + n + 1, cmp);
for (int i = 1; i <= n; i++){
T.modify(rt[i - 1], rt[i], VL, VR, inp[i].a);
}
solve(1, n, 1, n);
for (int i = 1; i <= n; i++){
cout << ans[i] << "\n";
}
}
QOJ4998 Keyboard Queries
原题链接:Keyboard Queries
分析
想到了一个什么 segtree+union 的做法,但是,我不会判断,而且,那个 2 1 3 我不会更新。
遇事不决,我们做个离线怎么样啊?我还是感觉线段树优化并查集是对的。
竟然是首先,我们使用并查集来维护(这个想到了),然后,为了判相等,我们需要使用哈希。然后,每次加进来,二分出来第一个不在同一个块内的,使用树状数组单点加,向暴力枚举数量少的一端启发式合并。做完了......
正解
cpp
#include <bits/stdc++.h>
#define int long long
#define mod 998244353
#define base 13331
using namespace std;
const int N = 100005;
int n, q;
int pw[N], inv[N];
int fa[N];
vector<int> e[N];
int qpow(int a, int b){
int res = 1;
while (b) {
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
struct BIT{
int c[N];
BIT(){
memset(c, 0, sizeof(c));
}
void add(int x, int val){
for (int i = x; i <= n; i += i & (-i)){
c[i] = (c[i] + val) % mod;
}
}
int query(int x){
int res = 0;
for (int i = x; i; i -= i & (-i)){
res = (res + c[i]) % mod;
}
return res;
}
}T1, T2;
void modify(int x, int y){
T1.add(x, y * pw[x] % mod);
T2.add(n - x + 1, y * pw[n - x + 1] % mod);
}
int find(int x){
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int u, int v){
int fu = find(u), fv = find(v);
if (fu == fv)
return ;
if (e[fu].size() < e[fv].size())
swap(fu, fv);
for (auto to : e[fv]){
fa[find(to)] = fu;
modify(to, (fu - fv + mod) % mod);
e[fu].push_back(to);
}
}
void solve(int l, int r){
if (l > r)
return ;
int L = 1, R = r - l + 1, pos = n + 1;
while (L <= R){
int mid = (L + R) >> 1;
int tmp1 = (T1.query(l + mid - 1) - T1.query(l - 1) + mod) % mod * inv[l] % mod;
int tmp2 = (T2.query(n - r + mid) - T2.query(n - r) + mod) % mod * inv[n - r + 1] % mod;
if (tmp1 != tmp2){
pos = mid;
R = mid - 1;
}
else{
L = mid + 1;
}
}
if (l + pos - 1 >= r - pos + 1)
return ;
merge(l + pos - 1, r - pos + 1);
solve(l + pos, r - pos);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
pw[0] = 1;
inv[0] = 1;
for (int i = 1; i <= n; i++){
pw[i] = pw[i - 1] * base % mod;
inv[i] = qpow(pw[i], mod - 2);
}
for (int i = 1; i <= n; i++){
fa[i] = i;
e[i].push_back(i);
modify(i, i);
}
for (int cs = 1, op, l, r, a, b, x, y; cs <= q; cs++){
cin >> op;
if (op == 1){
cin >> l >> r;
solve(l, r);
}
else if (op == 2){
cin >> a >> b >> x >> y;
if (b - a != y - x){
cout << "Not equal" << "\n";
}
else{
int tmp1 = (T1.query(b) - T1.query(a - 1) + mod) % mod * inv[a] % mod;
int tmp2 = (T1.query(y) - T1.query(x - 1) + mod) % mod * inv[x] % mod;
if (tmp1 == tmp2){
cout << "Equal" << "\n";
}
else{
cout << "Unknown" << "\n";
}
}
}
}
return 0;
}
/*
6 8
1 1 6
2 1 1 6 6
2 1 2 5 6
2 1 3 5 6
1 1 3
2 1 3 4 6
2 4 4 6 6
2 2 3 4 5
*/