AtCoder Grand Contest 003

AtCoder Grand Contest 003

A - Wanna go back home

翻译

告诉你一个人每天向哪个方向走,你可以自定义他每天走的距离,问它能否在最后一天结束之后回到起点。

题解

什么逗逼东西。。。

#include<cstdio>
#include<cstring>
using namespace std;
char s[1010];
bool W,E,S,N;
int main()
{
	scanf("%s",s+1);
	for(int i=1,l=strlen(s+1);i<=l;++i)W|=s[i]=='W',E|=s[i]=='E',S|=s[i]=='S',N|=s[i]=='N';
	if((W^E)||(N^S))puts("No");else puts("Yes");
	return 0;
}

B - Simplified mahjong

翻译

你有写着\([1,n]\)的卡片若干张,写着\(i\)的有\(a_i\)张,两两卡片可以配对当且仅当它们上面写的数字差的绝对值小于等于\(1\),求最大配对数。

题解

什么鬼玩意。。。。然而yyb怒交4发才AC

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 100100
#define ll long long
int a[MAX],n;
ll ans;
int main()
{
	cin>>n;
	for(int i=1;i<=n;++i)cin>>a[i];
	for(int i=1;i<=n;++i)
	{
		ans+=a[i]/2,a[i]%=2;
		if(a[i]&&a[i+1])++ans,--a[i+1];
	}
	printf("%lld\n",ans);
	return 0;
}

C - BBuBBBlesort!

翻译

给定一个序列\(a\),元素两两不同,可以使用两种操作。

  • 1.翻转相邻两个元素
  • 2.翻转相邻三个元素

求最少用几次1操作能够将序列排好序。

题解

第一个操作等价于交换\((i,i+1)\),第二个操作等价于交换\((i,i+2)\)。那么第一个操作会改变位置奇偶性,而第二个操作不会。计算一下有几个数需要改变奇偶性,最终答案就是他们的个数除二,因为每次可以改变一对数的奇偶性。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define MAX 100100
inline 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,a[MAX],b[MAX],ans;
int main()
{
	n=read();
	for(int i=1;i<=n;++i)b[i]=a[i]=read();
	sort(&b[1],&b[n+1]);
	for(int i=1;i<=n;++i)a[i]=lower_bound(&b[1],&b[n+1],a[i])-b;
	for(int i=1;i<=n;++i)ans+=abs(a[i]-i)&1;
	printf("%d\n",ans>>1);
	return 0;
}

D - Anticube

翻译

给定\(n\)个数,要求选出最多的数,满足任意两个数的乘积都不是完全立方数。

题解

把每个数分解质因数之后,所有指数模\(3\),那么现在显然如果一个数能够乘上另外一个数变成完全立方数,那么它分解之后指数模\(3\)的结果必然是固定的。并且是两两配对的关系,因此,从配对的集合中选择较大的那个即可。
然后就考虑怎么分解质因数,首先如果不想动脑子直接\(Pollard-rho\)。稍微动点脑子的话就是这样子:首先先用三次方根以内的所有质数分解,那么剩下的部分最多只有两个质因子,那么这两个质因子要么相同要么不同,直接判一下就好了。然后似乎就很简单了?

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
#define ll long long
#define MAX 100100
inline ll read()
{
	ll 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;
}
map<ll,int> M,vis;
int n,tot,ans;
ll a[MAX],b[MAX],p[MAX],pri[MAX];
int main()
{
	n=read();
	for(int i=1;i<=n;++i)a[i]=read();
	for(int i=2;i<=2500;++i)
	{
		bool fl=true;
		for(int j=2;j<i;++j)if(i%j==0){fl=false;break;}
		if(fl)pri[++tot]=i;
	}
	for(int i=1;i<=n;++i)
	{
		p[i]=b[i]=1;
		for(int j=1;j<=tot&&a[i]>1;++j)
			if(a[i]%pri[j]==0)
			{
				int cnt=0;
				while(a[i]%pri[j]==0)a[i]/=pri[j],++cnt;
				cnt%=3;if(!cnt)continue;
				if(cnt==1)p[i]*=1ll*pri[j]*pri[j],b[i]*=pri[j];
				else p[i]*=pri[j],b[i]*=1ll*pri[j]*pri[j];
			}
		if(a[i]==1)continue;
		ll s=sqrt(a[i]);b[i]*=a[i];
		if(s*s==a[i])p[i]*=s;
		else p[i]*=a[i]*a[i];
	}
	for(int i=1;i<=n;++i)++M[b[i]];
	for(int i=1;i<=n;++i)
	{
		if(vis[b[i]])continue;
		if(b[i]!=p[i])ans+=max(M[b[i]],M[p[i]]);
		else ans+=1;
		vis[b[i]]=vis[p[i]]=1;
	}
	printf("%d\n",ans);
	return 0;
}

