Southeastern European Regional Programming Contest (SEERC) 2017

A: Concerts

题目大意

有 26 种演唱会,John 希望按照某个顺序看某些类型的演唱会,但是看完某种类型的演唱会后需要至少休息一定的天数才能继续看演唱会,问有多少种看演唱会的方案

题解

简单的 dp,但是 CodeForces 上的数据范围是假的。。花了好些时间检查为什么 RE。。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 1e7+10, K = 1e7+10, MOD = 1e9+7;
#define FOR(i,j,k) for(int i=j;i<=k;++i)
#define FORD(i,j,k) for(int i=j;i>=k;--i)
int k, n;
int wait[30];
char line[K], a[N];
ll f[N];

int main()
{
	int k, n;
	scanf("%d%d", &k, &n);
	FOR(i,1,26) scanf("%d", &wait[i]);
	scanf("%s", line + 1);
	scanf("%s", a + 1);
	FOR(j,1,n) f[j] = (line[1] == a[j]);
	FOR(j,1,n) (f[j] += f[j - 1]) %= MOD;
	FOR(i,2,k)
	{
		FORD(j,n,1)
		{
			if (line[i] == a[j])
			{
				int d = wait[line[i - 1] - 'A' + 1];
				if (j - d - 1 <= 0) f[j] = 0;
				else f[j] = f[j - d - 1];
			}
			else
			{
				f[j] = 0;
			}
		}
		FOR(j,1,n) (f[j] += f[j - 1]) %= MOD;
	}
	printf("%lld\n", f[n]);
	return 0;
}

D: Harry Potter and The Vector Spell

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
#define FOR(i, j, k) for (int i = j; i <= k; ++i)
int f[N];
bool b[N];
int a[N][2];

int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }

void merge(int x, int y)
{
	int fx = find(x), fy = find(y);
	f[fx] = fy;
}

int main()
{
	int n, m;
	scanf("%d %d", &n, &m);
	FOR(i,1,n) f[i] = i;
	FOR(i,1,n)
	{
		int tot;
		scanf("%d", &tot);
		while (tot--)
		{
			int k;
			scanf("%d", &k);
			if (a[k][0]) a[k][1] = i;
			else a[k][0] = i;
		}
	}
	FOR(i,1,m) merge(a[i][0], a[i][1]);
	FOR(i,1,n) b[find(f[i])] = 1;
	int ans = 0;
	FOR(i,1,n) ans += b[i];
	printf("%d\n", n - ans);
	return 0;
}

F: Binary Transformations

待补

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e3 + 10;

int c[N];
char a[N], b[N];
ll ans1[N], ans2[N];

int main()
{
	int n;
	priority_queue<pair<int, int>> q1, q2;
	ll s1 = 0, s2 = 0;
	int cnt = 0, cnt1 = 0, cnt2 = 0;

	scanf("%d", &n);
	
	for (int i = 0; i < n; ++i)
		scanf("%d", &c[i]);
	
	scanf("%s%s", a, b);
	
	for (int i = 0; i < n; ++i)
	{
		if (a[i] == '1') s1 += c[i], q1.push(make_pair(c[i], i));
		if (b[i] == '1') s2 += c[i], q2.push(make_pair(c[i], i));
		if (a[i] == '1' && b[i] == '0') ++cnt1;
		if (a[i] == '0' && b[i] == '1') ++cnt2;
		if (a[i] == '1' && b[i] == '1') ++cnt;
	}
	
	int t1 = 0, t2 = 0, t13 = 0, t23 = 0;
		
	ll ts1 = s1;
	while (!q1.empty())
	{
		pair<int, int> now = q1.top();
		q1.pop();
		
		ts1 -= now.first;
		if (a[now.second] == '1' && b[now.second] == '0')
		{
			++t1;
			s1 -= now.first;
			ans1[0] += s1;
		}
		else
		{
			++t13;
			ans1[t13] += ts1;
			ans1[t13] -= 1ll * (cnt1 - t1) * now.first;
		}
	}
	
	ll ts2 = s2;
	while (!q2.empty())
	{
		pair<int, int> now = q2.top();
		q1.pop();
		
		if (a[now.second] == '0' && b[now.second] == '1')
		{
			++t2;
			ans2[0] += s2;
			s2 -= now.first;
		}
		else
		{
			++t23;
			ans2[t23] += ts2;
			ans2[t23] -= 1ll * (cnt2 - t2) * now.first;
		}
		ts2 -= now.first;
		
		q2.pop();
	}
	
	ll ans = ans1[0] + ans2[0];
	
	for (int i = 1; i <= cnt; ++i)
	{
		ans1[i] += ans1[i - 1];
		ans2[i] += ans2[i - 1];

		ans = min(ans, ans1[i] + ans2[i]);
	}
	
	printf("%lld\n", ans);
	
	return 0;
}

