首页 > 技术文章 > Educational Codeforces Round 103 A~E题解

HotPants 2021-01-30 10:50 原文

本场链接:Educational Codeforces Round 103 (Rated for Div. 2)

闲话

太搞笑了,A题被hack,D题没写出来,搞笑艺人实锤

A. K-divisible Sum

题目大意:构造一个长度为\(n\)的数组,每个数是正整数,要求整个数组的和是\(k\)的倍数,并且整个数组最大的数最小.

思路

如果\(n \leq k\)那么问题就等价于:平均分配每个元素,使最大值最小,并且整个数组的和是\(k\).直接做就可以了.

反之,先把\(k\)乘上一个正整数\(z\)使得\(z*k\geq n\)再同等去做.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	


int main() 
{
	int T;scanf("%d",&T);
	while(T--)
	{
		ll n,k;scanf("%lld%lld",&n,&k);
		k = k * ((n + k - 1) / k);
		if(n == k)
		{
			puts("1");
			continue;
		}
		ll x = k / n;
		ll q = (k - n * x + n - 1) / n;
		x += q;
		printf("%lld\n",x);
	}
	return 0;
}

B. Inflation

题目大意:给定一个数组\(p\),记\(s_i\)为前\(i\)项的前缀和,定义一个蛋疼率\(c_i=p_i / s_{i-1} * 100\%\).数组\(p\)\(0\)开始计数,要求每个\(i\geq 1\)的位置的蛋疼率\(c_i\)都不超过一个上界\(k\%\).当你处在\(i\)这个位置并且蛋疼率超标的时候,可以增大前面\([0,i-1]\)中的某些值使得蛋疼率不超标.求最少需要增加的总和是多少,能使每个位置都不引发蛋疼率超标.

思路

递推即可,为了避免引发出现小数的问题,所以把比值的计算换成完全的整数运算.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 105;
ll p[N];

int main() 
{
	int T;scanf("%d",&T);
	while(T--)
	{
		int n,k;scanf("%d%d",&n,&k);
		forn(i,0,n - 1)	scanf("%lld",&p[i]);
		
		ll sum = p[0],res = 0;
		forn(i,1,n - 1)
		{
			if(100 * p[i] > k * sum)
			{
				ll delta = (100 * p[i] - k * sum + k - 1) / k;
				res += delta;
				sum += delta;
			}
			sum += p[i];
		}
		printf("%lld\n",res);
	}
	return 0;
}

C. Longest Simple Cycle

题目大意:有\(n\)个链,每个链长度为\(c_i\)即带有\(c_i\)个节点.链的标号从\(0\)开始,从标号为\(1\)的链开始,每个链都带有两个值\(a_i\)\(b_i\)表示当前第\(i\)个链的第一个点会连向上一个点第\(a_i\)个点,最后一个点会连向上一个链的第\(b_i\)个点.求形成的整个图上,长度最长的简单环的长度.

思路

套了图论模型的一个模拟题,可以比较明显想到的一点是既然要形成环,就是看当前这条链的链长加上上一条链挂靠上去的两个点,他们两个点再往前伸出去能绕出一个多长的距离,两个拼起来就可以了.

\(dist[i]\)表示第\(i+1\)条链挂靠在\(i\)链上的两个点之间的最大距离,这个最大距离既可以是\(i\)链上本身的距离,也可以是往前延伸形成的不闭合路径,那么按情况模拟就可以了.值得注意的是,当\(a_{i+1}=b_{i+1}\)时,挂靠在此链条上的两个点是同一个点,此时根本不能形成一个不闭合的路径,只能是一个闭合的,此时直接记为\(0\).

答案的更新就通过此时的两个点往上往下延展到上一个链条的距离合并起来就可以了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1e5+7;
ll c[N],a[N],b[N];
ll dist[N];

int main() 
{
	int T;scanf("%d",&T);
	while(T--)
	{
		int n;scanf("%d",&n);
		forn(i,1,n)	scanf("%lld",&c[i]);
		forn(i,1,n)	scanf("%lld",&a[i]);
		forn(i,1,n)	scanf("%lld",&b[i]);
		
		dist[1] = abs(a[2] - b[2]);
		ll res = 0;
		forn(i,2,n)
		{
			res = max(res,c[i] + 1 + dist[i - 1]);
			if(i < n)
			{
				if(a[i + 1] == b[i + 1])	dist[i] = 0;
				else
				{
					if(a[i + 1] > b[i + 1])	swap(a[i + 1],b[i + 1]);
					dist[i] = dist[i - 1] + abs(a[i + 1] - 1) + abs(b[i + 1] - c[i]) + 2;
					dist[i] = max(dist[i],abs(a[i + 1] - b[i + 1]));
				}
			}
		}
		printf("%lld\n",res);
	}
	return 0;
}

D. Journey

