AGC026 解题记录

春归结局也挺好的...


A:DP

乱搞做法一大堆,反正是 \(A\) 题,数据范围也小,直接枚举上一维的颜色做dp。


B:简单数论

首先先特判一些情况。
1.b>d
这种情况会出现入不敷出,显然无法无限购买。
2.a<b
第一天就买不了
3.c>b
再去掉上面两种情况之后c>b的时候一定可以无限买,很显然,不解释了。

先令 \(a = a\mod b\) ,显然不影响答案。
去掉上面几种情况后,开始推式子。
设已经买了 \(x\) 次,进货了 \(y\) 次,那么可以得到

\[\begin{aligned} a+xd &\in(yb+c,(k+1)b)\\ xd-yb &\in(c-a,b-a)\\ (x-y)d-y(b-d)&\in(L,R)\\ \end{aligned} \]

注意到式 \(2\) 和式 \(3\) 形式一样,最后肯定有一边变成 \(0\) ,另一边变成 \(\gcd(x,y)\)
因此,等价于是否存在整数 \(k\) 满足 \(k\times \gcd(x,y)\in(L,R)\)

时间复杂度\(O(T \log V)\)

Code

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
    bool f=true;ll x=0;
    register char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    if(f) return x;
    return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
ll a,b,c,d;
il ll gcd(ll x,ll y){
    for(;y;x%=y,swap(x,y));
    return x;
}
int main(){
    for(ri t=read();t;--t){
        a=read(),b=read(),c=read(),d=read();
        if(a<b){
            puts("No");
            continue;
        }
        if(b>d){
            puts("No");
            continue;
        }
        if(c>b){
            puts("Yes");
            continue;
        }
        a%=b;
        if(a>c){
            puts("No");
            continue;
        }
        ll l=c-a,r=b-a;
        ll g=gcd(b,d),x=(l+g-1)/g*g;
        while(x<=l) x+=g;
        if(x<r) puts("No");
        else puts("Yes");
    }

    return 0;
}

C:折半搜索+哈希

这题挺简单的,就是有点难
这题都快把折半搜索写在题面里了,考虑如何折半。

如果前半部分搜出来的两种颜色字符串哈希值是 \((x_l,y_l)\) ,后半部分搜出来是 \((x_r,y_r)\),那么这两个能匹配起来的条件是:
\( \begin{aligned} x_l+D^n x_r&=y_r+D^n y_l\\ x_l+D^n y_l&=y_r -D^n x_r\\ \end{aligned} \)
到这一步就不用多解释了,直接上map。
时间复杂度 \(O(n2^n)\)

Code

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
    bool f=true;ll x=0;
    register char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    if(f) return x;
    return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
char s[40];
int n;
const ll mod1=998244353,mod2=1e9+7,d=131;  
struct node
{
    ll x,y;
    bool operator==(const node &p)const{
        return x==p.x&&y==p.y;
    }
    ll operator %(const ll &p)const{
        return x*y%p;
    }
    node operator *(const node &p)const{
        return (node){x*p.x%mod1,y*p.y%mod2};
    }
    node operator +(const node &p)const{
        return (node){(x+p.x)%mod1,(y+p.y)%mod2};
    }
    node operator -(const node &p)const{
        return (node){(x-p.x+mod1)%mod1,(y-p.y+mod2)%mod2};
    }
}B,D;
struct hmap{
    static const int P=2e6+3,M=3e7+10;
    int hed[M],nxt[M],cnt;node val[M];ll as[M];
    bool count(node x){
        int c=hed[x%P];
        while(c){
            if(val[c]==x) return 1;
            c=nxt[c];
        }return 0;
    }
    ll& operator [](node x){
        int c=hed[x%P];
        while(c){
            if(val[c]==x) return as[c];
            c=nxt[c];
        }
        ++cnt;val[cnt]=x;nxt[cnt]=hed[x%P];hed[x%P]=cnt;
        return as[cnt];
    }
    int size(){return cnt;}
}f;
#define T pair<node,node> 
ll base[42],ans;
T solve(ll x){
    node l=(node){0,0},r=(node){0,0};
    for(ri i=0;i<n;++i){
        if(base[i]&x){
            l=l*D;
            l=l+(node){s[i],s[i]};
        }
    }
    for(ri i=n-1;~i;--i){
        if(!(base[i]&x)){
            r=r*D;
            r=r+(node){s[i],s[i]};
        }
    }
    return (T){l,r};
}
int main(){
    n=read();
    scanf("%s",s);
    D=B=(node){d,d};
    base[0]=1;
    for(ri i=1;i<=n;++i) B=B*D,base[i]=base[i-1]<<1;   
    for(ri i=0;i<base[n];++i){
        T now=solve(i);
        f[now.first*B-now.second]++;
    }
    for(ri i=0;i<n;++i)s[i]=s[n+i];
    for(ri i=0;i<base[n];++i){
        T now=solve(i);
        if(f.count(now.second*B-now.first))ans+=f[now.second*B-now.first];
    }
    print(ans);
    return 0;
}

D:笛卡尔树+DP

