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

按随机顺序排序

[CF2110E] Melody

link

0.刚看到题

脑子一热直接分类讨论构造了,写着写着发现有很多没法容易讨论的情况,但上头了硬刚,很遗憾。

警钟长鸣,分讨讨论不了就要考虑换做法。

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=4e5+5;
int T, n;
int l[N], r[N];
int fa[N];
inline int get(int x){
	if(x==fa[x]) return x;
	return fa[x]=get(fa[x]);
}
inline void merge(int x, int y){
	x=get(x); y=get(y);
	fa[x]=y;
}
vector<pii> e[N];
inline ll hs(int x, int y){
	if(x<y) swap(x, y);
	return 1000000000ll*x+y;
}
bool vis[N];
int cnt[N];
vector<int> ans;
inline void dfs(int x){
	for(int &i=cnt[x]; i<(int)e[x].size(); ){
		if(vis[e[x][i].se]==0){
			vis[e[x][i].se]=1;
			++i;
			dfs(e[x][i-1].fi);
		}
		else{
			++i;
		}
	}
	ans.ep(x);
}
void solve(){
	read(n); 
	unordered_map<int, int> cl, cr;
	int idxl=0, idxr=n;
	for(int i=1; i<=n*2; ++i) fa[i]=i, e[i].clear(), cnt[i]=0;
	for(int i=1; i<=n; ++i) vis[i]=0;
	unordered_map<ll, int> h;
	for(int i=1; i<=n; ++i) {
		read(l[i]); read(r[i]);
		if(cl.find(l[i])==cl.end()) cl[l[i]]=++idxl;
		if(cr.find(r[i])==cr.end()) cr[r[i]]=++idxr;
		l[i]=cl[l[i]]; r[i]=cr[r[i]];
		merge(l[i], r[i]);
		h[hs(l[i], r[i])]=i;
		e[l[i]].ep(r[i], i);
		e[r[i]].ep(l[i], i);
	}
	for(int i=1; i<=idxl; ++i) if(get(i)!=get(1)){
		printf("NO\n"); return ;
	}
	for(int i=n+1; i<=idxr; ++i) if(get(i)!=get(1)){
		printf("NO\n"); return ;
	}
	int cnt=0;
	for(int i=1; i<=idxl; ++i) {
		if(e[i].size()&1) ++cnt;
	}
	for(int i=n+1; i<=idxr; ++i) {
		if(e[i].size()&1) ++cnt;
	}
	if(cnt!=0&&cnt!=2) {
		printf("NO\n");
		return ;
	}
	int st=1;
	for(int i=1; i<=idxl; ++i) if(e[i].size()&1) st=i;
	for(int i=n+1; i<=idxr; ++i) if(e[i].size()&1) st=i;
	ans.clear();
	dfs(st);
	printf("YES\n");
	for(int i=1; i<(int)ans.size(); ++i){
		printf("%d ", h[hs(ans[i-1], ans[i])]);
	}
	putchar('\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--){
		solve();
	}
	return 0;
}

[CF2110F] Faculty

link

警钟长鸣,不要看什么都想着拆贡献上 DS。

\(x<y\) 时,\(f(x,y)=x+y\mod x\)。这提示我们考虑增量求解,用新增的数是否超过前面的最大值等作为分类条件考虑。设已经求过答案的最大值为 \(mx\),新增的值为 \(a\)

  • \(a\leq mx\) 时, 让 \(a\) 作为 \(y\) 肯定不如让 \(mx\) 作为 \(y\) 优,用 \(f(a, mx)\) 更新答案。

  • \(mx<a<mx*2\) 时,最大值发生变化。不难发现 \(\forall w<mx, f(w, a)<f(mx, a)=a\),所以用 \(a\) 更新答案。

  • \(a\geq mx*2\) 时,第二类情况的性质失效,只能暴力求解。进行暴力求解时最大值至少翻倍,所以最多进行 \(O(\log V)\) 次暴力求解。

综上,复杂度为 \(O(n\log V)\)

代码
#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;
int a[N];
void solve(){
	read(n);
	int ans=0;
	int mx=0;
	for(int i=1; i<=n; ++i){
		read(a[i]);
		if(a[i]<=mx) {
			ans=max(ans, (a[i]%mx)+(mx%a[i]));
		}
		else if(a[i]<mx*2){
			mx=a[i];
			ans=max(ans, a[i]);
		}
		else{
			mx=a[i];
			for(int j=1; j<=i; ++j) ans=max(ans, (a[j]%a[i])+(a[i]%a[j]));
		}
		printf("%d ", ans);
	}
	putchar('\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--){
		solve();
	}
	return 0;
}

[CF2096F] Wonderful Impostors

link

不会官解的 $1\log $ 做法,简单讲讲我的 $2\log $ 做法。

对于一个右端点,显然存在一个断点使得左端点在其左侧时合法,在其右侧是不合法;左端点类似。

所以对于所有右端点,这个左端点是不降的,可以离线询问、双指针找断点、\(O(1)\) 回答。

\([1,1]\) 肯定是一个合法区间,我们不妨从 \(cl=1, i=2\) 开始双指针,减少讨论。

我们不断检测加入 \(i\) 后是否仍然合法,如果不合法则把 \(cl\) 对应的语句删除并右移,直到 \([i,i]\) 肯定是合法的。

假入 \(i\) 对应 "这段区间至少有一个 \(1\)",我们只需要检查这个区间是否被 "这段区间全 \(0\)" 完全覆盖,用线段树即可。

假入 \(i\) 对应 "这段区间全 \(0\)",我们需要检查这个区间内是否有一个子区间满足 "这段区间至少有一个 \(1\)"。

我们先删除 \(i\) 对应的区间的左右两侧连续的确定为 \(0\) 的位置,剩下的只需要查询是否有一个 "至少有一个 \(1\)" 区间落于删除后的区间内,这是一个在线的二维偏序,我的 $2\log $ 做法瓶颈就在这里,我实现了一个树套树。

