2019-2020 XX Opencup GP of Tokyo

链接

F. Robots

题解

I. Amidakuji

题解

H. Construct Points

\(\color{gray}{\texttt{skipped}}\)

E. Count Modulo 2

题意

\(n\) 个不同的物体,问完全背包选 \(n\) 个大小为 \(s\) 的方案数 \(\bmod 2\) 的结果。

题解

可以发现 \(n,s\) 都很大,显然不能直接用背包之类的方法做。但是每个物体的大小都很小,考虑从这方面入手。

考虑形式化问题,即 \(\sum p_i=n,\sum p_ia_i=s\) 的方案数。考虑一组 \(p_i\) 的贡献,即 \(\binom{n}{p_1,p_2,p_3\cdots}=\frac{n!}{\prod p_i!}\)

为了产生贡献上下 \(2\) 的因子个数要相同,又有 \(\sum p_i=n\),故每个 \(p_i\) 的任意位都不能相同。这样可以把 \(n\) 的每一位分配给一个 \(p_i\)

\(n\) 二进制拆分后变成 \(\sum_{i\in n}a_{x_i}=s\)

考虑从高到低位处理,用 bitset 优化可行的方案,复杂度 \(O(\frac{kV\log n}{\omega})\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset>
#define N 200010
#define ll long long
using namespace std;
int a[N];
bitset<N>f,g;
int main()
{
    int t;
    scanf("%d",&t);
    while(t --> 0)
    {
        ll n,s;int k;
        scanf("%lld%lld%d",&n,&s,&k);
        int m=0;
        for(int i=1;i<=k;i++) scanf("%d",&a[i]),m=max(m,a[i]);
        f.reset(),f.set(0);
        for(int i=0;i<=60;i++)
        {
            g.reset();
            if(n>>i&1) for(int j=1;j<=k;j++) g^=f<<a[j];
            else g=f;
            f.reset();
            for(int j=0;j<m;j++) f[j]=g[j<<1|(s>>i&1)];
        }
        printf("%d\n",f.test(0));
    }
    return 0;
}

L. Yosupo's Algorithm

又是一个 XJ 原题。

考虑将两部分分开做,那么等同于问红点和蓝点一段前缀的答案。

\(y\) 坐标分治,假设红区间为 \([l,m]\) 蓝区间为 \([m+1,r]\)。可以证明的是,假如一组答案分别位于两个区间内,那么其中一定包含左区间最大值或是右区间最大值。

然后将 \(O(n\log n)\) 个点用二维数点的方式扫一遍即可,复杂度 \(O(n\log^2 n+q\log n)\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define N 100010
#define M N*20
#define S 1000010
#define inf 1000000000
using namespace std;
struct node{
    int x,y,w;
    bool operator <(const node a)const{return x==a.x?y<a.y:x<a.x;}
}a[N],b[N],q[S];
int n;
typedef vector<int> vec;
void split(vec &v,vec &vl,vec &vr,int mid,node f[])
{
    for(int p:v) if(f[p].y<=mid) vl.push_back(p);
    else vr.push_back(p);
}
node g[M];int gt;
void solve(int l,int r,vec &va,vec &vb)
{
    if(va.empty() || vb.empty()) return;
    int mid=(l+r)>>1;
    vec vla,vra,vlb,vrb;
    split(va,vla,vra,mid,a);split(vb,vlb,vrb,mid,b);
    int ma=0,mb=0;
    for(int v:vla) if(a[v].w>a[ma].w) ma=v;
    for(int v:vrb) if(b[v].w>b[mb].w) mb=v;
    if(ma && mb)
    {
        for(int v:vrb) g[++gt]=(node){a[ma].x,b[v].x,a[ma].w+b[v].w};
        for(int v:vla) g[++gt]=(node){a[v].x,b[mb].x,a[v].w+b[mb].w};
    }
    solve(l,mid,vla,vlb),solve(mid+1,r,vra,vrb);
}
int py[S],yt;
int px[S],xt;
int val[S];
void adds(int x,int v){for(;x<=xt;x+=(x&(-x))) val[x]=max(val[x],v);}
void addp(int x,int v){for(;x;x-=(x&(-x))) val[x]=max(val[x],v);}
int qrys(int x,int v=-1){for(;x<=xt;x+=(x&(-x))) v=max(v,val[x]);return v;}
int qryp(int x,int v=-1){for(;x;x-=(x&(-x))) v=max(v,val[x]);return v;}
int ans[S];
int main()
{
    memset(val,-1,sizeof(val));
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w),py[++yt]=a[i].y;
    for(int i=1;i<=n;i++) scanf("%d%d%d",&b[i].x,&b[i].y,&b[i].w),py[++yt]=b[i].y;
    int m;
    scanf("%d",&m);
    for(int i=1;i<=m;i++) scanf("%d%d",&q[i].x,&q[i].y),q[i].w=i;
    sort(py+1,py+yt+1);
    yt=unique(py+1,py+yt+1)-py-1;
    for(int i=1;i<=n;i++) a[i].y=lower_bound(py+1,py+yt+1,a[i].y)-py;
    for(int i=1;i<=n;i++) b[i].y=lower_bound(py+1,py+yt+1,b[i].y)-py;
    for(int i=1;i<=n;i++) px[++xt]=a[i].x;
    for(int i=1;i<=m;i++) px[++xt]=q[i].x;
    sort(px+1,px+xt+1);
    xt=unique(px+1,px+xt+1)-px-1;
    for(int i=1;i<=n;i++) a[i].x=lower_bound(px+1,px+xt+1,a[i].x)-px;
    for(int i=1;i<=m;i++) q[i].x=lower_bound(px+1,px+xt+1,q[i].x)-px;
    xt=0;
    for(int i=1;i<=n;i++) px[++xt]=b[i].x;
    for(int i=1;i<=m;i++) px[++xt]=q[i].y;
    sort(px+1,px+xt+1);
    xt=unique(px+1,px+xt+1)-px-1;
    for(int i=1;i<=n;i++) b[i].x=lower_bound(px+1,px+xt+1,b[i].x)-px;
    for(int i=1;i<=m;i++) q[i].y=lower_bound(px+1,px+xt+1,q[i].y)-px;
    vec va,vb;
    for(int i=1;i<=n;i++) va.push_back(i),vb.push_back(i);
    solve(1,yt,va,vb);
    sort(q+1,q+m+1);sort(g+1,g+gt+1);
    for(int i=1;i<=m;i++) ans[i]=-1;
    // for(int i=1;i<=m;i++) printf("ques : %d %d %d\n",q[i].x,q[i].y,q[i].w);
    // for(int i=1;i<=gt;i++) printf("node : %d %d %d\n",g[i].x,g[i].y,q[i].w);
    int u=1;
    //x<X y>Y
    for(int l=1,r=1;l<=gt;l=r)
    {
        for(;u<=m && q[u].x<g[l].x;u++) ans[q[u].w]=max(ans[q[u].w],qrys(q[u].y));
        for(r=l;g[r].x==g[l].x;r++) addp(g[r].y,g[r].w);
    }
    for(;u<=m;u++) ans[q[u].w]=max(ans[q[u].w],qrys(q[u].y));
    u=1;
    memset(val,-1,sizeof(val));
    reverse(q+1,q+m+1);reverse(g+1,g+gt+1);
    //x>X y<Y
    for(int l=1,r=1;l<=gt;l=r)
    {
        for(;u<=m && q[u].x>g[l].x;u++) ans[q[u].w]=max(ans[q[u].w],qryp(q[u].y));
        for(r=l;g[r].x==g[l].x;r++) adds(g[r].y,g[r].w);
    }
    for(;u<=m;u++) ans[q[u].w]=max(ans[q[u].w],qryp(q[u].y));
    for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    return 0;
}
/*
2
-3 1 1
-6 3 10
3 4 100
5 2 1000
5
-5 4
-2 6
-4 1
-10 10
-1 2
*/

B. Evacuation

首先如果 \(S\) 个人的位置 \(p\) 满足 \(r-p>p-l\),那么他们一定不会往左跑出 \(l\)。这样可以按照中点 \(m\) 将区间分为 \([l,m],[m+1,r]\) 两段,左边最多只能跑出左边界,右边同理。

\(f(p,r)\) 表示 \(S\) 个人在 \(p\),当前限制为 \(r\)(超过 \(r\) 就安全了),可以发现 \(f\) 能够 \(O(1)\) 计算,具体来说预处理距离 \(p\) 满足能承受 \(S\) 个人的最小半径 \(R_p\),并统计 \(i\times a_i\) 的前缀和,如果 \(|r-p|\leq R_p\) 那么多出部分会直接到安全地带,分类讨论一下即可。

然后可以发现的是 \(f(p,r)-f(p,r+1)\geq f(p+1,r)-f(p+1,r+1)\)。正确性挺显然的,其实是我不会证。然后就有了决策单调性,即对于一个区间 \([l,r]\),令 \(f(x,r_i)\) 的最大位置为 \(x_i\),有 \(r_i\leq r_j\Leftrightarrow x_i\leq x_j\)

直接线段树套整体二分即可。复杂度 \(O(n\log^2 n)\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 200010
#define ll long long
using namespace std;
ll a[N],s[N],ss[N],S;
int n,len[N];
ll w0(int l,int r){return l*s[l]-l*s[r]-ss[l]+ss[r];}
ll W(int x,int y)
{
    if(!len[x]) return 0;
    int p=min(abs(x-y),len[x]-1);ll r=S-(s[x+p]-s[x-p-1]);
    return w0(x,x-p-1)+w0(x,x+p)+r*(p+1);
}
struct node{
    int u,v;
    node(int U=0,int V=0):u(U),v(V){}
};
ll ans[N];
bool operator <(const node a,const node b){return a.v<b.v;}
struct Con{
    vector<node>f[N<<2];
    void insert(int u,int l,int r,int L,int R,node v)
    {
        if(L>R) return;
        if(L<=l && r<=R){f[u].push_back(v);return;}
        int mid=(l+r)>>1;
        if(L<=mid) insert(u<<1,l,mid,L,R,v);
        if(R>mid) insert(u<<1|1,mid+1,r,L,R,v);
    }
    void solve(vector<node>&g,int L,int R,int l,int r)
    {
        if(L>R) return;
        int mid=(L+R)>>1,id=0;ll mx=-1;
        for(int i=l;i<=r;i++)
        if(W(i,g[mid].v)>mx) mx=W(i,g[mid].v),id=i;
        ans[g[mid].u]=max(ans[g[mid].u],mx);
        solve(g,L,mid-1,l,id);solve(g,mid+1,R,id,r);
    }
    void dfs(int u,int l,int r)
    {
        sort(f[u].begin(),f[u].end());
        solve(f[u],0,(int)f[u].size()-1,l,r);
        if(l==r) return;
        int mid=(l+r)>>1;
        dfs(u<<1,l,mid);dfs(u<<1|1,mid+1,r);
    }
}T[2];
int main()
{
    scanf("%d%lld",&n,&S);
    a[0]=a[n+1]=s[0]=S;
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(int i=1;i<=n+1;i++) s[i]=s[i-1]+a[i],ss[i]=ss[i-1]+a[i]*i;
    for(int i=1;i<=n;i++)
    {
        len[i]=i;
        int l=0,r=min(n-i+1,i-1);
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(s[i+mid]-s[i-mid-1]>=S) r=mid-1,len[i]=mid;
            else l=mid+1;
        }
    }
    // for(int i=1;i<=n;i++) printf("%d ",len[i]);puts("");
    int q;scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {
        int l,r;scanf("%d%d",&l,&r);
        int mid=(l+r)>>1;
        // for(int j=l;j<=mid;j++) ans[i]=max(ans[i],W(j,l));
        // for(int j=mid+1;j<=r;j++) ans[i]=max(ans[i],W(j,r));
        T[0].insert(1,1,n,l,mid,node(i,l));
        T[1].insert(1,1,n,mid+1,r,node(i,r));
    }
    T[0].dfs(1,1,n);T[1].dfs(1,1,n);
    for(int i=1;i<=q;i++) printf("%lld\n",ans[i]);
    return 0;
}

C. Sum Modulo

考虑设第 \(0\text{~}N-1\) 分别为 \(x_0,\cdots,x_{N-1}\) 。可以推出 \(\displaystyle x_i=\sum_{j>0}{\frac {a_j}{S}x_{(i+j)\bmod M}}+1\),并且有 \(x_K=0\)

前面那个就是常系数非齐次线性递推,由于这题 \(n\) 很小,可以直接绕一圈推出 \(x_i\)\(x_j\) 之间的 \(N-1\) 组关系。

然后递推出 \(x_K\)\(x_0,\cdots,x_{N-1}\) 的关系式,共 \(N\) 组关系就可以高斯消元解出 \(x_0,\cdots,x_{N-1}\)。总复杂度 \(O(N^2\log M+N^3)\)

代码咕咕咕了

A. Cookies

很妙的题。

首先考虑如果一个 cookie 最后被留下来了,那么开始时再加一个 cookie 它同样还是会被留下来。反过来说,如果一个 cookie 被留下来了,那么开始时就删掉它不会影响其他 cookie 的状态。

这样我们可以看成开始时只有一个 cookie,\(q\) 次给出开始时的 cookie,问最后剩下的 cookie。

题意可以转化为:有一个长度为 \(n\) 的序列 \(a_i\) 和操作 \(b_i\),对于每次询问,一开始有一个数字 \(v\)。从左往右扫,对于位置 \(i\),如果 \(b_i=\texttt{<}\)\(v< a_i\) 那么交换 \(v,a_i\),否则什么都不敢。\(b_i=\texttt{>}\) 同理。每次询问的改变永久有效。

考虑如果某个连续段 \(b_i\) 都是 \(\texttt{>}\),可以发现这一段只有可能弹出最大的那个元素。即维护一个大根堆,我们只需比较 \(v\)\(\text{Max}\) 的大小即可。对于 \(\texttt{<}\) 同理。

这样我们可以把序列变成 \(\text{Max}_1\text{Min}_1\text{Max}_2\text{Min}_2\cdots \text{Max}_t\text{Min}_t\) 这样若干个堆,只需要从左往右对每个堆的堆顶操作即可。但是直接做复杂度仍然不对。

考虑一个看起来只是优化常数的操作:如果相邻堆满足 \(\text{Min}_i\geq \text{Max}_i\),可以发现无论 \(v\) 是多少,\(\text{Min}_i\) 中弹出的数都不会进入到 \(\text{Max}_i\) 中。换句话说两者的位置无关,那么不妨交换这两个堆的位置,这样可以合并相同操作的两个堆 \(\text{Min}_{i-1} \text{Min}_i\)

这样看起来复杂度还是很假,但其实是正确的。

考虑这样分析:假设当前有 \(L\)\(\text{Min}_i\text{Max}_i\)。由于已经经过上述的操作,所以 \(\text{Min}_i<\text{Max}_i\)。可以发现无论 \(v\) 是多少,经过该组堆之后都一定会发生变化。

考虑分析势能函数,设 \(\phi_i\) 表示 \(\sum_{s\in\text{Min}_i\ \ t\in\text{Max}_i}[s< t]\)。可以发现一次操作过后,\(\phi_i\) 至少 \(-1\)。当 \(\phi_i=0\) 时,该组堆就会被拆开并被合并。

而对于任意时刻,一定有 \(\sum_{i}\phi_i=M\),根据鸽巢原理,经过 \(O(\frac ML)\) 后至少有一半的 \(\phi_i=0\),即 \(T(L)=T(\frac L2)+O(M\log L)\)。分析一下即为 \(O(M\log^2 L)\)

总复杂度 \(O((N+M\log L)\log L)\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define N 200010
#define ll long long
using namespace std;
typedef priority_queue<int> pq;
int a[N],b[N];
char op[N];
pq q[N];int L[N],R[N],cnt;
void merge(pq &u,pq &v){while(!v.empty()) u.push(v.top()),v.pop();}
void del(int x)
{
    merge(q[x],q[R[R[x]]]);
    merge(q[R[x]],q[R[R[R[x]]]]);
    R[R[x]]=R[R[R[R[x]]]];
    L[R[R[x]]]=R[x];
}
void check(int x)
{
    for(int _=4,y=x;_;_--,y=R[y]) if(y==R[y]) return;
    if(q[x].empty() || q[R[x]].empty()) return;
    if(q[R[x]].top()+q[R[R[x]]].top()<=0) del(x);
}
int main()
{
    int n,m;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    scanf("%d",&m);
    for(int i=1;i<=m;i++) scanf("%d",&b[i]);
    scanf("%s",op+1);
    for(int i=1,j=1;i<=m;i=j)
    {
        for(;j<=m && op[i]==op[j];j++);
        if(op[i]=='B')
        {
            ++cnt;
            for(int k=i;k<j;k++) q[cnt].push(b[k]);
        }
        else
        {
            ++cnt;
            for(int k=i;k<j;k++) q[cnt].push(-b[k]);
        }
    }
    for(int i=1;i<=cnt;i++) R[i]=i+1,L[i]=i-1;
    R[cnt]=cnt;
    for(int j=1;j!=R[j];j=R[j])
        check(j);
    ll las=0;
    for(int i=1;i<=n;i++)
    {
        ll w=(a[i]+las)%1000000000;
        for(int j=1;;j=R[j])
        {
            if(q[j].top()<0 && -q[j].top()<w)
            {
                ll v=-q[j].top();
                // printf("push %d %d\n",v,w);
                q[j].pop();q[j].push(-w);
                w=v;
            }
            else if(q[j].top()>0 && q[j].top()>w)
            {
                ll v=q[j].top();
                // printf("push %d %d\n",v,w);
                q[j].pop();q[j].push(w);
                w=v;
            }
            if(j==R[j]) break;
        }
        printf("%lld ",las+=w);
        for(int j=1;j!=R[j];j=R[j]) check(j);
    }
    return 0;
}

J. Median Replace Hard

XJ 原题。

考虑这玩意看起来很难求,但可以发现,任意时刻在末尾加两个字符,多出的选择只有一种:即先合并后面还是合并完前面再合并后面。事实上直接推就能推出一个大概只有不到 100 个状态的自动机。

当然按照 Cyanic 论文里的那个无脑求也是可以的。具体来说如果两个状态是等价的,那么他们后面加上任意字符后都是等价的。不妨钦定一个长度为 10,然后往后依次加长度为 10 的字符串,判断是否等价。

可以发现最后搜出的状态只有十几个。直接在 DFA 上跑 dp,复杂度 \(O(nM+M^3)\)\(M\) 是自动机大小。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<queue>
#define N 300010
#define mod 1000000007
using namespace std;
const int Len(const int x){return 31-__builtin_clz(x);}
int op;
int calc(int x,int p)
{
    int y=x&((1<<p)-1);
    if(op&(1<<((x>>p)&7))) y|=1<<p;
    return y|(x>>(p+3))<<(p+1);
}
map<int,bool>hv;
bool dfs(int x)
{
    int len=Len(x);
    if(len%2==0) return false;
    if(len==1) return x&1;
    if(hv.count(x)) return hv[x];
    for(int i=0;i+2<len;i++) if(dfs(calc(x,i))) return hv[x]=true;
    return hv[x]=false;
}
bool same(int x,int y,int l)
{
    for(int i=0;i<l;i++)
        for(int j=0;j<1<<i;j++)
        if(dfs(x<<i|j)!=dfs(y<<i|j)) return false;
    return true;
}
struct DFA{
    struct node{
        vector<pair<int,int>>to;
        bool las;
    };
    vector<node> g;
}d;
void init(int l=10)
{
    vector<int>V;
    queue<int>q;q.push(1);
    map<int,int>id;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        bool sm=false;
        for(int v:V) if(same(u,v,l)){id[u]=id[v];sm=true;break;}
        if(sm) continue;
        id[u]=V.size();
        V.push_back(u);
        q.push(u<<1),q.push(u<<1|1);
    }
    int n=V.size();
    d.g.resize(n);
    for(int i=0;i<n;i++)
    {
        d.g[i].to.push_back({0,id[V[i]<<1]});
        d.g[i].to.push_back({1,id[V[i]<<1|1]});
        if(dfs(V[i])) d.g[i].las=true;
    }
}
char s[8],t[N];
void solve()
{
    scanf("%s%s",s,t+1);
    int n=strlen(t+1);op=0;
    for(int i=0;i<8;i++) op|=(s[i]-'0')<<i;
    init();
    int m=d.g.size();
    vector<int> f(m);
    f[0]=1;
    for(int _=n;_;_--)
    {
        vector<int>g(m);
        char c=t[_];
        for(int i=0;i<m;i++)
            for(auto v:d.g[i].to)
            if(!((c=='0' && v.first==1) || (c=='1' && v.first==0))) g[v.second]=(g[v.second]+f[i])%mod;
        f=g;
    }
    int ans=0;
    for(int i=0;i<m;i++) if(d.g[i].las) ans=(ans+f[i])%mod;
    printf("%d\n",ans);
}
void clear(){d=DFA();hv.clear();op=0;}
int main()
{
    int t;
    scanf("%d",&t);
    while(t --> 0) solve(),clear();
    return 0;
}
posted @ 2021-07-27 11:38  Flying2018  阅读(485)  评论(0)    收藏  举报