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

按随机顺序排序

The 2nd Universal Cup Finals C Longest Increasing Subsequence

link

记录一个基于不等式的构造证明。

Naive

\(\text{LIS}(a)=A\)

记剩下的数字倒序形成的序列是 \(b_0\),我们有 $\text{LIS}(a+b_0), \text{LIS}(b_0+a)\in \lbrace A, A+1\rbrace $。

如果这两个值相同,那么 \(b_0\) 就是答案。

否则考虑在 \(b_0\) 的基础上构造。

先给出一些已知信息,方便下面构造:

  • \(\forall b, \text{LIS}(a+b)\geq \text{LIS}(a+b_0)\)

  • \(\forall b, \text{LIS}(b+a)\geq \text{LIS}(b_0+a)\)

  • 记剩下的数字正序形成的序列是 \(b^\prime_0\), \(\forall b, \text{LIS}(b+a)\leq \text{LIS}(a+b^\prime_0)\)

Case 1: \(\text{LIS}(a+b_0)=A+1\)

根据已知,我们构造时两个值都有可能增加,当 \(\text{LIS}(a+b_0)\) 增加后讨论的情况会增长到不可数。

最理想的情况是我们构造出 \(b_1\),使得 \(\text{LIS}(a+b_1)=\text{LIS}(b_1+a)=A+1\)

考虑一个构造 \(b_1\),使得 \(b_1\)\(b_0\) 通过翻转最短的后缀后满足 \(\text{LIS}(b_1+a)=A+1\)

我们证明,有解当且仅当 \(\text{LIS}(a+b_1)=A+1\)

\(\text{LIS}(a+b_1)\neq A+1\) 时,根据已知信息一定是 \(\text{LIS}(a+b_1)> A+1\),也可以推出 \(a+b_1\) 的 LIS 必然包括翻转的那一段后缀。

假设此时存在解 \(b^*\)

\(\text{LIS}(b^*)=\text{LIS}(b_1)+k\)

考虑 \(b_1\) 的 LIS 是由剩下的数中最小 \(\text{LIS}(b_1)\) 个数组成的,我们不难得到 \(\text{LIS}(a+b^*)=\text{LIS}(a+b_1)+k\),且 \(\text{LIS}(b^*+a)\leq \text{LIS}(b_1+a)+k\) (选了更大的数更不容易在 \(a\) 中找到后继)。

代入 \(\text{LIS}(a+b_1)> A+1\),我们可以得到 \(\text{LIS}(a+b^*)>A+1+k\),且 \(\text{LIS}(b^*+a)\leq A+1+k\),与 \(b^*\) 是合法解矛盾。

Case 2: \(\text{LIS}(a+b_0)=A\)

考虑对称构造 \(b_2\),使得 \(b_2\)\(b_0\) 通过翻转最短的前缀后满足 \(\text{LIS}(a+b_2)=A+1\),同样尝试证明有解当且仅当 \(\text{LIS}(b_2+a)=A+1\)

\(\text{LIS}(b_2+a)\neq A+1\) 时,根据已知信息一定是 \(\text{LIS}(b_2+a)> A+1\),也可以推出 \(b_2+a\) 的 LIS 必然包括翻转的那一段前缀。

假设此时存在解 \(b^*\)

\(\text{LIS}(b^*)=\text{LIS}(b_2)+k\)

考虑 \(b_2\) 的 LIS 是由剩下的数中最大 \(\text{LIS}(b_2)\) 个数组成的,我们不难得到 \(\text{LIS}(b^*+a)=\text{LIS}(b_2+a)+k\),且 \(\text{LIS}(a+b^*)\leq \text{LIS}(a+b_2)+k\) (选了更小的数更不容易在 \(a\) 中找到前驱)。

代入 \(\text{LIS}(b_2+a)> A+1\),我们可以得到 \(\text{LIS}(b^*+a)>A+1+k\),且 \(\text{LIS}(a+b^*)\leq A+1+k\),与 \(b^*\) 是合法解矛盾。

实现

综上,只需要用树状数组辅助构造过程即可。复杂度为 \(O(m\log m)\)

代码
#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, m;
int p[N];
int A, l1, l2;
bool vis[N];
vector<int> vec;
int tr[N];
void upd(int x, int v){
	for(; x<=m; x+=(x&-x)) tr[x]=max(tr[x], v);
}
int get(int x){
	int ret=0;
	for(; x; x-=(x&-x)) ret=max(ret, tr[x]);
	return ret;
}
inline int solve1(){
	for(int i=1; i<=m; ++i) tr[i]=0;
	for(int i=1; i<=n; ++i) upd(p[i], get(p[i])+1);
	for(int i=0; i<m-n; ++i) upd(vec[i], get(vec[i])+1);
	return get(m);
}
inline int solve2(){
	for(int i=1; i<=m; ++i) tr[i]=0;
	for(int i=0; i<m-n; ++i) upd(vec[i], get(vec[i])+1);
	for(int i=1; i<=n; ++i) upd(p[i], get(p[i])+1);
	return get(m);
}
void upd2(int x, int v){
	for(; x; x-=(x&-x)) tr[x]=max(tr[x], v);
}
int get2(int x){
	int ret=0;
	for(; x<=m; x+=(x&-x)) ret=max(ret, tr[x]);
	return ret;
}
void work1(){
	for(int i=1; i<=m; ++i) tr[i]=0;
	for(int i=n; i>=1; --i) upd2(p[i], get2(p[i])+1);
	int pos=-1;
	for(int i=m-n-1; i>=0; --i) {
		if(get2(vec[i])+m-n-i==A+1) {
			pos=i; break;
		}
	}
	if(pos==-1) {
		printf("No\n");
		return ;
	}
	for(int i=pos, j=m-n-1; i<j; ++i, --j) swap(vec[i], vec[j]);
	if(solve1()==solve2()){
		printf("Yes\n");
		for(auto t:vec) printf("%d ", t);
		putchar('\n');
	}
	else{
		printf("No\n");
	}
}
void work2(){
	for(int i=1; i<=m; ++i) tr[i]=0;
	for(int i=1; i<=n; ++i) upd(p[i], get(p[i])+1);
	int pos=-1;
	for(int i=0; i<m-n; ++i) {
		if(get(vec[i])+i+1==A+1) {
			pos=i; break;
		}
	}
	if(pos==-1) {
		printf("No\n");
		return ;
	}
	for(int i=0, j=pos; i<j; ++i, --j) swap(vec[i], vec[j]);
	if(solve1()==solve2()){
		printf("Yes\n");
		for(auto t:vec) printf("%d ", t);
		putchar('\n');
	}
	else{
		printf("No\n");
	}
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	read(T);
	while(T--){
		read(n); read(m);
		for(int i=1; i<=m; ++i) vis[i]=0;
		for(int i=1; i<=m; ++i) tr[i]=0;
		for(int i=1; i<=n; ++i) read(p[i]), vis[p[i]]=1, upd(p[i], get(p[i])+1);
		A=get(m);
		vec.clear();
		for(int i=1; i<=m; ++i) if(!vis[i]) vec.ep(i);
		sort(vec.begin(), vec.end(), greater<int>());
		l1=solve1(), l2=solve2();
		if(l1==l2){
			printf("Yes\n");
			for(auto t:vec) printf("%d ", t);
			putchar('\n');
			continue;
		}
		if(l1==A+1) work1();
		else work2();
	}
	return 0;
}

