Codeforces Round #542
abstract
I决策中的独立性,
II联通块染色板子
IIIVoronoi diagram O(N^2 logN)
VI环上距离分类讨论加取模,最值中的决定性元素
V代数构造
B
题意
2n个数排成一行,1..n各出现两次,两个人分别从1按顺序走到n(每个数只能被一个人路过),问他们两个人的距离和的最小值(两个数之间的距离等于他们位置(下标)值差)?
题解
将每个数的两个位置用pair存起来(eg : pos[1].x , pos[1].y)。
首先我们发现两个人的选择其实并不独立,如果一个人选择了一条路,另一个人的路也就确定了。其次我们发现每两个数之间的路径是相互独立的。
所以对于每一段 i to i+1 我们只要取min(abs(pos[i + 1].x - pos[i].x) + abs(pos[i + 1].y - pos[i].y), abs(pos[i + 1].x - pos[i].y) + abs(pos[i + 1].y - pos[i].x))即可。 最后求和
代码
//头文件省略
cin >> n;
rep(i, 1, 2 * n) {
int a;
cin >> a;
if (pos[a].x == 0)pos[a].x = i;
else pos[a].y = i;
}
ll ans = pos[1].x + pos[1].y - 2;;
rep(i, 1, n - 1)c[i] = min(abs(pos[i + 1].x - pos[i].x) + abs(pos[i + 1].y - pos[i].y), abs(pos[i + 1].x - pos[i].y) + abs(pos[i + 1].y - pos[i].x));
rep(i, 1, n-1)ans += c[i];
cout << ans;
C
题意
求网格图上联通块之间的最短距离平方。
sample input
5
1 1
5 5
00001
11111
00111
00110
00110
sample input
10
其中0代表陆地,1代表海洋
题解
直接跑一遍联通块染色,然后暴力O(n^4)就能过。
优化的方法是取边界,O(n^3)。
最优解是Voronoi diagram O(N^2 logN)
代码
#include<algorithm>
#include<iostream>
#include<sstream>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<math.h>
#include<stdio.h>
#include<vector>
#include<queue>
#include<string>
#include<ctime>
#include<stack>
#include<map>
#include<set>
#include<list>
using namespace std;
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
#define REP(i,j,k) for(int i = (int)j;i < (int)k;i ++)
#define per(i,j,k) for(int i = (int)j;i >= (int)k;i --)
#define debug(x) cerr<<#x<<" = "<<(x)<<endl
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define MD(x) x%=mod
#define FAST_IO ios_base::sync_with_stdio(false); cin.tie(nullptr)
#define precise(x) fixed << setprecision(x)
//#define x first
//#define y second
typedef double db;
typedef long long ll;
const int MAXN = 256;;
const int maxn = 2e5+2;
const int INF = 1e9;
const db eps = 1e-7;
const int mod = 1e9 + 7;
//a[maxn], b[maxn];
int A[maxn];
char mmp[55][55];
int color[55][55];
int n,m;
int dir[4][2] = { 0,1 ,1,0, 0,-1, -1,0 };
int colornum = 0;
void dfs(int x, int y) {
if (color[x][y])return;
color[x][y] = colornum;
rep(i, 0, 3) {
int dx = x + dir[i][0];
int dy = y + dir[i][1];
if (dx<1 || dx>n || dy<1 || dy>n)continue;
if(mmp[dx][dy]=='0')dfs(dx, dy);
}
}
void bfs(int x, int y) {
queue<pair<int, int> >Q;
Q.push({ x,y });
color[x][y] = colornum;
while (!Q.empty()) {
auto v = Q.front();
Q.pop();
rep(i, 0, 3) {
int dx = v.first + dir[i][0];
int dy = v.second + dir[i][1];
if (color[dx][dy])continue;
if (dx<1 || dx>n || dy<1 || dy>n)continue;
if (mmp[dx][dy] == '0')Q.push({ dx,dy }),color[dx][dy]=colornum;
}
}
}
int main() {
FAST_IO;
cin >> n;
int r1, c1, r2, c2;
cin >> r1 >> c1 >> r2 >> c2;
rep(i, 1, n)cin >> mmp[i] + 1;
rep(i, 1, n)rep(j, 1, n) if(mmp[i][j]=='0'){
if (!color[i][j]) {
colornum++;
bfs(i, j);
}
}
int ans = 1e9;
if (color[r1][c1] == color[r2][c2])ans = 0;
else {
rep(i, 1, n)rep(j, 1, n)if (color[i][j] == color[r1][c1]) {
rep(ii, 1, n)rep(jj, 1, n)if (color[ii][jj] == color[r2][c2]) {
ans = min(ans, (i - ii)*(i - ii) + (j - jj)*(j - jj));
}
}
}
cout << ans << endl;
cin >> n;
}
D
题意
有一个n个结点的环,每个结点上有一些糖果,每个糖果都有一个想要到达的结点,你现在从某个结点出发顺时针走,每次经过一个结点只能带一个糖果,问从某点出发最少走多少路程可以把所有糖果运送到它们想要到达的结点? 出发点取遍1到n。
题解
我们发现从某结点出发拿一个糖果,最坏情况下走一圈以后一定可以把它放好。而如果某节点有两颗糖果,那么至少要超过一圈才能放好。
所以 如果记所有结点中糖果数量最大值为mx,最多mx+1圈必定能全部送完。
而对一个结点来说,只有它的最后一个糖果想到达的结点决定了运完该点所有糖果总路程的长短,因此我们贪心地把路程最短的糖果留到最后拿即可。
于是问题就变成了环上求路程了,要用到取模,和分类讨论。(分类很不擅长,讨论了半天orz坑点:candy[now].size()==mx-1&&mx>1如果不加后面这个mx>1就会讨论根本没有糖果的情况,导致wa)
代码
//头文件省略
int n;
int a[maxn], b[maxn];
vector<int> candy[maxn];
int mindis[maxn];
int lastmin[maxn];
int main() {
FAST_IO;
int n, m;
cin >> n >> m;
rep(i, 1, n)mindis[i] = 1e9,lastmin[i]=0;
rep(i, 1, m) {
cin >> a[i] >> b[i];
candy[a[i]].push_back(b[i]);
mindis[a[i]] = min(mindis[a[i]], (b[i] - a[i]+n)%n);
}
int mx = 0;
rep(i, 1, n)mx = max(mx, (int)candy[i].size());
rep(i, 1, n) {
rep(j, 1, n) {
int now = (i - j+n)%n; if (now == 0)now = n;
if(candy[now].size()==mx)
lastmin[i] = max(lastmin[i],
(now - i + n) % n+mindis[now]);
else if(candy[now].size()==mx-1&&mx>1)
lastmin[i] = max(lastmin[i],
(now - i + n) % n + mindis[now]-n);
}
}
//rep(i, 1, n)cout << mindis[i] << ' '; cout << endl;
//rep(i, 1, n)cout << lastmin[i] << ' '; cout << endl;
rep(i, 1, n) {
cout <<n*(mx-1) + lastmin [i] << ' ';
}
cin >> n;
}
E
题意
构造题,给你一个问题,和一个错误算法,让你构造数据使得正确答案与错误代码答案相差k。
问题是有一个数列,a[i]<1e6,n<2e3;定义 f(l,r)=(sum[r]-sum[l-1])*(r-l+1),sum为前缀和,求max{f(l,r)} for l=1 to n, r=l to n;
错误代码是线性滑窗扫一遍,每次维护一个区间窗口,如果小于0就舍弃原区间,重新开一个新区间。如果和大于0就不停加数。
题解
代码很简单,构造很巧妙,证明很数学
考虑形如 -1 , x, x, x,...x,y的数列,共n个x,1个y都为正数(其中x都是1e6,为了满足原数列每个数<=1e6的条件),错误代码给出的解是(x*n+y)*(n+1);
而正确答案应是(x*n+y-1)*(n+2)
两者差为n*x+y-2
令x=1e6,n=k/x,y=k%x即可。(代数不好推了半天orz)
代码
//头文件省略
int k; cin >> k;
n = 1;
int S = -1;
int last = -1;
while (S-n+1<k) {
n++;
int delta = k-(S - n + 1) ;
if (delta <= 1e6) { last = delta; break; }
else S += 1e6;
}
cout << n << endl;
cout << -1 << ' ';
rep(i,1,n-2)cout<< 1000000 << ' ';
if (last != -1)cout << last << endl;