8.5集训模拟赛题解
A.猫和狗
题目描述
小\(k\)同学正在玩一个游戏,在游戏中他扮演了一个马戏团的老板,现在小\(k\)同学需要利用马戏团中的\(A\)只猫和\(B\)只狗举办一次表演,表演之前他让观众进行了投票,投票的类容是:我想看到第_号猫/狗的表演,不想看到第_号猫/狗的表演。
注意到每个观众都是更喜欢猫或更喜欢狗,所以两个空后面一定会被勾上不同的内容。喜欢猫的观众会在第一空后面选择猫,第二空后面选择狗;反之就会在第一空后面选择狗,第二空后面选择猫。
对于每一个观众,只有当他投票的内容都被满足了(即他想看到的动物出场表演,他不想看到的动物不参与表演)的时候,他才会来看表演。当然啦,看表演是要付门票钱的,作为马戏团的老板,小\(k\)自然是希望来的人越多越好了。他想找到一个安排动物表演的方案,使得来看表演的观众尽量多。
输入格式
第 1 行 3 个正整数 \(n,m,k\),分别表示猫、狗和观众的数量
第 \(2∼k+1\)行,每行描述了一个观众的投票内容。
首先输入一个字符\(C\)或\(D\)紧接着是一个数字,表示某个观众想看到的动物,然后是一个空格隔开,接下去又是一个\(C\)或\(D\)加上一个数字,表示这个观众不想看到的动物,同一行中一定不会出现两个\(C\)或两个\(D\)。
输出格式
输出一行一个正整数,表示小\(k\)在最优的安排下可以吸引多少观众来看表演。
样例
\(input\)
1 2 4
C1 D1
C1 D1
C1 D2
D2 C1
\(output\)
3
思路
把所有喜欢第\(i\)条猫和所有不喜欢第\(i\)条猫的建边,把所有喜欢第\(i\)条狗和不喜欢第\(i\)条狗的建边,然后跑匈牙利算法,求最大匹配,最大匹配中有一半的数可以不冲突,再用总人数减一下就是答案。
代码实现
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#define rint register int
using namespace std;
const int maxn = 600 + 50;
inline int read(){
int x = 0, f = 1; char ch = getchar();
for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int n, m, k;
string ch1[maxn], ch2[maxn];
vector<int> girls[maxn];
int girl_fri[maxn];
bool vis[maxn];
void readin(){
n = read(), m = read(), k = read();
for(rint i=1;i<=k;i++) cin>>ch1[i]>>ch2[i];
}
void Init(){
for(rint i = 1; i <= k; i++){
for(rint j = 1; j <= k; j++){
if(ch1[i] == ch2[j]){
girls[i].push_back(j), girls[j].push_back(i);
}
}
}
}
int check(int u){
for(rint i = 0; i < girls[u].size(); i++){
int v = girls[u][i];
if(vis[v] == 0){
vis[v] = 1;
if(!girl_fri[v] || check(girl_fri[v])==1){
girl_fri[v] = u;
return 1;
}
}
}
return 0;
}
void solve(){
int ans = 0;
for(rint i = 1; i <= k; i++){
memset(vis, 0, sizeof(vis));
ans = ans + check(i);
}
cout<<k - (ans / 2)<<endl;
}
int main(){
//freopen("a.in","r", stdin);
readin();
Init();
solve();
return 0;
}
B.旋转子段
题目描述
\(ZYL\)有\(N\)张牌编号分别为\(1,2,……,N\)他把这\(N\)张牌打乱排成一排,然后他要做一次旋转使得旋转后固定点尽可能多。
如果第\(i\)个位置的牌的编号为\(i\),我们就称之为固定点。旋转可以被认为是将其中的一个子段旋转\(180\)度,这意味着子段的第一张牌和最后一张牌交换位置,以及第二张牌和倒数第二张牌交换位置,等等。写一个程序,找到旋转子段(子段长度可以为 1)。
输入格式
第一行包含一个整数\(N\)(\(N (1 ≤ N ≤100 000)\))
第二行有\(N\)个数,第\(i\)个数表示旋转之前第\(i\)个位置的牌的编号。
样例
\(input\)
4
3 2 1 4
\(output\)
4
思路
维护一个前缀和数组,记录前i个数中有几个固定点,本题容易想到一个\(n^2\)的暴力方法,枚举每一个点作为翻折的中点,然后从小到打枚举长度,然后不停更新\(ans\),可以搞到大概65分,这算是最裸的一个暴力。
代码实现(\(n^2\)暴力60分代码)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 1e5+50;
inline int read(){
int x = 0, f = 1; char ch = getchar();
for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int n, a[maxn];
int sum[maxn];
int main(){
n = read();
for(register int i = 1; i <= n; i++){
a[i] = read();
sum[i] = sum[i - 1];
if(a[i] == i) sum[i]++;
}
int ans = 0;
int l, r;
int cnt = 0;
int len[maxn];
for(register int i = 1; i <= n; i++){
l = i - 1, r = i + 1;
if(a[i] == i) cnt++;
while(l >= 1 && r <= n){
if(a[r] == l) cnt++;
if(a[l] == r) cnt++;
ans = max(sum[l - 1] + sum[n] - sum[r] + cnt, ans);
l--, r++;
}
cnt = 0;
l = i, r = i + 1;
while(l >= 1 && r <= n){
if(a[r] == l) cnt++;
if(a[l] == r) cnt++;
ans = max(sum[l - 1] + sum[n] - sum[r] + cnt, ans);
l--, r++;
}
cnt = 0;
if(i == 8000){//既然要爆炸,为何不中间直接跳出呢2333
break;
}
}
ans = max(ans, sum[n]);
cout<<ans<<endl;
return 0;
}
然后在想一种优化,如果要翻转,那么左边界的点翻转过去肯定满足固定点或者右边界的点翻转过去一定满足固定点,否则不如不翻转,所以可以减去很多枝,代码也很好实现,找到每一个点的对应点,然后接着扫一遍
代码实现(\(n^2\)优化版,65分貌似没啥用啊喂)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 5e5+50;
inline int read(){
int x = 0, f = 1; char ch = getchar();
for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int n;
int a[maxn], sum[maxn];
int main(){
n = read();
for(register int i = 1; i <= n; i++){
a[i] = read();
sum[i] = sum[i - 1];
if(a[i] == i) sum[i]++;
}
int x;
int l, r;
int ans = 0;
int cnt;
for(register int i = 1; i <= n; i++){
x = a[i];
l = min(x, i), r = max(i, x);
if(l == r) continue;
cnt = 0;
while(l <= r){
if(l != r){
if(a[l] == r) cnt++;
if(a[r] == l) cnt++;
}else if(l == r){
if(a[l] == l) cnt++;
}
l++, r--;
}
ans = max(ans, cnt + sum[min(x, i)] + sum[n] - sum[max(x, i)]);
if(i == 90000) break;
}
cout<<ans<<endl;
}
然后就该正解了,找个规律,看样例要翻转的段1+3=4, 2+2=4,3+1=4,所以可以发现规律:设\(a[i]+i=n\),所有\(a[i]+i=n\)的点,都是关于一个对称轴对称的因为对称轴等于\(\frac{i+a[i]}{2}\)即\(\frac{n}{2}\),同样的如果\(j+a[j]=n\),对称轴也是\(\frac{n}{2}\),所以我们把同一个对称轴的点一起翻转,利用点到对称轴的距离从小到大排序,因为大的翻转一定包含了小的翻转,用vector存一下每个对称轴下的所有对称点,即可求解
代码实现(\(AC\))
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
const int maxn = 5e5+50;
inline int read(){
int x = 0, f = 1; char ch = getchar();
for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int n, a[maxn], sum[maxn];
vector<int> vec[maxn << 1];
bool cmp(int x, int y){
return abs ((x << 1) - a[x] - x ) < abs((y << 1) - a[y] - y);
}
int main(){
n = read();
for(register int i = 1; i <= n; i++){
a[i] = read();
vec[a[i] + i].push_back(i);
sum[i] = sum[i-1];
if(a[i] == i) sum[i]++;
}
int l, r;
int ans = 0;
for(register int i = 2; i <= 2 * n; i++){
if(!vec[i].empty()){
sort(vec[i].begin(), vec[i].end(), cmp);
for(register int j = 0; j < vec[i].size(); j++){
l = vec[i][j];
r = a[l];
if(l > r) swap(l, r);
ans = max(ans, sum[l - 1] + sum[n] - sum[r] + j + 1);
}
}
}
cout<<ans<<endl;
return 0;
}
C.走格子
题目描述
\(CYJ\)想找到他的小伙伴\(FPJ\),\(CYJ\)和\(FPJ\)现在位于一个房间里,这个房间的布置可以看成一个\(N\)行\(M\)列的矩阵,矩阵内的每一个元素会是下列情况中的一种:
障碍区域—这里有一堵墙(用 ‘#’表示).
这是\(CYJ\)最开始在的区域(用‘\(C\)’表示).
这是\(FPJ\)在的区域(用‘\(F\)’表示).
空区域(用‘.’表示).
\(CYJ\)携带了一个所谓传送枪的东西,这是一把可以创造传送门的枪械,在每一次行动中,他可以选择下列操作中的一项:
移向一个相邻的格子中(上,下,左,右,不能移到墙在的格子里).这个操作要消耗一个单位的时间.
转向一个墙(不需要相邻,只需面向即可),向其发射传送门,传送门会留在墙内面向你的地方(至多只能同时存在两扇传送门),若墙上已经有两扇传送门,而你发射了第三扇,那么最初发射的那一扇会消失。同时,你无法在一个位置制造两扇传送门(这个操作不会耗费时间)。
如果他与一块墙壁相邻且面前有一扇传送门,那么他可以移动到另一扇传送门前方的格子。这个操作会耗费一个单位的时间.
\(CYJ\)想要知道自己最少需要多少时间才能够从起点(‘\(C\)’)到达终点(‘\(F\)’).
请注意:我们保证地图边缘会是一圈墙壁且一定存在 ‘\(C\)’,‘\(F\)’.
输入格式
第一行输入两个正整数\(N\)和\(M\),(\(4≤N,M≤500\)).表示地图大小。
接下来的\(N\)行每行一个长度为\(M\)的字符串.表示地形。
输出格式
你需要输出最少的到达终点的时间,如果不能到达请输出”\(no\)”。
样例
\(input\)
6 8
########
#.##..F#
#C.##..#
#..#...#
#.....##
########
\(output\)
4
思路
就是建图然后跑spfa就完了,要是某个点在中间,不挨着墙,那么该点到边上一点的距离就是该点到墙的最短距离(利用传送门,走到最近的一面墙,走之前在要去的地方打上传送门),其他的就随便把该建的边都建上就好。
代码实现
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 1000 + 5, maxe = 6e6 + 5, INF = 0x3f3f3f3f;
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
int n, m, ans = INF, tx, vis[maxe], ty, qx, qy, head[maxe], tot, dis[maxe], inq[maxe];
char a[maxn][maxn];
struct Edge {
int to, next, from, dis;
} e[maxe];
void Add(int x, int y, int z) {
e[++tot].next = head[x];
e[tot].to = y;
e[tot].from = x;
e[tot].dis = z;
head[x] = tot;
}
void Spfa(int S) {
for (int i = 0; i <= n * m * 2; i++) dis[i] = INF;
dis[S] = 0;
queue<int> q;
q.push(S);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0; // cout<<u<<endl;
for (int x = head[u]; x; x = e[x].next) {
int v = e[x].to;
if (dis[v] > dis[u] + e[x].dis) {
dis[v] = dis[u] + e[x].dis;
if (!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
if (dis[(tx - 1) * m + ty] == INF)
puts("no");
else
cout << dis[(tx - 1) * m + ty] << endl;
}
int main() {
n = read();
m = read();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
if (a[i][j] == 'C')
qx = i, qy = j, a[i][j] = '.';
if (a[i][j] == 'F')
tx = i, ty = j, a[i][j] = '.';
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i][j] == '.') {
if (a[i + 1][j] == '.')
Add((i - 1) * m + j, i * m + j, 1);
if (a[i - 1][j] == '.')
Add((i - 1) * m + j, (i - 2) * m + j, 1);
if (a[i][j + 1] == '.')
Add((i - 1) * m + j, (i - 1) * m + j + 1, 1);
if (a[i][j - 1] == '.')
Add((i - 1) * m + j, (i - 1) * m + j - 1, 1);
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i][j] == '.') {
int x1 = i, x2 = i, y1 = j, y2 = j, diss = INF;
while (a[x1 + 1][j] == '.') x1++;
while (a[i][y1 + 1] == '.') y1++;
while (a[x2 - 1][j] == '.') x2--;
while (a[i][y2 - 1] == '.') y2--;
diss = min(min(x1 - i + 1, i - x2 + 1), min(y1 - j + 1, j - y2 + 1));
Add((i - 1) * m + j, (x1 - 1) * m + j, diss);
Add((i - 1) * m + j, (x2 - 1) * m + j, diss);
Add((i - 1) * m + j, (i - 1) * m + y1, diss);
Add((i - 1) * m + j, (i - 1) * m + y2, diss);
}
}
}
Spfa((qx - 1) * m + qy);
return 0;
}
D.柱状图
不会三分,不会模拟退火,咕了咕了