vp + 补题 + 随机做题 记录四

按随机顺序排序

2024“钉耙编程”中国大学生算法设计超级联赛(10) 1001 LIS

link

根据dilworth定理转化场上想到了,但是以为这东西能dp,然后怒写1h没过样例。

拆成 \(k\) 条反链后效性太强,可以看做dag上不重复计算点贡献的dp,好像就和原问题没区别了……

很多状态转移搞不太定的东西,其实可以想一想放图上搞,毕竟某些最优化dp就是一种最短路之类的图上问题。

本题里转化到费用流就一目了然,类似网络流24题里有一个的拆点方法,把覆盖问题转化成选择问题,那么只需要分别跑最大流是 \(1\sim n\) 的mfmc即可。

不会写原始对偶或消圈,留坑回头补。

代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
template <typename T>inline void read(T &x){
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())
    x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
const int N=1005, inf=1e9;
int Test, n;
int a[N], b[N];
int lin[N], nxt[N*N*3], to[N*N*3], tot, f[N*N*3], w[N*N*3];
inline void in(int x, int y, int _f, int _w){
    nxt[++tot]=lin[x]; lin[x]=tot; to[tot]=y; f[tot]=_f; w[tot]=_w;
    nxt[++tot]=lin[y]; lin[y]=tot; to[tot]=x; f[tot]=0; w[tot]=-_w;
}
int S, T;
int d[N], dis[N], v[N], cur[N], vis[N];
queue<int> que;
inline bool spfa(){
    for(int i=1; i<=T; ++i) d[i]=v[i]=vis[i]=cur[i]=0, dis[i]=-inf;
    d[S]=1; dis[S]=0; cur[S]=lin[S]; que.push(S);
    while(!que.empty()){
        int x=que.front(); que.pop(); v[x]=0;
        for(int i=lin[x]; i; i=nxt[i]){
            int y=to[i]; 
            if(f[i]&&dis[y]<dis[x]+w[i]){
                dis[y]=dis[x]+w[i]; d[y]=d[x]+1; cur[y]=lin[y];
                if(!v[y]) que.push(y), v[y]=1;
            }
        }
    }
    return dis[T]>-inf;
}
int mf, mc;
inline int dinic(int x, int nowf){
    if(x==T){
        mf+=nowf; mc+=dis[T]*nowf;
        return nowf;
    }
    vis[x]=1; int ret=0;
    for(int i=cur[x]; i&&ret<nowf; i=nxt[i]){
        cur[x]=i;
        int y=to[i];
        if(!vis[y]&&dis[y]==dis[x]+w[i]&&f[i]&&d[y]==d[x]+1){
            int res=dinic(y, min(f[i], nowf-ret));
            if(!res) d[y]=-1;
            f[i]-=res; f[i^1]+=res; ret+=res;
        }
    }
    if(ret==nowf) vis[x]=0;
    return ret;
}
int main(){
    // freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
    // freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
    read(Test);
    while(Test--){
        read(n);
        for(int i=1; i<=n; ++i) read(a[i]);
        int sum=0;
        for(int i=1; i<=n; ++i) read(b[i]), sum+=b[i];
        memset(lin, 0, sizeof lin);
        tot=1;
        S=n+n+1; T=S+1;
        for(int i=1; i<=n; ++i) in(S, i, 1, 0), in(i+n, T, 1, 0), in(i, i+n, 1, b[i]);
        for(int i=1; i<=n; ++i) for(int j=i+1; j<=n; ++j) if(a[i]>a[j]) in(i+n, j, 1, 0);
        mf=mc=0;
        for(int i=1; i<=n; ++i) {
            if(spfa()) dinic(S, 1);
            printf("%d ", sum-mc);
        }
        putchar('\n');
    }
    return 0;
}

2024“钉耙编程”中国大学生算法设计超级联赛(10) 1003 败北

link

子集DP好题。

第一步转化就没想到:考虑把点编号变成该点拓扑序,那么每条边都是从小点到大点,那么对于某个点只需要考虑之前填过的点中不能有编号比它小的。

层之间的转移时简单的,难点在于层内钦定若干点颜色相同的转移。

一种方法是用高维前缀和,我们要求强制从小到大放这些颜色相同的点,有一维无形的状态是当前已经考虑了编号前若干小的点。

那么我们转移就需要正着做,如下:

for(int i=1; i<=n; ++i){
    for(int j=0; j<(1<<n); ++j) f[i][j]=f[i-1][j];
    for(int k=0; k<n; ++k){
        int cur=id[k];
        for(int j=0; j<(1<<n); ++j) if(((j>>cur)&1)&&((j&to[cur])==0)) add(f[i][j], f[i][j^(1<<cur)]);
    }
    for(int j=0; j<(1<<n); ++j) del(f[i][j], f[i-1][j]);
}

其中 \(to_i\) 表示能到达 \(i\) 的点集。

这里 \(f_{p,s}\) 的含义就变为了前缀和的含义,带上无形的状态就相当于 \(f_{p,s}=g_{p,i,s}+g_{p,i,s\setminus \{i\}}\),这样就保证了同层内没有边相连。

因为这会存在这层一个点都不选的转移,所以最后需要减掉自身。

这样复杂度就优化为了 \(O(2^nn^2)\),总复杂度就是 \(O(2^nn^2+qn)\)(这居然能过 \(n=20\))。

代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
struct IO{
    static const int S=1<<21;
    char buf[S],*p1,*p2;int st[105],Top;
    ~IO(){clear();}
    inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
    inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}
    inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
    inline IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n'||x=='\r');return *this;}
    template<typename T>inline IO&operator >> (T&x){
        x=0;bool f=0;char ch=gc();
       while(!isdigit(ch)){if(ch=='-') f^=1;ch=gc();}
        while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
        f?x=-x:0;return *this;
    }
    inline IO&operator << (const char c){pc(c);return *this;}
    template<typename T>inline IO&operator << (T x){
        if(x<0) pc('-'),x=-x;
        do{st[++st[0]]=x%10,x/=10;}while(x);
        while(st[0]) pc('0'+st[st[0]--]);return *this;
    }
}fin,fout;
const int mod=1e9+7;
inline ll fpow(ll x, ll y){
	ll ret=1;
	while(y){
		if(y&1) ret=ret*x%mod;
		x=x*x%mod; y>>=1;
	}
	return ret;
}
inline int plu(int x, int y){x+=y; return x>=mod?x-mod:x;}
inline int sub(int x, int y){x-=y; return x<0?x+mod:x;}
inline void add(int &x, int y){x+=y; if(x>=mod) x-=mod;}
inline void del(int &x, int y){x-=y; if(x<0) x+=mod;}
int T, n, m, q;
bool e[21][21];
int deg[21];
int que[21], hh, tt;
int id[21];
int f[21][(1<<20)+5];
int to[21];
int C[21];
char s[21];
int main(){
	// freopen("D:\\nya\\acm\\B\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\B\\test.out","w",stdout);
	fin>>T;
	while(T--){
		fin>>n; fin>>m; fin>>q;
        memset(e, 0, sizeof e); memset(deg, 0, sizeof deg); memset(to, 0, sizeof to);
        for(int i=1, x, y; i<=m; ++i){
            fin>>x; fin>>y; --x; --y;
            if(e[x][y]) continue;
            e[x][y]=1; ++deg[y]; to[y]|=1<<x;
        }
        hh=0; tt=-1;
        for(int i=0; i<n; ++i) if(!deg[i]) que[++tt]=i;
        while(hh<=tt){
            int x=que[hh]; id[hh]=x; ++hh;
            for(int y=0; y<n; ++y) if(e[x][y]){
                --deg[y]; if(!deg[y]) que[++tt]=y;
            }
        }
        f[0][0]=1;
        for(int i=1; i<=n; ++i){
            for(int j=0; j<(1<<n); ++j) f[i][j]=f[i-1][j];
            for(int k=0; k<n; ++k){
                int cur=id[k];
                for(int j=0; j<(1<<n); ++j) if(((j>>cur)&1)&&((j&to[cur])==0)) add(f[i][j], f[i][j^(1<<cur)]);
            }
            for(int j=0; j<(1<<n); ++j) del(f[i][j], f[i-1][j]);
        }
        while(q--){
            int k; fin>>k; char cur=fin.gc();
            while(cur!='0'&&cur!='1') cur=fin.gc();
            int msk=0; for(int i=0; i<n; ++i) {
                if(cur=='1') msk|=1<<i;
                cur=fin.gc();
            }
            C[0]=1;
            for(int i=1; i<=min(n, k); ++i) C[i]=fpow(i, mod-2)*C[i-1]%mod*(k-i+1)%mod;
            int ans=0;
            for(int i=0; i<=min(n, k); ++i) add(ans, (ll)C[i]*f[i][msk]%mod);
            fout<<ans; fout.pc('\n');
        }
	}
	return 0;
}

2024“钉耙编程”中国大学生算法设计超级联赛(10) 1004 轰炸

link

\(f_{i,j}\) 表示用大小为 \(i\times j\) 的矩形能够最多覆盖的0的数量。

先做第一个单调性分析:\(f_{i,j}\geq f_{i+1, j},f_{i,j}\geq f_{i,j+1}\),这是因为能用 \((i+1)\times j\) 覆盖的位置一定能被 \(i\times j\) 覆盖,后者同理。

所以符合要求的 \((i,j)\) 对在整个 \(f\) 矩阵中排列成阶梯型,我们只需要求出阶梯的轮廓线即可。

接下来考虑一个 \(f_{i,j}\) 怎么在低于 \(O(n,m)\) 的时间求出。

考虑一个矩形至少会覆盖一个编号为 \(i\) 的倍数行,所以只需要枚举这些行的每一个位置,记录一下这个位置如果被包含在某个矩形内,矩形的上下边界范围。

于是需要提前预处理某个位置向上/下最多延伸的位置,求答案用区间rmq得到上下界,这里使用单调队列即可。

但这里得到的只是矩形右端点在此处的上下界,还需要再单调队列一遍求出只考虑包含在矩形里的上下界,倒过来再做一遍上下界的区间rmq即可,仍然可以单调队列。

求答案注意不能重复覆盖,要对当前上界和上一层下界分类讨论。

