Educational Codeforces Round 2 A---F

总结

本场涉及到两圆相交面积,启发式合并,二分图最小边颜色。
C题贪心挺有意思。启发式合并,优美的暴力yyds,二分图最小边颜色,匈牙利最大匹配的改版。

A. Extract Numbers

题意:

题意比较难懂的小模拟,给定一个字符串,单词用’‘,’’ ‘’;"隔开,如果全为不含前导0的数字放入一一行,否则放入第二行

char str[maxn];vector <string> ans1, ans2;
void check(int l, int r)
{
	bool flog = true;
	string temp;
	for(int i = l + 1 ; i <= r - 1 ; i ++)
	{
		temp += str[i];
		if(str[i] >= 48 && str[i] <= 57) continue;
		else 
		{
			flog = false;
		}
	}
	if(flog) //全是数字 
	{
		if(str[l + 1] != '0')
		ans1.push_back(temp);
		else  // 0
		{
			if(r - l - 1 > 1)
			ans2.push_back(temp); // r - l < 2  r - l
			else ans1.push_back(temp);
		}
	
	}
	else
	{
		ans2.push_back(temp);
	}
}
int main()//数字 / 字母 
{
	scanf("%s", str + 1);
	int len = strlen(str + 1);
	vector <int> alls;
	alls.push_back(0);
	for(int i = 1 ; i <= len ; i ++)
	{
		if(str[i] == ',' || str[i] == ';')
		alls.push_back(i);
	}
	alls.push_back(len + 1);
	
	for(int i = 0 ; i < alls.size() ; i ++)
	{
		if(i + 1 < alls.size())
		{
			if(alls[i + 1] - alls[i] == 1)
			ans2.push_back("*"); //
			else
			{
				check(alls[i], alls[i + 1]);
			}
		}
	}
	if(!ans1.empty())
	{
		cout << '"';
		for(int i = 0 ; i < ans1.size() ; i ++)
		{
			cout << ans1[i];
			if(i != int(ans1.size()) - 1)
			cout << ",";
		}
		cout << '"';
		cout << "\n";
	}
	else cout << "-" << "\n";
	if(!ans2.empty())
	{
		cout << '"';
		for(int i = 0 ; i < ans2.size() ; i ++)
		{
			if(ans2[i] != "*")
			cout << ans2[i];
			if(i != int(ans2.size()) - 1)
			cout << ","; 
		}
		cout << '"';
		cout << "\n";
	}
	else cout << "-" << "\n";
	return 0;
}

B. Queries about less or equal elements

题意:

给定一个数组A,一个数组B,对于B中每一个数B[i],输出A数组中<=B[i]的个数。

思路:

经典二分

int main()
{
	int n ,m;
	scanf("%d %d", &n, &m);
	for(int i = 1 ; i <= n ; i ++)
	scanf("%lld", &a[i]);
	for(int i = 1 ; i <= m ; i ++)
	scanf("%lld", &b[i]);
	sort(a + 1, a + n +1);
	for(int i = 1 ; i <= m ; i ++)
	{
		int l = 1, r = n;
		while(l < r)
		{
			int mid = (l + r + 1) >> 1;
			if(a[mid] <= b[i]) l = mid;
			else r = mid - 1; 
		}
		if(a[l] <= b[i])
		{
			printf("%d ", l);
		}
		else printf("0 ");
	}
	return 0;
}

C. Make Palindrome

题意:

给定一个字符串,每次操纵可把一个字符变成另一个字符,之后可随意重排字符的顺序,求使得此字符串代价最小且字典序最小的方案。

思路:

观察数据范围2e5,那么解法应为O(n) O(nlogn),且可随便重排字符的顺序。
假设给定的字符串长度为偶数,说明该串中存在偶数个出现奇数次的字符,要是其变成回文串,取其中任意两个,花费一个代价,即可把两个出现奇数次的字符变成偶数次,代价是固定的,为使得字典序最小,那么贪心使得字典序大的字符转化到字典序小的字符上即可。
同理,假设为奇数,说明该串中存在奇数个出现奇数次的字符,且该串的中间位置要至少放一个出现次数为奇数的字符,假设把其单独拿出来,那么此时字符次数的情况和偶数串一样。代价同样固定,要使得字典序最小,应贪心的取出现次数为奇数次的中间那个字符,因为此时只有中间那个字符还是奇数次,那么取它作为中间,就可将其转化成偶数次。

