Tinkoff Challenge - Elimination Round

时间有点晚, 第二天早上补的...... 比赛的时候只做出来前两道题......手动忽略了C题某条件, WA不止......D题差10分钟写完. 积累了一点有关eps的经验. 题解: 5/7.

A. Oleg and shares

n个数ai, 每次可以选一个减少k, 问最少多少次操作后, 所有数可以变得相等. (1≤n≤10^5, 1≤k,ai≤10^9)

a1 - t1 k = a2 - t2 k <=> a2 - a1 = (t2 - t1)k

令最小的ai为m, 当且仅当对所有j, (aj - m) mod k = 0时有解, 答案为 Σ(aj-m)/k.

typedef long long ll;

ll a[(int)1e5];

int main()
{
	ios::sync_with_stdio(false);
	int n;
	ll k, mn = 1e9 + 1;
	cin >> n >> k;
	rep (i, 0, n) {
		cin >> a[i];
		mn = min(a[i], mn);
	}
	ll ans = 0;
	rep (i, 0, n) {
		if ((a[i] - mn) % k) {
			cout << "-1" << endl;
			return 0;
		}
		ans += (a[i] - mn) / k;
	}
	cout << ans << endl;
	return 0;
}

B. Igor and his way to work

n行m列的地图, 可以上, 下, 左, 右走, 有些位置不可通行, 初始方向任意. 问方向改变次数不超过2, 起点是否可以到达终点. (1≤n,m≤1000)

BFS. 状态为 (行, 列, 当前方向, 方向改变次数).

const int N = 1e3, dx[] = {1, -1, 0, 0}, dy[] = {0, 0, -1, 1};
typedef pair<int, int> Pair;

bool f[N][N][4][3];
char s[N][N+1];

struct State {
	int x, y, d, c;
};

queue<State> Q;

inline void ext(const State& s)
{
	bool& g = f[s.x][s.y][s.d][s.c];
	if (!g) {
		g = true;
		Q.push(s);
	}
}

int main()
{
	int n, m;
	Pair S, T;
	scanf("%d%d", &n, &m);
	rep (i, 0, n) {
		scanf("%s", s[i]);
		rep (j, 0, m) {
			if (s[i][j] == 'S')
				S = Pair(i, j);
			else if (s[i][j] == 'T')
				T = Pair(i, j);
		}
	}
	
	rep (i, 0, 4)
		ext((State){S.first, S.second, i, 0});
	while (!Q.empty()) {
		State u = Q.front();
		if (u.x == T.first && u.y == T.second) {
			puts("YES");
			return 0;
		}
		Q.pop();
		if (u.c <= 1) {
			rep (i, 0, 4)
				ext((State){u.x, u.y, i, u.c + 1});
		}
		State v = (State){u.x + dx[u.d], u.y + dy[u.d], u.d, u.c};
		if (v.x >= 0 && v.x < n && v.y >= 0 && v.y < m && s[v.x][v.y] != '*')
			ext(v);
	}
	puts("NO");
	return 0;
}

C. Mice problem

二维平面上n只老鼠做匀速直线运动. 给出它们的初始位置坐标, 沿x轴, y轴的分速度, 问它们何时第一次同时严格位于某矩形内, 或报告无解. (1≤n≤10^5, 0≤坐标≤10^5, |分速度|≤10^5)

解一解方程求交集就好了, 然而......

题面中说strictly inside.

这个有点古怪. 想了想, 我把它手动忽略了...... (不明白自己为什么这么做)

然后WA.

可以把矩形的边界 (x1, x2) 当成 [x1+eps, x2-eps] 处理, 注意eps不能设得太大. 也可以手写分数类.

我保留了 (l, r), 判断是否有 r-l > eps. eps应小于10^-10.

const double eps = 5e-11, inf = 1e70;

bool cal(double r, double v, double x1, double x2, double& t1, double& t2)
{
	x1 -= r;
	x2 -= r;
	if (fabs(v) < eps) {
		if (x2 > eps && x1 < -eps) {
			t1 = 0;
			t2 = inf;
			return true;
		} else {
			return false;
		}
	} else if (v > 0) {
		t1 = max(0., x1/v);
		t2 = x2/v;
	} else {
		t1 = max(0., x2/v);
		t2 = x1/v;
	}
	return t2 - t1 > eps;
}

inline bool intersection(double a, double b, double c, double d, double& e, double& f)
{
	e = max(a, c);
	f = min(b, d);
	return f - e > eps;
}

const int N = 1e5;

double s[N], t[N];

int main()
{
	int n, x1, x2, y1, y2;
	scanf("%d%d%d%d%d", &n, &x1, &y1, &x2, &y2);
	rep (i, 0, n) {
		double t1, t2, t3, t4;
		int rx, ry, vx, vy;
		scanf("%d%d%d%d", &rx, &ry, &vx, &vy);
		if (!(cal(rx, vx, x1, x2, t1, t2)
			  && cal(ry, vy, y1, y2, t3, t4)
			  && intersection(t1, t2, t3, t4, s[i], t[i]))) {
			puts("-1");
			return 0;
		}
	}
	rep (i, 1, n)
		if (!intersection(s[0], t[0], s[i], t[i], s[0], t[0])) {
			puts("-1");
			return 0;
		}
	printf("%.8f\n", s[0]);
	return 0;
}

D. Presents in Bankopolis