所以只需要 \(O(\frac{n}{i}m)\) 时间求出 \(f_{i,j}\) 的答案,同样可以整体翻转做到 \(O(\frac{m}{j}n)\),两者直接肯定取最优。

所以我们只需要 \(O(\sum_{(i,j)\in 轮廓线}\frac{nm}{max(i,j)})\) 的复杂度,这个数量级上界是 \(O(nm\ln(nm))\) 的。

代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
template <typename T>inline void read(T &x){
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())
    x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
const int N=3005;
int Test, n, m, lim;
char s[N][N];
struct QUEUE{
    pii q[N];
    int hh, tt;
    inline void clr(){
        hh=1; tt=0;
    }
    inline void push(int val, int id){
        while(hh<=tt&&q[tt].fi>val) --tt;
        q[++tt]=mapa(val, id);
    }
    inline void pop(int id){
        while(hh<=tt&&q[hh].se<id) ++hh;
    }
    inline int top(){
        return q[hh].fi;
    }
};
struct DS{
    QUEUE q[2];
    int nn, mm;
    int a[N][N], up[N][N], down[N][N];
    inline void init(){
        for(int i=1; i<=nn; ++i) for(int j=1; j<=mm; ++j){
            if(a[i][j]) up[i][j]=0;
            else up[i][j]=up[i-1][j]+1;
        }
        for(int j=1; j<=mm; ++j) down[nn+1][j]=0;
        for(int i=nn; i>=1; --i) for(int j=1; j<=mm; ++j){
            if(a[i][j]) down[i][j]=0;
            else down[i][j]=down[i+1][j]+1;
        }
    }
    int lp[N][N], rp[N][N];
    inline int solve(int x, int y){
        int ret=0;
        for(int i=x; i<=nn; i+=x){
            q[0].clr(); q[1].clr();
            for(int j=1; j<=mm; ++j){
                q[0].push(up[i][j], j); q[1].push(down[i][j], j);
                q[0].pop(j-y+1); q[1].pop(j-y+1);
                if(j>=y){
                    lp[i][j]=q[0].top(); rp[i][j]=q[1].top();
                    if(rp[i][j]+lp[i][j]-1<x) lp[i][j]=rp[i][j]=0;
                }
            }
            q[0].clr(); q[1].clr();
            for(int j=1; j<=mm; ++j){
                if(j+y-1<=mm) q[0].push(-lp[i][j+y-1], j+y-1), q[1].push(-rp[i][j+y-1], j+y-1);
                q[0].pop(j); q[1].pop(j);
                lp[i][j]=-q[0].top(); rp[i][j]=-q[1].top();
            }
            for(int j=1; j<=mm; ++j){
                if(rp[i][j]+lp[i][j]-1<x) {continue;}
                ret+=min(rp[i][j], min(i+x-1, nn)-i+1)+min(x-min(x-1, max(0, rp[i-x][j]-1)), min(x, lp[i][j]))-1;
            }
        }
        return ret;

    }
}t[2];
inline int calc(int x, int y){
    if(x>=y) return t[0].solve(x, y);
    else return t[1].solve(y, x);
}
int main(){
    // freopen("D:\\nya\\acm\\C\\test.in","r",stdin);
    // freopen("D:\\nya\\acm\\C\\test.out","w",stdout);
    read(Test);
    while(Test--){
        read(n); read(m); read(lim);
        for(int i=1; i<=n; ++i) scanf("%s", s[i]+1);
        t[0].nn=n; t[0].mm=m;
        for(int i=1; i<=n; ++i) for(int j=1; j<=m; ++j) t[0].a[i][j]=(s[i][j]=='1');
        t[1].nn=m; t[1].mm=n;
        for(int i=1; i<=m; ++i) for(int j=1; j<=n; ++j) t[1].a[i][j]=(s[j][i]=='1');
        t[0].init(); t[1].init();
        int j=m;
        int ans=0;
        for(int i=1; i<=n; ++i){
            while(j&&calc(i, j)<lim) --j;
            if(!j) break;
            ans+=j;
        }
        printf("%d\n", ans);
    }
    return 0;
}

2024“钉耙编程”中国大学生算法设计超级联赛(10) 1012 花环

link

考虑有 \(k\) 个空缺,那么这个置换就是由若干置换环和 \(k\) 条置换链组成的,我们需要做的就是对于 \(k!\) 种置换链分组的方式计算答案。

考虑对 \(2^k-1\) 种子集分别求出来颜色数,然后去做子集dp。

具体的,对于所有颜色数相同的子集,我们设计 \(f_{i,s}\) 表示当前生成了 \(i\) 个置换环,使用的置换链组成的集合是 \(s\) 的方案,转移使用子集枚举(只枚举当前状态补集的所有子集),复杂度可以做到均摊 \(O(3^kk)\)

对于某个状态 \(f_{i,s}\) ,使用 \(\complement_{U}{s}\) 里面的元素仍然可能得到颜色数是当前所枚举的置换环。

一种思路是求至少有 \(i\) 个置换环的方案数,再二项式反演得到恰好的。

具体的,让 \(f_i\) 是恰好的方案数,那么 \(g_n=\sum_{i=n}^{k}C_{i}^nf_i\),同时有 \(g_n=\sum_{s}f_{n,s}*(k-|s|)!\),直接做就好了。

总体来看,时间复杂度是 \(O(\frac{n2^k}{w}+3^kk+nk^2)\)

代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
typedef unsigned int uint;
template <typename T>inline void read(T &x){
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())
    x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
typedef bitset<50001> bit;
const int N=5e4+5, mod=998244353;
inline ll fpow(ll x, ll y){
    ll ret=1;
    while(y){
        if(y&1) ret=ret*x%mod;
        x=x*x%mod; y>>=1;
    }
    return ret;
}
inline int plu(int x, int y){x+=y; return x>=mod?x-mod:x;}
inline int sub(int x, int y){x-=y; return x<0?x+mod:x;}
inline void add(int &x, int y){x+=y; if(x>=mod) x-=mod;}
inline void del(int &x, int y){x-=y; if(x<0) x+=mod;}
int Test, n, m;
int p[N], a[N], b[N];
bool vis[N];
int tot;
bit msk[16];
bit tem;
vector<int> vec;
int cnt[N];
bool e[N];
vector<int> bin[N];
int dp[16][(1<<15)+5];
bool ins[16][(1<<15)+5];
vector<int> trs[16];
inline void mrk(int dep, int x){if(ins[dep][x]) return ; ins[dep][x]=1; trs[dep].emplace_back(x);}
int f[16], g[16];
int C[16][16];
int frc[N];
int ppc[(1<<15)+5];
int mk[N];
int main(){
    // freopen("D:\\nya\\acm\\C\\test.in","r",stdin);
    // freopen("D:\\nya\\acm\\C\\test.out","w",stdout);
    read(Test);
    C[0][0]=1;
    for(int i=1; i<=15; ++i){
        C[i][0]=1;
        for(int j=1; j<=i; ++j) C[i][j]=plu(C[i-1][j-1], C[i-1][j]);
    }
    for(int i=1; i<(1<<15); ++i) ppc[i]=ppc[i>>1]+(i&1);
    frc[0]=1;
    for(int i=1; i<N; ++i) frc[i]=(ll)frc[i-1]*i%mod;
    while(Test--){
        read(n); read(m);
        memset(cnt, 0, sizeof cnt); memset(e, 0, sizeof e); memset(vis, 0, sizeof vis);
        for(int i=1; i<=n; ++i) bin[i].clear();
        for(int i=1; i<=n; ++i) read(p[i]), e[p[i]]=1;
        for(int i=1; i<=n; ++i) read(a[i]);
        for(int i=1; i<=n; ++i) read(b[i]);
        tot=0;
        tem.reset();
        for(int i=1; i<=n; ++i) if(e[i]==0){
            int x=i;
            msk[tot].reset();
            do{ 
                msk[tot].set(a[x]); vis[x]=1; x=p[x];
            }while(x);
            ++tot;
        }
        for(int i=1; i<=n; ++i) if(vis[i]==0){
            int x=i;
            int cur=0;
            do{
                vec.emplace_back(x);
                if(!mk[a[x]]) mk[a[x]]=1, ++cur; 
                vis[x]=1; x=p[x];
            }while(!vis[x]);
            ++cnt[cur];
            for(auto t:vec) mk[a[t]]=0;
            vec.clear();
        }
        for(int i=1; i<(1<<tot); ++i){
            tem.reset();
            for(int j=0; j<tot; ++j) if((i>>j)&1) tem|=msk[j];
            bin[tem.count()].emplace_back(i);
        }
        int ans=0;
        for(int v=1; v<=n; ++v){
            dp[0][0]=1; mrk(0, 0);
            int lim=min(m, (int)bin[v].size());
            for(auto t:bin[v]){
                int full=(1<<m)-1-t;
                for(int s=full; ; s=(s-1)&full){
                    for(int i=lim; i>0; --i) if(dp[i-1][s]){
                        mrk(i, s|t);
                        add(dp[i][s|t], (ll)dp[i-1][s]*frc[ppc[t]-1]%mod);
                    }
                    if(s==0) break;
                }
            }
            for(int i=0; i<=lim; ++i){
                f[i]=0;
                for(auto t:trs[i]) add(f[i], (ll)dp[i][t]*frc[m-ppc[t]]%mod), dp[i][t]=0, ins[i][t]=0;
                trs[i].clear();
            }
            for(int i=0; i<=lim&&i+cnt[v]<=n; ++i) {
                g[i]=0;
                for(int j=i; j<=lim&&j+cnt[v]<=n; ++j) {
                    if((j-i)&1) del(g[i], (ll)C[j][i]*f[j]%mod);
                    else add(g[i], (ll)C[j][i]*f[j]%mod);
                }
            }
            for(int i=0; i<=lim; ++i)    add(ans, (ll)g[i]*b[i+cnt[v]]%mod);
        }
        printf("%d\n", ans);
    }
    return 0;
}

[CTS2022] 袜子

link

因为最近在看TB5x,所以来看看这题,虽然我的做法和TB5x没啥关系。

没有颜色限制时就是【UNR #4】己酸集合,考虑有颜色限制怎么做。

一种思路是对每种颜色分别处理,套用那题的分块+旋转扫描线做法,这样做看似是 \(O(\sum_{i=1}^n\frac{s_i}{B}(B^2logB+qlogB))\approx O(n\sqrt qlogn)\)的,但因为有总数很少(小于块长)的颜色存在,最极端的就考虑每种颜色只有一个元素,那么这样做复杂度就退化到 \(O(nm)\) 了。