int len, book[30];
int main()
{
	scanf("%s", str + 1);
	len = strlen(str + 1);
	for(int i = 1 ; i <= len ; i ++)
	{
		book[str[i] - 'a'] ++; 
	}
	char ch;
	if(len % 2)
	{
		vector <int> alls;
		for(int i = 0 ; i <= 25 ; i ++)
		{
			if(book[i] % 2)
			{
				alls.push_back(i);	
			} 
		}
		ch = char(alls[int(alls.size()) / 2] + 'a');
		book[ch - 'a'] --;
	} //偶数个奇数 
	string ans;
	for(int i = 1 ; i <= len ; i ++)
	{
		for(int j = 0 ; j <= 25 ; j ++)
		{
			if(book[j] > 0 && book[j] % 2 == 0)
			{
				ans += char(j + 'a');
				book[j] -= 2;
				break;
			}
			else if(book[j] > 0 && book[j] % 2)
			{
				ans += char(j + 'a');
				book[j] -= 1;
				for(int k = 25 ; k >= 0 ; k --)
				{
					if(book[k] > 0 && book[k] % 2)
					{
						book[k] --;
						break;	
					}	
				}
				break;
			}
		}
	}
	string temp = ans;
	reverse(temp.begin(), temp.end());
	if(len % 2)
	{
		ans += ch;
		ans += temp; 
	}
	else
	{
		ans += temp;
	}
	cout << ans << "\n";
	return 0;
}

D. Area of Two Circles’ Intersection

题意:

给定两个圆,求两圆的面积并

#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
#include<iomanip>
using namespace std;
const long double PI=acos(-1.0);
struct Round
{
    long double x,y;
    long double r;
    long double K(long double x)
    {
        return x*x;
    }
    long double Dis(Round a,Round b)
    {
        return sqrt(K(a.x-b.x)+K(a.y-b.y));
    }
    long double Intersection_area(Round a,Round b)
    {
        long double dis=Dis(a,b);
        if(a.r==0||b.r==0||dis>=a.r+b.r)return 0;
        else if(dis<=fabs(a.r-b.r))return PI*K(min(a.r,b.r));
        else
        {
            long double angA=2*acos( (K(a.r)+K(dis)-K(b.r))/(2*a.r*dis) );
            long double angB=2*acos( (K(b.r)+K(dis)-K(a.r))/(2*b.r*dis) );
            long double areaA=K(a.r)*(angA-sin(angA))/2;
            long double areaB=K(b.r)*(angB-sin(angB))/2;
            return areaA+areaB;
        }
    }
}a,b;
int main()
{
    cin>>a.x>>a.y>>a.r>>b.x>>b.y>>b.r;
    cout<<setprecision(25)<<a.Intersection_area(a,b)<<endl;
    return 0;
}

E. Lomsat gelral

题意:

给定一棵树,根节点为1号点,每个节点都有一个颜色,求以1–n号节点的子树中出现次数最多的颜色之和。

思路:

最开始考虑树形DP。
发现无法满足O(1)递推。需要一直维护两个集合,然后暴力合并。
正解:树上启发式合并(dsu on tree)
树上启发式合并,应用于子树查询问题,巧妙利用轻重链的性质。每次都将同层节点的轻儿子向重儿子上合并。实际上就相当于暴力合并,然后巧妙利用一些性质。时间复杂度O(nlogn)。每次都先搜索当前点的轻儿子,最后搜索重儿子,在暴力统计轻儿子所在子树的贡献时,要及时将全局cnt[]数组清空。

code:

