Loading

agc003

题目连接

AT2001 [AGC003A] Wanna go back home
AT2002 [AGC003B] Simplified mahjong
AT2003 [AGC003C] BBuBBBlesort!
AT2004 [AGC003D] Anticube
AT2005 [AGC003E] Sequential operations on Sequence
AT2006 [AGC003F] Fraction of Fractal


A

显然:上下方向要么都不存在,要么都存在,左右同理,否则 NO

const int N = 114514;
int n, cnt[5];
char str[N];
signed main() {
	scanf("%s", str + 1) ,n = strlen(str + 1);
	rep (i, 1, n) 
		if (str[i] == 'E') ++cnt[0];
		else if (str[i] == 'W') ++cnt[1];
		else if (str[i] == 'S') ++cnt[2];
		else ++cnt[3];
	if ((cnt[0] && !cnt[1]) || (cnt[1] && !cnt[0]) || (cnt[2] && !cnt[3]) || (cnt[3] && !cnt[2])) puts("No");
	else puts("Yes");
}

B

奇数不太好处理,而把它直接消成 \(1\) 显然是对的 。然后让每一个 \(1\) 尽量与后面的匹配即可。

const int N = 100005;
int n, a[N];
LL ans;
signed main() {
	n = read();
	rep (i, 1, n) a[i] = read();
	rep (i, 1, n) if (a[i] & 1) ans += a[i] >> 1, a[i] &= 1;
	rep (i, 1, n) 
		if (a[i] & 1) {
			ans += a[i] >> 1, a[i] &= 1;
			if (a[i+1]) --a[i+1], --a[i], ++ans;
		}
	rep (i, 1, n) ans += a[i] >> 1;
	printf("%lld\n", ans);
	return 0;
}

C

reverse相邻三个数相当于隔一个数swap,swap的两个数的下标奇偶性相同。所以如果这个数应该在的位置(排序完的下标)和它的初始下标奇偶性不同那么计数器加一。

奇下标到偶下标与偶下标到奇下标均被统计了,但是一次swap就能各减少一个,所以答案要除以 \(2\)

const int N = 100005;
int n, a[N], ans, b[N];
map<int,int>to;
signed main() {
	n = read();
	rep (i, 1, n) a[i] = b[i] = read();
	sort (b + 1, b + n + 1);
	rep (i, 1, n) to[b[i]] = i;
	rep (i, 1, n) if( (i & 1) != (to[a[i]] & 1)) ++ans;
	printf("%d\n",ans >> 1);
	return 0;
}

D

开始不那么显然了。一开始净往图论想,怎么连边复杂度都不对qwq。

\(n=p_1^{x_1}p_2^{x_2}\cdots p_c^{x_c},n'=p_1^{x_1\%3}p_2^{x_2\%3}\cdots p_c^{x_c\%3},iv_{n'}=p_1^{3-x_1\%3}p_2^{3-x_2\%3}\cdots p_c^{3-x_c\%3}\)

