【题解】AtCoder Grand Contest 016

来源:互联网 发布:吉首大学网络教学 编辑:程序博客网 时间:2024/06/06 02:22

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;}

原创粉丝点击