【题解】AtCoder Grand Contest 016

A:Shrinking

题意:规定一种对字符串的操作:设字符串长度为N,一次操作之后可以得到一个长度为N-1的字符串,新字符串的第i个字符等于旧字符串的第i个字符或第i+1个字符。给定一个长度为n的字符串,问最少进行几次操作可以得到一个只含有一种字符的字符串。

分析:假如我们要将字符串最终变为"aa...a",首先原字符串中必须得有字符a。设原字符串中字符a的位置从小到大依次为x1,x2,...,xk(位置的范围为0~n-1)。那么对于最终的字符串,若长度不小于x1,则位置0~x1-1的字符a应由原字符串位置x1处的字符a得到,即考虑0~x1这一段,至少需要x1次操作。同样考虑x1+1~x2这一段、...、xk+1~n-1(可以认为位置n处的字符也是a)这一段至少需要的操作数,取个最大值就是把原字符串最终变为"aa...a"所需要的最少操作数。可以枚举最终字符串包含的那个字符,也可以直接扫一遍处理。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=200;
int n,Last[maxn],f[maxn];
char ch[maxn];
int idx(char c)
{
	return c-'a';
}
int main()
{
	cin>>ch;
	n=strlen(ch);
	memset(Last,-1,sizeof(Last));
	for (int i=0;i<n;i++)
	{
		int c=idx(ch[i]);
		f[c]=max(f[c],i-Last[c]-1);
		Last[c]=i;
	}
	for (int i=0;i<26;i++) f[i]=max(f[i],n-Last[i]-1);
	int ans=n;
	for (int i=0;i<26;i++) ans=min(ans,f[i]);
	cout<<ans;
	return 0;
}

B:Colorful Hats

题意:有n顶带颜色的帽子,告诉你n个事实,第i个事实是不算第i顶帽子,其余帽子总共有ai种颜色。问是否存在满足所有事实的n顶帽子。

分析:可以注意到以下事实

1.a1,a2,...,an中的最大数与最小数至多差1。

设n顶帽子总共有m种颜色,那么,若第i顶帽子的颜色是独一无二的,则ai=m-1,否则ai=m。

2.若a1=a2=...=an=n-1,则存在满足所有事实的n顶帽子。

此时所有帽子颜色互不相同。

3.若a1=a2=...=an<n-1,则每顶帽子颜色都不是独一无二的,此时存在满足所有事实的n顶帽子的充要条件为2*a1<=n。

显然。

4.若a1,...,an不全相等,设n顶帽子总共有m种颜色,并且有s顶帽子的颜色是独一无二的,显然有n>s。则存在满足所有事实的n顶帽子的充要条件为s<m且2*(m-s)<=n-s。

显然。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,a[maxn];
int m,s;
bool check()
{
	for (int i=1;i<=n;i++)
	    if (a[i]!=n-1) return 0;
	return 1;
}
int main()
{
	cin>>n;
	for (int i=1;i<=n;i++) scanf("%d",&a[i]),m=max(m,a[i]);
	if (check())
	{
		cout<<"Yes";return 0;
	}
	for (int i=1;i<=n;i++)
	    if (a[i]<m)
	    {
	    	if (a[i]==m-1) s++;
	    	else
	    	{
	    		cout<<"No";return 0;
			}
		}
	if (m<s||2*(m-s)>n-s||(n>s&&m-s<1)) cout<<"No";
	else cout<<"Yes";
	return 0;
}

C:+/- Rectangle

题意:问是否存在满足如下条件的元素为整数的矩阵

1.矩阵有H行W列;

2.矩阵的每个元素都在-1e9~1e9之间;

3.矩阵全体元素和为正;

4.矩阵的任意h行w列子矩阵元素和为负。

给定H、w,判断是否存在满足条件的矩阵,若存在就构造一个。

分析:存在的充要条件为H%h!=0或W%w!=0。若存在,可以这么构造:取t=(H/h)*(W/w),x=t+1,y=x*(h*w-1)+1。矩阵的第i行第j列当i%h==0且j%w==0时为-y,否则为x。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000;
int H,W,h,w;
int main()
{
	cin>>H>>W>>h>>w;
	if (H%h==0&&W%w==0)
	{
		cout<<"No";
		return 0;
	}
	else
	{
		cout<<"Yes"<<endl;
		int t=(H/h)*(W/w);
		int x=t+1,y=(t+1)*(h*w-1)+1;
		for (int i=1;i<=H;i++)
			for (int j=1;j<=W;j++)
			{
				if (i%h==0&&j%w==0) printf("%d",-y);
				else printf("%d",x);
				if (j<W) printf(" ");
				else printf("\n");
			}
	}
	return 0;
}

D:XOR Replace

