\(\cal T_1\) 草莓蛋糕 / cake

Description

题目背景

还有一种猜想是,你就是我宇宙的中心。

题目描述

在一起将 \(\rm a\) 城的蛋糕店都吃过一遍后,你和唐芯都各自选出了自己最心仪的蛋糕店 —— "兰璱辰" 店与 "澄璱辰" 店,下简记为 \(\rm L\) 店与 \(\rm C\) 店。作为甜品的狂热爱好者,唐芯每周都会去 \(\rm C\) 店买她心爱的草莓蛋糕来吃,不过,"两个总比一个好",所以她总是帮你去 \(\rm L\) 店也买一份,再 "顺便帮你吃一点儿"。

对于每个草莓蛋糕都有两个值 \(c,y\),分别代表蛋糕的卡路里和美味度,奇怪的是,美味度越小代表蛋糕越美味。同时,两家蛋糕店里卖的草莓蛋糕可以分别被表示成 多重集 \(L,C\).

蛋糕店里卖的草莓蛋糕不会是一成不变的。具体地,在唐芯相邻两次买蛋糕的时间间隔中,\(\rm L\) 店与 \(\rm C\) 店中会有一家店进行一个草莓蛋糕的上新或下架。另外需要强调的是,唐芯购买蛋糕并不会造成多重集 \(L,C\) 的变化。

由于唐芯会 "吃一点儿" 你的那份,所以对于一组买蛋糕方案 \(a\in L,b\in C\),唐芯会增加的卡路里是 \(a_c+b_c\),感受到的美味度是 \(a_y+b_y\). 作为一名女演员,唐芯必须控制她的体重,但她同时也不想在享受美食的过程中太委屈自己。也就是说,定义 \(\max\{a_c+b_c,a_y+b_y\}\) 为唐芯对一组买蛋糕方案的不喜爱度,她希望能找到一组买蛋糕方案,使得其不喜爱度最小。

由于唐芯马上要赶去《鲛人泪》剧组进行拍摄,所以她将接下来 \(Q\) 周的买蛋糕任务交给了你。为了方便起见,你只需要告诉她每周的最小不喜爱度即可。

\(Q\leqslant 10^6,1\leqslant c,y\leqslant 10^9\).

Solution

一些闲话 & 免责声明:

我真的 没想过卡常,真的 没想过卡常,真的 没想过卡常 இ௰இ!

