CSP-S 模拟 19/09/21

T1: FJ 打算好好修一下农场中某条凹凸不平的土路。按奶牛们的要求,修好后的路面高度应当
单调不上升或单调不下降,也就是说,高度上升与高度下降的路段不能同时出现在修好的路中。
整条路被分成了 N 段, N 个整数 A 1 , . . . , A N ( N ≤ 2 , 000 ) A_1, ... , A_N (N \le 2,000) A1,...,AN(N2,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| A1B1+A2B2+...+ANBN 请你计算一下, 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(fi1,k+hjhai)(kj) ,然后前缀 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 NM 的矩阵,有 0 / 1 0/1 0/1,每次翻转会翻转上下左右和本身 5 个,问最小字典序的翻转 N ≤ 15 N\le 15 N15


首先暴力是 2 N ∗ M 2^{N*M} 2NM
然后发现可以表示为一个异或方程,变成 ( N ∗ M ) 3 (N*M)^3 (NM)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 N1
最少的情况,从链尾往上打,有 ⌊ N 2 ⌋ \left \lfloor \frac{N}{2}\right \rfloor 2N
环:
最多:显然 N − 1 N-1 N1
最少:显然 ⌈ 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值