直线上依次有n个点. m条带权的单向道路. 要求选出一条恰经过k个不同点的路径, 并且, 如果走u->v, 那么(min(u,v), max(u,v))上不能有已经过的点. 求路径的最小总长度, 或报告无解. (1≤n,k≤80, 0≤m≤2000)

画一画图, 发现下一步的选择是一个区间, 所以状态可以这样表示: f[x][y][z][w], [x,y]是可选区间, 当前在z, 经过了w条边; 然后, 进行DP.

见过类似的题, 我刚理解题意, 它就被马老师秒掉了......

好像复杂度比较高......大胆猜测合法状态是稀疏的, 跑一个记忆化搜索, 然后就可以AC了.

实现的时候犯了个错误......dp数组中, inf是一个合法的量, 我却同时用它判断状态是否计算过.

Editorial提出了一个优化: [x,y] -> [z+1,y], 无须知道x; [x,y] -> [x,z-1], 无须知道y. 所以, 把状态压缩成三维: f[x][y][w], 直接规定下一步朝哪转移. 现在的位置是y. 当y>x, 下一步可以走[x,y-1]; 当y<x, 下一步可以走[y+1,x].

const int N = 80, inf = (1<<30)-1;
int k, f[N][N][N][N];
bool vis[N][N][N][N];

struct Edge {
	int v, w;
};
vector<Edge> adj[N];

int dp(int x, int y, int z, int w)
{
	int& now = f[x][y][z][w];
	bool& visited = vis[x][y][z][w];
	
	if (visited) return now;

	visited = true;
	
	if (w == k-1) return now = 0;
	now = inf;
	if (x > y) return now;

	rep (i, 0, adj[z].size()) {
		int t = adj[z][i].v;
		if (t >= x && t <= y) {
			if (t < z)
				now = min(now, dp(x, z-1, t, w+1) + adj[z][i].w);
			else
				now = min(now, dp(z+1, y, t, w+1) + adj[z][i].w);
		}
	}
	
	return now;
}

int main()
{
	int n, m;
	scanf("%d%d%d", &n, &k, &m);
	rep (i, 0, m) {
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		--u;
		--v;
		if (u != v)
			adj[u].push_back((Edge){v, w});
	}
	int ans = inf;
	rep (i, 0, n)
		ans = min(ans, dp(0, n-1, i, 0));
	printf("%d\n", ans == inf ? -1 : ans);
	return 0;
}

E. Problem of offices

n个点的有根树. a,b,c,d是叶子结点, 且两两之间的最短路经过根结点. 问是否存在一种欧拉序S, 头尾相接并合并后, S(a..b)上叶子的数目等于S(b..a)上叶子的数目, S(c..d)上叶子的数目等于S(d..c)上叶子的数目. (5≤n≤5000)

设叶子的总数为m. 当m为奇数时无解, 以下假设m为偶数.

如果只有一对点a,b, 很好办. 这样看待: CF793 E

问题转化为0-1背包.

但是有两对点. 怎样保证两个条件能同时满足呢?

看了题解, 需要一点构造.

可以约定这样走: a->c->b->d->a. 根据题设, a,b,c,d在根的不同子树中.

考虑a->c->b. 我们在c所在的子树中转了一圈. ban掉b,c所在子树, 做0-1背包.

同样地, c->b->d. ban掉a,b所在子树, 做0-1背包.

如果a->c->b存在一条经过(m/2-1-c所在子树的叶子数目)个叶子的欧拉环游路径, 并且c->b->d存在一条经过(m/2-1-b所在子树的叶子数目)个叶子的欧拉环游路径, 那么有解, 否则无解.

正确性yy一下......?

const int N = 5000;

bitset<N/2> ab, cd;
vector<int> adj[N+1];
int fa[N+1], sz[N+1];
bool ban[N+1];

int dfs(int u)
{
	if (!adj[u].size()) {
		sz[u] = 1;
	} else {
		rep (i, 0, adj[u].size())
			sz[u] += dfs(adj[u][i]);
	}
	return sz[u];
}

int up(int v)
{
	int u = v;
	while (fa[u] != 1)
		u = fa[u];
	ban[u] = true;
	return u;
}

void cal(bitset<N/2>& b, int v)
{
	int u = fa[v];
	while (u != 1) {
		rep (i, 0, adj[u].size()) {
			int w = adj[u][i];
			if (w != v)
				b |= b << sz[w];
		}
		v = u;
		u = fa[u];
	}
}

int main()
{
	int n, a, b, c, d;
	scanf("%d%d%d%d%d", &n, &a, &b, &c, &d);
	rep (i, 2, n+1) {
		scanf("%d", fa+i);
		adj[fa[i]].push_back(i);
	}
	int m = dfs(1);
	if (m & 1) {
		puts("NO");
		return 0;
	}
	up(a);
	up(d);
	int bb = up(b), cc = up(c);
	if (m/2-1 < sz[cc] || m/2-1 < sz[bb]) {
		puts("NO");
		return 0;
	}
	ab[0] = 1;
	rep (i, 0, adj[1].size()) {
		int v = adj[1][i];
		if (!ban[v])
			ab |= ab << sz[v];
	}
	cd = ab;
	cal(ab, a);
	cal(ab, b);
	if (!ab[m/2-1-sz[cc]]) {
		puts("NO");
		return 0;
	}
	cal(cd, c);
	cal(cd, d);
	if (!cd[m/2-1-sz[bb]]) {
		puts("NO");
		return 0;
	}
	puts("YES");
	return 0;
}