The 2nd Universal Cup Finals F World of Rains

link

小巧思。

0.刚看到题

考虑换参考系,假设是一个移动的矩形,每次按照给的参数移动,随机在矩形内生成雨滴,不难发现这是等价的。

考虑每个位置对答案的贡献。在时间轴上,我们会得到这个位置被矩形覆盖的若干个时刻,那么在连续的一段时刻内,如果某一时刻雨滴出现了,那么之后它一直存在,知道不被覆盖的时刻。

于是一个位置对答案的贡献是 \(\prod(\text{连续段的长度}+1)\)

但是计算这个答案不是简单的。

1.计算答案

先考虑一个长条的情况。

考虑矩形每次只会在 \(x\) 轴平移一个单位,所以如果能够得到这个长条被矩形覆盖的时间段,那么每个位置的覆盖时刻会长这样:

在考虑怎么把长条一起考虑,不难发现当前矩形所覆盖的位置可以按照初始加入时间划分成若干个子区间,矩形平移时只会在头尾增删元素。

于是使用一个双端队列维护即可。

考虑计算复杂度,每次做快速幂是 \(O(\log mod)\),其余操作都是均摊线性的。

所以总复杂度 \(O((S+n+m)\log mod)\)

代码
#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=5e5+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;
}
ll frc[N];
int T, n, m, S;
int d[N];
void solve(){
	read(n); read(m); read(S);
	deque<pair<pair<ll, ll>, int> > que;
	ll lst=0;
	ll ans=1;
	que.push_front(mapa(mapa(lst, lst+m-1), 0));
	for(int i=1; i<=S; ++i){
		read(d[i]);
		ll cl=lst+d[i], cr=cl+m-1;
		while(!que.empty()&&que.front().fi.se<cl){
			int dt=i-que.front().se;
			if(dt<n) ans=ans*fpow(frc[dt]*frc[dt]%mod*fpow(dt+1, n-dt+1)%mod, que.front().fi.se-que.front().fi.fi+1)%mod;
			else ans=ans*fpow(frc[n]*frc[n]%mod*fpow(n+1, dt-n+1)%mod, que.front().fi.se-que.front().fi.fi+1)%mod;
			que.pop_front();
		}
		while(!que.empty()&&que.back().fi.fi>cr){
			int dt=i-que.back().se;
			if(dt<n) ans=ans*fpow(frc[dt]*frc[dt]%mod*fpow(dt+1, n-dt+1)%mod, que.back().fi.se-que.back().fi.fi+1)%mod;
			else ans=ans*fpow(frc[n]*frc[n]%mod*fpow(n+1, dt-n+1)%mod, que.back().fi.se-que.back().fi.fi+1)%mod;
			que.pop_back();
		}
		if(!que.empty()&&que.front().fi.fi<cl){
			ll tl=que.front().fi.fi, tr=que.front().fi.se; int ts=que.front().se;
			int dt=i-que.front().se;
			if(dt<n) ans=ans*fpow(frc[dt]*frc[dt]%mod*fpow(dt+1, n-dt+1)%mod, cl-tl)%mod;
			else ans=ans*fpow(frc[n]*frc[n]%mod*fpow(n+1, dt-n+1)%mod, cl-tl)%mod;
			que.pop_front();
			que.push_front(mapa(mapa(cl, tr), ts));
		}
		if(!que.empty()&&que.back().fi.se>cr){
			ll tl=que.back().fi.fi, tr=que.back().fi.se; int ts=que.back().se;
			int dt=i-que.back().se;
			if(dt<n) ans=ans*fpow(frc[dt]*frc[dt]%mod*fpow(dt+1, n-dt+1)%mod, tr-cr)%mod;
			else ans=ans*fpow(frc[n]*frc[n]%mod*fpow(n+1, dt-n+1)%mod, tr-cr)%mod;
			que.pop_back();
			que.push_back(mapa(mapa(tl, cr), ts));
		}
		if(que.empty()){
			que.push_front(mapa(mapa(cl, cr), i));
		}
		else if(d[i]<0){
			que.push_front(mapa(mapa(cl, que.front().fi.fi-1), i));
		}
		else if(d[i]>0){
			que.push_back(mapa(mapa(que.back().fi.se+1, cr), i));
		}
		lst=cl;
	}
	while(!que.empty()){
		int dt=S+1-que.front().se;
		if(dt<n) ans=ans*fpow(frc[dt]*frc[dt]%mod*fpow(dt+1, n-dt+1)%mod, que.front().fi.se-que.front().fi.fi+1)%mod;
		else ans=ans*fpow(frc[n]*frc[n]%mod*fpow(n+1, dt-n+1)%mod, que.front().fi.se-que.front().fi.fi+1)%mod;
		que.pop_front();
	}
	printf("%lld\n", ans);
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	frc[0]=1;
	for(int i=1; i<N; ++i) frc[i]=frc[i-1]*i%mod;
	read(T);
	while(T--) solve();
	return 0;
}

[数据删除] 最小瓶颈

0.题意

给出 \(n\) 个点 \(m\) 条边的无向连通图,\(q\) 次询问区间内任意两点最小瓶颈路权和,强制在线。

\(n\leq 5\times 10^4, m\leq 10^5, q\leq 5\times 10^4\)

1.第一步转化

建出 ksk 重构树,问题转化为在线子区间 lca 权和问题。

2.大力分块

取块长为 \(B\)

先考虑整块,我们可以通过在树上标记关键点,然后对每个点计算与当前整块会产生的 lca 权和,求前缀和,询问时差分,我们就可以知道整块对区间的贡献。

对于每个块,这部分的的时间复杂度是线性的,所以这部分预处理时间复杂度为 \(O(\frac{n^2}{B})\),单次询问复杂度 \(O(\frac{n}{B})\)

再考虑散块,类似地用虚树处理即可。

求虚树需要按 dfn 排序,可以考虑提前把块内排序,每次提取散块使用桶排序,合并两端的散块使用归并,即可规避掉排序的那个 \(\log\)

所以散块的复杂度为单次询问 \(O(B)\)

一个特殊情况是整块内部会有一些细节,不太能用前缀和处理(也许能用,但是我的处理方式会有问题),所以整块内部需要用散块的处理方式处理一下。

所以整体时间复杂度为 \(O((n+m)\log n+\frac{n^2}{B}+Q(B+\frac{n}{B}))\),空间复杂度为 \(O(nB)\),取 \(B=\sqrt{n}\) 可以达到最优复杂度。

