Ural Championship 2010

18 篇文章 0 订阅
8 篇文章 0 订阅

B: Transsib

题目大意

现在有一个线性规划:
max ⁡ z = x 1 + x 2 + x 3 + x 4 s . t . { x 1 + x 4 ≤ k 1 x 1 + x 3 ≤ k 2 x 3 + x 4 ≤ k 3 x 2 + x 3 ≤ k 4 x 2 + x 4 ≤ k 5 x 1 + x 2 ≤ k 6 \max z=x_1+x_2+x_3+x_4\\ s.t. \begin{cases} x_1+x_4\leq k_1 \\ x_1+x_3\leq k_2 \\ x_3+x_4\leq k_3 \\ x_2+x_3\leq k_4 \\ x_2+x_4\leq k_5 \\ x_1+x_2\leq k_6 \\ \end{cases} maxz=x1+x2+x3+x4s.t.x1+x4k1x1+x3k2x3+x4k3x2+x3k4x2+x4k5x1+x2k6
z z z 取最大值的情况下的任意一个解。

题解

单纯形走起

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 25
const double eps=0.00000001,inf=1e15;

int n,m,id[maxn*2];
double a[maxn][maxn];
double myabs(double x) {return x>0?x:-x;}

void pivot(int l,int e)
{
    int tt=id[n+l];id[n+l]=id[e];id[e]=tt;
    int i,j;double t=a[l][e];a[l][e]=1;
    for (j=0;j<=n;j++) a[l][j]/=t;
    for (i=0;i<=m;i++)
     if (i!=l && myabs(a[i][e])>eps)
     {
        t=a[i][e];a[i][e]=0;
        for (j=0;j<=n;j++)
          a[i][j]-=a[l][j]*t;
     }
}

bool initialize()
{
    while (1)
    {
        int i,j,e=0,l=0;
        for (i=1;i<=m;i++) 
            if (a[i][0]<-eps && (!l || (rand()&1))) l=i;
        if (!l) break;
        for (j=1;j<=n;j++) 
          if (a[l][j]<-eps && (!e || (rand()&1))) e=j;
        if (!e) {printf("Infeasible\n");return 0;}
        pivot(l,e);
    }return 1;
}

bool simplex()
{
    int i,j;
    while (1)
    {
        int l=0,e=0;double minn=inf;
        for (j=1;j<=n;j++)
         if (a[0][j]>eps) {e=j;break;}
        if (!e) break;
        for (i=1;i<=m;i++)
         if (a[i][e]>eps && a[i][0]/a[i][e]<minn)
          minn=a[i][0]/a[i][e],l=i;
        if (!l) {printf("Unbounded\n");return 0;}
        pivot(l,e);
    }return 1;
}
double ans[maxn];

int main()
{
    srand(time(0));int i,j;
    n=4;m=6;
	a[0][1] = a[0][2] = a[0][3] = a[0][4] = 1;
	a[1][1] = 1; a[1][4] = 1; scanf("%lf", &a[1][0]);
	a[2][1] = 1; a[2][3] = 1; scanf("%lf", &a[2][0]);
	a[3][3] = 1; a[3][4] = 1; scanf("%lf", &a[3][0]);
	a[4][2] = 1; a[4][3] = 1; scanf("%lf", &a[4][0]);
	a[5][2] = 1; a[5][4] = 1; scanf("%lf", &a[5][0]);
	a[6][1] = 1; a[6][2] = 1; scanf("%lf", &a[6][0]);
    for (i=1;i<=n;i++) id[i]=i;
    if (initialize() && simplex())
    {
        for (i=1;i<=m;i++) ans[id[n+i]]=a[i][0];
        for (i=1;i<=n;i++) printf("%.8lf ",ans[i]);
    }
    return 0;
}

C: Error 404

题目大意

有 12 个扇叶,缺了几片,问最少去掉多少个扇叶使得重心落到某个扇叶的对称轴上。

D: Humpty Dumpty

题目大意

8 * 8 的棋盘,棋子一开始在第 4 行第 6 列,现在棋子随机走向周围的 8 个格子,概率对应格子在周围 8 个格子的数字的和的占比,问无限次移动后棋子在每个格子的概率是多少

题解

实际上我们只要预先构造一个 64*64 的矩阵表示一次移动,那么这个矩阵的无限次方就是我们要的概率,由于有精度限制,所以我们做 100 次平方就可以达到题目要求的精度限制(因为再算下去也不能提升精度)

#include <bits/stdc++.h>
using namespace std;
#define FOR(i, j, k) for (int i = j; i <= k; ++i)
int n, pos;

struct Matrix
{
	double m[65][65];
	
	Matrix() { memset(m, 0, sizeof(m)); }
	double *operator[](int i) { return m[i];  }
	const double *operator[](int i) const { return m[i]; }

	void print()
	{
		int k = 0;
		FOR(i,1,8) FOR(j,1,8)
			printf("%.12lf%c", m[++k][pos], j == 8 ? '\n' : ' ');
	}

	void div()
	{
		FOR(j,1,n) {
			double sum = 0;
			FOR(i,1,n) sum += m[i][j];
			FOR(i,1,n) m[i][j] /= sum;
		}
	}

	Matrix operator*(const Matrix &b) const
	{
		Matrix c;
		FOR(i,1,n) FOR(j,1,n) FOR(k,1,n)
			c[i][j] += m[i][k] * b[k][j];
		return c;
	}
};

int get(int i, int j)
{
	return (i - 1) * 8 + j;
}

int m[10][10];

int main() {
	n = 64;
	pos = get(4, 6);
	Matrix ans;
	
	FOR(i,1,8) FOR(j,1,8) scanf("%d", &m[i][j]);
	FOR(i,1,8) FOR(j,1,8) {
		int w1 = 0, w2 = 0, w3 = 0, w4 = 0, w5 = 0, w6 = 0, w7 = 0, w8 = 0;
		if (i > 1) w1 = m[i - 1][j];
		if (i < 8) w2 = m[i + 1][j];
		if (j > 1) w3 = m[i][j - 1];
		if (j < 8) w4 = m[i][j + 1];
		if (i > 1 && j > 1) w5 = m[i - 1][j - 1];
		if (i > 1 && j < 8) w6 = m[i - 1][j + 1];
		if (i < 8 && j > 1) w7 = m[i + 1][j - 1];
		if (i < 8 && j < 8) w8 = m[i + 1][j + 1];
		
		double sum = w1 + w2 + w3 + w4 + w5 + w6 + w7 + w8;
		int src = get(i, j);
		
		if (i > 1) ans.m[get(i - 1, j)][src] = w1 / sum;
		if (i < 8) ans.m[get(i + 1, j)][src] = w2 / sum;
		if (j > 1) ans.m[get(i, j - 1)][src] = w3 / sum;
		if (j < 8) ans.m[get(i, j + 1)][src] = w4 / sum;
		if (i > 1 && j > 1) ans.m[get(i - 1, j - 1)][src] = w5 / sum;
		if (i > 1 && j < 8) ans.m[get(i - 1, j + 1)][src] = w6 / sum;
		if (i < 8 && j > 1) ans.m[get(i + 1, j - 1)][src] = w7 / sum;
		if (i < 8 && j < 8) ans.m[get(i + 1, j + 1)][src] = w8 / sum;
	}
	
	FOR(i,1,100) {
		ans = ans * ans;
		ans.div();
	}

	ans.print();
	return 0;
}

E: The House of Doctor Dee

题目大意

两对起点到终点,在网格上分别走最短路最多重合边数为多少。

题解

讨论一下即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int main()
{
	ll s1[2], t1[2], s2[2], t2[2];
	scanf("%lld%lld", &s1[0], &t1[0]);
	scanf("%lld%lld", &s1[1], &t1[1]);
	scanf("%lld%lld", &s2[0], &t2[0]);
	scanf("%lld%lld", &s2[1], &t2[1]);
	
	if (s1[0] > s1[1]) swap(s1[0], s1[1]), swap(t1[0], t1[1]);
	if (s2[0] > s2[1]) swap(s2[0], s2[1]), swap(t2[0], t2[1]);
	
	bool check = (t1[1] > t1[0]) ^ (t2[1] > t2[0]);
	ll tx = -1, ty = -1;
	
	if (t1[0] > t1[1]) swap(t1[0], t1[1]);
	if (t2[0] > t2[1]) swap(t2[0], t2[1]);
	
	if (s1[0] <= s2[1] && s2[0] <= s1[1]) // cross
	{
		tx = min(s2[1] - s1[0], s1[1] - s2[0]);
		tx = min(tx, s1[1]-s1[0]);
		tx = min(tx, s2[1]-s2[0]);
	}
	 
	if (t1[0] <= t2[1] && t2[0] <= t1[1]) // cross
	{
		ty = min(t2[1] - t1[0], t1[1] - t2[0]);
		ty = min(ty, t1[1] - t1[0]);
		ty = min(ty, t2[1] - t2[0]);
	}
	
	if (tx < 0 || ty < 0) puts("0");
	else if (check) printf("%lld\n", max(tx, ty));
	else printf("%lld\n", tx+ty);
	
	return 0;
}

F: Circular Strings

题目大意

判断给定的点能不能组成正多边形(按照多边形上的点的顺序依次给出坐标)

题解

精度问题比较严重。
我的做法是先算出中心点,然后正多边形满足中心到正多边形上点的向量旋转 θ = 2 π n \theta=\frac{2\pi}{n} θ=n2π rad 后就是下一个点(也就是说 ∠ P i O P i + 1 = 2 π n \angle P_iOP_{i+1}=\frac{2\pi}{n} PiOPi+1=n2π)那么我们预先计算出初始的一个向量后,旋转 k θ k\theta kθ,再判断是不是相应的点,就可以了。

代码

#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-7, pi = acos(-1.0);
const int N = 105;
int dcmp(double x)
{
	return x > eps ? 1 : x < -eps ? -1 : 0;
}

struct Point
{
	double x, y;
	Point() : x(0), y(0) {}
	Point(double x, double y) : x(x), y(y) {}
	
	Point operator-(const Point &b) const
	{
		return Point(x - b.x, y - b.y);
	}
	
	Point operator+(const Point &b) const
	{
		return Point(x + b.x, y + b.y);
	}
	
	bool operator!=(const Point &b) const
	{
		return dcmp(x - b.x) != 0 || dcmp(y - b.y) != 0;
	}
	
	double dot(const Point &b) const
	{
		return x * b.x + y * b.y;
	}
	
	double cross(const Point &b) const
	{
		return x * b.y - y * b.x;
	}
	
	double len() const
	{
		return sqrt(x * x + y * y);
	}
	
	double angle() const
	{
		return atan2(y, x);
	}
} p[N];

bool check(int n)
{
	Point c;
	for (int i = 0; i < n; ++i)
		c = c + p[i];
	c.x /= n; c.y /= n;
	
	double len = (p[0] - c).len();
	double angle = (p[0] - c).angle();
	double delta = 2 * pi / n;
	bool flag = true;
	for (int i = 1; i < n; ++i)
	{
		Point t = c + Point(cos(angle + delta * i) * len, sin(angle + delta * i) * len);
		if (t != p[i])
			return false;
	}
	return true;
}

int main()
{
	int n;
	scanf("%d", &n);
	
	for (int i = 0; i < n; ++i)
		scanf("%lf%lf", &p[i].x, &p[i].y);
	if (check(n)) return puts("YES"), 0;
	reverse(p, p + n);
	if (check(n)) return puts("YES"), 0;
	return puts("NO"), 0;
}

G: Old Ural Legend

题目大意

给出一个字符串,问最小的数字使得这个数字不会出现在字符串中。

题解

由于这个字符串长度为 O ( 1 0 5 ) O(10^5) O(105),所以最小的数字最大也就 5 位( 1 0 k × k ≤ 1 0 5 → k ≈ 5 10^k\times k\leq 10^5\rightarrow k\approx 5 10k×k105k5),所以我们提取出所有的长度为1的子串,2的子串,。。。,5的子串,然后判断一下就好了。

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
char s[N];
bool vis[N];

int main()
{
	scanf("%s", s);
	int n = strlen(s);
	
	for (int i = 0; i < 6; ++i)
	{
		for (int j = i; j < n; ++j)
		{
			int t = 0;
			for (int k = j - i; k <= j; ++k)
				t = t * 10 + s[k] - '0';
			vis[t] = 1;
		}
	}
	
	for (int i = 1; ; ++i) {
		if (!vis[i]) {
			printf("%d\n", i);
			break;
		}
	}
	
	return 0;
}

H: The Party of Ural Champions

题目大意

一个完全图,一条边上可能有一个广告牌,但是只能从一个方向看到,给出两两之间的路最少能看到多少个广告牌,要求构造一个广告牌的放置方案满足输入条件。

题解

a i , j = a j , i = 0 a_{i,j}=a{j,i}=0 ai,j=aj,i=0,那么必须用无广告牌的路直接连起来。
对于只用无广告牌的路连起来的连通块,若 a i , j = 1 , a j , i = 0 a_{i,j}=1,a_{j,i}=0 ai,j=1,aj,i=0,则 i->j 连一条有广告牌的边,最后 floyd 判断是否满足输入即可。

#include<bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
#define FOR(i, j, k) for (int i = j; i <= k; ++i)
const int N = 305;
int d[N][N], a[N][N], ans[N][N];
int main(){
	int n;
	scanf("%d", &n);
	FOR(i,1,n) FOR(j,1,n) {
		scanf("%d", &d[i][j]);
		if (d[i][j] >= 2) a[i][j] = inf;
		else a[i][j] = d[i][j];
	}
	FOR(i,1,n) FOR(j,1,n) if (a[i][j] == inf) a[j][i] = inf;
	FOR(i,1,n) FOR(j,1,n) if (a[i][j] == 1 && a[j][i] == 1) a[i][j] = a[j][i] = inf;
	FOR(i,1,n) FOR(j,1,n) ans[i][j] = a[i][j];
	FOR(k,1,n) FOR(i,1,n) FOR(j,1,n) if (a[i][k] + a[k][j] < a[i][j])
		a[i][j] = a[i][k] + a[k][j];
	FOR(i,1,n) FOR(j,1,n) if (a[i][j] != d[i][j]) return puts("NO"), 0;
	puts("YES");
	FOR(i,1,n) {
		FOR(j,1,n) {
			if (i == j) putchar('0');
			else if (ans[i][j] == inf) putchar('0');
			else if (ans[i][j] == 0) putchar('1');
			else putchar('2');
		}
		puts("");
	}
}

J: Ski-Trails for Robots

题目大意

现在有 n 条水平跑道,人要从左边跑到右边,有 k 个竖直的障碍,上下边界已给出,你现在一开始在第 s 条跑道,在跑道内移动不花时间,移动到相邻跑道花费1个单位的时间(只能被障碍挡住了移动,所以只能移动到 l − 1 l-1 l1或者 r + 1 r+1 r+1),问最短时间越过这k个障碍。

题解

维护一个 set,保存当前能到的位置,那么对于障碍 [ l , r ] [l,r] [l,r],我们维护到 l − 1 l-1 l1 r + 1 r+1 r+1 要花费的时间(从 [ l , r ] [l,r] [l,r]内的点出发),那么 [ l , r ] [l,r] [l,r]内的点就会被删除,并新增两个点 l − 1 , r + 1 l-1,r+1 l1,r+1,其他的点能直接越过该障碍。
加入到 set 中的元素只有 2k 个,所以最后时间复杂度大概是 O ( n log ⁡ n ) O(n\log n) O(nlogn)?并不太会证这个的时间复杂度。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100005;
const ll inf = 0x3f3f3f3f3f3f3f3fLL;
ll t[N];
template <typename T>
inline void get_min(T &a, T b)
{
	if (a > b) a = b;
}
int main()
{
	int n, s, k, l, r;
	scanf("%d%d%d", &n, &s, &k);
	for (int i = 1; i <= n; ++i)
		t[i] = inf;
	
	set<int> y;
	y.insert(s);
	t[s] = 0;
	for (int i = 1; i <= k; ++i)
	{
		scanf("%d%d", &l, &r);
		auto L = y.lower_bound(l);
		auto R = y.upper_bound(r);
		if (L == R) continue;
		for (auto it = L; it != R; )
		{
			int x = *it;
			get_min(t[l - 1], t[x] + (x - l + 1));
			get_min(t[r + 1], t[x] + (r + 1 - x));
			auto j = it++;
			y.erase(j);
			t[x] = inf;
		}
		
		if (l - 1 >= 1) y.insert(l - 1);
		if (r + 1 <= n) y.insert(r + 1);
	}
	
	ll ans = inf;
	for (int i : y) get_min(ans, t[i]);
	printf("%lld\n", ans);
	
	return 0;
}

K: Metro to Every Home

题目大意

现在有 n 个纸片,每个纸片上有一条绿线,从左边界上的 l l l 处到右边界上的 r r r 处,问经过上下翻转重新排列并排放后绿线能不能合一成一条线段。

题解

因为上下翻转不会改变绿线的斜率,所以预先判断是不是所有的斜率都相等,否则就一定无解。

做法一

首先因为纸片可以上下翻转,所以我们选择将纸片本身和翻转后的纸片都加入到一个数组中并以左端点/右端点排序(注意斜率为负时降序否则升序)。这样我们从左到右扫一遍,如果可以加到答案序列中就加,否则就不加,之前加过的现在的也不加。最后得到的答案序列如果长度不为 n 就无解,否则就是答案。
这样做的正确性挺显然的。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
bool vis[N];

struct strip
{
	int i, id, l, r;
};

bool cmp1(const strip &a, const strip &b)
{
	return a.l < b.l;
}

bool cmp2(const strip &a, const strip &b)
{
	return a.l > b.l;
}

int main()
{
	int h, n, d;
	vector<strip> strips;
	scanf("%d%d", &h, &n);
	for (int i = 1; i <= n; ++i)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		strips.push_back(strip{i, i, l, r});
		strips.push_back(strip{-i, i, h - r, h - l});

		if (i == 1)
			d = l - r;
		else if (d != l - r)
			return puts("0"), 0;
	}
	sort(strips.begin(), strips.end(), d > 0 ? cmp2 : cmp1);
	vector<strip> res;
	for (const strip &s : strips)
	{
		if (res.empty())
		{
			res.push_back(s);
			vis[s.id] = 1;
			continue;
		}

		if (vis[s.id])
			continue;

		if (res.back().r != s.l)
			continue;

		res.push_back(s);
		vis[s.id] = 1;
	}

	if (res.size() != n)
		return puts("0"), 0;

	for (const strip &s : res)
		printf("%d ", s.i);

	return 0;
}