G: Robots

题目大意

有 n 次加速,每次加速的加速度和加速时间都给出,问重排加速后的经过的最大位移和当前顺序加速后的位移的差。

题解

排序以后就能得到最大位移

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
const int N = int(1e4) + 10;
struct Source
{
	ld a, d;
	bool operator<(const Source &b) const
	{
		return a > b.a;
	}
} s[N];

ld calc(Source s[], int n)
{
	ld ans = 0, now = 0;
	for (int i = 0; i < n; ++i)
	{
		ans += (now + now + s[i].a * s[i].d) * s[i].d / 2;
		now += s[i].a * s[i].d;
	}
	return ans;
}

int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 0; i < n; ++i)
	{
		ll a, d;
		scanf("%lld%lld", &a, &d);
		s[i].a = a; s[i].d = d;
	}
	
	ld a = calc(s, n);
	sort(s, s + n);
	ld b = calc(s, n);
	cout << fixed << setprecision(1) << b - a << endl;
	return 0;
}

J: Cunning Friends

题目大意

现在有两个人玩取石子游戏,先手只取一次,后手必须取两次,如果先手不能再取则先手输,后手无法取两次则后手输,问先手是否必胜。

题解

#include <bits/stdc++.h>
using namespace std;
int main()
{
	int n, a1 = 0, a2 = 0, x, win;
	scanf("%d",&n);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d", &x);
		if (x == 1) ++a1;
		else if (x == 2) ++a2;
	}

	if (n % 3 == 0)
		win = a1 == n - 1 || a1 == n - 2 && a2;
	else if (n % 3 == 1)
		win = a1 >= n - 1 || a1 == n - 2 && a2;
	else // n % 3 == 2
		win = a1 >= n - 1;
	puts(win ? "Win" : "Lose");
	return 0;
}

分情况讨论。由于后手有两次取石子的机会,因此原来 Nim 游戏的必败态在多拿一次后就转化为必胜态;原来的必胜态在将取的石子堆拆成两次就保持了必胜态,但前提是取的石子堆能被拆成两次,如果取的石子堆是 1,那么必胜态就变成了必败态。如果我们没有 1 的石子堆(且石子堆不少于1堆),那么先手无论怎么拿,后手都能取得必胜态(Nim 游戏中的必败态也能转化为必胜态),先手必输。所以接下来我们考虑有 1 的情况。

  1. 如果只有 1,那么显然如果 1 的个数不是 3 的倍数,那么先手必胜,否则必败;
  2. 如果有一堆不为 1,那么先手可以直接将这堆拿完或者剩下 1 使得剩下的 1 的个数是 3 的倍数或多 1,那么先手必胜。
  3. 如果有两堆不为 1,如果 1 的个数为 3 的倍数,那么无论先手怎么拿,后手都能取完这两堆,从而将局面变为第一种情况;如果 1 的个数 % 3 = 1,如果这两堆为 2,那么先手先将一个 2 取走一半之后,1 的个数变为 % 3 = 2,后手无法将局面变为 % 3 = 0;如果这两堆为 2、3,那么先手先将一个 3 取剩 1 之后和两个 2 的情况一致;如果这两堆均为 3,那么无论先手怎么取后手都能将局面变为 % 3 = 0,先手必输。
    如果有三堆及以上的不为 1,如果先手拿 1,那么后手跟着拿两个 1,如果 1 不够,那么用两次拿走一堆非一的就能转化为没有 1 的情况,这时先手必败;如果先手拿非 1,那么后手走两次 Nim 游戏局就能保持必胜态。所以先手必败。
#include <bits/stdc++.h>
using namespace std;

int main()
{
	int n, s1 = 0, s2 = 0, s3 = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
	{
		int x;
		scanf("%d", &x);
		if (x == 1) ++s1;
		else if (x == 2) ++s2;
		else ++s3;
	}
	
	if (s2 == 0 && s3 == 0 && s1 % 3 != 0 ||
		s2 == 0 && s3 == 1 ||
		s2 == 1 && s3 == 0 ||
		s2 == 1 && s3 == 1 && s1 % 3 != 0 ||
		s2 == 2 && s3 == 0 && s1 % 3 != 0)
		puts("Win");
	else
		puts("Lose");
	return 0;
}