挺不错一道题,对着题解想了很久都没想出来。
首先不难发现,如果是一个 \(n\times m\) 的矩阵,可以先枚举第一行,然后往上转移。
分两类:
1.上一行是 \(010101...\) 形式的,即 \(01\) 交错。
这种时候有两种可能,要么是和上一行完全一样,要么是上一行取反。
而且这两种情况依旧是 \(01\) 交错。

2.除了 1 中所有情况。
只能是上一行取反,所以这一行依旧是情况 2。

所以可以得到这样的DP方程:\( \left\{ \begin{aligned} &f_{i,0}=f_{i-1,0}\\ &f_{i,1}=2f_{i-1,1}\\ \end{aligned} \right. \)

其中初始化是 \(f_{1,1}=2,f_{1,0}=2^m-f_{1,1}\)

接下来考虑在柱状图中要如何转移。


可以发现,当柱状图是一个上凸的时候,可以从上往下进行转移,下面给出一个例子(本人在这个地方想了很久)。
\( \begin{matrix} &&?\\ &?&?&?\\ ?&?&?&?&? \end{matrix} \)

假设最后需要知道的是\(f_{3}\)(从上往下标号)。
首先有\(f_{1,0}=0,f_{1,1}=2\)
考虑第二行:
\( \begin{matrix} &?\\ ?&?&?\\ \end{matrix} \)
现在先求\(f_{2,1}\)
先不管中间,把两边填起来,用两种方法,分别是\( \begin{matrix} 0&?&0\\ \end{matrix} \)\(\begin{matrix} 1&?&1\\ \end{matrix} \)
不难发现中间在两种方案中中间可以填的方案数都等价于\(f_{1,1}\)
因为中间需要填上的是一段交错的 \(01\) ,并且填的方案已经确定了,剩下的都可以和上一行的 \(f_{1,1}\) 一一对应。
\(f_{2,0}\) 则可以先统计所有的方案,再减去 \(f_{2,1}\)

这一部分的转移

void solve(int u){
    f[u][0]=ksm(2,len[u]),f[u][1]=2;
    for(ri i=0;i<g[u].size();++i){
        int v=g[u][i];
        solve(v);
        f[u][0]=mul(f[u][0],f[v][0]+2*f[v][1]);  
        f[u][1]=mul(f[u][1],f[v][1]);
    }
    f[u][0]=add(f[u][0],mod-f[u][1]);
    f[u][1]=mul(f[u][1],ksm(2,h[u]-1));
}

而当有多个上凸的时候,从上往下考虑,它们先是不联通,直到某一行把它们连到了一起。

这不是一棵树吗?而且是一颗笛卡尔树!

于是,就可以建树跑树型DP了。

理论上建树可以 \(O(n)\) ,由于快速幂部分都是 \(2\) ,可以提前预处理实现光速幂做到预处理 \(O(\sqrt{V})\) ,查询 \(O(1)\)
所以总复杂度是 $ O(n+\sqrt{V}) $ ,完全可以把 \(n\) 设为 \(10^7\)结果只有100

Code

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
    bool f=true;ll x=0;
    register char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    if(f) return x;
    return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
const int MAXN=129;
/*
考虑从上往下一层一层构造 
可以建一颗笛卡尔树跑树型DP  
不过这棵笛卡尔树有点不太一样?    
*/
ll f[MAXN][2],a[MAXN];//0表示没有间隔,1表示有间隔
#define lc u<<1
#define rc u<<1|1
#define pii pair<int,int>
struct Seg
{
    struct T
    {
        int l,r;
        pii v;
    }t[MAXN<<2];
    void build(int u,int l,int r){
        t[u].l=l,t[u].r=r;
        if(l==r){
            t[u].v=(pii){a[l],l};
            return;
        }
        int mid=(l+r)>>1;
        build(lc,l,mid);
        build(rc,mid+1,r);
        t[u].v=min(t[lc].v,t[rc].v);
    }
    pii query(int u,int l,int r){
        if(t[u].l==l&&t[u].r==r) return t[u].v;
        int mid=(t[u].l+t[u].r)>>1;
        if(r<=mid) return query(lc,l,r);
        else if(l>mid) return query(rc,l,r);
        else return min(query(lc,l,mid),query(rc,mid+1,r));
    }
}T;
ll n,root;  
ll L[MAXN],R[MAXN],h[MAXN],len[MAXN];
int cnt;
vector<int> g[MAXN];
int build(int l,int r){//有用单调栈实现O(n)建树的方法,不过这里不是复杂度瓶颈,线段树比较好写
    vector<int> vec;
    vec.clear();
    int u=++cnt;
    len[u]=r-l+1,L[u]=l,R[u]=r,h[u]=T.query(1,l,r).first;
    vec.push_back(l-1);
    for(ri now=l;now<=r;){//把深度相同的丢到一起  
        pii res=T.query(1,now,r);
        if(res.first>h[u]) break;
        vec.push_back(res.second);
        now=res.second+1;
    }
    vec.push_back(r+1);
    for(ri i=0;i<vec.size()-1;++i){
        if(vec[i]+1>vec[i+1]-1) continue;
        int v=build(vec[i]+1,vec[i+1]-1);
        g[u].push_back(v);
        h[v]-=h[u];
        len[u]-=R[v]-L[v]+1;
    }
    return u;
}
const ll mod =1e9+7;
il ll ksm(ll d,ll t){
    ll res=1;
    for(;t;t>>=1,d=d*d%mod)
        if(t&1) res=res*d%mod;
    return res;
}
il ll add(ll x,ll y){return (x+=y)<mod?x:x-mod;}
il ll mul(ll x,ll y){return x*y%mod;}
void solve(int u){
    f[u][0]=ksm(2,len[u]),f[u][1]=2;
    for(ri i=0;i<g[u].size();++i){
        int v=g[u][i];
        solve(v);
        f[u][0]=mul(f[u][0],f[v][0]+2*f[v][1]);  
        f[u][1]=mul(f[u][1],f[v][1]);
    }
    f[u][0]=add(f[u][0],mod-f[u][1]);
    f[u][1]=mul(f[u][1],ksm(2,h[u]-1));
}
int main(){
    n=read();
    for(ri i=1;i<=n;++i) a[i]=read();
    T.build(1,1,n);
    root=build(1,n);
    solve(root);
    print(add(f[root][0],f[root][1]));
    return 0;
}

E:贪心+模拟

要是想到了点上应该马上就能切。
\(a=1,b=-1\) ,那么不难发现从前往后每个极短和为 \(0\) 区间都是互不干扰的。
并且,如果区间开头是 \(a\) ,那么一定有 \(pos_{a_i} < pos_{b_i}\),反之也成立。
这个结论比较显然,手模一下就能发现。
如果能够求出每一段区间的最大字典序,就可以用一个单调栈把它们拼接起来了。
接下来考虑怎么求区间的。
分类讨论:


1.区间开头是 \(a\)
那么最优的情况一定是若干段 \(ab\),又有 \(pos_{b_i}<pos_{b_{i+1}}\),所以贪心地能取就取。
2.区间的开头是 \(b\)
如果选择了第一个 \(b\) ,设它的下标为 \(l\) ,所对应的 \(a\) 的下标为 \(r\),那么不难发现区间 \((l,r)\) 内的都是 \(b\) ,一定是选择了更优。
这样一直选下去,会发现刚好会一直选到结尾,因为这个区间保证了只有在结尾处的前缀和为 \(0\)
所以可以枚举从第几个 \(b\) 开始选,记录一下字典序最大的。


上面的操作的总复杂度是 \(O(n^2)\) 的,单调栈过程中的复杂度是 \(O(\sum^{n}_{i=1}L_i)=O(n)\) 所以总复杂度是\(O(n^2)\)

这一部分的转移

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
    bool f=true;ll x=0;
    register char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    if(f) return x;
    return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
const int MAXN=6e3+7;
string b[MAXN];
bool cmp(string &x,string &y){
    for(ri i=0,len=min(x.size(),y.size());i<len;++i){
        if(x[i]<y[i]) return 1;
        if(x[i]>y[i]) return 0;
    }
    return 0;
}
string check(string &s,int len){
    string res;
    if(s[0]=='a'){
        res="";
        for(ri i=0,j=-1;i<len;++i){
            if(s[i]=='b') continue;
            if(i>j){
                res+="ab";
                ++j;
            }
            else{
                for(ri k=j+1;k<len;++k){
                    if(s[k]=='b'){
                        s.erase(s.begin()+k);
                        len--;
                        break;
                    }
                }
                s.erase(s.begin()+i);
                --len;
                --i,--j;
            }
            while(j<len&&s[j]!='b') ++j;
        }
        return res;
    }
    res=s;
    for(ri i=len/2,j;i;--i){
        for(j=0;j<len;++j){
            if(s[j]=='b'){
                s.erase(s.begin()+j);
                --len;
                break;
            }
        }
        for(j=0;j<len;++j){
            if(s[j]=='a'){
                s.erase(s.begin()+j);
                --len;
                break;
            }
        }
        if(s>res) res=s;
    }
    return res;
}
char s[MAXN];
int n,top;
int main(){
    //freopen("rand.in","r",stdin);
    //freopen("1.out","w",stdout);
    string res,now;
    n=read();
    scanf("%s",s+1);
    ll sum=0;
    for(ri i=1,lst=0;i<=(n<<1);++i){
        if(s[i]=='a') sum-=1;
        else sum+=1;
        if(!sum){
            res.clear();
            res.resize(i-lst);
            for(ri j=lst+1;j<=i;++j) res[j-lst-1]=s[j];
            now=check(res,i-lst);
            lst=i;
            while(top&&cmp(b[top],now)) --top;
            b[++top]=now;
        }
    }
    string ans="";
    for(ri i=1;i<=top;++i) ans+=b[i];
    cout<<ans<<endl;
    return 0;
}

F:无中文题面,鸽了。

posted @ 2021-04-13 20:12  krimson  阅读(98)  评论(1编辑  收藏  举报