T1: FJ 打算好好修一下农场中某条凹凸不平的土路。按奶牛们的要求,修好后的路面高度应当
单调不上升或单调不下降,也就是说,高度上升与高度下降的路段不能同时出现在修好的路中。
整条路被分成了 N 段, N 个整数
A
1
,
.
.
.
,
A
N
(
N
≤
2
,
000
)
A_1, ... , A_N (N \le 2,000)
A1,...,AN(N≤2,000)依次描述了每一段路的高度
A
i
<
=
1
e
9
A_i <=1e9
Ai<=1e9 。 FJ 希望找到一个恰好含 N 个元素的不上升或不下降序列
B
1
,
.
.
.
B
N
B_1, ... B_N
B1,...BN,作为修过的路中每个路段的高度。由于将每一段路垫高或挖低一个单位的花费相同,修
路的总支出可以表示为:
∣
A
1
−
B
1
∣
+
∣
A
2
−
B
2
∣
+
.
.
.
+
∣
A
N
−
B
N
∣
|A_1- B_1| + |A_2- B_2| + ... + |A_N -B_N|
∣A1−B1∣+∣A2−B2∣+...+∣AN−BN∣ 请你计算一下, FJ 在这
项工程上的最小支出是多少。
发现最后的
B
i
B_i
Bi一定会在
A
i
A_i
Ai中出现过,如果没有,那么根据绝对值的性质,我们可以挪一挪挪上去保持答案不变,于是先离散化然后
f
i
,
j
f_{i,j}
fi,j 表示到
i
i
i, 高度为
j
j
j,的最小支出
f
i
,
j
=
m
i
n
(
f
i
−
1
,
k
+
∣
h
j
−
h
a
i
∣
)
(
k
≤
j
)
f_{i,j} =min(f_{i-1,k}+|h_j - h_{a_i}|)(k\le j)
fi,j=min(fi−1,k+∣hj−hai∣)(k≤j) ,然后前缀
m
i
n
min
min 优化一下转移就可以了
#include<bits/stdc++.h>
#define N 2005
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = (cnt<<1) + (cnt<<3) + (ch-'0'), ch = getchar();
return cnt * f;
}
typedef long long ll;
ll f[N][N], mi[N][N]; int a[N], b[N], n;
const ll inf = 1e15;
int main(){
n = read();
for(int i = 1; i <= n; i++) a[i] = b[i] = read();
sort(b+1, b+n+1); int siz = unique(b+1, b+n+1) - (b+1);
for(int i = 1; i <= n; i++) a[i] = lower_bound(b+1, b+siz+1, a[i]) - b;
memset(f, 0x3f, sizeof(f));
memset(mi, 0x3f, sizeof(mi));
f[0][0] = 0;
memset(mi[0], 0, sizeof(mi[0]));
for(int i = 1; i <= n; i++){
for(int k = 1; k <= siz; k++){
f[i][k] = min(f[i][k], mi[i-1][k] + abs(b[a[i]] - b[k]));
}
for(int k = 1; k <= siz; k++) mi[i][k] = min(mi[i][k-1], f[i][k]);
} ll ans = inf;
for(int i = 1; i <= siz; i++) ans = min(ans, f[n][i]);
memset(f, 0x3f, sizeof(f));
memset(mi, 0x3f, sizeof(mi));
f[0][0] = 0;
memset(mi[0], 0, sizeof(mi[0]));
for(int i = 1; i <= n; i++){
for(int k = 1; k <= siz; k++){
f[i][k] = min(f[i][k], mi[i-1][k] + abs(b[a[i]] - b[k]));
}
for(int k = siz; k >= 1; k--) mi[i][k] = min(mi[i][k+1], f[i][k]);
}
for(int i = 1; i <= siz; i++) ans = min(ans, f[n][i]);
cout << ans; return 0;
}
T2: 一个 N ∗ M N*M N∗M 的矩阵,有 0 / 1 0/1 0/1,每次翻转会翻转上下左右和本身 5 个,问最小字典序的翻转 N ≤ 15 N\le 15 N≤15
首先暴力是
2
N
∗
M
2^{N*M}
2N∗M 的
然后发现可以表示为一个异或方程,变成
(
N
∗
M
)
3
(N*M)^3
(N∗M)3
但是
s
t
d
std
std 不是这么写的,考虑一个格子能影响上面,而当上面和上面的上面定了过后,就只有唯一格子可以影响它,如果它是一那么它下面的必须翻转,于是我们可以枚举第一行的状态,模拟一下能不能全部翻成 0 就可以了
#include<bits/stdc++.h>
#define N 20
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = (cnt<<1) + (cnt<<3) + (ch-'0'), ch = getchar();
return cnt * f;
}
int n, m, a[N][N], mp[N][N];
int vis[N][N];
void rev(int x, int y){ mp[x+1][y] ^= 1; mp[x-1][y] ^= 1; mp[x][y-1] ^= 1; mp[x][y+1] ^= 1; mp[x][y] ^= 1;}
int main(){
n = read(), m = read();
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) a[i][j] = read();
for(int S = 0; S < (1<<m); S++){
memcpy(mp, a, sizeof(mp));
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= m; i++){
if(S & (1<<(i-1))) rev(1, i), vis[1][i] = 1;
}
for(int i = 2; i <= n; i++){
for(int j = 1; j <= m; j++){
if(mp[i-1][j]) rev(i, j), vis[i][j] = 1;
}
}
int flag = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++) if(mp[i][j]) flag = 1;
} if(!flag){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cout << vis[i][j] << " ";
} cout << '\n';
} return 0;
}
} cout << "IMPOSSIBLE"; return 0;
}
WOJ4713 枪战
T3: 有 n 个人,每个人手里有一把手枪。一开始所有人都选定一个人瞄准(有可能瞄准自己)。
然后他们按某个顺序开枪,且任意时刻只有一个人开枪。因此,对于不同的开枪顺序,最后死的
人也不同。
好题,思维要求比较高
首先从链开始想:
最多的情况,从链头开始打,只有最后一个不死,有
N
−
1
N-1
N−1个
最少的情况,从链尾往上打,有
⌊
N
2
⌋
\left \lfloor \frac{N}{2}\right \rfloor
⌊2N⌋ 个
环:
最多:显然
N
−
1
N-1
N−1 个
最少:显然
⌈
N
2
⌉
\left \lceil \frac{N}{2}\right \rceil
⌈2N⌉ 个
基环数:
先不管大环,若以大环为根,那么就有一棵一棵的子树,受链的启发,我们子树最浅的点开始打,就只剩下叶子结点不会被打,在来考虑换,如果它被打了,它可以在被打之前把它下一个干掉,于是可以得出结论:基环数中最大情况只有叶子存活。
最小情况,受链的启发,我们可以拓扑排序,从入度为 0 的开始打,在下一个打人之前把它干掉就可以使死尸最少,于是拓扑,每次打死一个人后,把死人要打的人给解放去打别人,环怎么办?
分类讨论:如果环上有一个点被打了,那么它下一个点被我们解放去打别人,相当与破环为链,直接拓扑排序没有问题
如果环上没有一个点被打,那么类似与环的处理方式一样
然后可能有多个连通块,讨厌死了!
#include<bits/stdc++.h>
#define N 1000050
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = (cnt<<1) + (cnt<<3) + (ch-'0'), ch = getchar();
return cnt * f;
}
typedef long long ll;
int n, to[N], du[N]; bool kil[N], vis[N];
int cnt = 0;
vector<int> v[N];
vector<int> S;
int mi = 0, mx = 0;
void dfs(int u){
if(vis[u]) return; vis[u] = 1; S.push_back(u);
for(int i = 0; i < v[u].size(); i++) dfs(v[u][i]);
}
bool tg[N], exi[N];
bool check(){
int x = S[0], cnt = 1;
while(to[x] != S[0] && !tg[to[x]]){ tg[x] = true; x = to[x]; ++cnt; }
return (cnt == S.size()) && (to[x] == S[0]);
}
void calc(){
int siz = S.size();
if(check()){
if(siz == 1) ++mi, ++mx;
else mi += (siz+1) / 2, mx += siz-1;
return;
}
queue<int> q; mx += siz;
for(int i = 0; i < siz; i++) if(!du[S[i]]) q.push(S[i]), --mx;
while(!q.empty()){
int x = q.front(); q.pop(); exi[x] = 1;
if(kil[to[x]]) continue;
else{ kil[to[x]] = exi[to[x]] = 1; ++mi; if(--du[to[to[x]]] == 0) q.push(to[to[x]]); }
}
for(int i = 0; i < siz; i++){
if(!exi[S[i]]){
int cnt = 0;
for(int now = S[i]; !exi[now]; now = to[now]) ++cnt, exi[now] = 1;
mi += (cnt+1) / 2;
}
}
}
int main(){
n = read();
for(int i = 1; i <= n; i++){
to[i] = read(), ++du[to[i]];
v[i].push_back(to[i]);
v[to[i]].push_back(i);
}
for(int i = 1; i <= n; i++){
if(!vis[i]){ S.clear(); dfs(i); calc(); }
} cout << mi << " " << mx;
return 0;
}