做法二

我们开设一个 deque,表示当前的排列顺序。然后将纸片按照 ∣ l + r − h 2 ∣ \left|\frac{l+r-h}{2}\right| 2l+rh的大小进行排序(就是斜线中点到纸片中心的距离),排序好后,如果能加到deque的左边就加,右边就加到右边,翻转后能加就加,不能就无解。正确性也挺显然的。

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int h;
struct Strip
{
	int i, l, r;
	
	void reverse()
	{
		int L = h - r, R = h - l;
		l = L; r = R; i = -i;
	}
	
	bool operator<(const Strip &b) const
	{
		return abs(l + r - h) < abs(b.l + b.r - h);
	}
} s[N];

bool judge(const Strip &l, const Strip &r)
{
	return l.r == r.l;
}

int main()
{
	int n, d;
	scanf("%d%d", &h, &n);
	
	for (int i = 1; i <= n; ++i)
	{
		s[i].i = i;
		scanf("%d%d", &s[i].l, &s[i].r);
		if (i == 1) d = s[i].l - s[i].r;
		else if (d != s[i].l - s[i].r)
			return puts("0"), 0;
	}
	sort(s + 1, s + n + 1);
	
	deque<Strip> seq;
	seq.push_back(s[1]);
	for (int i = 2; i <= n; ++i)
	{
		if (judge(seq.back(), s[i]))
		{
			seq.push_back(s[i]);
		}
		else if (judge(s[i], seq.front()))
		{
			seq.push_front(s[i]);
		}
		else
		{
			s[i].reverse();
			
			if (judge(seq.back(), s[i]))
			{
				seq.push_back(s[i]);
			}
			else if (judge(s[i], seq.front()))
			{
				seq.push_front(s[i]);
			}
			else
			{
				return puts("0"), 0;
			}
		}
	}
	
	for (int i = 0; i < seq.size(); ++i)
	{
		printf("%d%c", seq[i].i, i + 1 == seq.size() ? '\n' : ' ');
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值