比赛地址
A. Fence Planning
题目大意
在二维坐标轴上给你n个点,并且告诉你m条边,求出需要完全围住一个联通块的矩形周长的最小值
题目分析
显然本题需要用到并查集,而围住一个集合中所有点的矩形周长最小值为:(y_max-y_min)*2 + (x_max-x_min)*2
代码
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int p[N];
LL n,m,x[N],y[N];
bool st[N];
struct Node
{
LL x,y;
}node[N];
vector<Node> q[N];
int find(int x)
{
if(p[x]!=x)p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++)p[i] = i;
for(int i = 1; i <= n; i ++)
scanf("%lld%lld",&node[i].x,&node[i].y);
for(int i = 1; i <= m; i ++)
{
int a,b;
scanf("%d%d",&a,&b);
p[find(a)] = p[find(b)];
}
for(int i = 1; i <= n; i ++)
{
int f = find(i);
q[f].push_back(node[i]);
st[f] = true;
}
LL ans =-1;
for(int i = 1; i <= n; i ++)
if(p[i]==i)
{
LL x_min = q[i][0].x, x_max = q[i][0].x, y_min = q[i][0].y, y_max = q[i][0].y;
for(int j = 0; j < q[i].size(); j ++)
x_min = min(x_min,q[i][j].x),y_min = min(y_min,q[i][j].y),x_max = max(x_max,q[i][j].x),y_max = max(y_max,q[i][j].y);
if(ans == -1 || ans > (x_max-x_min)+(y_max-y_min))ans = (x_max-x_min)+
(y_max-y_min);
}
cout << ans*2;
return 0;
}
B. Snakes
题目大意
有n组蛇,你需要用网来捕捉它们,网有一个大小属性s
ize,可以捕捉x条蛇(x<=size)同时会浪费size-x的空间,初始网的大小可以任意,中途可以换k次网,问最小浪费空间为多少。
题目分析
不难看出这是一道dp的问题,但个人认为本题的难度在于dp维度的选择,按照套路一般会有一个决定终点位置的维度,还有一个可以是最后一个网的起点位置,但是状态转移所需时间太长,且无法判断网的个数,若想判断网的个数,那么最好是以总共有多少种网作为第二维度。
而经过分析可以知道,在[l,r]这段区间内如果只能用一张网的话那么最小的浪费值为:m(r-l+1)-s[r]+s[l-1],其中m为该区间内的最大值,s数组为前缀和数组
所以会有三次遍历,首先遍历总共网的种数,然后遍历终点,最后遍历起点,如果想要在o(1)的时间内求出m,可以把起点从大到小遍历,然后在线更新即可,也可以先预处理出来这样的数组
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 410;
int n,k,a[N],s[N],f[N][N];
int main()
{
cin >> n >> k;
for(int i = 1; i <= n; i ++)
{
cin>>a[i];
s[i] = s[i-1]+a[i];
}
memset(f,0x3f,sizeof f);
for(int i = 0; i <= k+1; i ++)f[i][i] = 0;//初始状态为前i个位置用i个网,空间无浪费
for(int i = 1; i <= k+1; i ++)//枚举总共有几个网
{
for(int j = i+1; j <= n;j++)//枚举最后一个网终点位置
for(int t = j,tmp = 0; t>=i; t --)//枚举最后一个网起点位置
{
tmp = max(tmp,a[t]);
f[j][i] = min(f[j][i],f[t-1][i-1] + tmp*(j-t+1)-s[j]+s[t-1]);
}
}
cout << f[n][k+1];
return 0;
}
D. Left Out
题目大意
给你一个n×n并且只含LR的方阵,有以下两种操作:
1.把某一列L、R对调
2.把某一行L、R对调
问是否经历若干次操作后能否做到除了某一个点其余点都是L或R,若存在,输出这个点,某则输出-1
题目分析
首先,LR的方阵我们可以看成一个01矩阵
最暴力的做法:
枚举每一行每一列是否经行这个操作,那么操作的时间复杂度为O(4n)
如何优化:
如果我们想把第一行全部变为与(1,1)相同的元素,那么我们看第一行的2~n列是否与(1,1)元素相同,若不相同则对这一列经行操作,否则不操作,对于变为1或对于第一列也是同理,这样操作的复杂度就降为O(n)了
进一步分析:
但是我们发现程序连样例都过不了,因为如果错误的点恰好在第一行或者第一列的话,那么我们的操作就有问题了,但是我们将点的范围缩小了。
1.如果点为(1,1)
那么我们对列与对行的操作其实是相反的,也就是说你本应操作的但没操作,没有操作的却操作了,这样我们只需对2~n行与列再全部都进行一次操作即可,这样等同于对第一行与第一列经行操作
2.如果点不为(1,1)但在第一行或第一列上
这样我们对某一行或者某一列进行这样的操作就可以了
多组答案:
显然,如果有多组答案,那么n=2,且每个点都是答案
最后,我们只需按题目要求的排序方式来枚举答案即可
代码
#include <iostream>
using namespace std;
const int N = 1010;
char op[N];
bool st[N][N];
int n,x[N],y[N];
//修改行
void modir(int x)
{
for(int i = 1; i <= n; i ++)
st[x][i] = !st[x][i];
}
//修改列
void modic(int x)
{
for(int i = 1; i <= n; i ++)
st[i][x] = !st[i][x];
}
bool check()
{
int cnt[2]={0,0},x0,y0,x1,y1;
for(int i = 1; i <= n;i ++)
for(int j = 1; j <= n; j ++)
if(st[i][j]) cnt[1]++,x1=i,y1=j;
else cnt[0]++,x0=i,y0=j;
if(cnt[0]==1)
{
cout << x0<<' ' << y0;
return true;
}
else if(cnt[1]==1)
{
cout << x1 << ' ' << y1 ;
return true;
}
return false;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> op+1;
for(int j = 1; j <= n; j++)
st[i][j] = (op[j]=='L');
}
for(int i = 2; i <= n; i ++)
{
if(st[1][i]!=st[1][1])modic(i);
if(st[i][1]!=st[1][1])modir(i);
}
//检查(1,1)
modir(1),modic(1);
if(check())return 0;
modir(1),modic(1);
if(check())return 0;
int cnt = 0;
for(int i = 1; i<=n;i++)
for(int j = 1; j<=n; j ++)
if(st[i][j]!=st[1][1])
cnt++,x[i]++,y[j]++;
if(cnt==n-1)
{
for(int i = 2; i <= n; i++)
if(x[i]==n-1)
{
cout << i << ' ' << 1;
return 0;
}
else if(y[i]==n-1)
{
cout << 1 << ' ' <<i;
return 0;
}
}
cout << -1;
return 0;
}
F. Milk Factory
题目大意
有n个点,n-1条有向边,问是否存在一个点能从其他任何点过来
分析
因为数据较小,直接暴搜就ac
代码
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int N = 110;
vector <int> point[N];
bool vis[N];
void dfs(int now, int to, bool &flag)
{
if(now == to)flag = true;
if(flag)return;
vis[now] = true;
for(int i = 0; i < point[now].size(); i ++)
{
int t = point[now][i];
if(!vis[t])
dfs(t,to,flag);
}
}
int main()
{
int n;cin >> n;
for(int i = 1; i <= n-1; i ++)
{
int a,b;cin >> a >> b;
point[a].push_back(b);
}
for(int i = 1; i <= n; i ++)
{
bool flag;
for(int j = 1; j<=n;j++)
{
flag = false;
memset(vis,0,sizeof vis);
dfs(j,i,flag);
if(!flag)break;
}
if(flag)
{
cout << i << endl;
return 0;
}
}
cout << -1;
return 0;
}
G. Bucket Brigade
题目大意
给你一个起点和一个终点,还有一堆障碍物,求起点和终点的最短路
题目分析
BFS
代码
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
struct Point
{
int x,y;
};
char m[20][20];
int f[20][20],dx[4] = {0,0,-1,1},dy[4]={1,-1,0,0};
vector<Point> barn, lake, rock;
int main()
{
for(int i = 1; i <= 10; i ++)
cin >> m[i]+1;
queue<Point> q;
for(int i = 1; i <= 10; i ++)
for(int j = 1; j <= 10; j ++)
{
if(m[i][j] == 'B')
{
q.push({i,j});
f[i][j] = 1;
}
else if(m[i][j] == 'L')lake.push_back({i,j});
else if(m[i][j] == 'R')f[i][j] = -1;
}
while(q.size())
{
int x = q.front().x, y = q.front().y;
q.pop();
for(int i = 0; i < 4; i ++)
{
int tx = dx[i]+x, ty = y +dy[i];
if(tx>0&&tx<=10&&ty>0&&ty<=10&&m[tx][ty]!='R'&&!f[tx][ty])
{
q.push({tx,ty});
f[tx][ty] = f[x][y]+1;
}
}
}
int x = lake[0].x,y=lake[0].y;
cout << f[x][y] -2;
return 0;
}
H. I Would Walk 500 Miles
题目大意
有N头奶牛,编号为1~N。x,y(x<y)两头奶牛相见的距离为(2019201913x+2019201949y) mod 2019201997。现将N头奶牛分为k组,求两头在不同组的奶牛的距离的最小值的最大值。
题目分析
首先初读题,若把奶牛看为点很容易联想到图。分成k组相当与建立n-k条边,那么只需把所有边排序然后再添加适当的n-k条边就好了,但这种方法时间复杂度为n方logn,想要对此优化,可以处理出任意奶牛x与编号后面所有奶牛的距离的最小值,即设f(x) = min(g[x][x+1],…g[x][n]),则将f(x)排序后第n-k+1即为答案。
代码
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL N = 7510,mod = 2019201997;
LL ans[N];
int main()
{
LL n,k;
cin >> n >> k;
for(int i = 1; i <= n; i ++)
ans[i] = mod;
for(LL i = 1; i <= n; i ++)
for(LL j = i+1; j <= n; j ++)
ans[i] = min(ans[i],((LL)2019201913*i+(LL)2019201949*j)%mod );
sort(ans+1,ans+1+n);
cout <<ans[n-k+1]<<endl;
return 0;
}