Skip to content

AGC003 做题记录

A

每次走路距离至少为1,所以南北、东西必须同时出现才能回到原点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define ll long long
using namespace std;
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
char s[1005];
bool a,b,c,d;
int main(){
    scanf("%s",s+1);
    for(int i=1;s[i];++i){
        a|=(s[i]=='S');
        b|=(s[i]=='N');
        c|=(s[i]=='E');
        d|=(s[i]=='W');
    }
    puts((a==b&&c==d)?"Yes":"No");
    return 0;
}

B

考虑贪心。每种牌可以先自己消耗,如果还剩下一张牌,那么与后面一种牌配对最多会让后面那种牌浪费一张,这样操作比留下这张牌要更优(因为这张牌已经没用了),按该策略贪心即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define ll long long
using namespace std;
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
const int N=1e5+5;
int n,a[N];
ll ans;
int main(){
    n=read();
    rep(i,1,n)a[i]=read();
    rep(i,1,n){
        ans+=a[i]/2;
        a[i]%=2;
        if(a[i]&&a[i+1]){
            --a[i];--a[i+1];++ans;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

C

最小化操作一说明我们可以无限次使用操作二,不难发现这等价于交换 \(a_i,a_{i+2}\) 两个元素,也就是交换相邻的奇数或偶数下标的元素。这些对换可以生成所有奇数位和偶数位的置换,我们只需要对于排序前后的下标奇偶性不同的元素使用操作一即可。每次操作一可以减少两个这样的元素,所以答案要除以2。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define ll long long
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
const int N=1e5+5;
struct node{int v,id;}a[N];
bool cmp(node a,node b){return a.v<b.v;}
int n,ans;
int main(){
    n=read();
    rep(i,1,n)a[i]={read(),i};
    sort(a+1,a+n+1,cmp);
    rep(i,1,n){
        if(i%2!=a[i].id%2)ans++;
    }
    printf("%d\n",ans/2);
    return 0;
}

D

有一说一这题没到黑题难度吧,算是比较常规的数论题(

对于每个正整数 \(x\) 我们考虑两种数,\(Pair(x)\) 表示与 \(x\) 相乘为立方数的最小正整数,而 \(Norm(x)\) 表示 \(x\) 去掉所有立方因子后的正整数。可以发现两个数如果拥有同样的 \(Norm\) ,那么也会有同样的 \(Pair\) ,也就是这两个数本质上是相同的。而如果一个数的 \(Norm\) 是另一个数的 \(Pair\) ,那么这两数显然不能同时选入答案。于是答案就是对于 \(s[i]\) 中所有本质不同的 \(x\) ,求 \(s[i]\) 中本质为 \(Norm(x)\)\(Pair(x)\) 的个数的最大值。

下面考虑如何求解 \(Norm(x)\) ,其实只需要筛掉所有立方因子就可以了,也就是 \(\le 10^\frac{10}{3}\approx 2160\) 的质数的立方。筛完之后把这些质数剩下的次幂也从 \(x\) 中筛掉,并贡献给 \(Pair(x)\) 。由于剩下的因子大于原来 \(x^\frac{1}{3}\) ,推出最后剩下的 \(x\) 只有几类:

  • \(x\) 为质数
  • \(x\) 为质数的平方
  • \(x\) 为不同质数的积

对于第一类和第三类,容易得出 \(Pair(x)=x^2\) 。而对于第二类,\(Pair(x)=\sqrt x\) 。最后把这个数加入 \(Norm(x)\) 的桶里即可。

时间复杂度:\(O(N\frac{(MAX s_i)^\frac13}{\log MAX s_i})+O(N\log N)\)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define ll long long
using namespace std;
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
const int N=1e5+10;
ll n,pri[N],tot;
ll a[N],b[N],ans;
bool vis[N];
map<ll,ll>mp;
int main(){
    n=read();
    rep(i,2,2160){
        if(!vis[i])pri[++tot]=i;
        for(int j=1;j<=tot&&pri[j]*i<=2160;j++){
            vis[i*pri[j]]=1;
            if(i%pri[j]==0)break;
        }
    }
    rep(i,1,n){
        scanf("%lld",&a[i]);
        ll norm=1,pair=1;
        rep(j,1,tot){
            ll cube=pri[j]*pri[j]*pri[j];
            while(a[i]%cube==0)a[i]/=cube;
        }
        ++mp[a[i]];norm=a[i];
        rep(j,1,tot){
            if(a[i]%pri[j]!=0)continue;
            if(a[i]%(pri[j]*pri[j]))pair*=pri[j]*pri[j];
            else pair*=pri[j];
            while(a[i]%pri[j]==0)a[i]/=pri[j];
        }
        ll sqr=(ll)sqrt(a[i]);
        if(sqr*sqr!=a[i])pair*=a[i]*a[i];
        else pair*=sqr;
        a[i]=norm;b[i]=pair;
    }
    rep(i,1,n){
        if(a[i]==1)continue;
        ans+=max(mp[a[i]],mp[b[i]]);
        mp[a[i]]=mp[b[i]]=0;
    }
    printf("%lld\n",ans+!!mp[1]);
    return 0;
}

E

神仙题。

首先如果遇到了 \(A_{i+1}<A_i\) 的情况,那么不用考虑 \(A_i\) (因为后者取了更小的区间)。于是可以先用单调栈搞出一个单调递增的序列,这与原序列的操作是等价的。

不难发现第 \(i\) 次操作等价于重复目前的序列 \(\lfloor\dfrac{A_i}{A_{i-1}}\rfloor\) 次,然后再取原序列长度 \(d\equiv A_i\pmod{A_{i-1}}\) 的前缀。

问题在于这一小段不重复的前缀,而它在前面也一定是某一段 \(A_j\) 的重复出现。我们可以用类似的方法分解这段前缀,由于序列单调递增,我们可以找到满足 \(A_j<d,A_{j+1}>d\)\(j\) 并且用它来取模,递归求解即可。

注意到每次取模 \(d\) 至少减小一半,递归到底时就是 \(1\)\(d\) 的前缀对答案的贡献,可以用差分实现。

时间复杂度:\(O(n\log n)\)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5;
int n,q,len;
ll A[N],F[N],tim[N];
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
void Solve(ll d,ll w){
    int j=upper_bound(A+1,A+len+1,d)-A-1;
    if(!j) tim[1]+=w,tim[d+1]-=w;
    else F[j]+=d/A[j]*w,Solve(d%A[j],w);
}
int main(){
    n=read(),q=read();
    A[++len]=n;
    while(q--){
        ll x=read();
        while(len&&A[len]>=x) len--;
        A[++len]=x;
    }
    F[len]=1;
    for(int i=len;i>=2;i--) F[i-1]+=A[i]/A[i-1]*F[i],Solve(A[i]%A[i-1],F[i]);
    tim[1]+=F[1];tim[A[1]+1]-=F[1];
    for(int i=1;i<=n;i++) printf("%lld\n",tim[i]+=tim[i-1]);
}

F

首先看简单情形:

  • 当这个图形上下,左右拼接都是连通块,那么总连通块数为 \(1\)
  • 当这个图形上下,左右拼接都不是连通块,那么总连通块数为 \(cnt^{K-1}\) ,其中 \(cnt\) 为黑格数量。

唯一的问题就是这个图形上下和左右拼接只有一个是连通块的情况。不妨设左右连通。\(1\) 级分形的数量很好算而且是连通块,而我们又知道连通块数=黑点个数 - 边数,只用考虑如何统计边数。而上下不连通,所以答案就是 \(K-1\) 级分形中 黑点个数 - 左右相邻黑格的对数。

设原图左右相邻对数有 \(a\) 个,左右连通的行数为 \(b\) 。那么 \(K\) 级分形的左右连通行数为 \(K-1\) 分形的左右连通行数 \(\times b\)\(K\)级分形的相邻对数就是上级分形的相邻对数 \(\times b\) 加上黑格数量 \(\times a\) (黑格替换为原网格图)。

这个递推等价于用矩阵快速幂求 \(A=\begin{bmatrix} cnt &a \\ 0&b \end{bmatrix}^{K-1}\) ,答案为 \(A_{11}-A_{12}\)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define ll long long
using namespace std;
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
const int P=1e9+7;
int n,m,cnt,s1,s2,flag1,flag2;
ll k;
char s[1005][1005];
ll fpow(ll x,ll y){
    ll res=1;
    while(y){
        if(y&1)res=res*x%P;
        y>>=1;x=x*x%P;
    }
    return res;
}
struct Matrix{
    ll a[3][3];
    Matrix(){memset(a,0,sizeof(a));}
    Matrix operator*(const Matrix &M){
        Matrix ans;
        rep(i,1,2)rep(j,1,2)rep(k,1,2)
        ans.a[i][j]=(ans.a[i][j]+a[i][k]*M.a[k][j]%P)%P;
        return ans;
    }
}A,ans;
int main(){
    n=read(),m=read(),k=read()-1;
    rep(i,1,n)scanf("%s",s[i]+1);
    rep(i,1,n){
        rep(j,1,m){
            if(s[i][j]=='#'){
                ++cnt;
                s1+=s[i][j-1]=='#';
                s2+=s[i-1][j]=='#';
            }
        }
        if(s[i][1]=='#'&&s[i][m]=='#')++flag1;
    }
    rep(i,1,m)if(s[1][i]=='#'&&s[n][i]=='#')++flag2;
    if(flag1&&flag2)return puts("1"),0;
    if(!flag1&&!flag2){
        printf("%lld\n",fpow(cnt,k));
        return 0;
    }
    if(!flag1)swap(flag1,flag2),swap(s1,s2);
    A.a[1][1]=cnt,A.a[1][2]=s1,A.a[2][2]=flag1;
    ans.a[1][1]=1,ans.a[2][2]=1;
    while(k){
        if(k&1)ans=ans*A;
        k>>=1;A=A*A;
    }
    printf("%lld\n",(ans.a[1][1]-ans.a[1][2]+P)%P);
    return 0;
}

Comments