ll ans[maxn];
int h[maxn], ne[maxn * 2], e[maxn * 2], idx, siz[maxn], son[maxn], col[maxn], cnt[maxn], big;
ll maxx, sum;
void add(int u, int v)
{
	e[idx] = v;
	ne[idx] = h[u];
	h[u] = idx ++;
}
void dfs0(int sta, int fa)
{
	siz[sta] = 1;
	for(int i = h[sta] ; i != -1 ; i = ne[i])
	{
		int soon = e[i];
		if(soon == fa) continue;
		dfs0(soon, sta);
		if(siz[son[sta]] < siz[soon]) //找重儿子 
		{
			son[sta] = soon;
		}
		siz[sta] += siz[soon];
	}
}
void count(int sta, int fa, int w)
{
	cnt[col[sta]] += w;
	if(cnt[col[sta]] > maxx)
	{
		maxx = cnt[col[sta]];
		sum = col[sta];
	}
	else if(cnt[col[sta]] == maxx)
	{
		sum += col[sta];
	}
	for(int i = h[sta] ; i != -1 ; i = ne[i])
	{
		int soon = e[i];
		if(soon == fa || soon == big) continue;
		count(soon, sta, w);
	}	
}
void dfs1(int sta, int fa, bool keep)
{
	for(int i = h[sta] ; i != -1 ; i = ne[i])
	{
		int soon = e[i]; //儿子节点 
		if(soon == fa || soon == son[sta]) continue;
		dfs1(soon, sta, false); //先取搜索轻儿子 
	}
	if(son[sta]) //若有重儿子 
	{
		dfs1(son[sta], sta, true);//搜索重儿子
		big = son[sta]; 
	}
	count(sta, fa, 1); //暴力计算
	ans[sta] = sum; 
	big = 0;
	if(!keep)
	{
		count(sta, fa, -1);
		maxx = 0;
		sum = 0;
	}
}
int main()
{
	int n;
	scanf("%d", &n);
	for(int i = 1 ; i <= n ; i ++)
	scanf("%d", &col[i]);
	memset(h, -1, sizeof(h)), idx = 0;
	for(int i = 1 ; i < n ; i ++)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		add(u, v), add(v, u);
	}
	dfs0(1, -1), dfs1(1, -1, false);
	for(int i = 1 ; i <= n ; i ++)
	printf("%lld ", ans[i]);	 
	return 0;
}

F. Edge coloring of bipartite graph

题意:

给定一个二分图,左半部分为a个点,右半部分为b个点。给定m条边,使得有公共点的边颜色不同。求满足条件的颜色的最小颜色数量和m条边分别染成颜色的方案。

思路:

考虑二分图性质。不含奇数环。
那么满足条件的最小颜色的数量为点的最大度数。
考虑如何构造方案。数据范围a <= 1000, b <= 1000, m <= 1e5。
考虑二分图匈牙利最大匹配思想。
对于a图中每个点找到b图所连边的点。
如果b图中与之对应的点不存在匹配对象 / b图已经存在匹配对象且此时与之对应的a图的匹配对象还有别的点可以匹配(说明此时这样匹配,还是合法方案,且匹配数会增加), 那么更改此b图的匹配对象。(本质上就是暴力,如果更改还是合法的,那么此时匹配数量就 + 1)
考虑此题。
u – v,先可以找出此时u所对应的最小颜色col_u, v所对应的最小颜色col_v,先强行把v点颜色改成col_u,如果发生冲突,为了使得当前合法,可以把之前v点涂成col_u的点,强行改成col_v,继续递归。
那么总的时间复杂度为(m * 2 * n)

code

int f[3][1010][1010], pos[1010][1010], ans[maxn];
void dfs(int a, int b, int u, int v, int col_u, int col_v)
{
	if(col_u == col_v)
	{
		f[a][u][col_u] = v, f[b][v][col_u] = u;
		return ;
	}
	int temp = f[b][v][col_u];
	f[a][u][col_u] = v, f[b][v][col_u] = u;
	if(temp) //有矛盾 
	{
		dfs(b, a, v, temp, col_v, col_u);	
	}
	else //没有矛盾 
	{
		f[b][v][col_v] = 0;
		return ;
	}
}
int main()
{
	int a, b, m, maxx;
	scanf("%d %d %d", &a, &b, &m);
	maxx = 0;
	for(int i = 1 ; i <= m ; i ++)
	{
		int x, y;
		scanf("%d %d", &x, &y);
		pos[x][y] = i;
		int temp1 = 1,temp2 = 1;
		while(f[0][x][temp1]) temp1 ++;
		while(f[1][y][temp2]) temp2 ++;
		maxx = max({maxx, temp1, temp2});
		if(temp1 == temp2)
		f[0][x][temp1] = y, f[1][y][temp2] = x;
		else //暴力检查 
		{
			dfs(0, 1, x, y, temp1, temp2);
		}
	}
	for(int i = 1 ; i <= a; i ++)
	{
		for(int j = 1 ; j <= maxx ; j ++)
		{
			if(f[0][i][j])
			{
				ans[pos[i][f[0][i][j]]] = j;
			}
		}
	}
	printf("%d\n", maxx);
	for(int i = 1 ; i <= m ; i ++)
	printf("%d ", ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值