题意:给定长度为n的两个序列a、b。可以对a进行如下操作:设x=a1^a2^...^an,选择i∈{1,2,...,n},用x替换ai。问是否能经过有限次操作把序列a变成序列b,若能,给出最少操作次数。

分析:需要注意到如下2个事实:

1.设a0=a1^a2^...^an,那么每次操作相当于交换a0与ai。

因此能经过有限次操作把序列a变成序列b的充要条件是多重集合{a0,a1,...,an}={b0,b1,...,bn}。

2.若答案为能,按如下方式建图G,最少操作次数=G中的边数+G中的极大连通子图数-1。

初始将点a0,b0加入图G。第k步(k=1,2,...,n),若ai不等于bi,将ai,bi加入图G,并加入边ai->bi。

这里解释一下事实2:容易看出,图G的每个连通子图都是欧拉图(即可以“一笔画”),再适当加上G中的极大连通子图数-1条边可使G变成一个欧拉图,根据欧拉路依次交换即可完成把序列a变成序列b这件事,并且显然这样的操作次数是最少的。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,a[maxn],b[maxn],a1[maxn],b1[maxn];
vector<int> G[maxn];
map<int,int> mp;
bool vis[maxn];
void dfs(int u)
{
	vis[u]=1;
	for (int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if (!vis[v]) dfs(v);
	}
}
int main()
{
	int col=0;
	cin>>n;
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		a1[i]=a[i];
		a[n+1]^=a[i];
		if (!mp[a[i]]) mp[a[i]]=++col;
	}
	a1[n+1]=a[n+1];
	if (!mp[a[n+1]]) mp[a[n+1]]=++col;
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&b[i]);
		b1[i]=b[i];
		b[n+1]^=b[i];
		if (!mp[b[i]]) mp[b[i]]=++col;
	}
	b1[n+1]=b[n+1];
	if (!mp[b[n+1]]) mp[b[n+1]]=++col;
	sort(a1+1,a1+n+2);
	sort(b1+1,b1+n+2);
	for (int i=1;i<=n+1;i++)
	    if (a1[i]!=b1[i])
	    {
	    	cout<<"-1";
	    	return 0;
		}
	
	for (int i=1;i<=n+1;i++) a[i]=mp[a[i]],b[i]=mp[b[i]];
	int ans=0;
	for (int i=1;i<=n;i++)
	    if (a[i]!=b[i])
	    {
	    	ans++;
	        G[a[i]].push_back(b[i]);
	    }
	if (a[n+1]!=b[n+1]) G[a[n+1]].push_back(b[n+1]);
	for (int i=1;i<col;i++)
	    if (G[i].size()&&!vis[i])
	    {
		    dfs(i);
		    ans++;
		}
	if (!vis[col]) ans++;
	cout<<ans-1;
	return 0;
}


E:Poor Turkeys

题意:略。

分析:设集合S为{1,2,...,n}的子集,下面给出“经过t次选择后集合S中的火鸡都活着(即为事件A(S,t))”的一个判断方式:

1.若xt∈S且yt∈S,则事件A(S,t)不可能发生;

2.若xt∉S且yt∈S,则事件A(S,t)发生当且仅当事件A(S∪{xt},t-1)发生;

3.若xt∈S且yt∉S,则事件A(S,t)发生当且仅当事件A(S∪{yt},t-1)发生;

4.若xt∉S且yt∉S,则事件A(S,t)发生当且仅当事件A(S,t-1)发生。

由此我们可以判断出第i只火鸡是否能在n次选择后活着并且能求出要使第i只火鸡在n次选择后活着在进行选择之前必须活着的火鸡集合Si。可以看出,火鸡i与j在n次选择后能同时活着当且仅当:

1.第i只火鸡在n次选择后能活着;

2.第j只火鸡在n次选择后能活着;

3.Si∩Sj=∅。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=400+10,maxm=1e5+10;
int n,m,x[maxm],y[maxm];
bool ok[maxn],S[maxn][maxn];
bool check(int i,int j)
{
	for (int k=1;k<=n;k++)
	    if (S[i][k]&&S[j][k])
	        return 0;
	return 1;
}
int main()
{
	cin>>n>>m;
	for (int i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]);
	for (int i=1;i<=n;i++)
	{
	    ok[i]=1;
	    S[i][i]=1;
		for (int j=m;j>=1;j--)
		{
		    if (S[i][x[j]]&&S[i][y[j]])
		    {
		    	ok[i]=0;break;
			}
			if (S[i][x[j]]&&!S[i][y[j]])
			    S[i][y[j]]=1;
			if (!S[i][x[j]]&&S[i][y[j]])
			    S[i][x[j]]=1;
		}
	}
	int ans=0;
	for (int i=1;i<=n;i++)
	    for (int j=i+1;j<=n;j++)
	        if (ok[i]&&ok[j]&&check(i,j))
	            ans++;
	cout<<ans;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值