E - Sequential operations on Sequence

翻译

给定一个长度为\(n\)的数列,一开始就是\(1,2,...,n\),有\(m\)次操作,每次给定一个参数\(q_i\),首先把数列无限倍长,然后只取前\(q_i\)个数作为新的数列。求所有操作做完之后每个数出现了几次。

题解

深思熟虑一下,发现真正有用的位置一定是一个单增的序列。我们设\(S(i,l)\)表示第\(i\)个操作的前\(l\)位。
那么,有这样一个转移:\(S(i,l)=[\frac{l}{q_{i-1}}]*S(i-1,q[i-1])+S(i-1,l\%q_{i-1})\)
那么,当\(l<q_{i-1}\)时,显然有\(S(i,l)=S(i-1,l)\)
然后这样子就可以写成递归形式的暴力。
代码是这样的

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 100100
inline ll read()
{
	ll 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,Q,tot;
ll q[MAX],ans[MAX],s[MAX];
void Solve(ll k,int x,ll l)
{
	if(!k||!l)return;
	if(x==1){ans[n]+=k*(l/n);ans[l%n]+=k;return;}
	Solve(l/q[x-1]*k,x-1,q[x-1]);
	Solve(k,x-1,l%q[x-1]);
}
int main()
{
	n=read();Q=read();
	for(int i=1;i<=Q;++i)
	{
		ll x=read();
		while(tot&&q[tot]>=x)--tot;
		q[++tot]=x;
	}
	Q=tot;q[0]=n;Solve(1,Q,q[Q]);
	for(int i=n;i;--i)ans[i]+=ans[i+1];
	for(int i=1;i<=n;++i)printf("%lld\n",ans[i]);
	return 0;
}

发现在\(Solve\)的递归过程中,以任何一个位置开始的第二个递归,也就是取模的那个,模的次数不会超过\(log\),这样子可以提前把取模的时候,下一个能够取模的位置的贡献给算好,这个可以二分解决。
然后把所有后面提供的倍数的贡献合在一起算,这样子单次的复杂度也对了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 100100
inline ll read()
{
	ll 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,Q,tot;
ll q[MAX],ans[MAX],s[MAX];
int Binary(int l,int r,ll k)
{
	int ret=0;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(k>=q[mid])l=mid+1,ret=mid;
		else r=mid-1;
	}
	return ret;
}
int main()
{
	n=read();Q=read();q[tot=1]=n;
	for(int i=1;i<=Q;++i)
	{
		ll x=read();
		while(tot&&q[tot]>=x)--tot;
		q[++tot]=x;
	}
	Q=tot;s[Q]=1;
	for(int i=Q;i;--i)
	{
		ll l=q[i];int p=Binary(1,i-1,l);
		while(p)s[p]+=l/q[p]*s[i],l%=q[p],p=Binary(1,p-1,l);
		ans[l]+=s[i];
	}
	for(int i=n;i;--i)ans[i]+=ans[i+1];
	for(int i=1;i<=n;++i)printf("%lld\n",ans[i]);
	return 0;
}

F - Fraction of Fractal

翻译

LOJ

题解

首先给定了条件,满足所有黑格子都是四联通。那么图形在分形之后,我们考虑两个块的黑白格子是否联通,唯一需要考虑的就是某一行(列)在第一列(行)以及最后一列(行)是否同时有一个黑格子,如果有的话,那么这一侧的两个联通块是可以联通的。这么说可能不太清楚,按照网上的说法再写一遍。我们定义如果某一行在第一列和最后一列都是黑格子,则我们称之为一个左右接口。同理,对于某一列而言,如果在第一行和最后一行都是黑格子,我们称之为上下接口。

先考虑特殊情况,如果既没有左右接口,也没有上下接口,显然每一个单独的图就是一个联通块,这个直接快速幂即可,也就是\(s^{k-1}\)\(s\)是黑格子的数量。如果既有左右接口,又有上下接口,显然答案就是\(1\)

抛去这两种情况,剩下的显然就是只有左右接口或者上下接口中的一个。然而这两种情况是一样的(你把整个图形旋转\(90°\)就好了)。我们现在只考虑上下接口,那么显然最终的联通块都是类似于一条条的链的组成。至于怎么计算链的个数?我们可以用总共的点数减去链接在了一起的联通块的个数,这样就是链的个数了(神仙啊)。

这样子以来,我们认为一级分形图,即初始图为1个节点,每次增加一级的时候,我们认为将原先所有的黑格子用一个一级分形图代替。设\(V_k\)表示点数,\(E_k\)表示边数,假设存在的、满足连续的两个上下格子都是黑色的对数为\(c\),上下接口数为\(ud\)。那么可以得到递推式\(V_k=V_{k-1}*s\),\(E_k=E_{k-1}*s+c*ud_k\)

关于点数的递推很好理解,就是每次因为是替代关系,所以点数直接翻倍。
边数的关系是这样来的,首先当前每个图形中有\(E_{k-1}\)条边,然后分形之后重复了\(s\)次,所以这一部分的贡献是\(s*E_{k-1}\)。另外一部分贡献是新的联通块通过上下接口连接在一起形成的新的边数,那么,这里贡献的边数是\(c*ud_k\),即对于每个上下相邻的\(k-1\)层图,我们会贡献\(ud_k\)条边,而\(ud_k\)的含义是当前是第\(k\)层分形图的时候上下接口的个数,\(ud_k\)显然等于\(ud^k\)

那么直接矩阵快速幂就好了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MOD 1000000007
#define MAX 1010
struct Matrix
{
	int s[3][3];
	void clear(){memset(s,0,sizeof(s));}
	void init(){clear();s[1][1]=s[2][2]=1;}
	int* operator[](int x){return s[x];}
}ans;
Matrix operator*(Matrix a,Matrix b)
{
	Matrix ret;ret.clear();
	for(int i=1;i<=2;++i)
		for(int j=1;j<=2;++j)
			for(int k=1;k<=2;++k)
				ret[i][j]=(ret[i][j]+1ll*a[i][k]*b[k][j])%MOD;
	return ret;
}
Matrix fpow(Matrix a,ll b)
{
	Matrix s;s.init();
	while(b){if(b&1)s=s*a;a=a*a;b>>=1;}
	return s;
}
ll K;
int h,w,s,t1,t2,p1,p2;
char g[MAX][MAX];
int fpow(int a,int b)
{
	int s=1;
	while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
	return s;
}
int main()
{
	cin>>h>>w>>K;if(K<=1){puts("1");return 0;}
	for(int i=1;i<=h;++i)
	{
		scanf("%s",g[i]+1);
		for(int j=1;j<=w;++j)
			s+=g[i][j]=='#';
	}
	for(int i=1;i<=h;++i)
		if(g[i][1]=='#'&&g[i][w]=='#')++p1;
	for(int i=1;i<=w;++i)
		if(g[1][i]=='#'&&g[h][i]=='#')++p2;
	for(int i=1;i<=h;++i)
		for(int j=1;j<=w;++j)
			if(g[i][j]=='#')
			{
				if(j>1&&g[i][j-1]=='#')++t1;
				if(i>1&&g[i-1][j]=='#')++t2;
			}
	if(!p1&&!p2){printf("%d\n",fpow(s,(K-1)%(MOD-1)));return 0;}
	if(p1&&p2){puts("1");return 0;}
	if(!p1)swap(p1,p2),swap(t1,t2);
	ans[1][1]=s;ans[1][2]=0;ans[2][1]=t1;ans[2][2]=p1;
	ans=fpow(ans,K-1);
	printf("%d\n",(ans[1][1]-ans[2][1]+MOD)%MOD);
	return 0;
}
posted @ 2018-09-15 16:51  小蒟蒻yyb  阅读(311)  评论(0编辑  收藏  举报