先开始设置成 \(\text{3 s}\) 确实不太合理(虽然也不是我设的(进行推锅,后来改成 \(\text{4 s}\),想着赛后重测应该都可以 A(真的是标程的两倍啊),没有想到还是有人没有过 qwq(不过同队的一位不愿透露姓名的同学的 \(\text{1 log}\) 做法没有跑过 \(\text{2 log}\) 我也只能哈哈哈哈哈哈哈哈哈了)。总之被机房众 D 了,因为某种比较奇怪的原因这个 "被 D" 的 debuff 转移到另一位出题人身上了,感觉挺抱歉的。

总之,这里对因为被卡常没有随切这道题的同学说句对不起!我谢罪(滑跪)。

\(Q\leqslant 600\)

暴力维护 \(L,C\),每次询问进行两家店蛋糕的两两匹配取 \(\min\),复杂度 \(\mathcal O(Q^3)\).

\(Q\leqslant 5000\)

用两个 \(\rm multiset\) 维护 \(L,C\),再开一个 \(\rm multiset\) 维护 \(\max\{a_c+b_c,a_y+b_y\}\),当上新/下架一个草莓蛋糕时,将这个蛋糕的信息与另一家店的所有蛋糕进行匹配,修改维护 \(\max\)\(\rm multiset\) 的信息即可。复杂度 \(\mathcal O(Q^2\log Q)\).

\(Q\leqslant 10^6\)

题目要求 \(\min_{a\in L,b\in C}\max\{a_c+b_c,a_y+b_y\}\),这个 \(\max\) 很烦人,把两个蛋糕绑在一起,所以考虑分类讨论去除 \(\max\):若 \(a_c+b_c\geqslant a_y+b_y\),也就是 \(a_c-a_y\geqslant b_y-b_c\),对每个蛋糕记一个判据量 \(h\),对于 \(a\in L\)\(h=a_c-a_y\);对于 \(b\in C\)\(h=b_y-b_c\). 那么对于 \(a\in L\),有集合 \(B=\{b_h\leqslant a_h,b\in C\}\)\(a\) 与集合 \(B\) 中任意元素的最大值都是 \(a_c+b_c\),显然选择集合 \(B\)\(b_c\) 最小值是最优的。同时,如果从固定 \(b\in C\) 的角度来看,我们要选择对应集合 \(A\)\(a_c\) 的最小值。至此,我们成功地去除了 \(\max\).

考虑用线段树维护这个过程。发现选哪边作为最大值就是比较判据量的大小关系,所以开一个值域为 \(h\) 值域的线段树,每个节点维护判据量在此区间的两家蛋糕的信息。只需要分别维护两家蛋糕的 \(\min c,\min y\) 以及最终答案即可。加入与删除可以通过维护叶子节点的 \(\rm multiset\) 实现。复杂度 \(\mathcal O(Q\log Q)\).

这里更新一个彩蛋:赛时看到 \(\sf{Cirno\_9}\)\(\color{red}{\text{wa 95}'}\) 的时候非常慌张,害怕把标程写挂了。之后心惊胆战地 debug 了一会后发现他没有判等(等下为啥这个没写只 wa 了一组啊。

Code

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; char s; bool f=0;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp]=x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <set>
# include <iostream>
# include <algorithm>
using namespace std;

const int maxn = 1e6+5;
const int infty = 2e9+9;

int n,val[maxn];
struct Q { int opt,d,c,y; } s[maxn];
struct node {
    int v[2][2], ans;
    void pushUp() {
        if(v[0][0]!=infty && v[1][0]!=infty)
            ans = v[0][0]+v[1][0];
        else ans = infty;
    }
    node operator + (const node& t) const {
        node r;
        for(int i=0;i<2;++i) for(int j=0;j<2;++j)
            r.v[i][j] = min(v[i][j],t.v[i][j]);
        r.ans = min(ans,t.ans);
        if(v[0][1]==infty || t.v[1][1]==infty);
        else r.ans = min(r.ans,v[0][1]+t.v[1][1]);
        if(t.v[0][0]==infty || v[1][0]==infty);
        else r.ans = min(r.ans,t.v[0][0]+v[1][0]);
        return r;
    }
} t[maxn<<2];
multiset <int> st[maxn][2][2];

void ins(int o,int l,int r,int p,int c,int y,bool d) {
    if(l==r) {
        st[l][d][0].insert(c),
        st[l][d][1].insert(y);
        t[o].v[d][0] = min(t[o].v[d][0],c),
        t[o].v[d][1] = min(t[o].v[d][1],y);
        t[o].pushUp(); return;
    } int mid = l+r>>1;
    if(p<=mid) ins(o<<1,l,mid,p,c,y,d);
    else ins(o<<1|1,mid+1,r,p,c,y,d);
    t[o] = t[o<<1]+t[o<<1|1];
}

void del(int o,int l,int r,int p,int c,int y,bool d) {
    if(l==r) {
        st[l][d][0].erase(st[l][d][0].lower_bound(c)),
        st[l][d][1].erase(st[l][d][1].lower_bound(y));
        if(st[l][d][0].empty()) t[o].v[d][0] = infty;
        else t[o].v[d][0] = *st[l][d][0].begin();
        if(st[l][d][1].empty()) t[o].v[d][1] = infty;
        else t[o].v[d][1] = *st[l][d][1].begin();
        t[o].pushUp(); return;
    } int mid = l+r>>1;
    if(p<=mid) del(o<<1,l,mid,p,c,y,d);
    else del(o<<1|1,mid+1,r,p,c,y,d);
    t[o] = t[o<<1]+t[o<<1|1];
}

void build(int o,int l,int r) {
    for(int i=0;i<2;++i) for(int j=0;j<2;++j) 
        t[o].v[i][j] = infty; t[o].ans = infty;
    if(l==r) return; int mid = l+r>>1;
    build(o<<1,l,mid), build(o<<1|1,mid+1,r);
}

int main() {
    freopen("cake.in","r",stdin);
    freopen("cake.out","w",stdout);
    n = read(9);
    for(int i=1;i<=n;++i) {
        s[i].opt=read(9), s[i].d=read(9),
        s[i].c=read(9), s[i].y=read(9);
        val[i] = s[i].d? s[i].y-s[i].c: s[i].c-s[i].y;
    }
    sort(val+1,val+n+1);
    const int rane = unique(val+1,val+n+1)-val-1;
    build(1,1,rane);
    for(int i=1;i<=n;++i) { 
        int x = s[i].d? s[i].y-s[i].c: s[i].c-s[i].y;
        int h = lower_bound(val+1,val+rane+1,x)-val; 
        if(s[i].opt) ins(1,1,rane,h,s[i].c,s[i].y,s[i].d);
        else del(1,1,rane,h,s[i].c,s[i].y,s[i].d);
        print(t[1].ans==infty? -1: t[1].ans,'\n');
    }
    return 0;
}

\(\cal T_2\) 矩阵补全 / completion

Description

本来今天小 Q 是想让你补全一个矩阵的,但是他突然发现自己把矩阵剩下部分的信息也搞丢了。具体来讲,他的矩阵是一个 \(n\)\(m\) 列的 01 矩阵 \(A\)(下标从 \(0\) 开始),有如下性质:

  1. 把每一行的值拼起来形成一个二进制数,这个数属于一个给定的集合 \(S\),即满足 \(\displaystyle ∀0 \leqslant i < n, S ∋ \sum^{m−1}_{j=0} a_{ij}2^j\)
  2. 对于每一列的性质,我们用两个长度为 \(m\) 的数组 \(b\)\(c,c_i ∈ [0, 1]\) 来描述。对于每个 \(0 \leqslant i < m\)
    • \(b_i = 0\),则满足第 \(i\) 列的每个元素都等于 \(c_i\)
    • \(b_i = 1\),则满足第 \(i\) 列的所有元素的按位或和等于 \(c_i\)
    • \(b_i = 2\),则满足第 \(i\) 列的所有元素的按位与和等于 \(c_i\)
    • \(b_i = 3\),则满足第 \(i\) 列的所有元素的按位异或和等于 \(c_i\).

不幸的是,小 Q 把数组 \(c\) 也给搞忘了,只记得 \(S\)\(b\),所以你需要帮助他回忆起数组 \(c\).

具体而言,他会向你询问 \(Q\) 次,每次给出一个可能的 \(c\),你需要回答满足条件的矩阵的个数。

\(n\leqslant 10^9,m\leqslant 20\).

Solution

不妨先考虑 \(b_i=1\) 的情况,那么把每一行的元素看成一个数字,实际上就是对一个全为 \(1\) 的数组 \(P\) 进行 \(n\) 次或卷积得到 \(P'\),每次询问 \(c\) 就是 \(P'_c\)。这个可以用 \(\tt FWT\) + 快速幂加速到 \(\mathcal O((m+\log n)\cdot 2^m)\).

先抛出正解的写法:在 \(\tt FWT\) 途中考虑每个 \(2\) 的幂,对应的 \(b\) 是什么就做什么 \(\tt FWT\)\(b=0\) 时什么也不干)。为啥这是对的?考虑 \(\tt FWT\) 的过程实际上就是不断将二进制位拆分的迭代,所谓递推也是 模拟 此运算在这一位上如何运算,所以它本身就是可以被拆分的。

Code

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while(!isdigit(s=getchar())) f|=(s=='-');
	for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
	return f? -x: x;
}
template <class T>
inline void write(T x) {
	static int writ[50], w_tp=0;
	if(x<0) putchar('-'), x=-x;
	do writ[++w_tp] = x-x/10*10, x/=10; while(x);
	while(putchar(writ[w_tp--]^48), w_tp);
}

const int MAXN = 1<<20;
const int mod = 1e9+7, iv = (mod+1>>1);

int qkpow(int x,int y,int r=1) {
	for(; y; y>>=1, x=1ll*x*x%mod)
		if(y&1) r=1ll*r*x%mod; return r;
}
int adj(int x) { return (x+mod)%mod; }

char str[MAXN];
int n, m, lim, f[MAXN], b[30];

void fwt(int* f,int opt=1) {
	for(int w=0, l=2; w<m; ++w, l<<=1) if(b[w]) {
		if(b[w]==1) {
			for(int o=0, p=l>>1; o<lim; o+=l)
				for(int i=o; i<o+p; ++i)
					f[i+p] = (f[i+p]+f[i]*opt)%mod;
		} else if(b[w]==2) {
			for(int o=0, p=l>>1; o<lim; o+=l)
				for(int i=o; i<o+p; ++i)
					f[i] = (f[i]+f[i+p]*opt)%mod;
		} else {
			for(int o=0, p=l>>1; o<lim; o+=l)
				for(int i=o; i<o+p; ++i) {
					const int x=f[i], y=f[i+p];
					f[i] = (x+y)%mod, f[i+p] = (x-y)%mod;
					if(opt==-1) f[i] = 1ll*f[i]*iv%mod,
								f[i+p] = 1ll*f[i+p]*iv%mod;
				}
		}
	}
}

int main() {
	freopen("completion.in","r",stdin);
	freopen("completion.out","w",stdout);
	n=read(9), m=read(9), scanf("%s",str); lim=1<<m;
	for(int i=0;i<lim;++i) f[i] = str[i]-'0';
	for(int i=0;i<m;++i) b[i]=read(9);
	fwt(f); for(int i=0;i<lim;++i) f[i]=qkpow(f[i],n);
	fwt(f,-1); for(int i=0;i<lim;++i) f[i] = adj(f[i]);
	for(int Q=read(9); Q; --Q) print(f[read(9)],'\n');
	return 0;
}

\(\cal T_3\) 万灵药 / elixir

Description

给定只包含 \(\text{a,b,c,d}\) 的长度为 \(n\) 的字符串 \(S\),进行 \(Q\) 次操作 \((x,y)\)

  • 在集合中插入前缀 \(x,y\)最长公共后缀(注意 \(x,y\) 可以相同),若集合已经存在相同的 字符串(注意不是相同 \(x,y\)),则在集合中删除这个字符串;
  • 定义一个集合的 亲和指数 为集合中 所有字符串两两最长公共前缀 的长度之和,请输出当前集合的亲和指数。

\(n,Q\leqslant 5\cdot 10^5\).

Solution

首先建出 \(\rm sam\),那么任意两个前缀的最长公共后缀就是它们在 \(\text{parent tree}\)\(\rm lca\) 的最长串。由于 \(\rm sam\) 点数级别是 \(\mathcal O(n)\) 的,所以实际上集合大小级别也是 \(\mathcal O(n)\) 的。

考虑用 \(\rm trie\) 树维护这个集合,粗略来看插入复杂度是 \(\mathcal O(n^2)\) 的,然后就可以转化为求 \(\rm trie\) 树上两两关键节点的 \(\rm lca\) 的深度之和,这个问题有一个较为经典的解决方案 —— 插入点 \(u\) 时,查询 \(u\) 到根的权值和,就是此次操作亲和指数的增加量,然后将 \(u\) 到根路径上每个点都加一,删除同理,复杂度是两只 \(\log\) 的。或者也可以用全局平衡二叉树做到一只 \(\log\).

事实上,\(\rm trie\) 树可以做到 \(\mathcal O(n)\) 建树。考虑每个在 \(\text{parent tree}\) 上的节点 \(v\),一定存在一个节点 \(u\) 可以通过在后面加一个字符转移到 \(v\),且 \(u\) 内最长字符串为 \(v\) 去掉最后一个字符的字符串。\(u,v\) 的关系就和 \(\rm trie\) 树的父子关系非常类似了,这样建树即可做到 \(\mathcal O(n)\).

再提一嘴那个 \(\mathcal O(n^2)\) 的建树方法,实际上是对 \(\rm sam\) 上每个节点都维护 \(\text{R}\) 表示在原串上的右端点。叶子节点的 \(\rm R\) 是已知的,可以直接上传到 \(\text{parent tree}\) 上的父亲。事实上这个暴力比正解难写

Code

虽然写的是树剖,但还是跑得挺快的,至少交上去和全局平衡二叉树差不多来着

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; char s; bool f=0;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp] = x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <vector>
# include <cstring>
# include <iostream>
using namespace std;
typedef long long ll;

