AtCoder Beginner Contest 396
AtCoder Beginner Contest 396
A - Triple Four
给定一个数列,问是否存在连续三个数是一样的。
简单的判断,枚举第一个数的位置,然后检查它和后面两个数是否一致。
#include<iostream>
using namespace std;
int n;
int a[101];
bool fl=false;
int main()
{
cin>>n;
for(int i=1;i<=n;++i)cin>>a[i];
for(int i=1;i<=n-2;++i)
if(a[i]==a[i+1]&&a[i]==a[i+2])
fl=true;
puts(fl?"Yes":"No");
return 0;
}
B - Card Pile
有一个栈,里面有100个卡片。每个卡片上都是0。
执行Q次操作,每次操作可能是:
- 将一个写着数字x的卡片置于栈顶
- 将栈顶的卡片移除,并输出其卡片上的数字
#include<iostream>
using namespace std;
int S[202],top;
int Q;
int main()
{
for(int i=1;i<=100;++i)S[++top]=0;
cin>>Q;
while(Q--)
{
int a,x;
cin>>a;
if(a==1)
{
cin>>x;
S[++top]=x;
}
else
cout<<S[top--]<<endl;
}
return 0;
}
C - Buy Balls
有N个黑球,M个白球。
每个球上都有一个数字,第\(i\)个黑枪上的数字是\(B_i\),第\(j\)个白球上的数字是\(W_j\)。
现在要选择若干个球,使得黑色球的数量不少于白色球。
问所有选取方案中,球上的数字和最大是多少。
枚举白色球的数量,这样白色球一定会选数字最大的那些。
在确定了白色球的选择之后,黑色球可以这样贪心的选择:
- 如果黑色球非负数的数量大于白色球选择的数量,则全选这些黑球
- 否则选择最大的、数量与白色球相等的黑色球
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAX 200200
int n,m;
int W[MAX],B[MAX];
long long ans=-1e18,sum=0;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",&B[i]);
for(int i=1;i<=m;++i)scanf("%d",&W[i]);
sort(&B[1],&B[n+1]);
sort(&W[1],&W[m+1]);
int cntB=0,cntW=0;
for(int i=n;i;--i)
if(B[i]>=0)++cntB,sum+=B[i];
else break;
ans=sum;
for(int j=m;j;--j)
{
++cntW;
if(cntW<=cntB)sum+=W[j];
else
{
if(cntW>n)break;
sum+=W[j]+B[n-cntW+1];
}
ans=max(ans,sum);
}
cout<<ans<<endl;
return 0;
}
D - Minimum XOR Path
给定一张连通图,有\(n\)个点\(m\)条边。每条边有一个边权。
现在要选择一条路径,一条路径是指由边相连的一系列边,且不经过任何重复的点。
问所有从1出发到达N的路径中,经过的所有边的边权的异或和的最小值。
直接dfs所有可能的路径,依次检查即可。
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
ll ans=-1;
int n,m;
struct Edge{int v,nt;ll w;}e[1000];
int h[15],cnt=0;
void add(int u,int v,ll w)
{
e[++cnt]=(Edge){v,h[u],w};
h[u]=cnt;
}
bool vis[15];
void dfs(int u,ll val)
{
if(u==n)
{
if(ans==-1)ans=val;
else ans=min(ans,val);
return;
}
for(int i=h[u];i;i=e[i].nt)
{
int v=e[i].v;ll w=e[i].w;
if(!vis[v])
{
vis[v]=true;
dfs(v,val^w);
vis[v]=false;
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;++i)
{
int u,v;ll w;
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
vis[1]=true;
dfs(1,0);
cout<<ans<<endl;
return 0;
}
E - Min of Restricted Sum
给定N、M,还有三个长度为M的序列X、Y、Z。保证X和Y中的每一个元素都在1到N之间。
我们称一个长度为N的非负序列A是好的,当且仅当对于所有的\(1\le i\le M\),满足\(A_{x_i}\oplus A_{y_i}=Z_i\)
判断是否存在好的序列,如果存在,输出所有元素和最小的那一个。
首先判断序列是否存在。
构建一个\(N\)个点的图,对于\(X_i\)和\(Y_i\),在其间连一条边。这意味着,一旦我们知道了其中任何一个数是\(x\),那么我们就可以推出另一个数是\(Z_i\oplus x\)。
只要这些边不构成环,那么它们一定不会发生冲突。
但如果出现了环,我们需要沿着环检查所有边对应的\(Z_i\)的异或和是否为\(0\),如果不是,那么意味着会出现冲突,不可能符合条件,也即无法构建合法的序列。
那么如果没有冲突,只需要分连通块考虑。而一个连通块中,我们可以知道任何两个数之间的异或值。且只需要知道一个数,剩下所有数都唯一确定。
把上面这个过程重新理解一下,实际上我们只需要指定连通块中的某个点为根节点,其他点连接构成树,维护其到根节点的路径上的异或和。如果出现非树边,检查构成的环是否异或和为\(0\)即可。
对于合法的连通块,考虑根节点的取值。而异或和可以逐位考虑,因此对于每个二进制位,枚举根节点选择1或者0,这样就可以知道其他的所有节点是0还是1,选择其中1较少的那个即可。这样贪心构造就可以得到所有数的值。
一些推论:不难证明如果\(a\oplus b=c\),那么\(a+b\ge c\)。且我们发现当\(a\)或者\(b\)是\(0\)的时候,有\(a+b=c\)。因此,通过构造可以保证一定存在一个解,满足每个连通块里面一定有一个数会是\(0\)。
F - Rotated Inversions
给定整数N,M和一个长度为N的非负整数序列A。
对于k=0,1,...,M-1,解决如下问题:
定义一个长度为N的整数序列B,满足\(B_i\)是\(A_i+k\)除以M的余数。求出B中的逆序对的数量。
显然,要做的就是令$B_i=A_i\ mod\ M \(,然后每次令所有\)B_i$加一,模M。
注意到,如果没有模M这个操作,那么B中的数字的相对大小关系是不会发生改变的,因此逆序对的数量也不可能发生改变。
因此,只有模M会影响到相对大小关系,且当且仅当在某次加一取模之后变成0的数才会带来影响。
而0比任何其他数都小,其他数都因为加一且取模之后不会变小必定会比0大。因此0和其他所有在他之前的数除0外的所有数都必定构成逆序对。
而原本的M-1比其他数都大,和在它之后的、除M-1之外的所有数都必定构成逆序对。
于是问题就变成了,从k=0开始,计算逆序对,然后确定每次哪些数加一取模之后会变成0,计算一下逆序对的数量的变化值即可。
至于怎么求最开始的逆序对数量,可以用值域上的数据结构或者分治在一个log内完成计算。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define MAX 200200
int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
int n,m,a[MAX];
int t[MAX];
int lowbit(int x){return x&(-x);}
void add(int x,int a){while(x<=m)t[x]+=a,x+=lowbit(x);}
int getsum(int x){int r=0;while(x)r+=t[x],x-=lowbit(x);return r;}
long long ans=0;
vector<int> V[MAX];
int main()
{
n=read();m=read();
for(int i=1;i<=n;++i)
{
a[i]=read();
V[m-a[i]].push_back(i);
ans+=getsum(m-a[i]-1);
add(m-a[i],1);
}
printf("%lld\n",ans);
for(int k=1;k<m;++k)
{
for(int j=0,l=V[k].size();j<l;++j)
{
int p=V[k][j];
ans-=n-p-(l-j-1);
ans+=p-1-j;
}
printf("%lld\n",ans);
}
return 0;
}
G - Flip Row or Col
给定一个H行W列的01矩阵,每次可以翻转一行或者一列上的所有01元素,可以执行若干次操作。
问矩阵通过若干次操作之后,1的数量的最小值。
注意到H很大,W很小。
所以我们把每一行看成一个二进制数\(B_i\),翻转若干列等于所有数都异或上某个数\(X\)。
设\(f[X][j][c]\)表示在所有满足\(B_i\in\{X\oplus a|0\le a\le 2^j\}\)的\(B_i\)中,\(X\oplus B_i\)二进制位上有\(c\)个\(1\)的数的数量。
那么\(f[X][j+1][c]+=f[X][j][c],f[X][j+1][c+1]+=f[X\oplus 2^j][j][c]\)
这样子我们就可以知道对于每个\(X\),其和每个\(B_i\)异或之后,有多少位不同。
所以对于固定的\(X\),1的最小值为\(\sum_{c=0}^W f[X][W][c]\times \min{\{c,W-c\}}\)
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define MAX 200200
int a[MAX];
int f[20][20][1<<18];
int H,W,ans=1e9;
char s[20];
int main()
{
cin>>H>>W;
for(int i=1;i<=H;++i)
{
scanf("%s",s);
for(int j=0;j<W;++j)a[i]=a[i]<<1|(s[j]-48);
f[0][0][a[i]]+=1;
}
for(int j=1;j<=W;++j)
for(int c=0;c<=W;++c)
for(int k=0;k<(1<<W);++k)
{
f[j][c][k]+=f[j-1][c][k];
f[j][c+1][k]+=f[j-1][c][k^(1<<(j-1))];
}
for(int k=0;k<(1<<W);++k)
{
int sum=0;
for(int c=0;c<=W;++c)
sum+=f[W][c][k]*min(c,W-c);
ans=min(ans,sum);
}
cout<<ans<<endl;
return 0;
}

浙公网安备 33010602011771号