所以大小块分开做,大块跑分块+旋转扫描线,小块可以并起来跑选择扫描线,我们把小块元素总数之和控制在块长左右分组去做。

那么就需要考虑算贡献,大块是简单的,小块考虑求出来每个元素在相同颜色的排名,那么贡献就是2倍排名之和-总元素数量。

那么现在就需要做交换两个数,更新被修改的排名,查询区间内排名和(实际上只会是前缀或后缀)。

然后我不会了,然后我写了暴力,暴力把交换位置之间排名变化的位置枚举修改,然后卡常过了(?)。

理论来说,这样做复杂度是 \(O(\frac{n}{B}(B^3logB+qlogB))\approx O(nq^{\frac{2}{3}}logn)\) 的,但是卡常过了。

代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
struct IO{
    static const int S=1<<21;
    char buf[S],*p1,*p2;int st[105],Top;
    ~IO(){clear();}
    inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
    inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}
    inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
    inline IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n'||x=='\r');return *this;}
    template<typename T>inline IO&operator >> (T&x){
        x=0;bool f=0;char ch=gc();
       while(!isdigit(ch)){if(ch=='-') f^=1;ch=gc();}
        while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
        f?x=-x:0;return *this;
    }
    inline IO&operator << (const char c){pc(c);return *this;}
    template<typename T>inline IO&operator << (T x){
        if(x<0) pc('-'),x=-x;
        do{st[++st[0]]=x%10,x/=10;}while(x);
        while(st[0]) pc('0'+st[st[0]--]);return *this;
    }
}fin,fout;
const int N=50005, M=500005;
inline bool chk(int x1, int y1, int x2, int y2){
    bool op=1;
    if(x1<0) op^=1;
    if(x2<0) op^=1;
    if(op) return (ll)y1*x2<(ll)y2*x1;
    else return (ll)y1*x2>(ll)y2*x1;
}
inline bool chk2(int x1, int y1, int x2, int y2){
    bool op=1;
    if(x1<0) op^=1;
    if(x2<0) op^=1;
    if(op) return (ll)y1*x2<=(ll)y2*x1;
    else return (ll)y1*x2>=(ll)y2*x1;
}
int n, m, B;
vector<pii> vec[N];
struct node{
	int a, b, c, id;
	inline bool operator <(const node &x)const{
        return chk(b, -a, x.b, -x.a);
	}
}v[M];
int ans[M]; ll out[M];
vector<pii> bin;
struct node2{
	int a, b, x, y;
	inline bool operator <(const node2 &z)const{
        if((ll)a*z.b==(ll)z.a*b){
            if(x==z.x) return y<z.y;
            return x<z.x;
        }
        return chk(a, b, z.a, z.b);
	}
};
int pos[N], rev[N];
inline void work(int x, int y){
	if(pos[x]>pos[y]) return ;
	swap(rev[pos[x]], rev[pos[y]]);
	swap(pos[x], pos[y]);
}
int tot;
inline void solve(){
	sort(bin.begin(), bin.end());
	tot=bin.size();
	vector<node2> swp;
	for(int i=0; i<tot; ++i){
		pos[i]=i; rev[i]=i;
		for(int j=i+1; j<tot; ++j){
			if(bin[i].fi==bin[j].fi) continue;
			swp.emplace_back((node2){bin[j].fi-bin[i].fi, bin[j].se-bin[i].se, i, j});
		}
	}
	sort(swp.begin(), swp.end());
	int p=0;
	for(int i=1; i<=m; ++i){
		while(p<(int)swp.size()&&chk2(swp[p].a, swp[p].b, v[i].b, -v[i].a)) work(swp[p].x, swp[p].y), ++p;
        if(v[i].b>=0){
            int l=0, r=tot-1, res=-1;
            while(l<=r){
                int mid=(l+r)>>1;
                if((ll)bin[rev[mid]].fi*v[i].a+(ll)bin[rev[mid]].se*v[i].b+(ll)v[i].c<0ll) {res=mid, l=mid+1;}
                else r=mid-1;
            }
            ans[v[i].id]+=res+1;
        }
        else{
            int l=0, r=tot-1, res=tot;
            while(l<=r){
                int mid=(l+r)>>1;
                if((ll)bin[rev[mid]].fi*v[i].a+(ll)bin[rev[mid]].se*v[i].b+(ll)v[i].c<0ll) {res=mid, r=mid-1;}
                else l=mid+1;
            }
            ans[v[i].id]+=tot-res;
        }
	}
}
struct node3{
	int a, b, c;
	inline bool operator <(const node3 &x)const{
		if(a^x.a) return a<x.a;
		return b<x.b;
	}
};
vector<node3> bin2;
set<int> st[N];
int rk[N], rkk[N];
ll tr[N], trr[N];
inline void mdf(int x, int v){
    ++x;
    for(; x<=tot; x+=(x&-x)) tr[x]+=v;
}
inline ll get(int x){
    ++x; ll ret=0;
    for(; x; x-=(x&-x)) ret+=tr[x];
    return ret;
}
inline void mdff(int x, int v){
    ++x;
    for(; x; x-=(x&-x)) trr[x]+=v;
}
inline ll gett(int x){
    ++x; ll ret=0;
    for(; x<=tot; x+=(x&-x)) ret+=trr[x];
    return ret;
}
typedef set<int>::iterator IT;
inline void work2(int x, int y){
	if(pos[x]>pos[y]) return ;
	if(bin2[x].c==bin2[y].c) {
		swap(rev[pos[x]], rev[pos[y]]);
		swap(pos[x], pos[y]);
		return ;
	}
	IT it=st[bin2[x].c].lower_bound(pos[x]); 
	int nrk=rk[*it], nrkk=rkk[*it];
    int lstrk=rk[*it], lstrkk=rkk[*it];
	++it;
	while(it!=st[bin2[x].c].end()&&(*it)<pos[y]){
		--rk[*it]; mdf(*it, -1); ++nrk;
        ++rkk[*it]; mdff(*it, 1); --nrkk;
		++it;
	}
    mdf(pos[y], nrk-rk[pos[y]]);
    mdff(pos[y], nrkk-rkk[pos[y]]);
	st[bin2[x].c].erase(st[bin2[x].c].lower_bound(pos[x]));
	st[bin2[x].c].insert(pos[y]);
	it=st[bin2[y].c].lower_bound(pos[y]);
	int nrk2=rk[*it], nrkk2=rkk[*it]; rk[*it]=nrk; rkk[*it]=nrkk;
	if(it!=st[bin2[y].c].begin()) {
		--it;
		while((*it)>pos[x]){
			++rk[*it]; mdf(*it, 1); --nrk2;
            --rkk[*it]; mdff(*it, -1); ++nrkk2;
			if(it==st[bin2[y].c].begin()) break;
			--it;
		}
	}
	mdf(pos[x], nrk2-lstrk); rk[pos[x]]=nrk2;
    mdff( pos[x], nrkk2-lstrkk); rkk[pos[x]]=nrkk2;
	st[bin2[y].c].erase(st[bin2[y].c].lower_bound(pos[y]));
	st[bin2[y].c].insert(pos[x]);
	swap(rev[pos[x]], rev[pos[y]]);
	swap(pos[x], pos[y]);
}
inline void solve2(){
	sort(bin2.begin(), bin2.end());
	tot=bin2.size();
	vector<node2> swp;
	for(int i=0; i<tot; ++i){
		pos[i]=i; rev[i]=i; st[bin2[i].c].clear();
		for(int j=i+1; j<tot; ++j){
			if(bin2[i].a==bin2[j].a) continue;
			swp.emplace_back((node2){bin2[j].a-bin2[i].a, bin2[j].b-bin2[i].b, i, j});
		}
	}
    for(int i=1; i<=tot; ++i) tr[i]=trr[i]=0;
	for(int i=0; i<tot; ++i){
		st[bin2[i].c].insert(i);
		rk[i]=st[bin2[i].c].size();
        mdf(i, rk[i]);
	}
    for(int i=tot-1; i>=0; --i){
        rkk[i]=st[bin2[i].c].size()+1-rk[i];
        mdff(i, rkk[i]);
    }
	sort(swp.begin(), swp.end());
	int p=0;
	for(int i=1; i<=m; ++i){
		while(p<(int)swp.size()&&chk2(swp[p].a, swp[p].b, v[i].b, -v[i].a)) work2(swp[p].x, swp[p].y), ++p;
        if(v[i].b>=0){
            int l=0, r=tot-1, res=-1;
            while(l<=r){
                int mid=(l+r)>>1;
                if((ll)bin2[rev[mid]].a*v[i].a+(ll)bin2[rev[mid]].b*v[i].b+v[i].c<0) {res=mid, l=mid+1;}
                else r=mid-1;
            }
            out[v[i].id]+=2ll*get(res); out[v[i].id]-=res+1;
        }
        else{
            int l=0, r=tot-1, res=tot;
            while(l<=r){
                int mid=(l+r)>>1;
                if((ll)bin2[rev[mid]].a*v[i].a+(ll)bin2[rev[mid]].b*v[i].b+v[i].c<0) {res=mid, r=mid-1;}
                else l=mid+1;
            }
            out[v[i].id]+=2ll*gett(res); out[v[i].id]-=tot-res;
        }
	}
}
int main(){
	// freopen("D:\\nya\\acm\\B\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\B\\test2.out","w",stdout);
    fin>>n>>m; B=sqrt(m)*0.6;
	for(int i=1, x, y, z; i<=n; ++i){
		fin>>x>>y>>z;
        vec[z].emplace_back(x, y);
	}
	for(int i=1; i<=m; ++i){
		fin>>v[i].a>>v[i].b>>v[i].c; v[i].id=i;
	}
	sort(v+1, v+m+1);
	for(int i=1; i<=n; ++i){
		if((int)vec[i].size()>=B){
			for(int l=0; l<(int)vec[i].size(); l+=B){
				bin.clear();
				for(int r=0; r<B&&l+r<(int)vec[i].size(); ++r){
					bin.emplace_back(vec[i][l+r]);
				}
				solve();
			}
			for(int i=1; i<=m; ++i) {
				out[i]+=(ll)ans[i]*ans[i];
				ans[i]=0;
			}
		}
		else{
			for(auto t:vec[i]) bin2.push_back((node3){t.fi, t.se, i});
			if((int)bin2.size()>=B) solve2(), bin2.clear();
		}
	}
	if(bin2.size()) solve2();
	for(int i=1; i<=m; ++i) fout<<out[i], fout.pc('\n');
	return 0;
}