如果能离线的话,空间复杂度是线性的。

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, mod=1e9+7;
int n, m, q;
struct edge{
    int u, v, w;
    inline bool operator <(const edge &T)const{
        return w<T.w;
    }
}edg[N];
int fa[N], rt[N];
inline int get(int x){
    if(x==fa[x]) return x;
    return fa[x]=get(fa[x]);
}
inline bool merge(int x, int y){
    x=get(x); y=get(y);
    if(x==y) return false;
    fa[x]=y; return true;
}
vector<int> e[N], vec[N];
int idx;
int val[N];
const int B=250;
int bid[N], lp[N], rp[N];
ll sum[B][N], sum2[B];
int sz[N], tag[N];
int dfn[N], timer, pos[N], dep[N];
inline void get_dfn(int x, int d){
    dfn[++timer]=x; pos[x]=timer; dep[x]=d;
    for(auto y:e[x]){
        get_dfn(y, d+1);
        dfn[++timer]=x;
    }
}
int lg[N],st[25][N];
inline void lca_pre(){
    get_dfn(idx, 1);
    for(int i=2;i<=timer;i++) lg[i]=lg[i>>1]+1;
    for(int i=1;i<=timer;i++) st[0][i]=dfn[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){
    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];
}
inline void dfs1(int x){
    sz[x]=tag[x];
    for(auto y:e[x]) {
        dfs1(y); sz[x]+=sz[y];
    }
}
inline void dfs2(int x, int t, ll up){
    if(x<=n) sum[t][x]=up;
    for(auto y:e[x]) {
        dfs2(y, t, up+1ll*val[x]*(sz[x]-sz[y]));
    }
}
inline bool cmp(int x, int y){return pos[x]<pos[y];}
void add(int x,int y){
	vec[x].ep(y);
	vec[y].ep(x);
}
int rk[N];
int stk[N], top;
inline void dfs3(int x, int fa){
    sz[x]=tag[x];
    for(auto y:vec[x]) if(y^fa) {
        dfs3(y, x); sz[x]+=sz[y];
    }
}
inline void dfs4(int x, int fa, ll &ret){
    int cursz=sz[x];
    for(auto y:vec[x]) if(y^fa){
        cursz-=sz[y]; ret+=1ll*val[x]*sz[y]*cursz;
    }
    for(auto y:vec[x]) if(y^fa) dfs4(y, x, ret);
}
inline ll calc(vector<int> s){
	stk[top=1]=idx;vec[idx].clear();
    int k=s.size();
	for(int i=0;i<k;++i){
		if(s[i]==idx) continue;
		int lc=lca(s[i],stk[top]);
		if(lc!=stk[top]){
			while(pos[stk[top-1]]>pos[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]);
    ll ret=0;
    for(auto x:s) tag[x]=1;
    dfs3(idx, 0);
    dfs4(idx, 0, ret);
    for(auto x:s) tag[x]=0;
    return ret;
}
int bin[B];
inline vector<int> ext(int l, int r){
    for(int i=l; i<=r; ++i) bin[rk[i]]=i;
    vector<int> ret;
    for(int i=0; i<B; ++i) if(bin[i]) ret.ep(bin[i]);
    for(int i=l; i<=r; ++i) bin[rk[i]]=0;
    return ret;
}
inline vector<int> com(vector<int> v1, vector<int> v2){\
    vector<int> ret;
    int lp=0, rp=0;
    while(lp<(int)v1.size()||rp<(int)v2.size()){
        if(lp==(int)v1.size()) ret.ep(v2[rp++]);
        else if(rp==(int)v2.size()) ret.ep(v1[lp++]);
        else if(pos[v1[lp]]<pos[v2[rp]]) ret.ep(v1[lp++]);
        else ret.ep(v2[rp++]);
    }
    return ret;
}
int main(){
	freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	read(n); read(m);
    idx=n;
    for(int i=1; i<=m; ++i) read(edg[i].u), read(edg[i].v), read(edg[i].w);
    sort(edg+1, edg+m+1);
    for(int i=1; i<=n*2; ++i) fa[i]=i, rt[i]=i;
    for(int i=1; i<=m; ++i){
		int fu=get(edg[i].u), fv=get(edg[i].v);
        if(merge(edg[i].u, edg[i].v)){
            ++idx;
            val[idx]=edg[i].w;
            e[idx].ep(fu);
            e[idx].ep(fv);
			fa[fu]=fa[fv]=idx;
        }
    }
    lca_pre();
    for(int i=1; i<=n; ++i) bid[i]=(i-1)/B+1;
    for(int i=1; i<=bid[n]; ++i) lp[i]=rp[i-1]+1, rp[i]=rp[i-1]+B;
    rp[bid[n]]=n;
    for(int t=1; t<=bid[n]; ++t){
        for(int i=lp[t]; i<=rp[t]; ++i) tag[i]=1;
        dfs1(idx);
        dfs2(idx, t, 0ll);
        for(int i=2; i<=n; ++i) sum[t][i]+=sum[t][i-1];
        vector<int> v;
        for(int i=lp[t]; i<=rp[t]; ++i) tag[i]=0, v.ep(i);
        sort(v.begin(), v.end(), cmp);
        for(int i=0; i<(int)v.size(); ++i) rk[v[i]]=i;
        sum2[t]=calc(v);
    }
    read(q);
    ll ans=0;
    while(q--){
        int l, r;
        read(l); read(r);
        // l^=ans, r^=ans;
        if(bid[l]==bid[r]){
            ans=calc(ext(l, r));
        }
        else{
            ans=0;
            for(int i=bid[l]+1; i<bid[r]; ++i){
                ans+=sum[i][lp[i]-1]-sum[i][l-1];
                ans+=sum[i][r]-sum[i][lp[bid[r]]-1];
                ans+=sum2[i];
            }
            vector<int> v1=ext(l, rp[bid[l]]), v2=ext(lp[bid[r]], r);
            ans+=calc(com(v1, v2));
        }
        ans%=mod;
        printf("%lld\n", ans);
    }
    return 0;
}

因为一些原因,没有数据测试代码的正确性,博主利用下面的暴力代码进行了 2000 组的对拍,之后在 UOJ 环境下测试平均运行时间为 3.5s。

暴力代码
#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, mod=1e9+7;
int n, m, q;
struct edge{
    int u, v, w;
    inline bool operator <(const edge &T)const{
        return w<T.w;
    }
}edg[N];
int fa[N], rt[N];
inline int get(int x){
    if(x==fa[x]) return x;
    return fa[x]=get(fa[x]);
}
inline bool merge(int x, int y){
    x=get(x); y=get(y);
    if(x==y) return false;
    fa[x]=y; return true;
}
vector<int> e[N], vec[N];
int idx;
int val[N];
const int B=250;
int bid[N], lp[N], rp[N];
ll sum[B][N], sum2[B];
int sz[N], tag[N];
int dfn[N], timer, pos[N], dep[N];
inline void get_dfn(int x, int d){
    dfn[++timer]=x; pos[x]=timer; dep[x]=d;
    for(auto y:e[x]){
        get_dfn(y, d+1);
        dfn[++timer]=x;
    }
}
int lg[N],st[25][N];
inline void lca_pre(){
    get_dfn(idx, 1);
    for(int i=2;i<=timer;i++) lg[i]=lg[i>>1]+1;
    for(int i=1;i<=timer;i++) st[0][i]=dfn[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){
    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];
}
inline void dfs1(int x){
    sz[x]=tag[x];
    for(auto y:e[x]) {
        dfs1(y); sz[x]+=sz[y];
    }
}
inline void dfs2(int x, int t, ll up){
    if(x<=n) sum[t][x]=up;
    for(auto y:e[x]) {
        dfs2(y, t, up+1ll*val[x]*(sz[x]-sz[y]));
    }
}
inline bool cmp(int x, int y){return pos[x]<pos[y];}
void add(int x,int y){
	vec[x].ep(y);
	vec[y].ep(x);
}
int rk[N];
int stk[N], top;
inline void dfs3(int x, int fa){
    sz[x]=tag[x];
    for(auto y:vec[x]) if(y^fa) {
        dfs3(y, x); sz[x]+=sz[y];
    }
}
inline void dfs4(int x, int fa, ll &ret){
    int cursz=sz[x];
    for(auto y:vec[x]) if(y^fa){
        cursz-=sz[y]; ret+=1ll*val[x]*sz[y]*cursz;
    }
    for(auto y:vec[x]) if(y^fa) dfs4(y, x, ret);
}
inline ll calc(vector<int> s){
	stk[top=1]=idx;vec[idx].clear();
    int k=s.size();
	for(int i=0;i<k;++i){
		if(s[i]==idx) 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]);
    ll ret=0;
    for(auto x:s) tag[x]=1;
    dfs3(idx, 0);
    dfs4(idx, 0, ret);
    for(auto x:s) tag[x]=0;
    return ret;
}
int bin[B];
inline vector<int> ext(int l, int r){
    for(int i=l; i<=r; ++i) bin[rk[i]]=i;
    vector<int> ret;
    for(int i=0; i<B; ++i) if(bin[i]) ret.ep(bin[i]);
    for(int i=l; i<=r; ++i) bin[rk[i]]=0;
    return ret;
}
inline vector<int> com(vector<int> v1, vector<int> v2){
    vector<int> ret;
    int lp=0, rp=0;
    while(lp<(int)v1.size()||rp<(int)v2.size()){
        if(lp==(int)v1.size()) ret.ep(v2[rp++]);
        else if(rp==(int)v2.size()) ret.ep(v1[lp++]);
        else if(pos[v1[lp]]<pos[v2[rp]]) ret.ep(v1[lp++]);
        else ret.ep(v2[rp++]);
    }
    return ret;
}
int main(){
	freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	freopen("D:\\nya\\acm\\A\\test2.out","w",stdout);
	read(n); read(m);
    idx=n;
    for(int i=1; i<=m; ++i) read(edg[i].u), read(edg[i].v), read(edg[i].w);
    sort(edg+1, edg+m+1);
    for(int i=1; i<=n*2; ++i) fa[i]=i, rt[i]=i;
    for(int i=1; i<=m; ++i){
		int fu=get(edg[i].u), fv=get(edg[i].v);
        if(merge(edg[i].u, edg[i].v)){
            ++idx;
            val[idx]=edg[i].w;
            e[idx].ep(fu);
            e[idx].ep(fv);
			fa[fu]=fa[fv]=idx;
        }
    }
    lca_pre();
    read(q);
    ll ans=0;
    while(q--){
        int l, r;
        read(l); read(r);
        // l^=ans, r^=ans;
		ans=0;
		for(int i=l; i<r; ++i){
			for(int j=i+1; j<=r; ++j){
				ans+=val[lca(i, j)];
			}
		}
        ans%=mod;
        printf("%lld\n", ans);
    }
    return 0;
}

4.Bonus

可以进行矩乘规约,最优复杂度是矩乘复杂度。

The 2022 ICPC China Zhejiang Provincial Programming Contest H A=B

link

因为手速不够场上没时间实现口胡的做法,很遗憾。

0.刚看到题

这个 \(2L^2\) 次操作上限启发我们暴力做匹配,即每次拿出 \(s\) 的前缀和 \(t\) 匹配,我们现在需要做到单次匹配 \(O(L)\) 次操作。

我一开始想的是找个不碍事的地方,把 \(s\)\(t\) 分别运一个字符到这里,这个做法显然是单次 \(O(L^2)\),没有什么优化空间。

后来又想可不可以把某个串倒过来,这样匹配复杂度降为 \(O(L)\),但很遗憾我没想出来怎么翻转。

1.交错结构

之后又想到可以把两个串交错放,这样一个好处是当前失配后,大概是能构造出 \(O(L)\) 次操作把 \(t\) 全体平移一位的指令的。

事实上确实可以,但场上没来得及写,只趁队友调 E 的功夫写了个编译器,玩了一下样例的 A+B 是怎么实现的,但没太看明白(

2.具体的构造

首先我们在开头和结尾加一些特殊字符,方便判断终止状态。

我们考虑用多少从 \(t\) 里面取多少,即除非当前都匹配上了才移动 \(t\),这样我认为能让左边的结构清晰一些。

考虑用一个特殊字符表示匹配位置,我这里用了一个 P,如果匹配上,我们就把 P 向后跳两个。

如果失配了,我们用一个特殊字符向前回滚,沿途把 \(s\)\(t\) 对应位置交换,因为我们下一次要向后错一个进行匹配。

到头了,遇到我们事先放置的特殊字符,我们把回滚的特殊字符删了,同时把 \(s\) 的第一个字符删了,把 P 还原回来,继续即可。

终止条件是显然的。

需要注意一点是,我们移动 \(t\) 的时候,有可能会破坏 \(s,t\) 交错的结构,我们在匹配后再换一套字符即可。

我是手动写指令的,所以可以证明指令数量不超过 100(实际上是 90 ,不知道为什么官解 48 个指令就结束了,很厉害)。

这个构造不需要复制串,所以最大串长显然是小于 \(2n+10\) 这个限制的。

总操作次数为 \(O(L^2)\),应该是低于 \(L^2\) 的。

构造
S=TPO
O=XY
Ya=AY
Yb=BY
Yc=CY
aTP=TPa
bTP=TPb
cTP=TPc
PAa=DaP
PBb=EbP
PCc=FcP
PDa=DaP
PEb=EbP
PFc=FcP
PAb=fbD
PAc=fcD
PDb=fbD
PDc=fcD
PBa=faE
PBc=fcE
PEa=faE
PEc=fcE
PCa=faF
PCb=fbF
PFa=faF
PFb=fbF
Aaf=faA
Bbf=fbB
Ccf=fcC
Daf=faD
Ebf=fbE
Fcf=fcF
Tfa=TP
Tfb=TP
Tfc=TP
DDa=DaD
DDb=DbD
DDc=DcD
DEa=DaE
DEb=DbE
DEc=DcE
DFa=DaF
DFb=DbF
DFc=DcF
EDa=EaD
EDb=EbD
EDc=EcD
EEa=EaE
EEb=EbE
EEc=EcE
EFa=EaF
EFb=EbF
EFc=EcF
FDa=FaD
FDb=FbD
FDc=FcD
FEa=FaE
FEb=FbE
FEc=FcE
FFa=FaF
FFb=FbF
FFc=FcF
aA=Aa
aB=Ba
aC=Ca
bA=Ab
bB=Bb
bC=Cb
cA=Ac
cB=Bc
cC=Cc
DX=(return)0
EX=(return)0
FX=(return)0
XA=AX
XB=BX
XC=CX
PDD=(return)0
PDE=(return)0
PDF=(return)0
PED=(return)0
PEE=(return)0
PEF=(return)0
PFD=(return)0
PFE=(return)0
PFF=(return)0
XY=(return)1

附带一个应该没什么 bug 的编译器。

编译器
#pragma GCC optimize(2)
#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;
string s;
string f[101], g[101];
bool ed[101];
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	int n=0;
	while(true){
		cin>>s;
		++n; int i=n;
		for(int j=0; j<(int)s.size(); ++j) if(s[j]=='('){
			for(int k=j+1; k<(int)s.size(); ++k) if(s[k]==')'){
				s.erase(j, (k-j+1)); ed[i]=1;
				break;
			}
			break;
		}
		int pos=-1;
		for(int j=0; j<(int)s.size(); ++j) if(s[j]=='='){
			pos=j; break;
		}
		if(pos==-1){
			--n; break;
		}
		f[i]=s.substr(0, pos);
		g[i]=s.substr(pos+1, s.size());
	}
	do{
		bool flag=1;
		for(int i=1; i<=n; ++i){
			if(f[i]==""){
				if(ed[i]){
					s=g[i];
					break;
				}
				else{
					s=g[i]+s;
					flag=0;
					break;
				}
			}
			else{
				bool occ=0;
				for(int j=0; j+f[i].size()<=s.size(); ++j){
					if(s.substr(j, f[i].size())==f[i]){
						occ=1;
						if(ed[i]==1){
							s=g[i];
						}
						else{
							s.replace(j, f[i].size(), g[i]);
							flag=0;
						}
						break;
					}
				}
				if(occ){
					break;
				}
			}
		}
		cout<<s<<endl;
		if(flag) break;
	}while(true);
	return 0;
}

The 2025 Guangdong Provincial Collegiate Programming Contest C Cutting Cards

link

场切了,记录一下。

0.刚看到题

容易想到等价转化: 把给出的序列划分为若干个长度递减的连续段,从第二个段开始,每个段向上一个段找恰好是自己减 1 的位置连线,要求连线不相交,求合法划分方案数。

为了消歧义,这里的连线指:\(i\)\(a[i]-1\) 所在的位置连线。

然后队友突然提醒我:没取模?

然后我认为答案是小于等于 \(n\) 的,开始试图证明如果确定了第一段,后面如果有划分方式,则只有唯一一种。

证明:事实上,有合法划分方案当且仅当除了第一段剩下的连线都是向前的,且都不相交,划分的依据为某一个点的连线超过了上一段的末尾。

于是暴力的做法即为:找到最后一个向后连线的位置 (我们认为 \(1\) 向正无穷连线),以及从后往前最长的连线不相交的位置,把这两个位置取 \(\max\),答案就是 \(n+1\) 减去这个值。

1.多次询问

随便上个线段树维护那两个位置即可,我第一个用的是最大值,第二个用的是线段树上二分,应该不太容易做到线性。

复杂度为 \(O((n+m)\log n)\)

2.代码

怎么办呢,省赛没法拷代码,但我又懒得再写一遍了,很遗憾。

3.Bonus

区间翻转。

The 2022 ICPC China Zhejiang Provincial Programming Contest D The Profiteer

link

同样也是因为手速不够场上来不及写,赛后实现了一下过了,和标算思路基本一致。

0.刚看到题

对于一个固定的左端点,存在一个断点使得右端点在左侧不合法,在右侧合法。

进一步分析可以发现断点是非严格递增的,于是考虑决策单调性。

这是最大值背包,不可撤销,所以我们只能暴力存下原始状态回退。

直接按决策单调性那样写就行了,不过实现的时候需要精细一些,避免复杂度退化到 \(O(n^2k\log n)\)

具体的,每层只能加入当前层涉及到的元素,复杂度才是严格的 \(O(nk\log n)\)。达到这个目标需要在向两侧递归时精细地把两层相差的元素都插入背包里。

1.代码

代码
#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, E;
int v[N], a[N], b[N];
vector<int> dp;
int f[N];
inline void solve(int lp, int rp, int lv, int rv){
	if(lp>rp||lv>rv) return ;
	if(lv==rv){
		for(int i=lp; i<=rp; ++i) f[i]=lv;
		return ;
	}
	int midv=(lv+rv)>>1;
	vector<vector<int> > back;
	int L=lp, R=min(rp, midv), mid, ret=lp-1;
	for(int i=midv+1; i<=rv; ++i){
		back.ep(dp);
		for(int j=m-a[i]; j>=0; --j){
			dp[j+a[i]]=max(dp[j+a[i]], dp[j]+v[i]);
		}
	}
	for(int i=max(rp+1, lv); i<=midv; ++i){
		back.ep(dp);
		for(int j=m-b[i]; j>=0; --j){
			dp[j+b[i]]=max(dp[j+b[i]], dp[j]+v[i]);
		}
	}
	while(L<=R){
		mid=(L+R)>>1;
		for(int i=L; i<mid; ++i){
			back.ep(dp);
			for(int j=m-a[i]; j>=0; --j){
				dp[j+a[i]]=max(dp[j+a[i]], dp[j]+v[i]);
			}
		}
		for(int i=mid; i<=R; ++i){
			back.ep(dp);
			for(int j=m-b[i]; j>=0; --j){
				dp[j+b[i]]=max(dp[j+b[i]], dp[j]+v[i]);
			}
		}
		ll sum=0;
		int mx=0;
		for(int i=1; i<=m; ++i) mx=max(mx, dp[i]), sum+=mx;
		if(sum<=1ll*m*E){
			ret=mid; 
			for(int i=mid; i<=R; ++i) dp=back.back(), back.pop_back();
			back.ep(dp);
			for(int j=m-a[mid]; j>=0; --j){
				dp[j+a[mid]]=max(dp[j+a[mid]], dp[j]+v[mid]);
			}
			L=mid+1;
		}
		else{
			for(int i=mid; i<=R; ++i) dp=back.back(), back.pop_back();
			for(int i=L; i<mid; ++i) dp=back.back(), back.pop_back();
			for(int i=mid; i<=R; ++i){
				back.ep(dp);
				for(int j=m-b[i]; j>=0; --j){
					dp[j+b[i]]=max(dp[j+b[i]], dp[j]+v[i]);
				}
			}
			R=mid-1;
		}
	}
	while(back.size()){
		dp=back.back(); back.pop_back();
	}
	if(rp<lv){
		for(int i=ret+1; i<=rp; ++i){
			back.ep(dp);
			for(int j=m-b[i]; j>=0; --j){
				dp[j+b[i]]=max(dp[j+b[i]], dp[j]+v[i]);
			}
		}
	}
	else{
		for(int i=ret+1; i<lv; ++i){
			back.ep(dp);
			for(int j=m-b[i]; j>=0; --j){
				dp[j+b[i]]=max(dp[j+b[i]], dp[j]+v[i]);
			}
		}
	}
	for(int i=max(midv+1, ret+1); i<=rv; ++i){
		back.ep(dp);
		for(int j=m-a[i]; j>=0; --j){
			dp[j+a[i]]=max(dp[j+a[i]], dp[j]+v[i]);
		}
	}
	solve(lp, ret, lv, midv);
	while(back.size()){
		dp=back.back(); back.pop_back();
	}
	if(rp<lv){
		for(int i=lv; i<=midv; ++i){
			back.ep(dp);
			for(int j=m-b[i]; j>=0; --j){
				dp[j+b[i]]=max(dp[j+b[i]], dp[j]+v[i]);
			}
		}
	}
	else{
		for(int i=rp+1; i<=midv; ++i){
			back.ep(dp);
			for(int j=m-b[i]; j>=0; --j){
				dp[j+b[i]]=max(dp[j+b[i]], dp[j]+v[i]);
			}
		}
	}
	for(int i=lp; i<=ret&&i<=midv; ++i){
		back.ep(dp);
		for(int j=m-a[i]; j>=0; --j){
			dp[j+a[i]]=max(dp[j+a[i]], dp[j]+v[i]);
		}
	}
	solve(ret+1, rp, midv+1, rv);
	while(back.size()){
		dp=back.back(); back.pop_back();
	}
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	read(n); read(m); read(E);
	for(int i=1; i<=n; ++i){
		read(v[i]); read(a[i]); read(b[i]);
	}
	dp.resize(m+1);
	for(int i=1; i<=m; ++i) dp[i]=-2e9;
	dp[0]=0;
	solve(1, n, 1, n+1);
	ll ans=0;
	for(int i=1; i<=n; ++i) ans+=n+1-f[i];
	printf("%lld\n", ans);
	return 0;
}

The 2022 ICPC China Zhejiang Provincial Programming Contest K Dynamic Reachability

link

众所周知,DAG 可达性是规约的,12 秒时限大概率是压位做法。

询问离线,容易想到操作分块。

假如选出来其中 \(B\) 个操作为一块,那么涉及到的点数量为 \(2K\),我们大概只需要考虑他们的可达性。

具体的,我们把这些操作设计的边挖掉,先跑出来这些点之间的可达性,再依次加入边,回答每个询问。

然后很遗憾,本题是一般有向图,所以需要先缩点。

跑可达性的部分复杂度为 \(O(\frac{q}{B}(n+m))\),前提是 \(K\) 不能太大,需要保证可以在 \(O(1)\) 时间合并集合。

单次回答询问复杂度为 \(O(B^2)\)

\(B=(n+m)^{\frac{1}{3}}\) 理论上是最优的,不过实现时我们取 \(B=64\) 更方便。

细节非常多。

代码
#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 __int128 lint;
#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;
const int B=64;
int n, m, q;
int fx[N], fy[N];
vector<pii> e[N]; lint e2[N];
int tp[N], qu[N], qv[N];
bool ban[N], sp[N];
lint val[N]; 
lint f[N];
int deg[N];
int stk[N], top; bool ins[N];
int dfn[N], tim, low[N], scc[N], scccnt;
vector<int> e3[N];
inline void tarjan(int x){
	low[x]=dfn[x]=++tim;
	stk[++top]=x; ins[x]=true;
	for(auto edg:e[x]){
		if(sp[edg.se]||ban[edg.se]) continue;
		int y=edg.fi;
		if(!dfn[y]) tarjan(y), low[x]=min(low[x], low[y]);
		else if(ins[y]) low[x]=min(low[x], dfn[y]);
	}
	if(low[x]==dfn[x]){
		scccnt++;
		f[scccnt]=val[scccnt]=e2[scccnt]=0; deg[scccnt]=0; e3[scccnt].clear();
		do{
			scc[x]=scccnt;
			x=stk[top--]; ins[x]=false;
		}while(low[x]!=dfn[x]);
	}
}
lint pw[N];
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	read(n); read(m); read(q);
	pw[0]=1;
	for(int i=1; i<=128; ++i) pw[i]=pw[i-1]+pw[i-1];
	for(int i=1; i<=m; ++i){
		read(fx[i]); read(fy[i]);
		e[fx[i]].ep(fy[i], i);
	}
	for(int i=1; i<=q; ++i){
		read(tp[i]);
		if(tp[i]==2) read(qu[i]), read(qv[i]);
		else read(qu[i]);
	}
	for(int i=1; i<=q; i+=B){
		vector<int> vec;
		for(int j=i; j<i+B&&j<=q; ++j){
			if(tp[j]==2) vec.ep(qu[j]), vec.ep(qv[j]);
			else vec.ep(fx[qu[j]]), vec.ep(fy[qu[j]]), sp[qu[j]]=1;
		}
		top=tim=scccnt=0;
		for(int x=1; x<=n; ++x) dfn[x]=low[x]=stk[x]=0, ins[x]=0;
		for(int x=1; x<=n; ++x) if(!dfn[x]) tarjan(x);
		vector<int> vid;
		for(auto t:vec) vid.ep(scc[t]);
		sort(vid.begin(), vid.end());
		vid.erase(unique(vid.begin(), vid.end()), vid.end());
		for(int j=0; j<(int)vid.size(); ++j){
			val[vid[j]]=pw[j]; f[vid[j]]=pw[j];
		}
		for(int x=1; x<=n; ++x){
			for(auto edg:e[x]){
				if(sp[edg.se]||ban[edg.se]) continue;
				if(scc[x]!=scc[edg.fi]){
					++deg[scc[edg.fi]];
					e3[scc[x]].ep(scc[edg.fi]);
				}
			}
		}
		queue<int> que;
		for(int x=1; x<=scccnt; ++x) if(deg[x]==0) que.push(x);
		while(!que.empty()){
			int x=que.front(); que.pop();
			for(auto y:e3[x]){
				f[y]|=f[x];
				if(!--deg[y]){
					que.push(y);
				}
			}
		}
		for(int j=i; j<i+B&&j<=q; ++j){
			if(tp[j]==1) sp[qu[j]]=0;
		}
		for(int j=i; j<i+B&&j<=q; ++j){
			if(tp[j]==1) continue;
			if(f[scc[qv[j]]]&val[scc[qu[j]]]){
				printf("YES\n");
				continue;
			}
			for(auto t:vid) e2[t]=0;
			for(int p=0; p<(int)vid.size(); ++p){
				for(int q=0; q<(int)vid.size(); ++q){
					if((f[vid[p]]>>q)&1){
						e2[vid[q]]|=pw[p];
					}
				} 
			}
			vector<int> eid;
			for(int k=i; k<i+B&&k<=q; ++k){
				if(tp[k]==1) {
					eid.ep(qu[k]);
					if(k<j) sp[qu[k]]^=1;
				}
			}
			sort(eid.begin(), eid.end());
			eid.erase(unique(eid.begin(), eid.end()), eid.end());
			for(auto t:eid) if(sp[t]^ban[t]^1){
				if(scc[fx[t]]!=scc[fy[t]]) e2[scc[fx[t]]]|=val[scc[fy[t]]]; 
			}
			lint vis=0;
			for(int p=0; p<(int)vid.size(); ++p) {
				if(vid[p]!=scc[qu[j]]) vis|=pw[p];
			}
			que.push(scc[qu[j]]);
			while(!que.empty()){
				int x=que.front(); que.pop();
				lint tem=vis&e2[x];
				while(tem){
					int t=0;
					if(tem&(pw[64]-1)){
						t=__builtin_ctzll((ull)(tem&(pw[64]-1)));
					}
					else{
						t=64+__builtin_ctzll((ull)(tem/pw[64]));
					}
					vis^=pw[t];
					tem^=pw[t];
					que.push(vid[t]);
				}
			}
			if(vis&val[scc[qv[j]]]){
				printf("NO\n");
			}
			else{
				printf("YES\n");
			}
			for(auto t:eid) sp[t]=0;
		}
		for(int j=i; j<i+B&&j<=q; ++j){
			if(tp[j]==1) ban[qu[j]]^=1;
		}
	}
	return 0;
}