const int maxn = 5e5+5;
const int MAXN = maxn<<1;

char str[maxn];
bool vis[MAXN];
int n, all, Ref[maxn];

namespace fwt {
    ll c1[MAXN], c2[MAXN];
    int lowbit(int x) { return x&-x; }
    void add(int l,int r,int k) {
        for(const int coe=(l-1)*k; l<=all; l+=lowbit(l))
            c1[l] += k, c2[l] += coe; ++ r;
        for(const int coe=(r-1)*(-k); r<=all; r+=lowbit(r))
            c1[r] -= k, c2[r] += coe; 
    }
    ll ask(int l,int r,ll ret=0) {  
        for(const int coe=r; r; r-=lowbit(r)) 
            ret += c1[r]*coe-c2[r]; -- l;
        for(const int coe=l; l; l-=lowbit(l))
            ret -= c1[l]*coe-c2[l]; return ret;
    }
}

struct HLD {
    vector <int> e[MAXN];
    int dep[MAXN], son[MAXN], sz[MAXN];
    int Dfn, tp[MAXN], f[MAXN], dfn[MAXN];
    void dfs1(int u) {
        sz[u] = 1;
        for(const auto& v:e[u]) {
            dep[v] = dep[f[v]=u]+1;
            dfs1(v), sz[u]+=sz[v];
            if(sz[son[u]]<sz[v]) son[u]=v;
        }
    }
    void dfs2(int u,int t) {
        tp[u]=t, dfn[u]=++Dfn;
        if(son[u]) dfs2(son[u],t);
        for(const auto& v:e[u])
            if(son[u]!=v) dfs2(v,v);
    }
    int lca(int x,int y) {
        for(; tp[x]^tp[y]; x=f[tp[x]])
            if(dep[tp[x]]<dep[tp[y]]) swap(x,y);
        return dep[x]<dep[y]? x: y;
    }
    void modify(int x,int k) {
        for(; tp[x]^1; x=f[tp[x]])
            fwt::add(dfn[tp[x]],dfn[x],k);
        fwt::add(2,dfn[x],k);
    }
    ll query(int x,ll ret=0) {
        for(; tp[x]^1; x=f[tp[x]])
            ret += fwt::ask(dfn[tp[x]],dfn[x]);
        return ret+fwt::ask(2,dfn[x]);
    }
} T, trie;