综上,复杂度为 \(O(n\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=2e5+5;
int T, n, m, q;
struct D1{
	int tr[N<<2], tag[N<<2];
	inline void clr(int p, int l, int r){
		tr[p]=tag[p]=0;
		if(l==r) return ;
		int mid=(l+r)>>1;
		clr(p<<1, l, mid); clr(p<<1|1, mid+1, r);
	}
	inline void down(int p){
		if(tag[p]){
			tr[p<<1]+=tag[p]; tag[p<<1]+=tag[p];
			tr[p<<1|1]+=tag[p]; tag[p<<1|1]+=tag[p];
			tag[p]=0;
		}
	}
	inline int findl(int p, int l, int r, int L, int R){
		if(tr[p]>0) return 0;
		if(L>R) return 0;
		if(L<=l&&r<=R){
			if(l==r) return l;
			int mid=(l+r)>>1;
			down(p);
			if(tr[p<<1|1]==0) return findl(p<<1|1, mid+1, r, L, R);
			else return findl(p<<1, l, mid, L, R);
		}
		int mid=(l+r)>>1, ret=0;
		down(p);
		if(R>mid) ret=findl(p<<1|1, mid+1, r, L, R);
		if(L<=mid&&ret==0) ret=findl(p<<1, l, mid, L, R);
		return ret;
	}
	inline int findr(int p, int l, int r, int L, int R){
		if(tr[p]>0) return n+1;
		if(L>R) return n+1;
		if(L<=l&&r<=R){
			if(l==r) return l;
			int mid=(l+r)>>1;
			down(p);
			if(tr[p<<1]==0) return findr(p<<1, l, mid, L, R);
			else return findr(p<<1|1, mid+1, r, L, R);
		}
		int mid=(l+r)>>1, ret=n+1;
		down(p);
		if(L<=mid) ret=findr(p<<1, l, mid, L, R);
		if(R>mid&&ret==n+1) ret=findr(p<<1|1, mid+1, r, L, R);
		return ret;
	}
	inline void add(int p, int l, int r, int L, int R, int v){
		if(L<=l&&r<=R){
			tr[p]+=v;
			tag[p]+=v;
			return ;
		}
		int mid=(l+r)>>1;
		down(p);
		if(L<=mid) add(p<<1, l, mid, L, R, v);
		if(R>mid) add(p<<1|1, mid+1, r, L, R, v);
		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;
		down(p);
		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;
	}
}d1;
struct D2{
	int rt[N], tr[N*100], ls[N*100], rs[N*100], idx;
	inline void clr(){
		for(int i=1; i<=n; ++i) rt[i]=0;
		idx=0;
	}
	inline int gen(){
		++idx;
		tr[idx]=ls[idx]=rs[idx]=0;
		return idx;
	}
	inline void addp(int &p, int l, int r, int x, int v){
		if(!p) p=gen();
		tr[p]+=v;
		if(l==r) return ;
		int mid=(l+r)>>1;
		if(x<=mid) addp(ls[p], l, mid, x, v);
		else addp(rs[p], mid+1, r, x, v);
	}
	inline void add(int l, int r, int v){
		for(; r<=n; r+=(r&-r)) {
			addp(rt[r], 1, n, l, v);
		}
	}
	inline int getp(int p, int l, int r, int L, int R){
		if(!p) return 0;
		if(L<=l&&r<=R) return tr[p];
		int mid=(l+r)>>1, ret=0;
		if(L<=mid) ret=getp(ls[p], l, mid, L, R);
		if(R>mid) ret+=getp(rs[p], mid+1, r, L, R);
		return ret;
	}
	inline int get(int l, int r){
		int ret=0;
		for(; r; r-=(r&-r)) {
			ret+=getp(rt[r], 1, n, l, n);
		}
		return ret;
	}
}d2;
int ans[N];
vector<pii> qry[N];
int tp[N], lp[N], rp[N];
void solve(){
	read(n); read(m); 
	d1.clr(1, 1, n);
	d2.clr();
	for(int i=1; i<=m; ++i) read(tp[i]), read(lp[i]), read(rp[i]), qry[i].clear();
	read(q);
	for(int i=1, l, r; i<=q; ++i){
		ans[i]=0;
		read(l); read(r);
		if(r==1) {
			ans[i]=1; continue;
		}
		qry[r].ep(l, i);
	}
	int cl=1;
	if(tp[1]==0){
		d1.add(1, 1, n, lp[1], rp[1], 1);
	}
	else{
		d2.add(lp[1], rp[1], 1);
	}
	for(int i=2; i<=m; ++i){
		while(cl<i){
			if(tp[i]==0){
				int el=d1.findl(1, 1, n, 1, lp[i]-1), er=d1.findr(1, 1, n, rp[i]+1, n);
				++el; --er;
				if(d2.get(el, er)==0){
					break;
				}
			}
			else{
				if(d1.get(1, 1, n, lp[i], rp[i])==0){
					break;
				}
			}
			if(tp[cl]==0){
				d1.add(1, 1, n, lp[cl], rp[cl], -1);
			}
			else{
				d2.add(lp[cl], rp[cl], -1);
			}
			++cl;
		}
		for(auto t:qry[i]) ans[t.se]=t.fi>=cl;
		if(tp[i]==0){
			d1.add(1, 1, n, lp[i], rp[i], 1);
		}
		else{
			d2.add(lp[i], rp[i], 1);
		}
	}
	for(int i=1; i<=q; ++i) {
		if(ans[i]) printf("YES\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--){
		solve();
	}
	return 0;
}

[ABC349G] Palindrome Construction

link

备课时被要求讲的题。

考虑直接逆推 Manacher 的过程,把相等关系用并查集合并,不等关系因为只有 \(O(n)\) 个可以直接记录下来单独判断。

判断完是否有解后贪心构造即可,求 mex 的部分众所周知是 \(O(n)\) 的。

事实上,不用 Manacher,直接暴力枚举,遇到已经并在一起的就开始下一个,也是对的。正确性是和 Manacher 本质相同的,复杂度显然只会进行 \(O(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=2e5+5;
int n, a[N], p[N];
int fa[N];
vector<pii> vec;
inline int get(int x){
	if(x==fa[x]) return x;
	return fa[x]=get(fa[x]);
}
inline void merge(int x, int y){
	x=get(x); y=get(y);
	fa[x]=y;
}
vector<int> e[N];
vector<int> bin[N];
int ans[N];
vector<int> st[N];
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	read(n);
	for(int i=1; i<=n; ++i) read(a[i]), fa[i]=i, ++a[i];
	int mr=0, mid=0;
	for(int i=1; i<=n; ++i){
		if(i<=mr) {
			if(p[mid*2-i]<mr-i+1&&p[mid*2-i]!=a[i]){
				printf("No\n");
				return 0;
			}
			p[i]=min(p[mid*2-i], mr-i+1);
		}
		else p[i]=1;
		if(p[i]>a[i]){
			printf("No\n");
			return 0;
		}
		while(p[i]<=a[i]) merge(i-p[i]+1, i+p[i]-1), ++p[i];
		--p[i];
		if(i-p[i]>=1&&i+p[i]<=n) vec.ep(i-p[i], i+p[i]);
		if(i+p[i]>mr) mr=i+p[i]-1, mid=i;
	}
	for(auto t:vec){
		if(get(t.fi)==get(t.se)){
			printf("No\n");
			return 0;
		}
		e[get(t.fi)].ep(get(t.se));
		e[get(t.se)].ep(get(t.fi));
	}
	for(int i=1; i<=n; ++i) st[get(i)].ep(i);
	for(int i=1; i<=n; ++i){
		if(ans[i]) continue;
		int rt=get(i);
		int it=1;
		sort(bin[rt].begin(), bin[rt].end());
		bin[rt].erase(unique(bin[rt].begin(), bin[rt].end()), bin[rt].end());
		for(auto t:bin[rt]) {
			if(t==it) ++it;
			else break;
		}
		for(auto t:e[rt]) bin[t].ep(it);
		for(auto t:st[rt]) ans[t]=it;
	}
	printf("Yes\n");
	for(int i=1; i<=n; ++i) printf("%d ", ans[i]);
	return 0;
}

[CF2096G] Wonderful Guessing Game

link

最神奇的一集。

先考虑没有忽略询问的情况。

因为每次询问会返回 \(3\) 种状态,我们想要区分不同的状态,就需要至少 \(\lceil\log_{3} n\rceil\) 次询问,我们将在之后的构造中证明这也是上界。

我们把这些询问排成一个 \(n\times \lceil\log_{3}n\rceil\) 的矩阵,方便下文描述

根据询问的要求,我们需要满足每行 \(L\)\(R\) 出现的次数相等,我们不妨把 \(L\) 看做 \(1\)\(R\) 看做 \(-1\)\(N\) 看做 \(0\),即每行的和为 \(0\)

\(n\) 为偶数时,我们选择一个三进制状态后,下一个选择与其互补的状态即可 (即每一位换成相反数),每次构造两对,需要注意不能把全 \(0\) 放进去。当 \(n\) 为奇数时,再把全 \(0\) 放进去即可。

然后就构造完了,但这只是没有忽略询问的情况。

带上忽略询问,就需要我们在构造一行,满足任意两个状态互不相同,且这一行和为 \(0\)

这是一个很经典的问题,把每列的数值和凑成 \(0\) 即可。

最终我们用 \(\lceil\log_{3}n\rceil+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=1e7+5;
int T, n;
int pw[N];
bool vis[N];
int sum[N];
char str[N];
inline void solve(){
	cin>>n;
	int w=ceil(log(n)/log(3));
	pw[0]=1;
	for(int i=1; i<=w; ++i) pw[i]=pw[i-1]*3;
	for(int i=0; i<pw[w]; ++i) vis[i]=0; 
	vector<int> vec;
	vec.ep(-1);
	if(n&1) vec.ep(0);
	int s=1;
	for(int i=2; i<=n; i+=2){
		while(vis[s]) ++s;
		int t=0;
		for(int j=w-1; j>=0; --j) {
			t*=3;
			if((s/pw[j])%3==1) t+=2;
			if((s/pw[j])%3==2) t+=1;
		}
		vis[s]=vis[t]=1;
		vec.ep(s); vec.ep(t);
	}
	for(int i=1; i<=n; ++i) sum[i]=0;
	cout<<w+1<<endl;
	vector<int> vl, vr;
	for(int i=0; i<w; ++i){
		vl.clear(); vr.clear();
		for(int j=1; j<=n; ++j){
			if((vec[j]/pw[i])%3==1){
				vl.ep(j);
				sum[j]=(sum[j]+1)%3;
			}
			if((vec[j]/pw[i])%3==2){
				vr.ep(j);
				sum[j]=(sum[j]+2)%3;
			}
		}
		cout<<vl.size()+vr.size()<<' ';
		for(auto t:vl) cout<<t<<' ';
		for(auto t:vr) cout<<t<<' ';
		cout<<endl;
	}
	vl.clear(); vr.clear();
	for(int i=1; i<=n; ++i){
		if(sum[i]==1){
			vr.ep(i);
		}
		if(sum[i]==2){
			vl.ep(i);
		}
	}
	cout<<vl.size()+vr.size()<<' ';
	for(auto t:vl) cout<<t<<' ';
	for(auto t:vr) cout<<t<<' ';
	cout<<endl;
	scanf("%s", str);
	for(int i=1; i<=n; ++i){
		bool flag=1;
		for(int j=0; j<w; ++j){
			if(str[j]=='?') continue;
			if(str[j]=='N'&&(vec[i]/pw[j])%3!=0){
				flag=0; break;
			}
			if(str[j]=='L'&&(vec[i]/pw[j])%3!=1){
				flag=0; break;
			}
			if(str[j]=='R'&&(vec[i]/pw[j])%3!=2){
				flag=0; break;
			}
		}
		if(str[w]=='N'&&sum[i]!=0){
			flag=0; 
		}
		if(str[w]=='L'&&sum[i]!=2){
			flag=0; 
		}
		if(str[w]=='R'&&sum[i]!=1){
			flag=0; 
		}
		if(flag){
			cout<<i<<endl;
			return ;
		}
	}
	cout<<"???"<<endl;
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	cin>>T;
	while(T--){
		solve();
	}
	// system("pause");
	return 0;
}

[CF2111G] Divisible Subarrays

link

立正挨打!

最初的做法是每次找到区间最大和最小,再根据两侧和内部的情况分类讨论,只需要求最值的线段树和线段树上二分。

然后被 hack 了,下面是 hack 数据:

10
8 10 6 9 4 7 2 5 1 3
1
1 10

容易发现这种情况让上面的讨论出现的递归,所以这个做法肯定是寄了。

考虑另一个暴力,枚举区间后缀最大值,看是否小于前缀最小值。

用单调栈预处理出每个位置作为右端点且是后缀最大值的最远左端点,再跑相反符号的单调栈,用二分求出每个位置小于的最大的前缀值。

单次判断只需要分别判断该位置预处理的两个值和 \(l-1\) 的关系即可。

优化考虑倍增,就做完了。

写 ds 一定要冷静啊……

代码
#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, a[N];
struct D{
    int f1[N][20], f2[N][20];
    int stk[N], top;
    vector<int> vec[N];
    void build(){
        for(int i=1; i<=n; ++i){
            while(top&&a[i]>a[stk[top]]) --top;
            vec[stk[top]].ep(i);
            f1[i][0]=stk[top];
            stk[++top]=i;
        }
        top=0;
        for(int i=1; i<=n; ++i){
            while(top&&a[i]<a[stk[top]]) --top;
            stk[++top]=i;
            for(auto t:vec[i]){
                int l=1, r=top, mid, ret=0;
                while(l<=r){
                    mid=(l+r)>>1;
                    if(a[stk[mid]]<a[t]){
                        ret=mid; l=mid+1;
                    }
                    else r=mid-1;
                }
                f2[t][0]=stk[ret];
            }
        }
        f2[0][0]=n+1;
        for(int t=1; t<20; ++t){
            for(int i=0; i<=n; ++i){
                f1[i][t]=f1[f1[i][t-1]][t-1];
                f2[i][t]=min(f2[i][t-1], f2[f1[i][t-1]][t-1]);
            }
        }
    }
    bool qry(int l, int r){
        for(int t=19; t>=0; --t){
            if(f1[r][t]<=l) continue;
            if(f2[r][t]<=l) return true;
            r=f1[r][t];
        }
        return false;
    }
}d1, d2;
int main(){
	// freopen("D:\\nya\\acm\\B\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\B\\test.out","w",stdout);
    read(n);
    for(int i=1; i<=n; ++i) read(a[i]);
    d1.build();
    reverse(a+1, a+n+1);
    d2.build();
    read(m);
    while(m--){
        int l, r; read(l); read(r);
        if(d1.qry(l-1, r)||d2.qry(n-r, n-l+1)){
            printf("YES\n");
        }
        else{
            printf("NO\n");
        }
        fflush(stdout);
    }
	return 0;
}

[CF2118F] Shifts and Swaps

link

对于差为 \(1\) 的邻项,等效钦定了他们的相对位置。

\(i\) 向最近的 \(a[i]+1\) 的位置连边以表明他们的相对位置(注意这里连边是在环上的),如果两个数组都只有一个 \(m\),那么相当于每个数组都是一个树,两个数组可以转化当且仅当两棵树同构(注意这里需要考虑子节点的顺序)。

如果有多个根,那么就等价于是否存在循环位移相等,可以用 KMP 解决。

代码
#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;
int n, m;
int a[N], b[N];
int occ[N];
vector<int> e[N], g[N];
const ull mask = mt19937_64(time(nullptr))();
ull shift(ull x) {
	x ^= mask;
	x ^= x << 13;
	x ^= x >> 7;
	x ^= x << 17;
	x ^= mask;
	return x;
}
ull hs[N], hs2[N];
void dfs(int x){
	hs[x]=1;
	for(auto y:g[x]){
		if(hs[y]!=0) continue;
		dfs(y);
		hs[x]+=shift(hs[y]);
		hs[x]=shift(hs[x]);
	}
}
void dfs2(int x){
	hs2[x]=1;
	for(auto y:e[x]){
		if(hs2[y]!=0) continue;
		dfs2(y);
		hs2[x]+=shift(hs2[y]);
		hs2[x]=shift(hs2[x]);
	}
}
ull s[N], t[N];
int lens, lent;
int nxt[N];
void solve(){
	read(n); read(m);
	for(int i=1; i<=n; ++i) read(a[i]), a[n+i]=a[i], occ[i]=0, e[i].clear(), e[i+n].clear(), hs[i]=0, hs2[i]=0;
	for(int i=1; i<=n; ++i) read(b[i]), b[n+i]=b[i], g[i].clear(), g[i+n].clear();
	for(int i=1; i<=n*2; ++i){
		if(occ[a[i]+1]!=0) {
			if(i<=n) e[occ[a[i]+1]].ep(i);
			else e[occ[a[i]+1]].ep(i-n);
		}
		if(i<=n) occ[a[i]]=i;
		else occ[a[i]]=i-n;
	}
	for(int i=1; i<=m; ++i) occ[i]=0;
	for(int i=1; i<=n*2; ++i){
		if(occ[b[i]+1]!=0) {
			if(i<=n) g[occ[b[i]+1]].ep(i);
			else  g[occ[b[i]+1]].ep(i-n);
		}
		if(i<=n) occ[b[i]]=i;
		else occ[b[i]]=i-n;
	}
	lens=0; lent=0;
	for(int i=1; i<=n; ++i) if(b[i]==m){
		dfs(i); s[++lens]=hs[i]; 
	}
	for(int i=1; i<=n; ++i) if(a[i]==m){
		dfs2(i); t[++lent]=hs2[i];
	}
	if(lens!=lent){
		printf("NO\n");
		return;
	}
	for(int i=1; i<=lent; ++i) t[i+lent]=t[i];
	lent*=2;
	for(int i=2, j=0; i<=lens; ++i){
		while(j&&s[j+1]!=s[i]) j=nxt[j];
		if(s[j+1]==s[i]) ++j;
		nxt[i]=j;
	}
	for(int i=1, j=0; i<=lent; ++i){
		while(j&&s[j+1]!=t[i]) j=nxt[j];
		if(s[j+1]==t[i]) ++j;
		if(j==lens){
			printf("YES\n");
			return ;
		}
	}
	printf("NO\n");
}
int main(){
	// freopen("D:\\nya\\acm\\C\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\C\\test.out","w",stdout);
	read(T);
	while(T--){
		solve();
	}
	return 0;
}

[CF710F] String Set Queries

link

备课时遇到的 trick(不过肯定是不会给小登讲这个的)。

以这个题为契机记录一个将复杂数据结构加 \(\log\) 在线化的的通法(应该还是比较通用的,虽然我暂时没想出非常巧妙的运用)。

假设我们要维护某种数据结构,需要支持在线插入一个新元素,查询某些信息。我们将这些元素分组,具体的,以总元素数目的二进制拆分为标准分组。

进行修改时,我们会将连续的低位合并,这时我们进行暴力合并。

将复杂度分摊到每个元素身上,不难发现,每个元素所处的组的编号是只会增加的,所以每个元素最多被暴力合并 \(\lceil\log_2 n\rceil\) 次。

于是这个做法的合并复杂度为 \(O(n\log n)\),通常时间复杂度和合并复杂度是接近的,于是我们就实现了复杂数据结构的在线化。

以这个题为例,时间复杂度就是 \(O(\sum |s_i|\log (\sum|s_i|))\),空间复杂度精细实现是线性的。

代码使用了 Trie 图,时空复杂度又多乘了一个字符集,仅供思路参考。

代码
#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=3e5+5;
int n, m;
char s[N];
struct DS{
	vector<string> vec[N];
	int tr[N][26], sz[N], rt[N], idx;
	vector<int> bin[N];
	vector<int> dust;
	int nxt[N];
	int cnt;
	vector<int> e[N];
	int gen(int id){
		int ret;
		if(!dust.empty()) ret=dust.back(), dust.pop_back();
		else ret=++idx;
		for(int i=0; i<26; ++i) tr[ret][i]=0;
		nxt[ret]=0; sz[ret]=0; e[ret].clear();
		bin[id].ep(ret);
		return ret;
	}
	void dfs(int x){
		for(auto y:e[x]){
			sz[y]+=sz[x];
			dfs(y);
		}
	}
	void build(int id){
		rt[id]=gen(id);
		for(auto t:vec[id]){
			int p=rt[id];
			for(int j=0; j<(int)t.size(); ++j){
				int c=t[j]-'a';
				if(!tr[p][c]) tr[p][c]=gen(id);
				p=tr[p][c];
			}
			++sz[p];
		}
		nxt[rt[id]]=rt[id];
		queue<int> q;
		for(int i=0; i<26; ++i) {
			if(tr[rt[id]][i]) {
				q.push(tr[rt[id]][i]); nxt[tr[rt[id]][i]]=rt[id]; 
			}
			else{
				tr[rt[id]][i]=rt[id];
			}
		}
		while(!q.empty()){
			int t=q.front(); q.pop();
			e[nxt[t]].ep(t);
			for(int i=0; i<26; ++i){
				int &c=tr[t][i];
				if(!c) c=tr[nxt[t]][i];
				else {
					nxt[c]=tr[nxt[t]][i];
					q.push(c);
				}
			}
		}
		dfs(rt[id]);
	}
	void ins(){
		++cnt;
		int id=__builtin_ctz(cnt)+1;
		for(int i=1; i<id; ++i) {
			for(auto t:vec[i]) vec[id].ep(t);
			vec[i].clear();
			for(auto t:bin[i]) dust.ep(t);
			bin[i].clear();
			rt[i]=0;
		}
		string s; cin>>s;
		vec[id].ep(s);
		build(id);
	}
	ll calc(){
		ll ret=0;
		for(int u=1, c=1; u<=cnt; u<<=1, ++c){
			if((cnt&u)==0) continue;
			int p=rt[c];
			for(int i=1; i<=m; ++i){
				p=tr[p][s[i]-'a'];
				ret+=sz[p];
			}
		}
		return ret;
	}
}d1, d2;
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	read(n);
	while(n--){
		int op; read(op);
		if(op==1){
			d1.ins();
		}
		else if(op==2){
			d2.ins();
		}
		else{
			scanf("%s", s+1);
			m=strlen(s+1);
			ll ans1=d1.calc();
			ll ans2=d2.calc();
			// printf("%lld %lld\n", ans1, ans2);
			printf("%lld\n", ans1-ans2);
			fflush(stdout);
		}
	}
	return 0;
}

[CCPCF2024L] Yearning for Yonder

link

致第一次梦碎,致第一次落泪,致第一次争执,致第一次沉思。

某个点出发的最长链长度期望为 \(O(\sqrt{n})\),链上每个点的子树大小期望为 \(O(\sqrt{n})\)

询问总次数为 \(O(n\log \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=100;
int T, n;
vector<pair<pii, int>> e;
vector<int> g[N];
int ret;
void dfs(int x, int fa, int d, int goal){
	if(x==goal){
		ret=d; return ;
	}
	for(auto y:g[x]) if(y^fa) dfs(y, x, d+1, goal);
}
inline int get(int x, int y){
	// dfs(x+1, 0, 0, y+1);
	// return ret;
	cout<<"? "<<x+1<<' '<<y+1<<endl;
	cout.flush();
	int ret; cin>>ret;
	return ret;
}
void solve(vector<int> v, int s, int t, int len, vector<int> dis_s){
	if(v.size()==1) return ;
	if(v.size()==2){
		e.ep(mapa(v[0], v[1]), len);
		return ;
	}
	vector<int> dis_t;
	for(auto x:v){
		if(x==t) {
			dis_t.ep(0);
		}
		else{
			int d=get(x, t);
			dis_t.ep(d);
		}
	}
	// cout<<"s: "<<s+1<<endl;
	// cout<<"t: "<<t+1<<endl;
	// cout<<"len: "<<len<<endl;
	// for(int i=0; i<(int)v.size(); ++i){
	// 	cout<<v[i]+1<<' '<<dis_s[i]<<' '<<dis_t[i]<<endl;
	// }
	vector<pii> rt;
	for(int i=0; i<(int)v.size(); ++i){
		if(dis_s[i]+dis_t[i]==len){
			rt.ep(dis_s[i], v[i]);
		}
	}
	sort(rt.begin(), rt.end());
	// cout<<"rt:"<<endl;
	// for(auto t:rt){
	// 	cout<<t.se+1<<' '<<t.fi<<endl;
	// }
	for(int i=0; i+1<(int)rt.size(); ++i) e.ep(mapa(rt[i].se, rt[i+1].se), rt[i+1].fi-rt[i].fi);
	vector<vector<int> > v2;
	v2.resize(rt.size());
	for(int i=0; i<(int)v.size(); ++i){
		if(dis_s[i]+dis_t[i]==len){
			continue;
		}
		int u=(dis_s[i]+dis_t[i]-len)/2;
		int ds=dis_s[i]-u;
		int crt=upper_bound(rt.begin(), rt.end(), mapa(ds, -1))-rt.begin();
		v2[crt].ep(i);
	}
	for(int i=0; i<(int)rt.size(); ++i){
		if(v2[i].empty()) continue;
		int ns=rt[i].se;
		vector<int> nv, ndis_s;
		int nmx=0, nt=0;
		nv.ep(ns); ndis_s.ep(0);
		for(auto x:v2[i]){
			int d=dis_s[x]-rt[i].fi;
			if(d>nmx) nmx=d, nt=v[x];
			nv.ep(v[x]);
			ndis_s.ep(d);
		}
		solve(nv, ns, nt, nmx, ndis_s);
	}
}
void solve(){
	cin>>n;
	// for(int i=1; i<=n; ++i) g[i].clear();
	// for(int i=1, x, y; i<n; ++i){
	// 	cin>>x>>y;
	// 	g[x].ep(y); g[y].ep(x);
	// }
	if(n==1){
		cout<<"!"<<endl;
		cout.flush();
		return ;
	}
	e.clear();
	int mx=0, s=0, t=0;
	vector<int> cur, dis_s;
	dis_s.ep(0);
	for(int i=1; i<n; ++i){
		int x=get(0, i);
		dis_s.ep(x);
		if(x>mx) mx=x, t=i;
	}
	for(int i=0; i<n; ++i) cur.ep(i);
	solve(cur, s, t, mx, dis_s);
	cout<<"! ";
	for(auto t:e) cout<<t.fi.fi+1<<' '<<t.fi.se+1<<' '<<t.se<<' ';
	cout<<endl;
	cout.flush();
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	cin>>T;
	while(T--){
		solve();
	}
	return 0;
}

[CCPCF2024B] Add One 3

link

致一种永不言败的倔强,致一颗伤痕累累的心。

与标算和可能现在的大多数做法不同的做法。

首先考虑向两侧延伸,如果存在一段非负的前缀/后缀,则无解。

否则容易发现只需要考虑内部的 MSS,外部的 MSS 只需要最后把答案和 \(\max\{MSS[1, l-1], MSS[r+1, n]\}-\sum_{i=l}^{r}a_i\)\(\max\),证明显然。

从前缀和角度考虑,等价于我们需要前缀和均为正数,且最后一个前缀和最大。

第一项可以通过查找区间最小值解决。

我们找到非正数的递减子序列,等价于我们给这些位置加上这个递减序列的差分。

第二项可以通过查询修改后前面的最大值解决。

具体的,先找到第一个是非整数的位置,然后查询 \(sum[i]-\min_{j\leq i}sum_[j]\) 的最大值即可。

这些操作都是容易加 $\log $ 实现的,时间复杂度为 \(O((n+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;
#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=1e18;
int T;
int n, m;
ll a[N];
ll sum[N];
ll lmx[N], rmx[N];
ll lms[N], rms[N];
ll mn[N<<2], mx[N<<2], dt[N<<2];
void build(int p, int l, int r){
	if(l==r){
		mn[p]=mx[p]=sum[l];
		dt[p]=1;
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1, l, mid); build(p<<1|1, mid+1, r);
	mn[p]=min(mn[p<<1], mn[p<<1|1]);
	mx[p]=max(mx[p<<1], mx[p<<1|1]);
	dt[p]=max(max(dt[p<<1], dt[p<<1|1]), mx[p<<1|1]-mn[p<<1]+1);
}
ll get(int p, int l, int r, int L, int R){
	if(L<=l&&r<=R) return mn[p];
	int mid=(l+r)>>1; ll ret=inf;
	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;
}
ll get1(int p, int l, int r, int L, int R){
	if(L<=l&&r<=R) return mx[p];
	int mid=(l+r)>>1; ll ret=-inf;
	if(L<=mid) ret=get1(p<<1, l, mid, L, R);
	if(R>mid) ret=max(ret, get1(p<<1|1, mid+1, r, L, R));
	return ret;
}
void get2(int p, int l, int r, int L, int R, ll &pmx, ll &pmn, ll &mdt){
	if(L>R) return ;
	if(L<=l&&r<=R) {
		mdt=max(max(mdt, dt[p]), mx[p]-pmn+1);
		pmx=max(pmx, mx[p]);
		pmn=min(pmn, mn[p]);
		return ;
	}
	int mid=(l+r)>>1;
	if(L<=mid) get2(p<<1, l, mid, L, R, pmx, pmn, mdt);
	if(R>mid) get2(p<<1|1, mid+1, r, L, R, pmx, pmn, mdt);
}
int fnd(int p, int l, int r, int L, int R, ll lim){
	if(mn[p]>lim) return -1;
	if(L<=l&&r<=R){
		if(l==r) return l;
		int mid=(l+r)>>1;
		if(mn[p<<1]<=lim) return fnd(p<<1, l, mid, L, R, lim);
		else return fnd(p<<1|1, mid+1, r, L, R, lim);
	}
	int mid=(l+r)>>1, ret=-1;
	if(L<=mid) ret=fnd(p<<1, l, mid, L, R, lim);
	if(R>mid&&ret==-1) ret=fnd(p<<1|1, mid+1, r, L, R, lim);
	return ret;
}
void solve(){
	read(n); read(m);
	for(int i=1; i<=n; ++i) read(a[i]), sum[i]=sum[i-1]+a[i];
	lms[0]=0; rms[n+1]=-inf;
	for(int i=1; i<=n; ++i) lms[i]=min(lms[i-1], sum[i]);
	for(int i=n; i>=1; --i) rms[i]=max(rms[i+1], sum[i]);
	lmx[0]=0;
	for(int i=1; i<=n; ++i) lmx[i]=max(max(0ll, a[i]), a[i]+lmx[i-1]);
	for(int i=1; i<=n; ++i) lmx[i]=max(lmx[i], lmx[i-1]);
	rmx[n+1]=0;
	for(int i=n; i>=1; --i) rmx[i]=max(max(0ll, a[i]), a[i]+rmx[i+1]);
	for(int i=n; i>=1; --i) rmx[i]=max(rmx[i], rmx[i+1]);
	build(1, 0, n);
	while(m--){
		int l, r; read(l); read(r);
		ll pmx;
		if(l==1) pmx=-inf;
		else pmx=sum[l-1]-lms[l-2];
		pmx=max(pmx, rms[r+1]-sum[r]);
		if(pmx>=0){
			printf("-1\n");
			continue;
		}
		ll vmx=max(lmx[l-1], rmx[r+1]);
		ll s=sum[r]-sum[l-1];
		ll ans=max(0ll, vmx-s+1);
		ll smn=get(1, 0, n, l, r);
		smn-=sum[l-1];
		if(l==r){
			ans=max(ans, max(0ll, -smn+1));
			printf("%lld\n", ans);
			continue;
		}
		if(smn>0){
			ans=max(ans, max(0ll, get1(1, 0, n, l-1, r-1)+1-sum[r]));
			printf("%lld\n", ans);
			continue;
		}
		int pos=fnd(1, 0, n, l, r, sum[l-1]);
		ll smx=-inf, tem1=-inf, tem2=inf;
		get2(1, 0, n, pos, r-1, tem1, tem2, smx);
		smx=max(smx, get1(1, 0, n, l-1, pos-1)-sum[l-1]);
		ans=max(ans, -smn+1+max(0ll, smx+1-(sum[r]-sum[l-1]-smn+1)));
		printf("%lld\n", ans);
	}
}
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;
}

[CCPCF2024K] Grotesque Team Reconstruction

link

多个连通块的情况不再赘述。

场上的想法是求边双考虑要切的边,但最后时间全用来调 L 了,这个做法也就没有后话了。

为了方便,补题的时候使用了点双(我的圆方树板子用的是点双)。

如果有一个割点的子树大小模 3 为 2,那么这个割点和其圆方树子树就是一个合法划分方案。

否则割点的子树大小模 3 的只有 0 和 1。0 不会产生作用;如果只有 1 个 1 ,这个点双不能产生合法解。

否则至少有 4 个 1,考虑我们一定能构造一个方案,使得通过一些 0 把某两个 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=1e6+5;
int T, n, m, idx;
vector<int> e[N], g[N];
int fa[N];
int sz[N];
int get(int x){
	if(x==fa[x]) return x;
	return fa[x]=get(fa[x]);
}
void merge(int x, int y){
	x=get(x); y=get(y);
	fa[x]=y;
}
int dfn[N], low[N], timer, stk[N], top;
void tarjan(int x){
	dfn[x]=low[x]=++timer; stk[++top]=x;
	for(auto y:e[x]){
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x], low[y]);
			if(low[y]==dfn[x]){
				++idx;
				for(int t=0; t!=y; --top){
					t=stk[top];
					g[idx].ep(t); g[t].ep(idx);
				}
				g[idx].ep(x); g[x].ep(idx);
			}
		}
		else low[x]=min(low[x], dfn[y]);
	}
}
vector<int> bin[N];
int siz[N];
void dfs(int x, int fa){
	siz[x]=(x<=n);
	for(auto y:g[x]){
		if(y==fa) continue;
		dfs(y, x);
		siz[x]+=siz[y];
		if(x>n) bin[x].ep(siz[y]%3);
	}
	if(x>n) bin[x].ep((n-siz[x])%3);
}
void solve(){
	read(n); read(m);
	for(int i=1; i<=n; ++i) e[i].clear(), fa[i]=i, sz[i]=0;
	for(int i=1, x, y; i<=m; ++i){
		read(x); read(y);
		merge(x, y);
		e[x].ep(y); e[y].ep(x);
	}
	vector<int> rt;
	for(int i=1; i<=n; ++i) {
		if(i==get(i)) rt.ep(i);
		++sz[get(i)];
	}
	if(rt.size()>2){
		printf("No\n");
		return ;
	}
	if(rt.size()==2){
		if(sz[rt[0]]%3!=2){
			printf("No\n");
		}
		else{
			printf("Yes\n");
		}
		return ;
	}
	for(int i=1; i<=n*2; ++i) g[i].clear(), dfn[i]=low[i]=0;
	timer=0;
	idx=n;
	tarjan(1);
	for(int i=n+1; i<=idx; ++i) bin[i].clear();
	dfs(1, 0);
	for(int i=n+1; i<=idx; ++i){
		sort(bin[i].begin(), bin[i].end());
		if(bin[i].back()==2){
			printf("Yes\n");
			return ;
		}
		int cnt=0;
		while(!bin[i].empty()&&bin[i].back()==1) ++cnt, bin[i].pop_back();
		if(cnt>=4){
			printf("Yes\n");
			return ;
		}
	}
	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--){
		solve();
	}
	return 0;
}

[GP of Potyczki L] Directed Hanoi

link

两点间能经过的点只能做一次暂存处。

于是可以写出 dp 式:\(f[i][j]=\sum_{i\rightarrow k, k\rightarrow j, k\neq i, k\neq j} \min\{f[i][k], f[k][j]\}+1\),当然要求 \(i\neq j\wedge i\rightarrow j\)

给出的图为 DAG,直接在拓扑的过程中 dp。

复杂度为 \(O(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=505;
int T, n, m;
ll f[N][N];
bitset<505> g[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);
	for(int i=1, x, y; i<=m; ++i){
		read(x); read(y);
		g[x][y]=1;
	}
	for(int k=1; k<=n; ++k){
		for(int i=1; i<=n; ++i) if(g[i][k]){
			g[i]|=g[k];
		}
	}
	for(int i=n; i>=1; --i){
		for(int j=i+1; j<=n; ++j) if(g[i][j]){
			for(int k=i+1; k<j; ++k){
				f[i][j]+=min(f[i][k], f[k][j]);
			}
			++f[i][j];
		}
	}
	printf("%lld\n", f[1][n]);
	return 0;
}

P7883 平面最近点对

link

参考 OIWIKI

考虑分治,按横坐标排序,先算左右两侧内部,再算一个在左一个在右的。但我们会发现不好计算跨过中点的最近点对。

考虑一个暴力:递归获得两个子问题的最近点对距离 \(h\) 后,我们只保留距离中点小于 \(h\) 的点。进一步的,我们按照纵坐标排序,每个点只和纵坐标距离它小于 \(h\) 的点计算距离并更新。

正确性显然,我们来考虑复杂度:将这个矩形划分成 8 个小正方形,容易发现每个正方形内最多只有一个点。

于是复杂度为 \(O(n\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=1e6+5;
const ll inf=1e18;
int n;
struct node{
	int x, y;
	inline bool operator <(const node &t)const{
		if(x!=t.x) return x<t.x;
		return y<t.y;
	}
}a[N];
int p[N], _p[N];
inline bool cmp(int x, int y){
	return a[x]<a[y];
}
inline ll dis(node x, node y){
	return 1ll*(x.x-y.x)*(x.x-y.x)+1ll*(x.y-y.y)*(x.y-y.y);
}
ll solve(int l, int r){
	if(l==r){
		return inf;
	}
	int mid=(l+r)>>1;
	int bs=a[p[mid]].x;
	ll h1=solve(l, mid), h2=solve(mid+1, r);
	ll h=min(h1, h2);
	ll ret=h;
	vector<int> vec;
	int lp=l, rp=mid+1, it=l;
	while(lp<=mid||rp<=r){
		if(lp>mid) {
			if(1ll*(a[p[rp]].x-bs)*(a[p[rp]].x-bs)<h) vec.ep(p[rp]);
			_p[it++]=p[rp++];
		}
		else if(rp>r){
			if(1ll*(a[p[lp]].x-bs)*(a[p[lp]].x-bs)<h) vec.ep(p[lp]);
			_p[it++]=p[lp++];
		}
		else{
			if(a[p[lp]].y>a[p[rp]].y) {
				if(1ll*(a[p[rp]].x-bs)*(a[p[rp]].x-bs)<h) vec.ep(p[rp]);
				_p[it++]=p[rp++];
			}
			else {
			if(1ll*(a[p[lp]].x-bs)*(a[p[lp]].x-bs)<h) vec.ep(p[lp]);
				_p[it++]=p[lp++];
			}
		}
	}
	int bd=0;
	for(int i=1; i<(int)vec.size(); ++i){
		while(bd<i&&1ll*(a[vec[bd]].y-a[vec[i]].y)*(a[vec[bd]].y-a[vec[i]].y)>=h) ++bd;
		for(int j=bd; j<i; ++j) ret=min(ret, dis(a[vec[j]], a[vec[i]]));
	}
	for(int i=l; i<=r; ++i) p[i]=_p[i];
	return ret;
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	read(n);
	for(int i=1; i<=n; ++i){
		read(a[i].x); read(a[i].y);
		p[i]=i;
	}
	sort(p+1, p+n+1, cmp);
	printf("%lld\n", solve(1, n));
	return 0;
}

[CCPCF2024E] Omniscient Artist / 全知艺术家

link

很难想象这是可以放在正式赛的题。

暴力用分块维护区间修改即可。

复杂度为 \(O(\frac{n\sum max-min}{m})\),因为每个区间最多让两个块的 \(max-min\) 增加 1,所以复杂度为 \(O(\frac{n^2}{m})\)

因为用 std::unordered_map 会T,没办法写了根号空间的做法。

代码
#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=3e5+5;
const int B=550;
int n, m;
vector<pii> add[N], del[N];
int pos[N], lp[N], rp[N];
ll ans[N];
int a[N];
int mx[B], mn[B], tag[B];
int h[B][N+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);
	for(int i=1, x, xx, y, yy; i<=n; ++i){
		read(x); read(xx); read(y); read(yy);
		add[x].ep(y, yy-1);
		del[xx].ep(y, yy-1);
	}
	--n;
	for(int i=1; i<=n; ++i) pos[i]=(i-1)/B+1;
	for(int i=1; i<=pos[n]; ++i) lp[i]=rp[i-1]+1, rp[i]=rp[i-1]+B;
	rp[pos[n]]=n;
	for(int i=1; i<=pos[n]; ++i) h[i][N]=rp[i]-lp[i]+1;
	for(int i=1; i<=n; ++i){
		for(auto t:add[i]){
			int l=t.fi, r=t.se;
			if(pos[l]==pos[r]){
				for(int j=l; j<=r; ++j) {
					h[pos[l]][a[j]+N]--;
					++a[j]; mx[pos[l]]=max(mx[pos[l]], a[j]+tag[pos[l]]);
					h[pos[l]][a[j]+N]++;
				}
			}
			else{
				for(int j=pos[l]+1; j<pos[r]; ++j) tag[j]++, mx[j]++, mn[j]++;
				for(int j=l; j<=rp[pos[l]]; ++j) {
					h[pos[l]][a[j]+N]--;
					++a[j]; mx[pos[l]]=max(mx[pos[l]], a[j]+tag[pos[l]]);
					h[pos[l]][a[j]+N]++;
				}
				for(int j=lp[pos[r]]; j<=r; ++j) {
					h[pos[r]][a[j]+N]--;
					++a[j]; mx[pos[r]]=max(mx[pos[r]], a[j]+tag[pos[r]]);
					h[pos[r]][a[j]+N]++;
				}
			}
		}
		for(auto t:del[i]){
			int l=t.fi, r=t.se;
			if(pos[l]==pos[r]){
				for(int j=l; j<=r; ++j) {
					h[pos[l]][a[j]+N]--;
					--a[j]; mn[pos[l]]=min(mn[pos[l]], a[j]+tag[pos[l]]);
					h[pos[l]][a[j]+N]++;
				}
			}
			else{
				for(int j=pos[l]+1; j<pos[r]; ++j) tag[j]--, mx[j]--, mn[j]--;
				for(int j=l; j<=rp[pos[l]]; ++j) {
					h[pos[l]][a[j]+N]--;
					--a[j]; mn[pos[l]]=min(mn[pos[l]], a[j]+tag[pos[l]]);
					h[pos[l]][a[j]+N]++;
				}
				for(int j=lp[pos[r]]; j<=r; ++j) {
					h[pos[r]][a[j]+N]--;
					--a[j]; mn[pos[r]]=min(mn[pos[r]], a[j]+tag[pos[r]]);
					h[pos[r]][a[j]+N]++;
				}
			}
		}
		for(int j=1; j<=pos[n]; ++j){
			int c=(mn[j]-1)/m+1;
			for(c*=m; c<=mx[j]; c+=m){
				ans[c]+=h[j][c-tag[j]+N];
			}
		}
	}
	for(int i=m; i<=n+1; i+=m) printf("%lld\n", ans[i]);
	return 0;
}
posted @ 2025-10-10 22:17  Displace  阅读(17)  评论(0)    收藏  举报