2022高考集训2 [交通 冒泡排序 矩阵 花瓶]
作者:互联网
\({\LaTeX \, \TeX \, \LaTeX \, \TeX \, \LaTeX \, \TeX \, \LaTeX \, \TeX \,}\)
WintersRain: 為什麼要用繁體啊?
Me: 比較好
WintersRain: 比較♂♂♂♂♂♂
Me: ......
昨天留的坑,今天補一下)
\(SubTask\) 是個什麼玩意)
這次的題確實比較難,具體剖析一下
T1. 交通
題解做法太nb了。。
假設一個點的兩條出邊爲 \(i\) 和 \(j\),我們建一個圖給 \(i\) 和 \(j\) 連邊(注意是把邊的編號當成點連邊),同樣給一個點的兩個入邊連邊
不難發現新圖上每個點的度數恰好爲二,並且只有偶環
我們要求的就是在新圖上選 \(n\) 個不在一個環上的點
於是我們直接 \(DFS\) 統計環數(根本不用題解說的並查集,其實就是聯通塊數),答案即爲 \(2 ^ {環數}\)
Talk is cheap, Show you the Code
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cmath>
#include <bitset>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read()
{
int x = 0;
char c;
bool f = 0;
while (!isdigit(c = getchar()))
{
if (c == '-')
{
f = 1;
}
}
do
{
x = (x << 1) + (x << 3) + (c ^ 48);
} while (isdigit(c = getchar()));
if (f)
{
return -x;
}
return x;
}
const int maxn = 2e5 + 10, maxe = 4e5 + 10, mod = 998244353;
struct Graph
{
int head[maxn], len;
int nxt[maxe], to[maxe];
void Ins(int u, int v)
{
nxt[++len] = head[u];
to[len] = v;
head[u] = len;
}
} G;
bitset<maxn> vis;
void Paint(int u)
{
vis[u] = 1;
for (int i = G.head[u]; i; i = G.nxt[i])
{
int v = G.to[i];
if (!vis[v])
{
Paint(v);
}
}
}
int out[maxn];
int in[maxn];
ll qPow(int a, int b)
{
ll ans = 1;
while (b)
{
if (b & 1)
{
ans = ans * a % mod;
}
a = a * a % mod;
b >>= 1;
}
return ans;
}
int main()
{
#ifndef DEBUG
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
#endif
int n = read();
for (int i = 1, e = n << 1; i <= e; ++i)
{
int u = read(), v = read();
if (in[v])
{
G.Ins(i, in[v]);
G.Ins(in[v], i);
}
else
{
in[v] = i;
}
if (out[u])
{
G.Ins(i, out[u]);
G.Ins(out[u], i);
}
else
{
out[u] = i;
}
}
int cnt = 0;
for (int i = 1, e = n << 1; i <= e; ++i)
{
if (!vis[i])
{
++cnt;
Paint(i);
}
}
printf("%lld\n", qPow(2, cnt));
}
T2. 冒泡排序
大佬的題解這題都講的很迷惑,我這篇絕對保證能看懂)
這題確實比較難想)
題目裏下標從零開始有點詭異,我們直接把下標和數都 +1,改成從 \(1\) 開始
我們稱將一個位置與它的後一個位置互換的過程爲對一個位置進行交換,也稱交換一個位置,那麼對一個位置進行交換也對應着題目中的一個 \(P_i\)
分析題目以後發現數列中每個位置都要經過一次交換,並且只能交換一次
那麼顯然如果有 \(A_i = i\),這個數一定會被交換走,不會留在原位,直接判掉無解
如果 \(A_i > i\),那麼這個數需要往右换到原位
考慮如下序列 \(3\) \(1\) \(2\) \(4\)
我們要把 \(3\) 換到第三個位置,那麼我們需要交換第一個位置,再交換第二個位置,否則如果先交換第二個位置,那麼即使我們交換了第一個位置使 \(3\) 這個數到達了第二個位置,它也無法再交換到第三個位置了(每個位置只能交換一次)
可以發現交換之間是有限制關係的,\(A_i < i\) 的情況同理
那麼我們用布爾數組 \(Pre\) 和 \(Nxt\) 記錄交換之間的限制關係,如果 \(Pre_i = 1\) 則第 \(i - 1\) 個位置必須先於第 \(i\) 個位置交換,如果 \(Nxt_i = 1\) 則第 \(i\) 個位置必須先於第 \(i - 1\) 個位置交換
可以發現若 \(A_i > i\),則 \(Pre[i + 1, A_i - 1] = 1\),若 \(A_i < i\),則 \(Nxt[A_i + 1, i - 1] = 1\),如果有一個位置 \(i\) 滿足 \(Pre_i = Nxt_i = 1\),那麼就無解了
(那麼算 \(Pre\) 和 \(Nxt\) 的過程直接 \(O(n ^ 2)\) 暴力就行了,根本不用 \(jijidawang\) 的差分前綴和)
那么現在我们考虑 \(Dynamic\,\,Program\)
設 \(f_{i, j}\) 爲 \(P\) 中已經放了第一個位置到第 \(i\) 個位置的交換,且第 \(i\) 個位置的交換在 \(P\) 中下標爲 \(j\) 的方案數 (也就是 \(P_j = i\) )
賦初值 \(f_{1, 1} = 1\)
那麼我們的轉移過程就是考慮把第 \(i \, (1 \le i < n)\) 個位置交換插到 \(P\) 的第 \(j \, (1 \le j \le i)\) 個位置前,通過插入的方式使 \(P_j = i\),原來在 \(P_j\) 及它後面的位置交換統一往後挪
我們考慮引入下標 \(k\) 進行轉移,就相當於是第 \(i - 1\) 個位置交換處在在 \(P_k\) 的位置,即 \(P_k = i - 1\)
如果 \(Pre_i = 1\),那麼第 \(i - 1\) 個位置先於第 \(i\) 個位置交換,\(k < j\),則 \(f_{i, j} = \displaystyle\sum_{k = 1}^{j - 1} f_{i - 1, k}\)
如果 \(Nxt_i = 1\),那麼第 \(i\) 個位置先於第 \(i - 1\) 個位置交換,\(j <= k\),則 \(f_{i, j} = \displaystyle\sum_{k = j}^{i - 1} f_{i - 1, k}\)
如果 \(Pre_i = Nxt_i = 0\),那麼這個交換的位置不受限制,\(k\) 不受限制,\(f_{i, j} = \displaystyle\sum_{k = 1}^{i - 1} f_{i - 1, k}\)
我們發現這玩意可以直接前綴和優化到 \(O(n ^ 2)\)
原因是根據上式,我們直接用以下方式計算:
如果 \(Pre_i = 1\),我們正序枚舉 \(j\),那麼 \(f_{i, j} = f_{i, j - 1} + f_{i - 1, j - 1}\)
如果 \(Pre_j = 1\),我們\(\color{red}{倒序}\)枚舉 \(j\),那麼 \(f_{i, j} = f_{i, j + 1} + f_{i - 1, j}\)
如果 \(Pre_i = Nxt_j = 0\),我們發現 \(f_{i, j}\) 都相等,我們直接算一下答案然後都賦值上這個值就行了
注意各個變量的取值範圍,注意取模
最終答案 \(ans = \displaystyle\sum_{i = 1}^{n - 1} f_{n - 1, i}\)
這個 \(f\) 數組顯然可以滾動數組優化,但沒必要)
点击查看代码
#include <cstdio>
#include <cstring>
#include <cctype>
#include <iostream>
#include <sstream>
#include <stack>
#include <cmath>
#include <algorithm>
#include <map>
#include <queue>
#include <list>
#include <set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
int read()
{
int x = 0;
char c;
bool f = 0;
while (!isdigit(c = getchar()))
{
if (c == '-')
{
f = 1;
}
}
do
{
x = (x << 1) + (x << 3) + (c ^ 48);
} while (isdigit(c = getchar()));
if (f)
{
return -x;
}
return x;
}
const int maxn = 5e3 + 10, mod = 1e9 + 7;
int n, f[maxn][maxn], pre[maxn], nxt[maxn];
int main()
{
#ifndef DEBUG
freopen("mp.in", "r", stdin);
freopen("mp.out", "w", stdout);
#endif
n = read();
for (int i = 1; i <= n; ++i)
{
int x = read() + 1;
if (x == i)
{
puts("0");
return 0;
}
if (x > i)
{
for (int j = i + 1; j < x; ++j)
{
pre[j] = 1;
}
}
else
{
for (int j = x + 1; j < i; ++j)
{
nxt[j] = 1;
}
}
}
for (int i = 1; i <= n; ++i)
{
if (pre[i] && nxt[i])
{
puts("0");
return 0;
}
}
f[1][1] = 1;
for (int i = 2; i < n; ++i)
{
if (pre[i])
{
for (int j = 1; j <= i; ++j)
{
f[i][j] = (f[i][j - 1] + f[i - 1][j - 1]) % mod;
}
}
else if (nxt[i])
{
for (int j = i; j; --j)
{
f[i][j] = (f[i][j + 1] + f[i - 1][j]) % mod;
}
}
else
{
ll sum = 0;
for (int j = 1; j <= i; ++j)
{
sum = (sum + f[i - 1][j]) % mod;
}
for (int j = 1; j <= i; ++j)
{
f[i][j] = sum;
}
}
}
ll ans = 0;
for (int i = 1; i < n; ++i)
{
ans = (ans + f[n - 1][i]) % mod;
}
printf("%lld\n", ans);
}
T3. 矩陣
直說構造方案,畢竟是個構造題)
有一說一,如果虎哥不給大樣例,這題我肯定過不去,懂的都懂)
首先我們通過行的變換,把第二列第二行及它下面的數全都加(減)成和它左上角一樣的數
然後我們通過列的變換,把第二行第二列及它右邊的數全都加(減)成和它左上角一樣的數
然後就會驚奇的發現對角線上的數全都一樣,直接用對角線消就行了
如果按正常步驟消完矩陣裏還有非 \(0\) 的數,那就是無解了
点击查看代码
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cmath>
#include <bitset>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
ll read()
{
ll x = 0;
char c;
bool f = 0;
while (!isdigit(c = getchar()))
{
if (c == '-')
{
f = 1;
}
}
do
{
x = (x << 1) + (x << 3) + (c ^ 48);
} while (isdigit(c = getchar()));
if (f)
{
return -x;
}
return x;
}
const int maxn = 1e3 + 10;
int n, m;
ll num[maxn][maxn];
struct OPT
{
int a, b;
ll c;
OPT()
{
}
OPT(int A, int B, ll C)
{
a = A;
b = B;
c = C;
}
} q[6500];
int tot;
void Hang(int i, ll val)
{
q[++tot] = OPT(1, i, val);
for (int j = 1; j <= m; ++j)
{
num[i][j] += val;
}
}
void Lie(int i, ll val)
{
q[++tot] = OPT(2, i, val);
for (int j = 1; j <= n; ++j)
{
num[j][i] += val;
}
}
void Jiao(int i, ll val)
{
q[++tot] = OPT(3, i, val);
// printf("3 %d %lld\n", i, val);
for (int j = 1; j <= m; ++j)
{
if (j - i < 1 || j - i > n)
{
continue;
}
num[j - i][j] += val;
}
}
void Print()
{
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
printf("%lld ", num[i][j]);
}
putchar('\n');
}
}
int main()
{
#ifndef DEBUG
freopen("c.in", "r", stdin);
freopen("c.out", "w", stdout);
#endif
// 大样例具有丰富的参考价值!!!!!!!
n = read(), m = read();
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
num[i][j] = read();
}
}
for (int i = 2; i <= n; ++i)
{
Hang(i, num[i - 1][1] - num[i][2]);
}
for (int i = 3; i <= m; ++i)
{
Lie(i, num[1][i - 1] - num[2][i]);
}
for (int i = n; i; --i)
{
Jiao(1 - i, -num[i][1]);
}
for (int i = 2; i <= m; ++i)
{
Jiao(i - 1, -num[1][i]);
}
// Print();
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
if (num[i][j])
{
puts("-1");
return 0;
}
}
}
printf("%d\n", tot);
for (int i = 1; i <= tot; ++i)
{
printf("%d %d %lld\n", q[i].a, q[i].b, q[i].c);
}
}
T4. 花瓶
斜率優化鬼題)
一心想著 平衡樹/\(CDQ\) 維護橫座標、斜率不單調遞增的凸包,後來掙紮了半天放棄了)
懶得講了,粘題解了)
有幾個需要注意的點
判斜率大小的時候不要直接比斜率,把他寫成 \((y - y) * (x - x) < (y - y) * (x - x)\) 的形式(會意就行)
然後記得開 long long,dp數組記得初始化負無窮
然後乘的時候如果乘爆了可以直接轉成 \(long \, double\)
点击查看代码
#include <cstdio>
#include <cstring>
#include <cctype>
#include <iostream>
#include <sstream>
#include <stack>
#include <cmath>
#include <algorithm>
#include <map>
#include <queue>
#include <list>
#include <set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
int read()
{
int x = 0;
char c;
bool f = 0;
while (!isdigit(c = getchar()))
{
if (c == '-')
{
f = 1;
}
}
do
{
x = (x << 1) + (x << 3) + (c ^ 48);
} while (isdigit(c = getchar()));
if (f)
{
return -x;
}
return x;
}
const int maxn = 5e3 + 10;
const ld eps = 1e-9;
int num[maxn];
ll sum[maxn];
ll f[maxn][maxn];
int id[maxn];
int q[maxn], head, tail;
bool cmp(const int &a, const int &b)
{
return sum[a] < sum[b];
}
ll X(int pos)
{
return sum[pos];
}
ll Y(int j, int pos)
{
return f[j][pos];
}
ld Slope(int j, int x, int y)
{
if (X(x) - X(y) == 0)
{
return 1e19;
}
return 1.0l * ((Y(j, x) - Y(j, y)) / (X(x) - X(y)));
}
int main()
{
#ifndef DEBUG
freopen("d.in", "r", stdin);
freopen("d.out", "w", stdout);
#endif
memset(f, 0xc0, sizeof f);
int n = read();
for (int i = 1; i <= n; ++i)
{
num[i] = read();
sum[i] = sum[i - 1] + num[i];
id[i] = i;
}
sort(id, id + n + 1, cmp);
for (int i = 0; i <= n; ++i)
{
f[i][0] = 0;
}
for (int j = 1; j <= n; ++j)
{
head = 1, tail = 0;
for (int si = 0; si <= n; ++si)
{
int i = id[si];
if (i >= j)
{
continue;
}
while (tail > 1 && 1.0l * (f[j][q[tail - 1]] - f[j][q[tail]]) * (sum[q[tail]] - sum[i]) <= 1.0l * (f[j][q[tail]] - f[j][i]) * (sum[q[tail - 1]] - sum[q[tail]]))
{
--tail;
}
q[++tail] = i;
}
for (int si = n; si >= 0; --si)
{
int i = id[si];
if (i <= j)
{
continue;
}
ld slp = sum[i] - sum[j];
while (head < tail && 1.0l * f[j][q[head]] - f[j][q[head + 1]] <= 1.0l * slp * (sum[q[head]] - sum[q[head + 1]]))
{
++head;
}
int k = q[head];
f[i][j] = max(f[i][j], f[j][k] + (sum[i] - sum[j]) * (sum[j] - sum[k]));
}
}
ll ans = 0;
for (int i = 0; i <= n; ++i)
{
ans = max(ans, f[n][i]);
}
printf("%lld\n", ans);
}
标签:我們,int,位置,long,冒泡排序,2022,include,集训,交換 来源: https://www.cnblogs.com/eafoo/p/16355006.html