The 1st Universal Cup. Stage 15: Hangzhou L Barkley

link

"我觉得我能抢首杀。"

"艹怎么T了?"

"艹我比标算复杂度低怎么被卡常了?"

0.刚看到题

事已至此,先 st 预处理一下吧,复杂度 \(O(n\log n\log V)-O(\log V)\)

1.\(k=1\)

先特殊考虑挖的点在区间两端的情况,然后考虑一般的情况。

然后从左侧开始的连续区间 \(gcd\) 值只有 \(O(\log V)\) 段,可以通过二分得到相等段的末端。

考虑右边,越短越有可能让 \(gcd\) 越大,所以对每个左侧相等段,我们选择在末端断开即可。

之后对右侧对称地再做一遍即可。

这部分复杂度是单次 \(O(\log n\log^2 V)\) 的。

2.\(k=2\)

还是特殊考虑挖的点都在两端的情况。

之后考虑从左侧和右侧分别延伸,然后中间独立一段。

继续 \(k=1\) 的结论,我们分别把左侧和右侧相等段的末端求出来。

那么可以 \(O(\log^2 V)\) 枚举左右两侧的末端来求答案,原理同 \(k=1\)

这部分复杂度也是单次 \(O(\log n\log^2 V)\) 的。

3.\(k=3\)

仍然特殊考虑挖的点都在两端的情况。

此时只有 \(6\) 组询问,我们可以把算法写的大胆一些。

仍然考虑求出左右两侧相等段的末端,那么只需要枚举中间在哪里断开即可。

这部分复杂度单次 \(O(\log n\log^2 V+n\log^3 V)\).

注意实现细节,不要过度使用 std::__gcd 就可以通过了。

代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
const int N=1e5+5;
int n, m;
ll a[N];
ll st[20][N]; int lg[N];
inline ll get(int l, int r){
	if(l>r) return 0;
	if(l==r) return st[0][l];
	int t=lg[r-l+1];
	return __gcd(st[t][l], st[t][r-(1<<t)+1]);
}
inline ll get1(int l, int r){
	ll ans=0;
	for(int cl=l, cr; cl<r;){
		int lp=cl+1, rp=r-1, mid, ret=cl;
		ll curv=get(l, cl);
		while(lp<=rp){
			mid=(lp+rp)>>1;
			if(get(l, mid)==curv){
				ret=mid; lp=mid+1; 
			}
			else{
				rp=mid-1;
			}
		}
		cr=ret;
		ans=max(ans, __gcd(curv, get(cr+2, r)));
		cl=cr+1;
	}
	for(int cr=r, cl; cr>l;){
		int lp=l+1, rp=cr-1, mid, ret=cr;
		ll curv=get(cr, r);
		while(lp<=rp){
			mid=(lp+rp)>>1;
			if(get(mid, r)==curv){
				ret=mid; rp=mid-1; 
			}
			else{
				lp=mid+1;
			}
		}
		cl=ret;
		ans=max(ans, __gcd(curv, get(l, cl-2)));
		cr=cl-1;
	}
	return ans;
}
inline ll get2(int l, int r){
	ll ans=max(get1(l+1, r), get1(l, r-1));
	ans=max(ans, max(get(l+2, r), get(l, r-2)));
	vector<pair<int, ll> > p1, p2;
	for(int cl=l, cr; cl<r;){
		int lp=cl+1, rp=r-1, mid, ret=cl;
		ll curv=get(l, cl);
		while(lp<=rp){
			mid=(lp+rp)>>1;
			if(get(l, mid)==curv){
				ret=mid; lp=mid+1; 
			}
			else{
				rp=mid-1;
			}
		}
		cr=ret;
		p1.ep(cr, curv);
		cl=cr+1;
	}
	for(int cr=r, cl; cr>l;){
		int lp=l+1, rp=cr-1, mid, ret=cr;
		ll curv=get(cr, r);
		while(lp<=rp){
			mid=(lp+rp)>>1;
			if(get(mid, r)==curv){
				ret=mid; rp=mid-1; 
			}
			else{
				lp=mid+1;
			}
		}
		cl=ret;
		p2.ep(cl, curv);
		cr=cl-1;
	}
	for(auto t:p1) for(auto tt:p2){
		if(t.fi+1<tt.fi-1) ans=max(ans, __gcd(__gcd(t.se, tt.se), get(t.fi+2, tt.fi-2)));
	}
	return ans;
}
ll pre[40][N], suf[40][N];
inline ll get3(int l, int r){
	ll ans=max(get2(l+1, r), get2(l, r-1));
	ans=max(ans, max(get1(l+2, r), get1(l, r-2)));
	ans=max(ans, max(get(l+3, r), get(l, r-3)));
	vector<pair<int, ll> > p1, p2;
	for(int cl=l, cr; cl<r;){
		int lp=cl+1, rp=r-1, mid, ret=cl;
		ll curv=get(l, cl);
		while(lp<=rp){
			mid=(lp+rp)>>1;
			if(get(l, mid)==curv){
				ret=mid; lp=mid+1; 
			}
			else{
				rp=mid-1;
			}
		}
		cr=ret;
		p1.ep(cr, curv);
		cl=cr+1;
	}
	for(int cr=r, cl; cr>l;){
		int lp=l+1, rp=cr-1, mid, ret=cr;
		ll curv=get(cr, r);
		while(lp<=rp){
			mid=(lp+rp)>>1;
			if(get(mid, r)==curv){
				ret=mid; rp=mid-1; 
			}
			else{
				lp=mid+1;
			}
		}
		cl=ret;
		p2.ep(cl, curv);
		cr=cl-1;
	}
	for(int _=0; _<(int)p1.size(); ++_){
		pre[_][p1[_].fi+1]=0;
		for(int i=p1[_].fi+2; i<=p2[0].fi; ++i) pre[_][i]=__gcd(pre[_][i-1], a[i]);
	}
	for(int _=0; _<(int)p2.size(); ++_){
		suf[_][p2[_].fi-1]=0;
		for(int i=p2[_].fi-2; i>=p1[0].fi; --i) suf[_][i]=__gcd(suf[_][i+1], a[i]);
	}
	for(int _=0; _<(int)p1.size(); ++_){
		for(int __=0; __<(int)p2.size(); ++__){
			if(p1[_].fi+1<p2[__].fi-1) {
				ll tem=__gcd(p1[_].se, p2[__].se);
				for(int o=p1[_].fi+2; o<=p2[__].fi-2; ++o)
				ans=max(ans, __gcd(tem, __gcd(pre[_][o-1], suf[__][o+1])));
			}
		}
	}
	return ans;
}
int main(){
	// freopen("D:\\nya\\acm\\C\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\C\\test.out","w",stdout);
	read(n); read(m);
	for(int i=1; i<=n; ++i) read(a[i]), st[0][i]=a[i];
	for(int i=2; i<=n; ++i) lg[i]=lg[i>>1]+1;
	for(int t=1; t<=lg[n]; ++t){
		for(int i=1; i+(1<<t)-1<=n; ++i){
			st[t][i]=__gcd(st[t-1][i], st[t-1][i+(1<<(t-1))]);
		}
	}
	while(m--){
		int l, r, k; read(l); read(r); read(k);
		if(k==1){
			printf("%lld\n", get1(l, r));
		}
		else if(k==2){
			printf("%lld\n", get2(l, r));
		}
		else if(k==3){
			printf("%lld\n", get3(l, r));
		}
	}
	return 0;
}

The 2nd Universal Cup. Stage 22: Hangzhou F Top Cluster

link

[2024/12/11 14:05] 我觉得这 F 不太好做啊,不过我大概会了,这个颜色不重复很有用
[2024/12/11 14:06] 两个log,整体二分+虚树,感觉剩下两个小时都只写这个题了
[2024/12/11 15:02] 过样例了,我交了
[2024/12/11 15:03] WA???
[2024/12/11 15:15] 事已至此,先对拍吧
[2024/12/11 15:18] 艹怎么一组就挂了
[2024/12/11 15:20] 坏了,要是颜色有重复咋办,我结论好像推假了
[2024/12/11 15:40] 感觉只能树分块了。
[2024/12/11 15:45] 题目保证了颜色不重复????
[2024/12/11 15:46] 我对拍用的数据有重复颜色
[2024/12/11 15:46] /zhm/fn
[2024/12/11 16:03] ?怎么WA了
[2024/12/11 16:04] [图片消息]
[2024/12/11 16:05] 什么罐头我说
[2024/12/11 17:32] 怎么还卡常?
[2024/12/11 17:53] 今日收获:不要唐挑战

本文为 \(2\log\) 复杂度的解法,\(1\log\) 解法参考官解。

0.刚看到题

MEX 这类的题上 DS 很多都要二分,先考虑对一个询问,有很显然的 \(O(n\log n)\) 二分做法。

因为二分形成的二叉树对所有询问都是相同的,这启发我们使用整体二分优化。

1.先整体二分

那么就需要对子问题内的所有询问判断是否在前半值域内是否有点在邻域外,如果有那么答案在前半值域,否则在后半值域。

那么就只需要设计出判定的算法就做完了。

2.再建虚树

既然整体二分要求判定算法复杂度只跟子问题元素数量有关,那么树上问题就很容易联想到虚树。

我们把子问题中涉及到的所有点拿出来建虚树,那么之后只需要跑换根 dp 求出每个点到标记点的最远距离即可。

这一部分是建虚树复杂度,瓶颈在排序。

总体来看,复杂度是 \(O((n+q)\log^2n)\),卡常后可以通过。