K: Escape Room

题目大意

给出每个位置开始的最长上升子序列长度,构造一个方案满足条件。

题解

显然第一个1就一定是n,第二个1一定是n-1,接下来就很容易构造了。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, a[N], b[N];
vector<int> id[N];

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d", &a[i]);
		id[a[i]].push_back(i);
	}
	int num = n;
	for (int i = 1; i <= n; ++i)
		for (auto j : id[i])
			b[j] = num--;
	for (int i = 1; i <= n; ++i)
		printf("%d%c", b[i], i == n ? '\n' : ' ');
	return 0;
}

L: Divide and Conquer

题目大意

给出由两棵树合并的一张图(n 个点, 2 ( n − 1 ) 2(n-1) 2(n1) 条边),询问最少删掉多少条边使得图不连通,同时输出方案数。

题解

要使得图不连通,分开来考虑两棵树,首先砍断 A 树的一条边 &lt; u , v &gt; &lt;u,v&gt; <u,v> 后 A 树就一定不连通了,因为 A 树和 B 树在此处形成了几个环,所以此时我们要求的是砍断 A 树这条边后还要砍掉一些 B 树的边,从而把这些环都砍断从而使得图不连通。对于环的定义是:环上只有 1 条边是 B 树的边,如果环上总有多条 B 树的边,我们总是可以调整成只有一条 B 树的边。那么我们就要考察砍的这条 A 树上的边所属的环了,由于只有一条 B 树的边,所以对于每条 B 树的边,我们在 A 树中把两个点间的路径(A 树上的路径)+1,那么这样砍的这条边的边权就是要砍的 B 树的边数。路径加使用树上差分即可。
然后答案只能是 2 或者 3,挺显然的。然后答案为 3 时要交换 A、B 再判断一次

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2 * N, K = 18;
int n;
struct tree
{
  int x[N], y[N], id;
  int fa[N][K];
  int h[N], p[M], v[M], edge;
  int f[N], dep[N];

  tree() : id(0), edge(0) {}

  void add(int a, int b)
  {
    x[++id] = a; y[id] = b;
    p[++edge] = h[a]; v[edge] = b; h[a] = edge;
    p[++edge] = h[b]; v[edge] = a; h[b] = edge;
  }

  void read()
  {
    for (int i = 1; i < n; ++i)
    {
      int x, y;
      scanf("%d%d", &x, &y);
      add(x, y);
    }
  }

  void sum(int u, int fa)
  {
    for (int i = h[u]; i; i = p[i])
      if (v[i] != fa)
      {
        sum(v[i], u);
        f[u] += f[v[i]];
      }
  }

  void dfs(int x, int f) {
    fa[x][0] = f; dep[x] = dep[f] + 1;
    for (int i = 1; i < K; ++i)
      fa[x][i] = fa[fa[x][i - 1]][i - 1];
    for (int i = h[x]; i; i = p[i])
      if (v[i] != f) dfs(v[i], x);
  }

  int lca(int x, int y) {
    if (dep[x] < dep[y]) swap(x, y);
    int t = dep[x] - dep[y];
    for (int i = 0; i < K; ++i)
      if ((1 << i) & t) x = fa[x][i];
    for (int i = K - 1; i >= 0; --i)
      if (fa[x][i] != fa[y][i])
        x = fa[x][i], y = fa[y][i];
    return x == y ? x : fa[x][0];
  }

  void mark(tree &other)
  {
    for (int i = 1; i <= id; ++i)
    {
      other.f[x[i]]++;
      other.f[y[i]]++;
      other.f[other.lca(x[i], y[i])] -= 2;
    }
  }
} t1, t2;

int main()
{
  scanf("%d", &n);
  t1.read(); t1.dfs(1, 0);
  t2.read(); t2.dfs(1, 0);
  t1.mark(t2); t2.sum(1, 0);
  t2.mark(t1); t1.sum(1, 0);
  int road = 0x3f3f3f3f, ans = 0;
  for (int i = 2; i <= n; ++i)
    road = min(road, min(t1.f[i], t2.f[i]));
  for (int i = 2; i <= n; ++i)
  {
    if (road == t1.f[i]) ++ans;
    if (road == t2.f[i]) ++ans;
  }
  if (road == 1) ans /= 2;
  printf("%d %d\n", road + 1, ans);

  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值