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\)
直接矩阵转移:
矩阵快速幂求转移矩阵的 \(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;
}