卡常前的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
const int N=1e6+5;
int n, q;
int w[N];
int lin[N], nxt[N*2], to[N*2], val[N*2], tot;
inline void in(int x, int y, int z){
	nxt[++tot]=lin[x]; lin[x]=tot; to[tot]=y; val[tot]=z;
	nxt[++tot]=lin[y]; lin[y]=tot; to[tot]=x; val[tot]=z;
}
int dep[N], f[N][20], dfn[N], tim;
ll dis[N];
void dfs1(int x, int fa){
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	dfn[x]=++tim;
	for(int i=1; i<=18; i++) f[x][i]=f[f[x][i-1]][i-1];
	for(int i=lin[x]; i; i=nxt[i]){
		int y=to[i]; if(y==fa) continue;
		dis[y]=dis[x]+val[i];
		dfs1(y, x);
	}
}
inline int lca(int x,int y){
	if(dep[x]>dep[y]) swap(x,y);
	for(int i=18; i>=0; i--) if(dep[f[y][i]]>=dep[x]) y=f[y][i];
	if(y==x) return x;
	for(int i=18; i>=0; i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
}
int qx[N]; ll qk[N];
int ans[N];
int seq[N], _seq[N];
vector<int> node[N];
int s[N], k;
int vis[N];
inline bool cmp(int x,int y){return dfn[x]<dfn[y];}
int stk[N], top;
vector<int> vec[N];
void add(int x,int y){
	// cout<<x<<' '<<y<<endl;
	vec[x].ep(y);
	vec[y].ep(x);
}
inline ll getd(int x, int y){
	int z=lca(x, y);
	return dis[x]+dis[y]-dis[z]*2ll;
}
ll mxup[N], mxdown[N];
inline void build(){
	sort(s+1,s+k+1,cmp);
	stk[top=1]=1;vec[1].clear();
	for(int i=1;i<=k;++i){
		if(s[i]==1) continue;
		int lc=lca(s[i],stk[top]);
		if(lc!=stk[top]){
			while(dfn[stk[top-1]]>dfn[lc]) add(stk[top],stk[top-1]),top--;
			if(lc!=stk[top-1]) vec[lc].clear(),add(lc,stk[top]),stk[top]=lc;
			else add(lc,stk[top]),top--;
		}
		vec[s[i]].clear();stk[++top]=s[i];
	}
	for(int i=1;i<top;i++) add(stk[i],stk[i+1]);
}
inline void dfs2(int x, int fa){
	if(vis[x]==2) mxdown[x]=0;
	else mxdown[x]=-1e18;
	for(auto y:vec[x]) if(y^fa){
		dfs2(y, x);
		mxdown[x]=max(mxdown[y]+dis[y]-dis[x], mxdown[x]);
	}
}
inline void dfs3(int x, int fa, ll dist){
	if(vis[x]==2){
		dist=max(dist, 0ll);
	}
	mxup[x]=dist;
	ll mx1=dist, mx2=-1e18;
	for(auto y:vec[x]) if(y^fa) {
		ll curd=dis[y]-dis[x];
		if(curd+mxdown[y]>=mx1){
			mx2=mx1; mx1=curd+mxdown[y];
		}
		else{
			mx2=max(mx2, curd+mxdown[y]);
		}
	}
	for(auto y:vec[x]) if(y^fa){
		ll curd=dis[y]-dis[x];
		if(mx1!=mxdown[y]+curd) dfs3(y, x, mx1+curd);
		else dfs3(y, x, mx2+curd);
	}
}
inline void solve(int lv, int rv, int lp, int rp){
	// cout<<"cur:"<<lv<<' '<<rv<<endl;
	// cout<<"qry:";
	// for(int i=lp; i<=rp; ++i) cout<<seq[i]<<' ';
	// cout<<endl;
	if(lp>rp) return ;
	if(lv==rv){
		for(int i=lp; i<=rp; ++i) ans[seq[i]]=lv;
		return ;
	}
	int mid=(lv+rv)>>1;
	bool flg=0;
	for(int i=lv; i<=mid; ++i){
		if(node[i].empty()) {
			flg=1;
			break;
		}
	}
	if(flg){
		solve(lv, mid, lp, rp);
		return ;
	}
	k=0;
	for(int i=lv; i<=mid; ++i){
		for(auto t:node[i]) s[++k]=t, vis[t]=2;
	}
	for(int i=lp; i<=rp; ++i) if(!vis[qx[seq[i]]]) s[++k]=qx[seq[i]], vis[qx[seq[i]]]=1;
	build();
	dfs2(1, 0);
	dfs3(1, 0, -1e18);
	for(int i=1;i<=k;i++) vis[s[i]]=0;
	int nlp=lp-1, nrp=rp+1;
	for(int i=lp; i<=rp; ++i){
		// cout<<seq[i]<<' '<<qx[seq[i]]<<' '<<qk[seq[i]]<<' '<<mxup[qx[seq[i]]]<<' '<<mxdown[qx[seq[i]]]<<endl;
		if(max(mxup[qx[seq[i]]], mxdown[qx[seq[i]]])>qk[seq[i]]){
			_seq[++nlp]=seq[i];
		}
		else{
			_seq[--nrp]=seq[i];
		}
	}
	for(int i=lp; i<=rp; ++i) seq[i]=_seq[i];
	solve(lv, mid, lp, nlp);
	solve(mid+1, rv, nrp, rp);
}
int main(){
	// freopen("D:\\nya\\acm\\C\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\C\\test.out","w",stdout);
	read(n); read(q);
	for(int i=1; i<=n; ++i) {
		read(w[i]);
		if(w[i]<=n) node[w[i]].ep(i);
	}
	for(int i=1, x, y, z; i<n; ++i){
		read(x); read(y); read(z);
		in(x, y, z);
	}
	dfs1(1, 0);
	for(int i=1; i<=q; ++i) read(qx[i]), read(qk[i]), seq[i]=i;
	solve(0, n, 1, q);
	for(int i=1; i<=q; ++i) printf("%d\n", ans[i]);
	return 0;
}

The 3rd Universal Cup. Stage 23: Hong Kong G Yelkrab

link

不要唐挑战。

0.刚看到题

先考虑所有串都是 a 的情况,问题变为 \(\forall i\)\(\bigoplus_{j=1}^{i}\lfloor\frac{i}{j}\rfloor\times j\)

考虑下取整的一个很显然的性质:

  • 定义 \(i\) 的下取整表为按顺序把 \(\forall j, \lfloor\frac{i}{j}\rfloor\) 列出来。

  • \(i+1\) 的下取整表是在 \(i\) 的基础上把 \(i+1\) 的所有因数下标位置的值 \(+1\) 得到的。

根据这个性质,容易发现这个东西最简单的做法是增量,这启发我们把原问题在增量上考虑。

1.把深度放在路径上

对于一个 \(f(i,j)\),考虑把这些串放在 trie 上,直接按深度贪心就是对的。

进一步的,把深度拆到路径上的每个点,容易通过取整运算证明这个贪心等价于求 \(\sum_{x\in \text{trie}}\lfloor \frac{sz_x}{j}\rfloor\)

这个式子和上一部分的式子都是可以用增量计算的。

时间复杂度为 \(O(n+\sum |s_i|d(n))\),但这个上界很难卡到。

代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
const int N=1e6+5;
int T, n;
char s[N];
int tr[N][26], sz[N], idx;
vector<int> d[N];
ll cnt[N];
int main(){
	// freopen("D:\\nya\\acm\\B\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\B\\test.out","w",stdout);
	read(T);
	for(int i=1; i<N; ++i) for(int j=i; j<N; j+=i) d[j].ep(i);
	while(T--){
		read(n);
		for(int i=0; i<=n; ++i) cnt[i]=0;
		for(int i=1; i<=idx; ++i) {
			sz[i]=0;
			for(int j=0; j<26; ++j) tr[i][j]=0;
		}
		idx=1;
		ll ans=0;
		while(n--){
			scanf("%s", s+1);
			int p=1;
			for(int i=1; s[i]; ++i){
				int c=s[i]-'a';
				if(!tr[p][c]) tr[p][c]=++idx;
				p=tr[p][c];
				++sz[p];
				for(auto t:d[sz[p]]){
					ans^=cnt[t]*t; ++cnt[t]; ans^=cnt[t]*t;
				}
			}
			printf("%lld ", ans);
		}
		putchar('\n');
	}
	return 0;
}

The 3rd Universal Cup. Stage 23: Hong Kong D Master of Both VI

link

形式化题意:在一棵树的每个节点维护一个栈,每次操作为路径压栈,单点查询:从栈顶开始,不能同时把相邻两个元素变成一半,弹出元素和不超过 \(h\) 的前提下,最多可以弹出多少个元素。

0.刚看到题

你要先写个暴力来猜测出题人到底要求啥。

1.用矩阵描述

单点查询可以用 dp 来解决,这个 dp 很容易用 \((\min, +)\) 矩乘描述。

所以可以线段树维护矩乘,查询时用线段树上二分找到第一个大于等于 \(h\) 的位置。

2.路径差分

因为询问离线,所以考虑把路径差分,每个点维护动态开点线段树,子节点向上线段树合并。

剩下的就是细节的问题了。

时间复杂度为 \(O(n+q(\log q+\log n))\)

代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
typedef unsigned int uint;
#define ep emplace_back
template <typename T>inline void read(T &x){
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
const int N=5e5+5;
const ll inf=1e9;
int n, m;
vector<int> e[N];
char s[2];
int dfn[N], top[N], timer, f[N], tsz[N], son[N], dep[N];
void dfs(int x, int fa){
	f[x]=fa; tsz[x]=1; dep[x]=dep[fa]+1;
	for(int y:e[x]) if(y^fa){
		dfs(y, x);
		tsz[x]+=tsz[y];
		if(tsz[y]>tsz[son[x]]) son[x]=y;
	}
}
void dfs2(int x, int tp){
	top[x]=tp;
	dfn[x]=++timer;
	if(son[x]) dfs2(son[x], tp);
	for(int y:e[x]) if(y!=f[x]&&y!=son[x]){
		dfs2(y, y);
	}
}
inline int lca(int x, int y){
	while(top[x]^top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x, y);
		x=f[top[x]];
	}
	return (dep[x]<dep[y])?x:y;
}
int ans[N];
vector<pii> ins[N], pop[N], qry[N];
struct mat{
	ll a00, a01, a10, a11;
	mat(ll _a00=0, ll _a01=inf, ll _a10=inf, ll _a11=0){a00=_a00; a01=_a01; a10=_a10; a11=_a11;}
}emp;
inline mat operator +(mat x, mat y){
	return (mat){x.a00+y.a00, x.a01+y.a01, x.a10+y.a10, x.a11+y.a11};
}
inline mat operator *(mat x, mat y){
	mat z;
	z.a00=min(x.a00+y.a00, x.a01+y.a10);
	z.a01=min(x.a00+y.a01, x.a01+y.a11);
	z.a10=min(x.a10+y.a00, x.a11+y.a10);
	z.a11=min(x.a10+y.a01, x.a11+y.a11);
	return z;
}
mat tr[N*50];
int sz[N*50], ls[N*50], rs[N*50];
int rt[N], idx;
inline void add(int &p, int l, int r, int x, ll v){
	if(!p) p=++idx;
	if(l==r){
		sz[p]++; 
		if(sz[p]==1) tr[p]=(mat){v, v/2, v, inf};
		else tr[p]=tr[p]+(mat){v, v/2, v, inf};
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid) add(ls[p], l, mid, x, v);
	else add(rs[p], mid+1, r, x, v);
	tr[p]=tr[rs[p]]*tr[ls[p]];
	sz[p]=sz[rs[p]]+sz[ls[p]];
}
inline void del(int &p, int l, int r, int x, ll v){
	if(!p) p=++idx;
	if(l==r){
		sz[p]--; 
		if(sz[p]==0) tr[p]=emp;
		else tr[p]=tr[p]+(mat){-v, -v/2, -v, -inf};
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid) del(ls[p], l, mid, x, v);
	else del(rs[p], mid+1, r, x, v);
	tr[p]=tr[rs[p]]*tr[ls[p]];
	sz[p]=sz[rs[p]]+sz[ls[p]];
}
inline int merge(int p, int q, int l, int r){
	if((!p)||(!q)) return p+q;
	if(l==r){
		sz[p]=sz[p]+sz[q];
		tr[p]=tr[p]+tr[q];
		return p;
	}
	int mid=(l+r)>>1;
	ls[p]=merge(ls[p], ls[q], l, mid);
	rs[p]=merge(rs[p], rs[q], mid+1, r);
	sz[p]=sz[ls[p]]+sz[rs[p]];
	tr[p]=tr[rs[p]]*tr[ls[p]];
	return p;
}
inline int get(int p, int l, int r, int L, int R, ll lim, mat &w){
	if(L<=l&&r<=R){
		mat tem=w*tr[p];
		if(min(tem.a00, tem.a01)<lim){
			w=tem; return 0;
		}
		if(l==r) return l;
		int mid=(l+r)>>1;
		tem=w*tr[rs[p]];
		if(min(tem.a00, tem.a01)<lim) {
			w=tem; return get(ls[p], l, mid, L, R, lim, w);
		}
		else{
			return get(rs[p], mid+1, r, L, R, lim, w);
		}
	}
	int mid=(l+r)>>1, ret=0;
	if(R>mid) ret=get(rs[p], mid+1, r, L, R, lim, w);
	if(ret==0&&L<=mid) ret=get(ls[p], l, mid, L, R, lim, w);
	return ret; 
}
inline int gets(int p, int l, int r, int L, int R){
	if(L<=l&&r<=R) return sz[p];
	int mid=(l+r)>>1, ret=0;
	if(L<=mid) ret+=gets(ls[p], l, mid, L, R);
	if(R>mid) ret+=gets(rs[p], mid+1, r, L, R);
	return ret;
}
inline void solve(int x){
	for(int y:e[x]) if(y^f[x]) {
		solve(y); rt[x]=merge(rt[x], rt[y], 1, m);
	}
	for(auto t:ins[x]){
		add(rt[x], 1, m, t.se, t.fi);
	}
	for(auto t:pop[x]){
		del(rt[x], 1, m, t.se, t.fi);
	}
	for(auto t:qry[x]){
		mat tem;
		tem.a00=tem.a01=0; tem.a10=tem.a11=inf;
		int pos=get(rt[x], 1, m, 1, t.se, t.fi, tem);
		ans[t.se]=gets(rt[x], 1, m, pos+1, t.se);
	}

}
int main(){
	// freopen("D:\\nya\\acm\\B\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\B\\test2.out","w",stdout);
	read(n); read(m);
	for(int i=1, x, y; i<n; ++i){
		read(x); read(y);
		e[x].ep(y); e[y].ep(x);
	}
	dfs(1, 0); dfs2(1, 1);
	for(int i=1; i<=m; ++i){
		scanf("%s", s+1);
		if(s[1]=='A'){
			ans[i]=-1;
			int x, y, z;
			read(x); read(y); read(z); z<<=1;
			ins[x].ep(z, i);
			ins[y].ep(z, i);
			pop[x=lca(x, y)].ep(z, i);
			pop[f[x]].ep(z, i);
		}
		else{
			int x, h;
			read(x); read(h); h<<=1;
			qry[x].ep(h, i);
		}
	}
	solve(1);
	for(int i=1; i<=m; ++i) if(ans[i]!=-1) printf("%d\n", ans[i]);
	return 0;
}

The 1st Universal Cup. Stage 15: Hangzhou K Shuttle Tour

link

简单虚树题

0.刚看到题

题意要求的旅行路线实际上就是虚树的大小。

按照套路,把虚树大小转化为按 dfs 序排序后相邻路径长度和。

肯定是不能直接把区间内所有标记点拿来跑虚树的,而题目给出的树是特殊的树,所以考虑求虚树过程是否能简化。

1.虚树的可描述性

只有 50 个 1 度点的意思是只有50个叶子,放在虚树上则意味着这棵虚树只需要 51 个点就能描述。

考虑直接暴力,维护每个叶子返祖链上最深和最浅的标记点,这部分复杂度是 \(O(50n\log n+50q\log n)\) 的。

那么虚树根就是最浅的那些点的公共 lca ,把公共 lca 和剩下的 50 个点跑虚树即可,这部分复杂度是 \(O(50q\log 50)\)

总体而言,时间复杂度是 \(O(50(n+q)\log n)\),需要卡常(实际上使用 \(O(1)\) 就可以通过了)。

代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
const int N=2e5+5;
int n, m;
char str[N];
int state[N];
vector<pii> e[N];
int dep[N], dfn[N]; ll dis[N];
int ft[N][18];
vector<int> leaf;
int leafcnt;
ll bel[N];
int seq[N<<1], timer, pos[N], lg[N<<1];
void dfs(int x, int fa){
	dfn[x]=++dfn[0];
	seq[++timer]=x; pos[x]=timer;
	bool isleaf=1;
	ft[x][0]=fa;
	for(int i=1; ft[x][i-1]; ++i) ft[x][i]=ft[ft[x][i-1]][i-1];
	for(auto edg:e[x]) if(edg.fi^fa){
		dep[edg.fi]=dep[x]+1; dis[edg.fi]=dis[x]+edg.se;
		isleaf=false;
		dfs(edg.fi, x);
		bel[x]|=bel[edg.fi];
		seq[++timer]=x;
	}
	if(isleaf){
		bel[x]=1ll<<(ll)leaf.size();
		leaf.ep(x);
	}
}
int st[20][N<<1];
inline void lca_pre(){
	for(int i=2;i<=timer;i++) lg[i]=lg[i>>1]+1;
	for(int i=1;i<=timer;i++) st[0][i]=seq[i];
	for(int i=1;i<=lg[timer];i++){
		for(int j=1;j+(1<<i)-1<=timer;j++){
			st[i][j]=dep[st[i-1][j]]<dep[st[i-1][j+(1<<(i-1))]]?
			st[i-1][j]:st[i-1][j+(1<<(i-1))];
		}
	}
}
inline int lca(int x,int y){
	if(x==0||y==0) return x|y;
	x=pos[x];y=pos[y];
	if(x>y) x^=y^=x^=y;
	int t=lg[y-x+1];
	return dep[st[t][x]]<dep[st[t][y-(1<<t)+1]]?
	st[t][x]:st[t][y-(1<<t)+1];
}
struct sgt{
	int mx[N<<2], mn[N<<2];
	inline int cmp(int x, int y){
		if(x==0||y==0) return x|y;
		return dep[x]>dep[y]?x:y;
	}
	inline int cmp2(int x, int y){
		if(x==0||y==0) return x|y;
		return dep[x]<dep[y]?x:y;
	}
	inline void up(int p){
		mx[p]=cmp(mx[p<<1], mx[p<<1|1]);
		mn[p]=cmp2(mn[p<<1], mn[p<<1|1]);
	}
	void mdf(int p, int l, int r, int x, int v){
		if(l==r){
			mx[p]=mn[p]=v;
			return ;
		}
		int mid=(l+r)>>1;
		if(x<=mid) mdf(p<<1, l, mid, x, v);
		else mdf(p<<1|1, mid+1, r, x, v);
		up(p);
	}
	int getmx(int p, int l, int r, int L, int R){
		if(L<=l&&r<=R) return mx[p];
		int mid=(l+r)>>1, ret=0;
		if(L<=mid) ret=getmx(p<<1, l, mid, L, R);
		if(R>mid) ret=cmp(ret, getmx(p<<1|1, mid+1, r, L, R));
		return ret;
	}
	int getmn(int p, int l, int r, int L, int R){
		if(L<=l&&r<=R) return mn[p];
		int mid=(l+r)>>1, ret=0;
		if(L<=mid) ret=getmn(p<<1, l, mid, L, R);
		if(R>mid) ret=cmp2(ret, getmn(p<<1|1, mid+1, r, L, R));
		return ret;
	}
}T[55];
bool vis[N];
inline bool cmp(int x, int y){
	return dfn[x]<dfn[y];
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	read(n); read(m);
	scanf("%s", str+1);
	for(int i=1; i<=n; ++i) state[i]=str[i]=='1';
	for(int i=1, x, y, w; i<n; ++i){
		read(x); read(y); read(w);
		e[x].ep(y, w); e[y].ep(x, w);
	}
	dep[1]=1;
	dfs(1, 0);
	lca_pre();
	leafcnt=(int)leaf.size();
	for(int i=1; i<=n; ++i) if(state[i]){
		for(int j=0; j<leafcnt; ++j) if((bel[i]>>j)&1){
			T[j].mdf(1, 1, n, i, i);
		}
	}
	int op, x, l, r;
	while(m--){
		read(op);
		if(op==1){
			read(x);
			state[x]^=1;
			for(int j=0; j<leafcnt; ++j) if((bel[x]>>j)&1){
				T[j].mdf(1, 1, n, x, x*state[x]);
			}
		}
		else{
			read(l); read(r);
			int alllca=0;
			for(int i=0; i<leafcnt; ++i){
				alllca=lca(alllca, T[i].getmn(1, 1, n, l, r));
			}
			if(alllca==0){
				printf("-1\n");
				continue;
			}
			ll ans=0;
			vector<int> vec;
			vec.ep(alllca);
			vis[alllca]=true;
			for(int i=0; i<leafcnt; ++i){
				int x=T[i].getmx(1, 1, n, l, r);
				if(x!=0&&!vis[x]) vis[x]=true, vec.ep(x);
			}
			sort(vec.begin(), vec.end(), cmp);
			for(int i=0; i+1<(int)vec.size(); ++i) {
				ans+=dis[vec[i]]+dis[vec[i+1]]-(dis[lca(vec[i], vec[i+1])]<<1ll);
			}
			ans+=dis[vec.back()]-dis[alllca];
			for(int t:vec) vis[t]=false;
			printf("%lld\n", ans);
		}
	}
	return 0;
}

The 2019 ICPC Asia East Continent Final Contest K All Pair Maximum Flow

link

一种做法是转对偶图建树,变成求 \(\sum_{i=1}^{n-1}\sum_{j=i+1}^{n-1}\min_{x\in [i, j), y\in [1, n]\setminus[i, j)} path_{x, y}\),参考 EI的博客

另一种做法是考虑最外层的最小边,因为要求最小割,所以这条边必然会被割掉,那么考虑在原图上直接割掉,然后把平面图上和这条边属于一个面(也就是最小环)的这些边加上这条边的权值。

只需要重复执行这样的缩图操作即可,最后缩成一个树,就变成了瓶颈路计数的问题,直接用最大生成树即可。

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

代码实现参考了 这篇文章

代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
const int N=6e5+5;
const int mod=998244353;
const int inf=1e9;
int n, m;
int u[N], v[N]; ll w[N];
set<pair<ll, int> > s;
set<pii> e[N];
bool ban[N];
void dfs(int x, int fa, int ed, ll add){
	if(x==ed) return ;
	int y=0, id=0;
	if(e[x].upper_bound(mapa(fa, inf))!=e[x].end()){
		y=(*e[x].upper_bound(mapa(fa, inf))).fi;
		id=(*e[x].upper_bound(mapa(fa, inf))).se;
	}
	else{
		y=(*e[x].begin()).fi;
		id=(*e[x].begin()).se;
	}
	if(s.find(mapa(w[id], id))==s.end()){
		w[id]+=add;
		if(u[id]!=x) swap(u[id], v[id]);
		s.insert(mapa(w[id], id));
	}
	else{
		e[u[id]].erase(e[u[id]].find(mapa(v[id], id)));
		e[v[id]].erase(e[v[id]].find(mapa(u[id], id)));
		s.erase(mapa(w[id], id));
		w[id]+=add;
	}
	dfs(y, x, ed, add);
}
inline void work(int x){
	ban[x]=1;
	s.erase(s.find(mapa(w[x], x)));
	e[u[x]].erase(e[u[x]].find(mapa(v[x], x)));
	e[v[x]].erase(e[v[x]].find(mapa(u[x], x)));
	dfs(u[x], v[x], v[x], w[x]);
}
int edg[N], tot;
inline bool cmp(int x, int y){
	return w[x]>w[y];
}
int fa[N], sz[N];
inline int get(int x){
	while(x!=fa[x]) x=fa[x]=fa[fa[x]];
	return x;
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	read(n); read(m);
	for(int i=1; i<=m; ++i){
		read(u[i]); read(v[i]); read(w[i]);
		if(u[i]>v[i]) swap(u[i], v[i]);
		if(u[i]+1==v[i]||(u[i]==1&&v[i]==n)) s.insert(mapa(w[i], i));
		if(u[i]==1&&v[i]==n) swap(u[i], v[i]);
		e[u[i]].insert(mapa(v[i], i));
		e[v[i]].insert(mapa(u[i], i));
	}
	for(int i=1; i<=m-n+1; ++i) work((*s.begin()).se);
	for(int i=1; i<=m; ++i) if(!ban[i]){
		edg[++tot]=i;
	}
	sort(edg+1, edg+tot+1, cmp);
	for(int i=1; i<=n; ++i) fa[i]=i, sz[i]=1;
	int ans=0;
	for(int i=1; i<=tot; ++i){
		int x=u[edg[i]], y=v[edg[i]];
		x=get(x); y=get(y);
		ans=(ans+w[edg[i]]%mod*sz[x]%mod*sz[y]%mod)%mod;
		fa[x]=y; sz[y]+=sz[x];
	}
	printf("%d\n", ans);
	return 0;
}

The 2024 ICPC Asia Nanjing Regional Contest H Border Jump 2

link

1.回文串

显然回文串 \(s\) 的答案至少是 \(\left\lceil\frac{|s|}{2}\right\rceil-1\)

进一步的,如果回文串的中心是连续的相同字符,那么额外会产生连续段长度一半的答案。

容易发现,这个就是回文串的最优答案。

2.不劣的策略

当操作过程中出现了回文串,我们后续操作得到的串都一定是回文串。

不知道为什么 xqw 在题解里面还证了一下,感觉很显然啊?

再考虑从初始串到回文串的这个过程。结论是从这个回文串不断找左侧最近的(和反串相同的串)合并。

找最近的是显然的,因为更远的串一定会包含当前串,而且后继状态数肯定小于等于最近的串。

那么只需要证明存在一个操作策略使得到回文串之前选择的右端点相同。

假设串的形态为 \(S=A+B+X+B+Y+B+X^\prime+B+A^\prime\),其中 \(B\) 是回文串, \(s^\prime\) 表示 \(s\) 的反串。

不难发现先把开头的 \(A\) 和结尾的 \(A^\prime\) 删掉不影响答案。

如此可以归纳证明。

3.DS

利用 PAM 或者 manacher 求出每个前缀的最长回文后缀,这部分复杂度 \(O(n)\)

利用增量求每个位置向左的最长连续串,复杂度 \(O(n)\)

每次合并操作会让串的长度至少翻倍,所以总询问量为 \(O(n\log n)\)

利用 SAM + 倍增在 SAM 上定位子串,单次询问复杂度 \(O(\log n)\)

利用线段树合并和线段树上二分找最近的位置,单次询问复杂度 \(O(\log n)\)

所有的预处理的复杂度不超过 SAM 上线段树合并复杂度,为 \(O(n\log n)\)

综上,时间复杂度为 \(O(n\log^2 n)\),空间复杂度 \(O(n\log n+n|\sum|)\)

代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
const int N=4e5+5;
int T, n;
char s[N];

struct SAM{
	int len[N], fa[N], ch[N][26];
	int cnt, lst;
	int rt[N], idx;
	int pos[N];
	int gen(){
		++idx;
		occ[idx]=false; ls[idx]=rs[idx]=0;
		return idx;
	}
	int f[N][20];
	bool occ[N*60]; int ls[N*60], rs[N*60];
	void ins(int &p, int l, int r, int x){
		if(!p) p=gen();
		occ[p]=true;
		if(l==r) return ;
		int mid=(l+r)>>1;
		if(x<=mid) ins(ls[p], l, mid, x);
		else ins(rs[p], mid+1, r, x);
	}
	int merge(int p, int q, int l, int r){
		if((!p)||(!q)) return p+q;
		int np=gen();
		if(l==r){
			occ[np]=occ[p]|occ[q];
			return np;
		}
		int mid=(l+r)>>1;
		ls[np]=merge(ls[p], ls[q], l, mid);
		rs[np]=merge(rs[p], rs[q], mid+1, r);
		occ[np]=occ[ls[np]]|occ[rs[np]];
		return np;
	}
	void extend(int c, int lim){
		int p=lst, np=lst=++cnt;
		if(lim<=n) ins(rt[np], 1, n, lim);
		pos[lim]=np;
		len[np]=len[p]+1;
		for(; p&&!ch[p][c]; p=fa[p]) ch[p][c]=np;
		if(!p) fa[np]=1;
		else {
			int q=ch[p][c];
			if(len[q]==len[p]+1) fa[np]=q;
			else{
				int nq=++cnt;
				for(int i=0; i<26; ++i) ch[nq][i]=ch[q][i];
				fa[nq]=fa[q];
				len[nq]=len[p]+1;
				fa[q]=fa[np]=nq;
				for(; p&&ch[p][c]==q; p=fa[p]) ch[p][c]=nq;
			}
		}
	}
	vector<int> e[N];
	void init(){
		for(int i=1; i<=cnt; ++i) {
			for(int c=0; c<26; ++c) ch[i][c]=0;
			fa[i]=len[i]=rt[i]=0;
			e[i].clear();
			for(int t=0; t<20; ++t) f[i][t]=0;
		}
		cnt=lst=1; idx=0;
	}
	void dfs(int x){
		for(int y:e[x]) {
			f[y][0]=x;
			for(int i=1; f[y][i-1]; ++i) f[y][i]=f[f[y][i-1]][i-1];
			dfs(y); 
			rt[x]=merge(rt[x], rt[y], 1, n);
		}
	}
	void merge(){
		for(int i=2; i<=cnt; ++i) e[fa[i]].ep(i);
		dfs(1);
	}
	int locate(int l, int r){
		int x=pos[r];
		for(int t=19; t>=0; --t) if(len[f[x][t]]>=r-l+1) x=f[x][t];
		return x;
	}
	int get(int p, int l, int r, int L, int R){
		if(!occ[p]) return 0;
		if(L<=l&&r<=R){
			if(l==r) return l;
			int mid=(l+r)>>1;
			if(occ[rs[p]]) return get(rs[p], mid+1, r, L, R);
			else return get(ls[p], l, mid, L, R);
		}
		int mid=(l+r)>>1, ret=0;
		if(R>mid) ret=get(rs[p], mid+1, r, L, R);
		if(ret==0&&L<=mid) ret=get(ls[p], l, mid, L, R);
		return ret;
	}
}S;
struct PAM{
	int ch[N][26], fail[N], len[N];
	int lst, cnt;
	void init(){
		for(int x=0; x<=cnt; ++x) {
			fail[x]=len[x]=0;
			for(int i=0; i<26; ++i) ch[x][i]=0;
		}
		len[0]=0; len[1]=-1;
		fail[0]=1; fail[1]=0;
		lst=0; cnt=1;
	}
	int get_fail(int x, int lim){
		while(s[lim-len[x]-1]!=s[lim]) x=fail[x];
		return x;
	}
	void insert(int c, int lim){
		int p=get_fail(lst, lim);
		if(!ch[p][c]){
			++cnt;
			len[cnt]=len[p]+2;
			int q=get_fail(fail[p], lim);
			fail[cnt]=ch[q][c];
			ch[p][c]=cnt;
		}
		lst=ch[p][c];
	}
}P;
int jmp[N];
void solve(){
	scanf("%s", s+1);
	n=strlen(s+1);
	S.init();
	for(int i=1; i<=n; ++i) {
		S.extend(s[i]-'a', i);
		if(s[i]==s[i-1]) jmp[i]=jmp[i-1]+1;
		else jmp[i]=1;
	}
	for(int i=n; i>=1; --i) S.extend(s[i]-'a', 2*n+1-i);
	S.merge();
	P.init();
	int mxans=0;
	for(int i=1; i<=n; ++i){
		P.insert(s[i]-'a', i);
		int len=P.len[P.lst];
		// printf("%d %d\n", i, len);
		int ans=0;
		if(len&1){
			int mid=i-len/2;
			int cont=jmp[mid]-1;
			ans=cont*2+len/2-cont;
		}
		else{
			int mid=i-len/2;
			int cont=jmp[mid];
			ans=cont*2-1+len/2-cont;
		}
		// printf("working:%d\n", i);
		int l=i-len+1;
		while(true){
			// printf("%d %d\n", l, i);
			if(l-1<i-l+1) break;
			int node=S.locate(2*n+1-i, 2*n+1-l);
			int nxtl=S.get(S.rt[node], 1, n, i-l+1, l-1);
			if(nxtl==0) break;
			l=nxtl;
			++ans;
		};
		mxans=max(mxans, ans);
	}
	printf("%d\n", mxans);
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	read(T);
	while(T--) solve();
	return 0;
}

The 2nd Universal Cup. Stage 25: Shenzhen K Quad Kingdoms Chess

link

0.刚看到题

容易观察得到,只有所有后缀最大值(包括相同的)有用,夹在中间的数一定会被吃掉。

两个人和棋的充要条件为后缀最大值序列完全相同。

(其实可以通过比较字典序来判断输赢,可以出一个加强的版本。)

1.楼房重重重建

维护前/后缀最值的数据结构直接使用楼房重建即可,通过线段树上二分实现 push_up() 操作。

本题中还需要额外维护"最小值"辅助二分、"数量"辅助求哈希值。

询问时只需要判断树根的哈希值是否相同即可。

代码实现使用了双质数模数哈希。

代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
inline int fpow(ll x, int y, int mod){
	ll ret=1;
	while(y){
		if(y&1) ret=ret*x%mod;
		x=x*x%mod; y>>=1;
	}
	return ret;
}
const int N=2e5+5, m1=1e9+7, m2=998244353;
int n1, n2, m;
struct node{
    int x, y;
};
node bs=(node){131, 137};
inline node operator +(node x, node y){
    return (node){(x.x+y.x)%m1, (x.y+y.y)%m2};
}
inline node operator -(node x, node y){
    return (node){(x.x-y.x+m1)%m1, (x.y-y.y+m2)%m2};
}
inline node operator *(node x, node y){
    return (node){(int)(1ll*x.x*y.x%m1), (int)(1ll*x.y*y.y%m2)};
}
inline bool operator !=(node x, node y){
    return x.x!=y.x||x.y!=y.y;
}
inline bool operator ==(node x, node y){
	return (x.x==y.x)&&(x.y==y.y);
}
node pw[N], ipw[N];
struct sgt{
	int sz[N<<2]; node hs[N<<2]; int mx[N<<2], mn[N<<2];
	pair<node, int> find(int p, int l, int r, int x){
		if(l==r){
			if(mx[p]>=x) return mapa(hs[p], 1);
			else return mapa((node){0, 0}, 0); 
		}
		if(mn[p]>=x) return mapa(hs[p], sz[p]);
		int mid=(l+r)>>1;
		if(mx[p<<1|1]>=x){
			node reths=(hs[p]-hs[p<<1|1])*ipw[sz[p<<1|1]];
			int retsz=sz[p]-sz[p<<1|1];
			pair<node, int> rs_info=find(p<<1|1, mid+1, r, x);
			return mapa(reths*pw[rs_info.se]+rs_info.fi, retsz+rs_info.se);
		}
		else{
			return find(p<<1, l, mid, x);
		}
	}
	void up(int p, int l, int r){
		mx[p]=max(mx[p<<1], mx[p<<1|1]);
		mn[p]=mn[p<<1|1];
		hs[p]=hs[p<<1|1];
		int mid=(l+r)>>1;
		pair<node, int> tem=find(p<<1, l, mid, mx[p<<1|1]);
		sz[p]=tem.se+sz[p<<1|1];
		hs[p]=tem.fi*pw[sz[p<<1|1]]+hs[p<<1|1];
	}
	void build(int p, int l, int r){
		if(l==r){
			read(mx[p]); mn[p]=mx[p];
			sz[p]=1;
			hs[p]=(node){mx[p], mn[p]};
			return ;
		}
		int mid=(l+r)>>1;
		build(p<<1, l, mid); build(p<<1|1, mid+1, r);
		up(p, l, r);
	}
	void mdf(int p, int l, int r, int x, int v){
		if(l==r){
			mx[p]=mn[p]=v;
			hs[p]=(node){v, v};
			return ;
		}
		int mid=(l+r)>>1;
		if(x<=mid) mdf(p<<1, l, mid, x, v);
		else mdf(p<<1|1, mid+1, r, x, v);
		up(p, l, r);
	}
}T1, T2;
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	pw[0]=(node){1, 1};
	for(int i=1; i<N; ++i) pw[i]=pw[i-1]*bs;
	ipw[0]=(node){1, 1};
	ipw[1]=(node){fpow(bs.x, m1-2, m1), fpow(bs.y, m2-2, m2)};
	for(int i=2; i<N; ++i) ipw[i]=ipw[i-1]*ipw[1];
	read(n1);
	T1.build(1, 1, n1);
	read(n2);
	T2.build(1, 1, n2); 
	read(m);
	while(m--){
		int op, x, v;
		read(op); read(x); read(v);
		if(op==1) T1.mdf(1, 1, n1, x, v);
		else T2.mdf(1, 1, n2, x, v);
		// printf("%d %d %d %d\n", T1.hs[1].x, T1.hs[1].y, T2.hs[1].x, T2.hs[1].y);
		// printf("%d %d\n", T1.sz[1], T2.sz[1]);
		if(T1.hs[1]==T2.hs[1]&&T1.sz[1]==T2.sz[1]) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

2025 ICPC Asia Pacific Championship H Secret Lilies and Roses

link

0.刚看到题

一个朴素的想法是利用相邻两项可以算出来一个位置具体的 \(l\)\(r\),通过二分找到两个单调函数的焦点。

这样会使用 \(2\lceil\log_2 n\rceil\) 个询问,并且当相邻都问出来0的时候没法得出真实值。

并且这个做法没有用到 1 操作,于是剩下的时间我都用在了思考怎么利用 1 操作优化这个二分。

1.两个单调函数的构造形式

也就是说,如果抛开题目背景,只是给两个单调函数去做这个东西,是没有办法做的。

于是我们要思考一下这两个函数是怎么构造的。

不难发现,如果已经知道 1 的数量,那么答案直接就是输出 1 的数量,证明显然。

(但是这结论到底怎么一下想到啊,感觉很脑电波。)

于是问题转化为求 1 的数量。

2.求解转化后的问题

我们期望利用一个 \(l\) 为1的位置去求它右侧所有 \(r\) 的数量,但是这并不是容易的。

别忘了如果相邻两项问出来不一样,我们可以利用这两项算出来点值,就可以求解了。

于是我们要找这两项,容易发现我们需要找一个相邻的 \(10\) 的结构。

我们不妨假设最左边有个 1,最右边有个 0,只需要利用 1 操作二分,找最靠右的 1 或者最靠左的 0 即可,这需要消耗 \(\lceil\log_2 n\rceil\) 次 1 询问。

剩下的只需要再问两次相邻的 2 操作即可。

会有一点 corner case,需要小分类讨论一下,有一个特殊情况需要询问相邻三项,不过仍然在限制之内。

代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
const int N=105;
int T, n;
bool get(int x){
	cout<<"type "<<x<<endl;
	string s;
	cin>>s;
	return s=="rose";
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	cin>>T;
	while(T--){
		cin>>n;
		int l=1, r=n, mid, ret=0;
		while(l<=r){
			mid=(l+r)>>1;
			if(get(mid)){
				ret=mid; l=mid+1;
			}
			else{
				r=mid-1;
			}
		}
		int v1, v2;
		cout<<"multi "<<ret<<endl;
		cin>>v1;
		if(ret!=n){
			cout<<"multi "<<ret+1<<endl;
			cin>>v2;
			if(v1==0&&v2==0){
				if(ret==0){
					cout<<"answer "<<0<<endl;
					continue;
				}
				else{
					cout<<"multi "<<ret-1<<endl;
					cin>>v2;
					if(v1==0&&v2==0){
						cout<<"answer "<<ret<<endl;
						continue;
					}
					int l0=v2-v1;
					int r1=v1/l0;
					int allr=r1+ret-l0;
					cout<<"answer "<<allr<<endl;
					continue;
				}
			}
			int r1=v2-v1;
			int l0=v1/r1;
			int allr=r1+ret-l0;
			cout<<"answer "<<allr<<endl;
		}
		else{
			cout<<"multi "<<ret-1<<endl;
			cin>>v2;
			if(v1==0&&v2==0){
				cout<<"answer "<<n<<endl;
				continue;
			}
			int l0=v2-v1;
			int r1=v1/l0;
			int allr=r1+ret-l0;
			cout<<"answer "<<allr<<endl;
		}
	}
	return 0;
}

posted @ 2025-10-10 21:36  Displace  阅读(11)  评论(0)    收藏  举报