\(n\) 归到 \(n'\) 这一类里面去。

可以发现 \(n\) 可以分解成唯一确定 \(n'\) ,每一个 \(n'\) 对应着唯一确定的 \(iv_{n'}\) ,而题目的限制就是 \(n'\) 这一类与 \(iv_{n'}\) 这两类只能取一类。

所以直接把每一个 \(n'\) 的数量丢到哈系表里,比较 \(num_{n'}\)\(num_{iv_{n'}}\) 的大小,取较大的即可。

应当注意到 \(10^{10}\) 是没法质因数分解的,只能处理 \(\le \sqrt[3]{n}\) 的因数。

发现把这部分质因子去除之后,\(n\) 一定是 \(p,pq,p^2\) 三种形式之一(\(p,q\) 均为质数),我们维护出这个 \(n'\) 以及这个 \(n'\) 对应的 \(iv_{n'}\)

  • 如果分解完的 \(n>10^5\)

    • 如果 \(n=p\) ,那么 \(iv_{n'}\) 会乘上 \(p^2\) ,那么 \(iv_{n'}\) 必然大于 \(10^{10}\) 可以直接舍掉,不用维护。

    • 如果 \(n=p^2\) ,那么 \(iv_{n'}\) 会乘上 \(p\) ,直接乘就好了。判断它是不是完全平方数可以直接用 sqrt(x)*sqrt(x)==x 来判断。

    • 如果 \(n=pq\) ,那么 \(iv_{n'}\) 会乘上 \(p^2q^2\) ,必然大于 \(10^{10}\) ,可以舍掉。

所以判断 \(n\) 分解完是否是完全平方数即可。

  • 如果分解完的 \(n\le 10^5\)\(iv_{n'}\) 直接乘 \(n^2\) 即可。

注意特判分解完 \(n\)\(1\) 的情况,只能取 \(1\) 个。

const int N=100005;
int n,ans;
int pct,pri[N];
bool vis[N];
LL a[N];
map<LL,LL>to;
map<LL,int>num;
void Sieve(const int&n){
	for(int i=2;i<=n;++i){
		if(!vis[i])pri[++pct]=i;
		for(int j=1;j<=pct&&i*pri[j]<=n;++j){
			vis[i*pri[j]]=1;
			if(i%pri[j]==0)break;
		}
	}
}
void insert(LL x){
	static int cnt_1=0;
	LL pre=x,iv=1;
	for(int i=1;i<=pct;++i){
		if(pri[i]>2155)break;
		if(x%pri[i])continue;
		LL tmp=1ll*pri[i]*pri[i]*pri[i];
		while(x%tmp==0)x/=tmp,pre/=tmp;
		if(x%pri[i])continue;
		while(x%pri[i]==0)x/=pri[i],tmp/=pri[i];
		iv*=tmp;
	}
	if(x>100000){
		LL tmp=sqrt(x);
		if(tmp*tmp==x)iv*=tmp;
		else iv=-1;
	}else{
		iv*=x*x;
	}
	if(pre>1)to[pre]=iv,++num[pre];
	else ans+=cnt_1^1,cnt_1=1;
}
signed main(){
	scanf("%d",&n);
	Sieve(100000);
	rep(i,1,n)scanf("%lld",&a[i]),insert(a[i]);
	for(auto i:to){
		if(num[i.first]>=num[i.second])ans+=num[i.first],num[i.second]=0;
	}
	printf("%d\n",ans);
	return 0;
}

E

发现只需要维护以 \(q_{m}\) 结尾的一个上升子序列,只有这个子序列的值才会对答案有贡献。注意初始化 \(q_0=n\)

过程中维护每一个值的出现次数不是很方便,考虑维护 \([1,i]\) 的出现次数,最后求后缀和就是每一个数的出现次数。

设这个上升子序列为 \(a\) ,那么 \(a_i\) 这个操作的效果就是把 \(a_{i-1}\) 复制了 \(\lfloor \dfrac{a_i}{a_{i-1}} \rfloor\) 次然后加入了 \(a_i\% a_{i-1}\) 这个前缀。

复制一个前缀可以考虑对每一个操作记录复制的次数,注意应该倒序操作(其实本质是拓扑排序)

这个前缀不是很好处理,值域太大,卡了我好久。

发现这个前缀也可以拆成 \(\lfloor \dfrac{len}{a_t} \rfloor\) 次复制加上一个前缀,其中 \(len=a_i\%a_{i-1}\)\(a_t\)\(\le len\) 的最大的 \(a\),这个前缀可以直接递归。

不存在 \(t\) 则说明 \(len\le n\) ,那么直接修改后缀和即可。

可以发现,一个数 \(x\) 不断 \(\bmod\) 一个不大于 \(x\) 的数不超过 \(\log x\) 次可以变成 \(0\)

再算上二分的 \(\log n\) ,复杂度上限 \(O(n\log n\log |V|)\) ,卡不满。

const int N=100005;
int n,tot,m;
LL q[N],a[N],cpy[N],ans[N];
void add(LL x,LL tim){
	if(!x)return;
	int i=upper_bound(a+1,a+tot+1,x)-a-1;
	if(!i)ans[x]+=tim;
	else cpy[i]+=x/a[i]*tim,add(x%a[i],tim);
}
signed main(){
	scanf("%d%d",&n,&m);
	rep(i,1,m)scanf("%lld",&q[i]);
	q[0]=n,a[tot=1]=q[m];
	per(i,m-1,0)if(q[i]<a[tot])a[++tot]=q[i];
	reverse(a+1,a+tot+1);
	cpy[tot]=1;
	per(i,tot,2)add(a[i]%a[i-1],cpy[i]+=a[i+1]/a[i]*cpy[i+1]);
	ans[a[1]]+=cpy[1]+=a[2]/a[1]*cpy[2];
	per(i,n,1)ans[i]+=ans[i+1];
	rep(i,1,n)printf("%lld\n",ans[i]);
	return 0;
}

F

千万注意一开始格子四联通,不然很难做!!!

做了好几个小时终于搞出来了/ll

开始大眼观察法,大概发现了如下性质(想象几个例子即得):

答案与 #的总个数(cnt)横向相邻的#个数(cnt1)纵向相邻的#个数(cnt2)这个矩阵往右复制,分界线上左右相邻的#个数(x)这个矩阵往下复制,分界线上上下相邻的#个数(y) 有关。

如果 \(x>0,y>0\) ,答案必然是 \(1\)

如果 \(x=0,y=0\) ,答案为 \(cnt^{k-1}\)

还剩两种棘手的情况不是很会,接着大眼观察+推公式无果,于是开始打表找规律。

打表程序

char ans[5005][5005],e[5005][5005];
int n,m,k;
int qpow(int n,int k){int res=1;for(;k;k>>=1,n*=n)if(k&1)res*=n;return res;}
void solve(int a,int b,int c,int d,int op,int k){
	if(!op){
		rep(i,a,c)rep(j,b,d)ans[i][j]='.';
		return;
	}
	if(k==1){
		rep(i,a,c)rep(j,b,d)ans[i][j]=e[i-a+1][j-b+1];
		return;
	}
	int lx=qpow(n,k-1),ly=qpow(m,k-1),t=(c-a+1)/lx;
	rep(i,1,t)rep(j,1,t)solve(a+(i-1)*lx,b+(j-1)*ly,a+i*lx-1,b+j*ly-1,e[i][j]=='#',k-1);
}
int F[5005*5005];
int find(int x){return x==F[x]?x:F[x]=find(F[x]);}
int id(int x,int y,int m){return (x-1)*m+y;}
int calc(int n,int m){
	rep(i,1,n*m)F[i]=i;
	rep(i,1,n)rep(j,1,m){
		if(ans[i][j]!='#')continue;
		if(i>1&&ans[i-1][j]=='#')F[find(id(i-1,j,m))]=find(id(i,j,m));
		if(i<n&&ans[i+1][j]=='#')F[find(id(i+1,j,m))]=find(id(i,j,m));
		if(j>1&&ans[i][j-1]=='#')F[find(id(i,j-1,m))]=find(id(i,j,m));
		if(j<m&&ans[i][j+1]=='#')F[find(id(i,j+1,m))]=find(id(i,j,m));
	}
	int res=0;
	rep(i,1,n)rep(j,1,m)if(ans[i][j]=='#'&&find(id(i,j,m))==id(i,j,m))++res;
	return res;
}
signed main(){
	n=read(),m=read(),k=read();
	rep(i,1,n)scanf("%s",e[i]+1);
	solve(1,1,qpow(n,k),qpow(m,k),1,k);
	rep(i,1,qpow(n,k)){
		rep(j,1,qpow(m,k))putchar(ans[i][j]);
		puts("");
	}
	cout<<calc(qpow(n,k),qpow(m,k))<<'\n';
}

手动构造各种情况得到如下的表以及找到的规律(序列是 \(k=1,2,...\) 时的答案):

.#.
###
#.#

1,4,20,112,656

\((((1\times 6-2)\times 6-4)\times 6-8)\times 6-16\)

..#
###
#..

1,3,13,63,313

\((((1\times 5-2)\times 5-2)\times5-2)\times 5-2\)

#..
###
.##

1,3,15,87,519

\((((1\times6-3)\times 6-3)\times 6-3)\times 6-3\)

.##.
####
#..#
#..#

1,6,48,444

\(((1\times 10-4)\times 10-12)\times 10-36\)

.##.
####
#..#
##.#

1,1,1,1,1

..#.
####
####
#...

1,4,28,256

\(((1\times10-6)\times 10-12)\times 24\)

....
.##.
.##.
....

1,4,16

.#.
.##
.#.

1,2,6,22

\(((1\times 4-2)\times 4-2)\times 4-2\)

规律清晰了起来。

这里为了方便只讲解 \(x=0,y>0\) 的情况,\(x>0,y=0\) 同理。

首先注意到每次乘的数为 \(cnt\),想了想很有道理。

然后结合最后一个数据可以发现,每次减的第一个数是 \(cnt2\)

结合所有数据,每次减的数是等比数列,并且公比为 \(y\)

规律出来了。怎么维护???

想到了矩阵乘法。

定义函数 solve(x,y,z,k)\(((((1*x-yz^0)*x-yz^1)*x-yz^2)\cdots)*x-yz^k\)

直接矩阵转移:

\[\begin{bmatrix} ans,y \end{bmatrix} * \begin{bmatrix} x,0\\-1,z \end{bmatrix} = \begin{bmatrix} ans\times x-y,y\times z \end{bmatrix} \]

矩阵快速幂求转移矩阵的 \(k\) 次幂即可。

复杂度 \(O(nm+8\log k)\)

深深的感受到了矩阵的强大!我这种完全不会矩阵的人居然能自己推出矩阵了!感动啊

int rdc(){
	char ch=getchar();
	while(ch!='#'&&ch!='.')ch=getchar();
	return ch=='#';
}
#define mod 1000000007
void fmod(int&x){x+=x>>31&mod,x-=mod,x+=x>>31&mod;}
const int N=1005;
int n,m,a[N][N];
int cnt,cnt1,cnt2,x,y,ans;
LL k;
int qpow(int n,LL k){int res=1;for(;k;k>>=1,n=1ll*n*n%mod)if(k&1)res=1ll*res*n%mod;return res;}
struct Matrix{
	int a[2][2];
	Matrix(){a[0][0]=a[0][1]=a[1][0]=a[1][1]=0;}
	int*operator [] (const int&k){return a[k];}
	friend Matrix operator * (const Matrix&a,const Matrix&b){
		Matrix res;
		rep(i,0,1)rep(j,0,1)rep(k,0,1)fmod(res.a[i][j]+=1ll*a.a[i][k]*b.a[k][j]%mod);
		return res;
	}
	void e(){
		a[0][0]=1,a[0][1]=0;
		a[1][0]=0,a[1][1]=1;
	}
}sta,tur;
Matrix Matrix_pow(Matrix a,LL k){
	Matrix res;res.e();
	for(;k;k>>=1,a=a*a)if(k&1)res=res*a;
	return res;
}
int solve(int x,int y,int z,LL k){
	sta[0][0]=1,sta[0][1]=y;
	tur[0][0]=x,tur[0][1]=0;
	tur[1][0]=-1,tur[1][1]=z;
	tur=Matrix_pow(tur,k);
	sta=sta*tur;
	return sta[0][0];
}
signed main(){
	scanf("%d%d%lld",&n,&m,&k);
	rep(i,1,n)rep(j,1,m)a[i][j]=rdc(),cnt+=a[i][j];
	rep(i,1,n)rep(j,1,m-1)cnt1+=a[i][j]&&a[i][j+1];
	rep(i,1,n-1)rep(j,1,m)cnt2+=a[i][j]&&a[i+1][j];
	rep(i,1,n)x+=a[i][1]&&a[i][m];
	rep(j,1,m)y+=a[n][j]&&a[1][j];
	if(x&&y)return puts("1"),0;
	if(!x&&!y)return printf("%d\n",qpow(cnt,k-1)),0;
	if(x&&!y)return printf("%d\n",solve(cnt,cnt1,x,k-1)),0;
	if(y&&!x)return printf("%d\n",solve(cnt,cnt2,y,k-1)),0;
	return 0;
}
posted @ 2020-12-23 21:37  zzctommy  阅读(141)  评论(0编辑  收藏  举报