题目大意:有\(n+1\)个城市,标号从\(0\)开始,用一个字符串表示每个城市的链接情况,如果第\(i\)位为L表示有一条有向边从\(i\)连向\(i-1\)反之对应.在这张图上,每从一个节点走到另外一个节点上,就会导致整个图上的所有边的方向反转一次.求从所有的点出发,能走到多少个点的个数.

思路

当前每个边的状态,只与当前一共走了几步的奇偶性相关,所以比较明显的是可以建两张图:一张是本来的图,如果当前的步数是偶数步,那么下一步就在这张图上移动,另外一张图对应的就是奇数步数.但是这个题要求每个点能走到的点数,显然也不可能从每个点出发去搜索:但是有个很重要的性质,如果能从一个点走向另外一个点,那么从这个点也一定能走回来,也就是说从偶数图上的一个点走向奇数图的一个点这个移动是可逆的,也就是无向边而非有向边.

所以可以把所有点按奇偶性拆点,如果有链接关系,就建立无向边,并且整个联通块的点都是互相可达的,求所有联通块的大小即是能走到的点数.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)

const int N = 6e5+7;
int fa[N],siz[N];	
char s[N];

int find(int x)
{
	if(fa[x] == x)	return x;
	return fa[x] = find(fa[x]);
}

int main() 
{
	int T;scanf("%d",&T);
	while(T--)
	{
		int n;scanf("%d",&n);
		forn(i,1,2 * n + 2)	fa[i] = i,siz[i] = 1;
		scanf("%s",s + 1);
		forn(i,1,n)
		{
			int x = i + 1,y = i;
			if(s[i] == 'L')	x = find(x),y = find(y + n + 1);
			else if(s[i] == 'R')	x = find(x + n + 1),y = find(y);
			siz[y] += siz[x];
			fa[x] = y;
		}
		forn(i,1,n + 1)	printf("%d ",siz[find(i)]);
		puts("");
	}
	return 0;
}

E. Pattern Matching

题目大意:有\(n\)个模式串\(p\)以及\(m\)个字符串\(s\).模式串只包含通配符和小写字符.对于每个字符串\(s\)有一个下标\(mt\),现在要求重排所有模式串的顺序,并开始从前往后尝试每个字符串\(s\)的匹配,如果\(s\)与模式串中按顺序的第一个匹配成功,那么检查这个匹配到的模式串在本来的顺序中的位置是否和字符串指定的下标\(mt\)相同,如果不相同则失败,如果所有的字符串都能匹配上并且下标对应,就认为这个排列是正确的,输出一种正确的\(p\)的排列或者报告无解.

思路

首先可以排除一个无解的情况:对于一个字符串\(s\)如果指定的模式串不和他匹配,则一定无解.

其次考虑这个题的顺序安排方式:如果一个模式串\(p_i\)匹配到字符串\(s_j\)那么在\(mt_j\)不与\(p_i\)在原序列中位置相同的时候,\(p_i\)的在序列里出现的位置必须要晚于\(mt_j\)这个指定的模式串出现的位置,这恰好就是拓扑序.

\(p_i\)向匹配到的\(mt_j\)连一条有向边,那么如果建出来的图有环,一定无解,反之输出拓扑序的逆序就可以了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	
#define forr(i,x,n)	for(int i = n;i >= x;--i)

const int N = 1e6+7,M = 2 * N;
int edge[M],succ[M],ver[N],idx;
int q[N],deg[N];
int n,m,k;

bool topo()
{
	int hh = 0,tt = -1,time_stamp = 0;
	forn(i,1,n)	if(!deg[i])	q[++tt] = i;
	while(hh <= tt)
	{
		int u = q[hh++];
		++time_stamp;
		for(int i = ver[u];~i;i = succ[i])
		{
			int v = edge[i];
			if(--deg[v] == 0)	q[++tt] = v;
		}
	}
	return time_stamp == n;
}

void add(int u,int v)
{
	edge[idx] = v;
	succ[idx] = ver[u];
	ver[u] = idx++;
}

int main() 
{
	memset(ver,-1,sizeof ver);
	ios::sync_with_stdio(0);cin.tie(0);
	cin >> n >> m >> k;
	map<string,int> cache;
	forn(i,1,n)
	{
		string s;cin >> s;
		cache[s] = i;
	}
	
	forn(i,1,m)
	{
		string s;int mt;cin >> s >> mt;
		int ok = 0;string t(k,'_');
		forn(S,0,(1 << k) - 1)
		{
			forn(j,0,k - 1)
			{
				if(S >> j & 1)	t[j] = s[j];
				else t[j] = '_';
			}
			if(cache.count(t))
			{
				if(cache[t] != mt)	++deg[mt],add(cache[t],mt);
				else ok = 1;
			}
		}
		if(!ok)	return cout << "NO",0;
	}
	
	if(!topo())	cout << "NO";
	else 
	{
		cout << "YES\n";
		forr(i,0,n - 1)	cout << q[i] << " ";
	}
	return 0;
}

推荐阅读