namespace sam {
    int las=1, cnt=1; 
    struct node { int len,fa,to[4]; } t[MAXN];
    void ins(int id) {
        int c = str[id]-'a'; Ref[id] = cnt+1;
        int cur=++cnt, p=las; t[cur].len=t[las].len+1;
        for(; p && !t[p].to[c]; p=t[p].fa) t[p].to[c]=cur;
        if(!p) t[cur].fa=1;
        else {
            int q = t[p].to[c];
            if(t[q].len==t[p].len+1) t[cur].fa=q;
            else {
                int now = ++cnt; t[now]=t[q];
                t[now].len = t[p].len+1;
                t[cur].fa = t[q].fa = now;
                for(; p && t[p].to[c]==q; p=t[p].fa) t[p].to[c]=now;
            }
        }
        las=cur;
    }
    void consTree() {
        for(int i=2;i<=cnt;++i) 
            T.e[t[i].fa].emplace_back(i);
        for(int i=1;i<=cnt;++i) {
            for(int j=0;j<4;++j) {
                if(t[i].to[j] && t[t[i].to[j]].len==t[i].len+1)
                    trie.e[i].emplace_back(t[i].to[j]);
            }
        }
    }
}

void CandyMyWife() {
    static long long ans=0;
    int x=read(9), y=read(9);
    int z = T.lca(Ref[x],Ref[y]);
    if(vis[z]) {
        trie.modify(z,-1);
        ans -= trie.query(z);
    } else {
        ans += trie.query(z);
        trie.modify(z,1);
    } vis[z] ^= 1;
    print(ans,'\n');
}

int main() {
    scanf("%s",str+1), n=strlen(str+1);
    for(int i=1;i<=n;++i) sam::ins(i);
    sam::consTree();
    T.dfs1(1), T.dfs2(1,1), trie.dfs1(1), trie.dfs2(1,1);
    all = T.Dfn;
    for(int Q=read(9); Q; --Q) CandyMyWife();
    return 0;
}
posted on 2022-07-18 20:31  Oxide  阅读(298)  评论(3编辑  收藏  举报