The 2022 ICPC Asia Nanjing Regional Contest E Color the Tree

link

记录一个很有意思的发现。

0.刚看到题

暴力 dp 可以写为区间转移的形式,即把这层深度的点按 dfs 序排序后,一个子树根对应的一定是一段连续区间。暴力显然是 \(O(n^2)\) 的。

不过大概想一下,有效的转移不会太多,事实上对于固定的右端点,有效转移的左端点数量是至少低于 \(O(\sqrt{n})\) 的,考虑每次向左扩展,都需要一个至少和区间长度相当的链。

我一开始想实现这个很暴力的做法,还好被队友及时制止了。

1.进一步减少数量级

考虑对这层深度的点建虚树,容易发现只有虚树根对应有效转移。

所以实际上有效转移总数量级是 \(O(n)\) 的。

建出虚树后暴力 dp 即可,复杂度 \(O(n\log n)\)

代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
int T;
int n;
vector<int> e[N];
int a[N];
int tr[N<<2];
inline void build(int p, int l, int r){
    if(l==r){
        tr[p]=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(p<<1, l, mid); build(p<<1|1, mid+1, r);
    tr[p]=min(tr[p<<1], tr[p<<1|1]);
}
inline int get(int p, int l, int r, int L, int R){
    if(L<=l&&r<=R) return tr[p];
    int mid=(l+r)>>1, ret=1e9;
    if(L<=mid) ret=get(p<<1, l, mid, L, R);
    if(R>mid) ret=min(ret, get(p<<1|1, mid+1, r, L, R));
    return ret;
}
int f[N][25];
int dfn[N], tim, dep[N];
vector<int> vec[N];
inline void dfs(int x, int fa){
    dfn[x]=++tim;
    dep[x]=dep[fa]+1;
    vec[dep[x]].push_back(x);
    f[x][0]=fa;
    for(int i=1; i<=20; ++i) f[x][i]=0;
    for(int i=1; f[x][i-1]; ++i) f[x][i]=f[f[x][i-1]][i-1];
    for(auto y:e[x]){
        if(y==fa) continue;
        dfs(y, x);
    }
}
inline bool cmp(int x, int y){
    return dfn[x]<dfn[y];
}
inline int lca(int x, int y){
    if(dep[x]>dep[y]) swap(x, y);
    for(int i=20; i>=0; --i) if(dep[f[y][i]]>=dep[x]) y=f[y][i];
    if(y==x) return x;
    for(int i=20; i>=0; --i) if(f[y][i]!=f[x][i]) x=f[x][i], y=f[y][i];
    return f[x][0];
}
int stk[N], top;
vector<int> g[N];
inline void add(int x, int y){
    g[x].push_back(y); 
    g[y].push_back(x);
}
inline void build(int id){
    stk[top=1]=1; g[1].clear();
    for(auto x:vec[id]){
        int lc=lca(stk[top], x);
        if(lc!=stk[top]){
            while(dfn[stk[top-1]]>dfn[lc]) add(stk[top], stk[top-1]), --top;
            if(lc!=stk[top-1]) g[lc].clear(), add(lc, stk[top]), stk[top]=lc;
            else add(lc, stk[top]), --top;
        }
        g[x].clear(); stk[++top]=x;
    }
    for(int i=1; i<top; ++i) add(stk[i], stk[i+1]);
}
ll dp[N];
int rk[N], lp[N], rp[N];
void dfs2(int x, int fa, int id){
    if(dep[x]==id){
        dp[rk[x]]=min(dp[rk[x]], dp[rk[x]-1]+get(1, 1, n, 1, id-dep[fa]));
        lp[x]=rp[x]=rk[x];
        return ;
    }
    lp[x]=n, rp[x]=1;
    for(auto y:g[x]){
        if(y==fa) continue;
        dfs2(y, x, id);
        lp[x]=min(lp[x], lp[y]); rp[x]=max(rp[x], rp[y]);
    }
    dp[rp[x]]=min(dp[rp[x]], dp[lp[x]-1]+get(1, 1, n, id-dep[x]+1, id-dep[fa]));
}
void solve(){
    scanf("%d", &n);
    for(int i=1; i<=n; ++i) {
        scanf("%d", &a[i]);
        e[i].clear();
        vec[i].clear();
    }
    build(1, 1, n);
    for(int i=1, x, y; i<n; ++i){
        scanf("%d%d", &x, &y);
        e[x].push_back(y); 
        e[y].push_back(x);
    }
    tim=0;
    dfs(1, 0);
    ll ans=a[1];
    for(int i=2; i<=n; ++i){
        if(vec[i].empty()) break;
        sort(vec[i].begin(), vec[i].end(), cmp);
        build(i);
        for(int j=1; j<=(int)vec[i].size(); ++j) dp[j]=1e18, rk[vec[i][j-1]]=j;
        dp[0]=0;
        dfs2(1, 0, i);
        ans+=dp[vec[i].size()];
    }
    printf("%lld\n", ans);
}
int main(){
    scanf("%d", &T);
    while(T--){
        solve();
    }
}

The 3rd Universal Cup. Stage 39: Tokyo I Insert AB or BA

link

不会 \(n^2\) 做法,简单写写我的带一个堆的 \(\log\) 的做法。

这显然是一个区间划分问题,一段区间只要 A 和 B 数量相同,就一定存在一个操作方案得到这个区间,这是显然的。

不妨设 \(x>y\),我们尽量减少使用 AB 的数量。

考虑一段区间,将 A 视作左括号,B 视作有括号,我们相当于要贪心匹配最少的括号。

这是显然的结论,即:将 A 视作 \(1\),B 视作 \(-1\),最大的前缀和为至少需要 AB 的数量。

考虑 DP,可以写出暴力的转移方程:

\[dp_{i, j}=\max_{0\leq k<j}dp_{i-1, k}+\max_{k\leq p< j}sum[p]-sum[k] \]

这里需要保证 \(s[i]=t[j]\),且 \(s[i-1]=t[k]\),还可能有其他一些条件,总之需要满足能匹配上。

这个式子应该是能做线性的,把后面那部分看做区间贡献函数看上去很有四边形不等式,但是我没能实现出来(主要是在于不匹配的转移不太能和四边形不等式一起用)。

所以就加了一个 \(\log\),用单调栈和堆进行 DP。

时间复杂度 \(O(n^2\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;
#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=8005;
const int inf=1e9;
int n, m;
char s[N], t[N];
int dp[N][N];
int sum[N], sum2[N];
int x, y;
struct myheap{
    priority_queue<int, vector<int>, greater<int> > p, q;
    void push(int x){
        p.push(x);
    }
    void pop(int x){
        q.push(x);
    }
    int top(){
        while(q.size()&&p.top()==q.top()) p.pop(), q.pop();
        return p.top();
    }
};
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
    scanf("%s", s+1);
    scanf("%s", t+1);
    n=strlen(s+1); m=strlen(t+1);
    read(x); read(y);
    if(x<y){
        for(int i=1; i<=n; ++i) s[i]=(s[i]=='A')?'B':'A';
        for(int i=1; i<=m; ++i) t[i]=(t[i]=='A')?'B':'A';
        swap(x, y);
    }
    for(int i=1; i<=n; ++i){
        sum2[i]=sum2[i-1]+((s[i]=='B')?-1:1);
    }
    for(int i=1; i<=m; ++i){
        sum[i]=sum[i-1]+((t[i]=='B')?-1:1);
    }
    sum2[n+1]=sum2[n];
    sum[m+1]=sum[m];
    for(int i=0; i<=n+1; ++i){
        for(int j=0; j<=m+1; ++j){
            dp[i][j]=inf;
        }
    }
    dp[0][0]=0;
    for(int i=1; i<=n+1; ++i){
        vector<pii> rem;
        myheap pq;
        for(int j=1; j<=m+1; ++j){
            int cur=dp[i-1][j-1];
            while(rem.size()&&rem.back().fi<sum[j-1]){
                pq.pop(rem.back().se);
                cur=min(cur, rem.back().se+sum[j-1]-rem.back().fi);
                rem.pop_back();
            }
            if(rem.size()&&rem.back().fi==sum[j-1]){
                if(rem.back().se>cur){
                    pq.pop(rem.back().se);
                    rem.back().se=cur;
                    pq.push(cur);
                }
                else{
                    cur=rem.back().se;
                }
            }
            else{
                pq.push(cur);
                rem.ep(sum[j-1], cur);
            }
            if(s[i]==t[j]&&sum2[i]==sum[j]){
                dp[i][j]=min(dp[i][j], pq.top());
                // int mx=-m;
                // for(int k=j-1; k>=0; --k){
                //     mx=max(mx, sum[k]);
                //     dp[i][j]=min(dp[i][j], dp[i-1][k]+mx-sum[k]);
                // }
            }
        }
    }
    if(dp[n+1][m+1]==inf) {
        printf("-1\n");
    }
    else{
        printf("%lld\n", 1ll*dp[n+1][m+1]*x+1ll*((m-n)/2-dp[n+1][m+1])*y);
    }
	return 0;
}

[数据删除] [数据删除]

link

题意:多测求 \(\prod_{x_1=1}^{N}\prod_{x_2=1}^{N}\cdots\prod_{x_k=1}^{N}\gcd(x_1, x_2, \cdots, x_k)\)\(N\leq 10^6, k\leq 10^{100}, T\leq 10^3\)

直接推式子:

\[\begin{aligned} \text{原式}&=\prod_{d=1}^{N}d^{\sum_{x_1=1}^{\frac{N}{d}}\sum_{x_2=1}^{\frac{N}{d}}\cdots\sum_{x_k=1}^{\frac{N}{d}}[\gcd(x_1, x_2, \cdots, x_k)=1]} \\\\ &=\prod_{d=1}^{N}d^{\sum_{x_1=1}^{\frac{N}{d}}\sum_{x_2=1}^{\frac{N}{d}}\cdots\sum_{x_k=1}^{\frac{N}{d}}\sum_{t|\gcd(x_1, x_2, \cdots, x_k)}\mu(t)} \\\\ &=\prod_{d=1}^{N}d^{\sum_{t=1}^{\frac{N}{d}}\mu(t)[\sum_{t|x_1}^{\frac{N}{d}}\sum_{t|x_2}^{\frac{N}{d}}\cdots\sum_{t|x_k}^{\frac{N}{d}} 1]}\\\\ &=\prod_{d=1}^{N}d^{\sum_{t=1}^{\frac{N}{d}}{\lfloor\frac{N}{dt}\rfloor}^k\times \mu(t)} \end{aligned} \]

交换枚举顺序得到:

\[\begin{aligned} \text{原式}&=\prod_{w=1}^{N}\prod_{d|w}d^{{\lfloor\frac{N}{w}\rfloor}^k\times \mu(\frac{w}{d})}\\\\ \end{aligned} \]

定义 \(f(w):=\prod_{d|w}d^{\mu(\frac{w}{d})}\)

\[\begin{aligned} \text{原式}&=\prod_{w=1}^{N}f(w)^{{\lfloor\frac{N}{w}\rfloor}^k}\\\\ \end{aligned} \]

考虑离散对数:\(\ln f(w)=\sum_{d|w}\mu(\frac{w}{d})\ln d\)

根据莫比乌斯反演有 \(\ln w=\sum_{d|w}\ln f(d)\)

即:\(w=\prod_{d|w}f(d)\)

通过OEIS数论意义可以得到

\[f(n)= \begin{cases} p,&\text{if $n=p^k$ , $p\in \lbrace \text{prime}\rbrace, k\in \mathbb{N^+}$ }\\ 1,&\text{otherwise} \end{cases} \]

于是 \(f\) 可以在线性时间内预处理。

询问时使用整除分块。

时间复杂度为 \(O(maxN+T\sqrt N\log mod)\)

代码
#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 mod=998244353, N=1e6+6, phi2=402653184;
int T;
int n, k; 
char s[N];
int u[N], prm[N]; bool vis[N];
ll f[N];
int f_pre[N], f_inv[N];
void pre_u(){
	u[1]=1;
	for(int i=2; i<N; ++i){
		if(!vis[i]){
			prm[++prm[0]]=i;
			u[i]=-1;
		}
		for(int j=1; j<=prm[0]&&i*prm[j]<N; ++j){
			vis[i*prm[j]]=1;
			if(i%prm[j]==0){
				u[i*prm[j]]=0;
				break;
			}
			u[i*prm[j]]=-u[i];
		}
	}
}
inline ll fpow(ll x, ll y, ll p){
	ll ret=1;
	while(y){
		if(y&1) ret=ret*x%p;
		x=x*x%p; y>>=1;
	}
	return ret;
}
void pre_f(){
	for(int i=1; i<N; ++i) f[i]=1;
	for(int i=1; i<=prm[0]; ++i){
		for(ll j=prm[i]; j<N; j*=prm[i]) f[j]=prm[i];
	}
	f_pre[0]=1;
	for(int i=1; i<N; ++i) f[i]=__gcd(f[i], (ll)i), f_pre[i]=(ll)f_pre[i-1]*f[i]%mod;
	f_inv[N-1]=fpow(f_pre[N-1], mod-2, mod);
	for(int i=N-2; i>=0; --i) f_inv[i]=(ll)f_inv[i+1]*f[i+1]%mod;
}
void solve(){
	read(n); 
	scanf("%s", s+1);
	k=0;
	for(int i=1; s[i]; ++i) k=(10ll*k+s[i]-'0')%phi2;
	if(__gcd(k, phi2)!=1){
		if(strlen(s)>=10) k+=phi2;
		else{
			ll real_k=0;
			for(int i=1; s[i]; ++i) real_k=10ll*real_k+s[i]-'0';
			if(real_k>=phi2) k+=phi2;
		}
	}

	ll ans=1;
	for(int l=1, r; l<=n; l=r+1){
		r=n/(n/l);
		ans=ans*fpow((ll)f_pre[r]*f_inv[l-1]%mod, fpow(n/l, k, mod-1), mod)%mod;
	}
	printf("%lld\n", ans);
}
int main(){
	// freopen("D:\\nya\\acm\\B\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\B\\test2.out","w",stdout);
	pre_u(); 
	pre_f();
	read(T);
	while(T--){
		solve();
	}
	return 0;
}
posted @ 2025-10-10 22:06  Displace  阅读(10)  评论(0)    收藏  举报