2022高考集训2
这场是难度最大的一场,并且带捆绑测试,坐了四个半小时连暴力分都拿不到
成绩
拉大胯了
T1 交通
题解思路太NB了,代码实现倒是不难。
假设一个点的两条出边为 \(i,j\) ,我们新建一个图给 \(i,j\) 连边。如果一个点的两条入边为 \(i,j\) ,我们也给 \(i,j\) 连边。
把题解翻译成人话,就是:
把原图中的边当作节点,新建一个无向图,
如果两条原图中的边在原图中指向同一个节点或者由同一个节点引出,就给代表这两条边的新图中的节点连一条无向边。
不难发现新图上每个点度数恰好为二,并且只有偶环。我们的要求事实上就是在新图上选 \(n\) 个不相邻的点,设新图中有 \(x\) 个环,于是答案显然是 \(2^{x}\) ,直接并查集即可。
题解又不说人话了,并且这个全是偶环存疑,还有自环的情况。
其他情况因为原图中每个节点各有两条入边、出边,所以新图中除了自环都是偶环
建完新图之后,要找到 \(n\) 个不相邻的节点,设环的节点数为 \(size\) ,除自环外,每个环就要找 \(\frac{size}{2}\) 个不相邻的节点,可归纳找到规律,每个环最多找到两组这样的节点。
对于自环的情况,可以把自环看作一个双连通分量,找 \(1\) 个点,当然只有两种情况。
所以,答案是 \(2^{x}\) 。这哪来的显然啊!
Code
//要删去边,在原图上不太好实现
//所以可以给边建图
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAX = 1e5 + 10, Mod = 998244353;
int n, cnt;
long long tot;
int fa[MAX << 1];
vector<int> in[MAX]; //指向 i 节点的边
vector<int> out[MAX]; //由 i 节点引出的边
bool used[MAX << 1];
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
long long Fast_pow(long long a, long long w, int p){
long long ans = 1;
while(w){
if(w & 1) ans = ans * a % p;
a = a * a % p;
w >>= 1;
}
return ans;
}
//判环,唔
//和 DB( ̄(工) ̄) 探讨了下造的数据
//考场上打死我都想不出给边建图
void Clean(){
for(register int i = 1; i <= 2 * n; i++)
fa[i] = i;
}
int Find(int x){
return x == fa[x] ? x : fa[x] = Find(fa[x]);
}
void Merge(int x, int y){
x = Find(x), y = Find(y);
if(x != y) fa[y] = x;
}
//写代码一定要把脑子缕清楚了再写!!!!!!!!!!!
int main(){
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
n = read();
for(register int i = 1; i <= n * 2; i++){
int u, v;
u = read(), v = read();
in[u].push_back(i);
out[v].push_back(i);
}
Clean();
for(register int i = 1; i <= n; i++){
Merge(in[i][0], in[i][1]); //每个节点只各有两条出入边
Merge(out[i][0], out[i][1]);
}
for(register int i = 1; i <= n * 2; i++)
if(fa[i] == i) tot++;
printf("%lld", Fast_pow(2, tot, Mod));
return 0;
}
T2
神仙题,全场最高分 \(0\)。
先粘题解:
引入优先级的概念,如果要在 \(a\) 之前先把 \(b\) 调整那么 \(b\) 的优先级大于 \(a\)。
比如 \(1432\) 必须要把 \(4\) 在 \(3\) 之前调换不然 是过不去的。
然后推 \(DP\) 式子
设第一维表示目前到了哪个节点,第二维表示拓扑序,即优先级,数组存储方案数
对于一个点 \(i + 1\) ,如果 \(i\) 是依赖于它的,那么
\(dp[i + 1][d] = \sum_{j = d + 1}^{n}dp[i][j]\)
这是因为如果 \(i\) 依赖于 \(i + 1\) 那么 \(i\) 必然在 \(i + 1\) 之后才能被遍历到。
同理,如果对于一个点 \(i + 1\) ,如果它依赖于 \(i\) ,那么有
\(dp[i + 1][d] = \sum_{j = 1}^{n}dp[i][j]\)
如果这两个点没有依赖关系那么 ( \(update\) \(at\) \(2022.6.12\) 的确有这种情况, \(Eafoo\) 太强了)
\(dp[i + 1][d] = \sum_{j = 1}^{n}dp[i][j]\)
之后统计答案即可。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 5010, Mod = 1e9 + 7;
int n, cnt;
long long ans;
int a[MAXN];
long long dp[MAXN][MAXN];
struct Binary_Indexed_Tree{
long long tr[MAXN];
long long sum[MAXN]; //维护一个前缀和
Binary_Indexed_Tree(){
memset(tr, 0, sizeof(tr));
memset(sum, 0, sizeof(sum));
}
int lowbit(int x){
return x & (-x);
}
void Add(int pos, long long data){
while(pos <= n){
tr[pos] += data;
pos += lowbit(pos);
}
}
long long Query(int pos){
return tr[pos] + sum[pos - lowbit(pos)];
}
}B1, B2;
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
//一,既然要排序,那么 a[i] = i 的话,无解,因为需要换走,那换走后就再也换不回来了,显然无解
//二,还有一种性质,当逆序对个数不为(n-1)时判无解:仅换一次,那跟着换走的那个就换不回来了
//根据 Kaguya 大佬提供的思路,这道题需要引入一个优先级的思想
//如果一个序列 a, b, c, d
//如果在 a 之前要先调整 b,那么 b 的优先级要大于 a
//such as 这个序列 1 4 2 3,要升序排列的话,必须要把 4 在 2 之前调换,不然 4 过不去
//这里就有了个差分约束的感觉,所以可以树状数组优化
//DP 式子直接贺了
//设 dp[i][j] ,i 表示到了哪个节点,j 代表拓扑序,其实就是优先级,储存的是方案数
//对于节点 i + 1, 若节点 i 依赖于它,则:
//dp[i + 1][d] = ∑dp[i][j] (j = d + 1; j <= n)
//如果 i 依赖于 i + 1 ,那么 i 必然在 i + 1 之后才能被遍历到
//若 i + 1 依赖于节点 i ,则:
//dp[i + 1][d] = ∑dp[i][j] (j = 1; j <= d)
//wintersrain 大佬还写了个没有依赖关系的,但我觉得这种情况不成立
int main(){
freopen("mp.in", "r", stdin);
freopen("mp.out", "w", stdout);
n = read();
for(register int i = 1; i <= n; i++){
a[i] = read() + 1; //因为排列从 0 开始,整体加 1 不影响大小和优先级,但方便下面操作
if(a[i] == i){ //特判一
puts("0");
return 0;
}
else if(a[i] > i){
B1.Add(i, 1);
B1.Add(a[i] - 1, -1);
}
else{
B2.Add(a[i], 1);
B2.Add(i - 1, -1);
}
}
for(register int i = 1; i <= n; i++)
for(register int j = i + 1; j <= n; j++)
if(a[j] < a[i]) cnt++; //找逆序对
if(cnt != n - 1){ //特判二
puts("0");
return 0;
}
//之后就可以愉悦的 dp 了
for(register int i = 1; i < n; i++){ //交换 n - 1 次
B1.sum[i] = B1.Query(i);
B2.sum[i] = B2.Query(i);
}
dp[1][1] = 1;
for(register int i = 2; i < n; i++){
int j = i - 1;
if(B1.sum[j]){
for(register int k = 2; k <= i; k++)
dp[i][k] = (dp[i][k - 1] + dp[j][k - 1]) % Mod;
}
else if(B2.sum[j]){
for(register int k = j; k > 0; k--)
dp[i][k] = (dp[i][k + 1] + dp[j][k]) % Mod;
}
else{
for(register int k = 1; k <= j; k++)
dp[i][1] = (dp[i][1] + dp[j][k]) % Mod;
for(register int k = 2; k <= i; k++)
dp[i][k] = dp[i][k - 1];
}
}
for(register int i = 1; i < n; i++)
ans = (ans + dp[n - 1][i]) % Mod;
printf("%lld", ans);
return 0;
}
T3
构造好题,但是证明存疑。
题解:
Code
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX = 1010;
int n, m, cnt, tot;
long long now;
long long num[MAX][MAX];
struct Answer{
int opt, line;
long long ans;
}ans[MAX * 6];
inline long long read(){
long long x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
void Clean_line(int x, long long data){ //清理行, x 代表行
ans[++tot].opt = 1;
ans[tot].line = x;
ans[tot].ans = -data;
for(register int j = 1; j <= m; j++)
num[x][j] -= data;
}
void Clean_row(int y, long long data){ //清理列,y 代表列
ans[++tot].opt = 2;
ans[tot].line = y;
ans[tot].ans = -data;
for(register int i = 1; i <= n; i++)
num[i][y] -= data;
}
void Clean_angle(int x, int y, long long data){ //清理对角线 啊啊啊啊啊啊,开long long 啊啊啊啊啊啊啊
int len = y - x; //len 代表对角线常数
ans[++tot].opt = 3;
ans[tot].line = len;
ans[tot].ans = -data;
int k = max(1, 1 - len);
for(register int i =k, j = k + len; i <= n && j <= m; i++, j++)
num[i][j] -= data;
}
void Print(){
for(register int i = 1; i <= n; i++){
for(register int j = 1; j <= m; j++)
printf("%lld ", num[i][j]);
puts("");
}
}
bool Check(){
for(register int i = 1; i <= n; i++)
for(register int j = 1; j <= m; j++)
if(num[i][j] != 0) return false;
return true;
}
int main(){
freopen("c.in", "r", stdin);
freopen("c.out", "w", stdout);
n = read(), m = read();
for(register int i = 1; i <= n; i++)
for(register int j = 1; j <= m; j++)
num[i][j] = read();
//Print();
//先构造,最后要构造成每条对角线上的数全部相等
//把第二列第二行下边的数都通过行操作变成和左上角相同的数
//把第二列第二行右边的数都通过列操作变成和左上角相同的数
for(register int j = m; j >= 1; j--){
Clean_row(j, num[1][j]);
Clean_angle(2, j, num[2][j]);
}
for(register int i = 3; i <= n; i++){
Clean_line(i, num[i][2]);
Clean_angle(i, 1, num[i][1]);
}
//Print();
if(!Check()){
puts("-1");
return 0;
}
printf("%d\n", tot);
for(register int i = 1; i <= tot; i++)
printf("%d %d %lld\n", ans[i].opt, ans[i].line, ans[i].ans);
return 0;
}
T4
斜率优化,并且这个斜率优化很神奇。
Code
//花瓶?!花瓶好耶!
//首先我不是 CP 党,其次,最好是个女儿
//哈哈哈哈
//魔怔了
//不过考试的时候的的确确想到这去了,以后考试绝对不能分心啊喂
//不过花瓶确实好嗑
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 5010;
int n;
long long ans;
int a[MAXN];
long long pre[MAXN];
long long dp[MAXN][MAXN];
int head, tail, q[MAXN];
struct Nzh_Haya{ //嘿嘿嘿嘿嘿
int id;
long long data;
}sum[MAXN];
bool operator < (const Nzh_Haya &a, const Nzh_Haya &b){
if(a.data == b.data) return a.id < b.id;
return a.data < b.data;
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
//虽然题解上说这题很显然,实则不然
//这题能看出来是 DP ,但斜率优化自己压根就没学明白,这题还是斜率的变式(横坐标不单调)
//毕竟正解都是从暴力的基础上得到的,所以我们先去推最暴力的 DP 式
//设 dp[i][j] 代表是 前 i 个花瓶,最后一段是 [j + 1, i] 的最大值
//推出式子 dp[i][j] = max{dp[j][k] + (sum[i] - sum[j]) * (sum[j] - sum[k])} (j - 1, k = 0)
//这的的确确要斜率优化,但我不会(悲)
//暴力的话 DP 是 O(n * n) 的,但是状态转移是 O(n) 的, 乘起来 O(n * n * n),铁定 TLE
//所以就要斜率优化,但这道题的凸包不单调,就很奇怪
//继续看 DP
//DP 式子可以转化为 dp[i][j] = (sum[i] - sum[j]) * sum[j] + max{f[j][k] - (sum[i] - sum[j]) * sum[k]} (0 <= k < j)
//对于 sum[a] < sum[b] 并且 b 优于 a 就有:
//(dp[i][a] - dp[j][b]) / (sum[a] - sum[b]) > (sum[i] - sum[j])
//这样,维护一个上凸包就可以了
int main(){
freopen("d.in", "r", stdin);
freopen("d.out", "w", stdout);
memset(dp, 0x8f, sizeof(dp)); //这里的初值一定要设大些!!!!!!!
n = read();
for(register int i = 1; i <= n; i++){
a[i] = read();
pre[i] = sum[i].data = sum[i - 1].data + a[i] * 1LL;
sum[i].id = i;
dp[i][0] = dp[i][i] = 0;
}
sort(sum, sum + n + 1);
for(register int j = 1; j <= n; j++){
head = 1, tail = 0;
for(register int i = 0; i <= n; i++){
if(sum[i].id >= j) continue;
while(head < tail && (dp[j][sum[i].id] - dp[j][sum[q[tail]].id]) *
(sum[q[tail]].data - sum[q[tail - 1]].data) >=
(dp[j][sum[q[tail]].id] - dp[j][sum[q[tail - 1]].id]) *
(sum[i].data - sum[q[tail]].data)) tail--; //丑死了
q[++tail] = i;
}
for(register int i = n; i >= 0; i--){
if(sum[i].id <= j) continue;
while(head < tail && (dp[j][sum[q[head]].id] - dp[j][sum[q[head + 1]].id]) <
(sum[i].data - pre[j]) * (sum[q[head]].data - sum[q[head + 1]].data)) head++;
dp[sum[i].id][j] = max(dp[sum[i].id][j], (sum[i].data - pre[j]) * pre[j] +
dp[j][sum[q[head]].id] - (sum[i].data - pre[j]) * sum[q[head]].data);
}
}
for(register int i = 0; i <= n; i++)
ans = max(ans, dp[n][i]);
printf("%lld", ans);
return 0;
}
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16596436.html