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

按随机顺序排序

The 3rd Universal Cup. Stage 40: Potyczki F Puzzle IV

link

记录一个关于 fib 的构造。

如果能把序列变为全 1,那么我们只需要最后回滚一遍就解决了此题。

只对两个数操作比较难,考虑对相邻的三个数操作。为了进一步简化,我们设前两个数为 1。

利用加法可以让第二个数变为 fib 的偶数项,我们猜测利用 fib 去构造数和二进制差不了太多。

通过打表发现,30000 内的数最多需要 14 个 fib 偶数项组成,于是把第三个数变成 1 最多 42 次操作。

于是总操作数为 \(n+n+42n=44n\approx 1,320,000\),实现时会有一些常数级别的误差。

代码
#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;
int a[N];
int f[25];
char s[2]={'-', '+'};
struct node{
	int x, op, y; 
};
vector<node> vec;
void perform(int x, int op, int y){
	assert(abs(x-y)==1);
	vec.push_back((node){x, op, y});
	if(op==0){
		a[x]-=a[y];
	}
	else{
		a[x]+=a[y];
	}
}
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]);
	f[0]=1; f[1]=1;
	for(int i=2; i<=24; ++i) f[i]=f[i-1]+f[i-2];
	a[0]=n+1; a[n+1]=n+1;
	int pos=0;
	for(int i=1; i<=n; ++i){
		if(a[i]!=1) continue;
		int x=i-1;
		if(a[i+1]<a[i-1]) x=i+1;
		while(a[x]!=1) perform(x, 0, i);
		if(x<i) pos=x;
		else pos=i;
	}
	for(int i=pos+2; i<=n; ++i){
		int cur=0;
		while(f[cur+2]<a[i]) {
			if(cur==0){
				perform(i-1, 1, i-2);
			}
			else{
				perform(i-2, 1, i-1);
				perform(i-1, 1, i-2);
			}
			cur+=2;
		}
		while(a[i]!=1){
			if(f[cur]<a[i]){
				perform(i, 0, i-1);
			}
			else{
				perform(i-1, 0, i-2);
				cur-=2;
				if(cur!=0) perform(i-2, 0, i-1);
			}
		}
		while(a[i-1]!=1||a[i-2]!=1){
			if(a[i-2]>a[i-1]) perform(i-2, 0, i-1);
			else perform(i-1, 0, i-2);
		}
	}
	for(int i=pos-1; i>=1; --i){
		int cur=0;
		while(f[cur+2]<a[i]) {
			if(cur==0){
				perform(i+1, 1, i+2);
			}
			else{
				perform(i+2, 1, i+1);
				perform(i+1, 1, i+2);
			}
			cur+=2;
		}
		while(a[i]!=1){
			if(f[cur]<a[i]){
				perform(i, 0, i+1);
			}
			else{
				perform(i+1, 0, i+2);
				cur-=2;
				if(cur!=0) perform(i+2, 0, i+1);
			}
		}
		while(a[i+1]!=1||a[i+2]!=1){
			if(a[i+2]>a[i+1]) perform(i+2, 0, i+1);
			else perform(i+1, 0, i+2);
		}
	}
	for(int i=2; i<=n; ++i) perform(i, 1, i-1);
	// for(int i=1; i<=n; ++i) cout<<a[i]<<' '; cout<<endl;
	printf("%d\n", (int)vec.size());
	for(auto t:vec){
		printf("%d %c %d\n", t.x, s[t.op], t.y);
	}
	return 0;
}

The 3rd Universal Cup. Stage 40: Potyczki G Furniture

link

部分思路参考了思路打开的提交

考虑最终放置的形态是把家具划分两个集合,按高度从左下到右上排列。

按照 \(\frac{B}{2}\) 划分大小块,大块之间不能重叠放,小块之间可以重叠。

考虑大小块之间,如果按照 \(\min{d, B-d}\) 降序排序,那么大块只能找排名再它后面的小块重叠。

我们把放置的方式改为从中间开始放,维护贴上边界放的宽度和为 \(w\) 时的方案可行性,下边界放的可以用之前的和减去 \(w\) 得到。

考虑从高到低时新的大块不能和前面的小块重叠,只能放在空位上,所以需要给后面的大块留足空间。

具体的,记 \(sum\) 为之后大块的宽度和,已经填过的所有块的宽度和为 \(pre\),一个 \(w\) 合法,当且仅当 \(w+sum\leq A\)\(pre-w+sum\leq A\)

容易发现合法的是一个区间,于是用 bitset 维护即可。

复杂度为 \(O(\frac{nA}{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;
#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, A, B, n;
struct node{
	int tp, id, h;
	bool operator <(const node &t)const{
		return h>t.h;
	}
}c[N];
int a[N], b[N];
bitset<N> f;
ll sum[N];
void solve(){
	read(A); read(B); read(n);
	ll suma=0;
	for(int i=1; i<=n; ++i) {
		read(a[i]), read(b[i]);
		c[i].tp=(b[i]>B-b[i]);
		c[i].id=i;
		if(c[i].tp) c[i].h=B-b[i];
		else c[i].h=b[i];
		suma+=a[i];
	}
	sort(c+1, c+n+1);
	sum[n+1]=0;
	for(int i=n; i>=1; --i){
		sum[i]=sum[i+1];
		if(c[i].tp) sum[i]+=a[c[i].id];
	}
	if(sum[1]>A){
		printf("NIE\n");
		return ;
	}
	f.reset();
	f[0]=1;
	int pre=0;
	for(int i=1; i<=n; ++i){
		int w=a[c[i].id];
		pre+=w;
		f|=f<<w;
		if(i==n||c[i].h!=c[i+1].h){
			int l=max(0ll, pre-(A-sum[i+1])), r=min(N-1ll, A-sum[i+1]);
			if(l>r){
				printf("NIE\n");
				return ;
			}
			f>>=l; f<<=l;
			f<<=(N-r-1); f>>=(N-r-1);
		}
		if(!f.any()){
			printf("NIE\n");
			return ;
		}
	}
	printf("TAK\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;
}

CCPC Final 2022 I Not Another Range Query Problem

link

令题目的操作为每次删连读段的第一个字符。考虑区间操作 \([l, r]\) 和前缀操作 \([1, r]\) 的区别,除了开头,后面部分是完全一致的,区间操作会额外从开头删一些。

考虑多删的那些是怎么来的,我们把操作变成删连续段的最后一个字符,不难发现应该删除的字符就落在 \([1, l-1]\) 的剩余字符中。

于是只需要模拟出每个位置的删除时间,做两个数点问题即可。

复杂度 \(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=1e6+5;
int T, n, m;
char s[N];
int tim[N];
vector<pii> add[N], del[N];
int ans[N];
int lp[N], rp[N];
int tr[N];
void mdf(int x){
	for(; x; x-=(x&-x)) ++tr[x];
}
int get(int x){
	int ret=0;
	for(; x<=n; x+=(x&-x)) ret+=tr[x];
	return ret;
}
void solve(){
	read(n); read(m);
	scanf("%s", s+1);
	for(int i=1, l, r, k; i<=m; ++i){
		read(l); read(r); read(k);
		add[r].ep(k, i);
		del[n-l+1].ep(k, i);
	}
	vector<int> vec;
	for(int i=1; i<=n; ++i) {
		lp[i]=i-1, rp[i]=i+1;
		if(s[i]!=s[i-1]) vec.ep(i);
	}
	int cur=0;
	while(!vec.empty()){
		++cur;
		vector<int> vec2;
		for(auto t:vec){
			vec2.ep(rp[t]);
			rp[lp[t]]=rp[t];
			lp[rp[t]]=lp[t];
			tim[t]=cur;
		}
		vec.clear();
		for(auto t:vec2){
			if(t<1||t>n) continue;
			if(tim[t]) continue;
			if(s[t]!=s[lp[t]]) vec.ep(t);
		}
	}
	for(int i=1; i<=n; ++i){
		mdf(tim[i]);
		for(auto t:add[i]){
			ans[t.se]+=get(t.fi+1);
		}
	}
	reverse(s+1, s+n+1);
	for(int i=1; i<=n; ++i) {
		lp[i]=i-1, rp[i]=i+1;
		tr[i]=0; tim[i]=0;
		if(s[i]!=s[i-1]) vec.ep(i);
	}
	cur=0;
	while(!vec.empty()){
		++cur;
		vector<int> vec2;
		for(auto t:vec){
			vec2.ep(rp[t]);
			rp[lp[t]]=rp[t];
			lp[rp[t]]=lp[t];
			tim[t]=cur;
		}
		vec.clear();
		for(auto t:vec2){
			if(t<1||t>n) continue;
			if(tim[t]) continue;
			if(s[t]!=s[lp[t]]) vec.ep(t);
		}
	}
	for(int i=n; i>=1; --i){
		for(auto t:del[i]){
			ans[t.se]-=get(t.fi+1);
		}
		mdf(tim[i]);
	}
	for(int i=1; i<=m; ++i){
		printf("%d\n", max(0, ans[i]));
	}
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	// read(T);
	T=1;
	while(T--){
		solve();
	}
	return 0;
}

The 2021 ICPC Asia East Continent Final Contest F Vacation

link

你的细节很多,但是你的时限弥补了这一点。

考虑每 \(C\) 个划分一个块,那么一个合法的最大子段和要么在块内,要么跨两个相邻的块。

第一类的维护是简单的。

第二类考虑前缀和,对于相邻的两段,一个合法的最大子段和需要满足 \(l> r-C\),放在前缀和上就是 \(l\geq r-C\)

在线段树上用半在线的方式就能维护这个东西,再用一棵线段树维护相邻整块的答案。

剩下的就是散块对相邻的整块。

可能有好处理的方法,我的方法比较笨:把其他的都赋值为负无穷,再在半在线线段树上询问。

复杂度是 \(O(n\log n)\),常数很大。

代码
#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=4e5+5;
int n, m, c;
int a[N];
ll sum[N];
struct T1{
	struct info{
		ll ans, ls, rs, sum;
		void set(int x){
			sum=x;
			ans=max(0, x);
			ls=rs=ans;
		}
	}tr[N<<2];
	inline friend info operator +(info x, info y){
		info z;
		z.ans=max(x.ans, y.ans);
		z.sum=x.sum+y.sum;
		z.ls=max(x.ls, x.sum+y.ls);
		z.rs=max(y.rs, y.sum+x.rs);
		z.ans=max(z.ans, x.rs+y.ls);
		return z;
	}
	void build(int p, int l, int r){
		if(l==r){
			tr[p].set(a[l]);
			return ;
		}
		int mid=(l+r)>>1;
		build(p<<1, l, mid);
		build(p<<1|1, mid+1, r);
		tr[p]=tr[p<<1]+tr[p<<1|1];
	}
	void mdf(int p, int l, int r, int x, int v){
		if(l==r){
			tr[p].set(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);
		tr[p]=tr[p<<1]+tr[p<<1|1];
	}
	void get(int p, int l, int r, int L, int R, info &ret){
		if(L>R) return ;
		if(L<=l&&r<=R){
			ret=ret+tr[p];
			return ;
		}
		int mid=(l+r)>>1;
		if(L<=mid) get(p<<1, l, mid, L, R, ret);
		if(R>mid) get(p<<1|1, mid+1, r, L, R, ret);
	}
}t1;
struct T2{
	ll a[N<<2], b[N<<2], mx[N<<2], taga[N<<2], tagb[N<<2];
	void build(int p, int l, int r){
		if(l==r){
			a[p]=sum[l]; b[p]=sum[l+c];
			mx[p]=b[p]-a[p];
			return ;
		}
		int mid=(l+r)>>1;
		build(p<<1, l, mid); build(p<<1|1, mid+1, r);
		a[p]=min(a[p<<1], a[p<<1|1]);
		b[p]=max(b[p<<1], b[p<<1|1]);
		mx[p]=max(max(mx[p<<1], mx[p<<1|1]), b[p<<1]-a[p<<1|1]);
	}
	void down(int p){
		if(taga[p]!=0){
			taga[p<<1]+=taga[p];
			a[p<<1]+=taga[p];
			mx[p<<1]-=taga[p];
			taga[p<<1|1]+=taga[p];
			a[p<<1|1]+=taga[p];
			mx[p<<1|1]-=taga[p];
			taga[p]=0;
		}
		if(tagb[p]!=0){
			tagb[p<<1]+=tagb[p];
			b[p<<1]+=tagb[p];
			mx[p<<1]+=tagb[p];
			tagb[p<<1|1]+=tagb[p];
			b[p<<1|1]+=tagb[p];
			mx[p<<1|1]+=tagb[p];
			tagb[p]=0;
		}
	}
	void mdfa(int p, int l, int r, int L, int R, ll v){
		if(L>R) return ;
		if(L<=l&&r<=R){
			taga[p]+=v;
			a[p]+=v;
			mx[p]-=v;
			return ;
		}
		down(p);
		int mid=(l+r)>>1;
		if(L<=mid) mdfa(p<<1, l, mid, L, R, v);
		if(R>mid) mdfa(p<<1|1, mid+1, r, L, R, v);
		a[p]=min(a[p<<1], a[p<<1|1]);
		b[p]=max(b[p<<1], b[p<<1|1]);
		mx[p]=max(max(mx[p<<1], mx[p<<1|1]), b[p<<1]-a[p<<1|1]);
	}
	void mdfb(int p, int l, int r, int L, int R, ll v){
		if(L>R) return ;
		if(L<=l&&r<=R){
			tagb[p]+=v;
			b[p]+=v;
			mx[p]+=v;
			return ;
		}
		down(p);
		int mid=(l+r)>>1;
		if(L<=mid) mdfb(p<<1, l, mid, L, R, v);
		if(R>mid) mdfb(p<<1|1, mid+1, r, L, R, v);
		a[p]=min(a[p<<1], a[p<<1|1]);
		b[p]=max(b[p<<1], b[p<<1|1]);
		mx[p]=max(max(mx[p<<1], mx[p<<1|1]), b[p<<1]-a[p<<1|1]);
	}
	void qry(int p, int l, int r, int L, int R, ll &pa, ll &pb, ll &pans){
		if(L>R) return ;
		if(L<=l&&r<=R){
			pans=max(max(pans, mx[p]), pb-a[p]);
			pa=min(pa, a[p]);
			pb=max(pb, b[p]);
			return ;
		}
		int mid=(l+r)>>1;
		down(p);
		if(L<=mid) qry(p<<1, l, mid, L, R, pa, pb, pans);
		if(R>mid) qry(p<<1|1, mid+1, r, L, R, pa, pb, pans);
	}
}t2;
struct T3{
	ll mx[N<<2], mx2[N<<2];
	void build(int p, int l, int r){
		if(l==r){
			T1::info tem; tem.set(0);
			t1.get(1, 1, n, (l-1)*c+1, l*c, tem);
			mx2[p]=tem.ans;
			mx[p]=0;
			ll pa=1e18, pb=-1e18;
			t2.qry(1, 1, n-c, (l-1)*c+1, l*c, pa, pb, mx[p]);
			return ;
		}
		int mid=(l+r)>>1;
		build(p<<1, l, mid); build(p<<1|1, mid+1, r);
		mx[p]=max(mx[p<<1], mx[p<<1|1]);
		mx2[p]=max(mx2[p<<1], mx2[p<<1|1]);
	}
	void mdf(int p, int l, int r, int x){
		if(l==r){
			T1::info tem; tem.set(0);
			t1.get(1, 1, n, (l-1)*c+1, l*c, tem);
			mx2[p]=tem.ans;
			mx[p]=0;
			ll pa=1e18, pb=-1e18;
			t2.qry(1, 1, n-c, (l-1)*c+1, l*c, pa, pb, mx[p]);
			return ;
		}
		int mid=(l+r)>>1;
		if(x<=mid) mdf(p<<1, l, mid, x);
		else mdf(p<<1|1, mid+1, r, x);
		mx[p]=max(mx[p<<1], mx[p<<1|1]);
		mx2[p]=max(mx2[p<<1], mx2[p<<1|1]);
	}
	ll get(int p, int l, int r, int L, int R){
		if(L>R) return 0;
		if(L<=l&&r<=R) return max(mx[p], mx2[p]);
		int mid=(l+r)>>1; ll ret=0;
		if(L<=mid) ret=get(p<<1, l, mid, L, R);
		if(R>mid) ret=max(ret, get(p<<1|1, mid+1, r, L, R));
		return ret;
	}
}t3;
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(c);
	for(int i=1; i<=n; ++i) read(a[i]), sum[i]=sum[i-1]+a[i];
	if(n==c) a[++n]=-1e9, sum[n]=sum[n-1]+a[n];
	while(n%c) a[++n]=-1e9, sum[n]=sum[n-1]+a[n];
	t1.build(1, 1, n);
	t2.build(1, 1, n-c);
	t3.build(1, 1, n/c-1);
	int tp, x, y;
	while(m--){
		read(tp); read(x); read(y);
		if(tp==1){
			t1.mdf(1, 1, n, x, y);
			t2.mdfa(1, 1, n-c, x, n-c, y-a[x]);
			t2.mdfb(1, 1, n-c, max(1, x-c), n-c, y-a[x]);
			if((x-1)/c>=1) t3.mdf(1, 1, n/c-1, (x-1)/c);
			if((x-1)/c+1<=n/c-1) t3.mdf(1, 1, n/c-1, (x-1)/c+1);
			a[x]=y;
		}
		else{
			ll ans=0;
			ll pa=1e18, pb=-1e18;
			t2.mdfa(1, 1, n-c, 1, x-1, 1e16); t2.mdfa(1, 1, n-c, y+1, n-c, 1e16);
			t2.mdfb(1, 1, n-c, 1, x-c-1, -1e16); t2.mdfb(1, 1, n-c, max(1, y-c+1), n-c, -1e16);
			t2.qry(1, 1, n-c, x, min(n-c, x+c-1), pa, pb, ans);
			pa=1e18, pb=-1e18;
			t2.qry(1, 1, n-c, max(1, y-c*2+1), y-c, pa, pb, ans);
			t2.mdfa(1, 1, n-c, 1, x-1, -1e16); t2.mdfa(1, 1, n-c, y+1, n-c, -1e16);
			t2.mdfb(1, 1, n-c, 1, x-c-1, 1e16); t2.mdfb(1, 1, n-c, max(1, y-c+1), n-c, 1e16);
			T1::info tem; tem.set(0);
			t1.get(1, 1, n, x, min(y, x+c-1), tem);
			ans=max(ans, tem.ans);
			tem.set(0);
			t1.get(1, 1, n, max(x, y-c+1), y, tem);
			ans=max(ans, tem.ans);
			if((y-1)/c>(x-1)/c+1){
				// cout<<(x-1)/c+2<<' '<<(y-1)/c-1<<endl;
				ans=max(ans, t3.get(1, 1, n/c-1, (x-1)/c+2, (y-1)/c-1));
				tem.set(0);
				t1.get(1, 1, n, ((y-1)/c-1)*c+1, ((y-1)/c)*c, tem);
				ans=max(ans, tem.ans);
			}
			printf("%lld\n", ans);
		}
	}
	return 0;
}

IOI 2025 Day 1 Souvenirs

link

我的天哪,IOI 里放暴力交互题吗。

因为每次询问必须至少买一个,我们最开始只知道 \(p_0\),为了操作合法第一次只能问 \(p_0-1\)

此时肯定把第一个物品买了,可能会附加一些小物品和零钱。

如果只买了第一个,那么问题规模减 1。

否则我们可以知道最小的物品的价格要小于总钱数除以物品数量,可以递归。

我们的目的是求出每个纪念品的价格,最后把每个物品买够数量。

写一个暴力递归上去,发现直接过了。

我们来仔细思考一下为什么对。

考虑第二类情况会让返回的物品集合的最小下标至少增加 1,这意味这一个后面的物品最多被每个前面的物品一起买一次,所以购买数不会超过 \(i\) 的限制。

时间复杂度不是本题重点。

代码
#include "souvenirs.h"
#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);
}
struct node{
	vector<int> vec;
	ll sum;
};
int cnt[100];
ll p[100];
void buy_souvenirs(int N, long long P0){
	vector<node> bin;
	{
		vector<int> tem; tem.push_back(0);
		bin.push_back((node){tem, P0});
	}
	while(!bin.empty()){
		node cur=bin.back(); bin.pop_back();
		while(!cur.vec.empty()){
			int it=-1;
			for(int t:cur.vec){
				if(p[t]!=0){
					it=t; break;
				}
			}
			if(it!=-1) {
				cur.sum-=p[it];
				vector<int> tem;
				for(int t:cur.vec) if(t!=it) tem.push_back(t);
				cur.vec.swap(tem);
			}
			else{
				break;
			}
		}
		if(cur.vec.size()==1){
			int x=cur.vec[0];
			p[x]=cur.sum;
			if(x!=N-1&&p[x+1]==0){
				pair<vector<int>, ll> tem=transaction(p[x]-1);
				node ttem;
				ttem.vec=tem.fi;
				ttem.sum=p[x]-1-tem.se;
				bin.push_back(ttem);
				for(int t:tem.fi) ++cnt[t];
			}
			continue;
		}
		bin.push_back(cur);
		ll ask=cur.sum/cur.vec.size();
		pair<vector<int>, ll> tem=transaction(ask);
		node ttem;
		ttem.vec=tem.fi;
		ttem.sum=ask-tem.se;
		bin.push_back(ttem);
		for(int t:tem.fi) ++cnt[t];
	}
	// for(int i=1; i<N; ++i) cout<<cnt[i]<<' ';
	// cout<<endl;
	// for(int i=1; i<N; ++i) cout<<p[i]<<' ';
	// cout<<endl;
	for(int i=1; i<N; ++i){
		for(int j=cnt[i]+1; j<=i; ++j){
			transaction(p[i]);
		}
	}
}

The 2021 ICPC Asia East Continent Final Contest M Prof. Pang and Ants

link

答案具有单调性,考虑二分答案,设当且要验证的答案是 \(T\)

先考虑最优情况下怎么安排蚂蚁进洞和出洞。我们从 \(\frac{T}{2}\) 截成两部分。

第一个观察是对称性,即可以把每个蚂蚁的进洞和出洞时刻翻转。

考虑只有一个洞的特例,我们发现是先让所有蚂蚁出洞,再进洞,这已经是其中一个最优方案。这启发我们尝试证明存在时间截断点 \(\frac{T}{2}\),使得前一半进洞和后一半出洞。

先假设只有一只蚂蚁不符合这个安排,因为前一半里面少了两个向后半的匹配,所以这只蚂蚁一定可以等待到超过 \(\frac{T}{2}\) 而不超过 \(T\) 时刻进那个洞。

再假设有 \(k\) 只不符合,这时上下会各产生 \(k\) 个空缺,我们一定存在方案让一只蚂蚁等待或者提前出洞,归纳到子问题。

有了这个限制,问题变为了二分图最大匹配,左右部的点都形成了若干区间。

如果 \(T\) 是偶数,上述判定方法是没有问题的。否则会在中间时刻产生问题,因为此时可以同时存在进洞和出洞。

我们可以证明,在中间时刻产生的进洞和出洞数量相等。同时因为洞就那么多,所以两者都不超过 \(\frac{n}{2}\)

这个证明我们可以假想把中间时刻拆解成一段连续的时刻,套用上面的证明。

于是我们可以设置为原图中每个点的流量为 2, 中间时刻的点流量为 1,做最大匹配即可。

朴素的实现复杂度为 \(O(n\log n\log V)\),观察发现是类似区间平移的过程,可以做到 \(O(n(\log n+\log V))\)

下面的代码是朴素实现。

代码
#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;
int T, n; ll m;
ll a[N];
struct node{
	ll x; int tp, cnt;
	inline bool operator <(const node &t)const{
		return x<t.x;
	}
};
bool check(ll lim){
	ll h=lim/2;
	vector<node> vec;
	for(int i=1; i<=n; ++i){
		vec.push_back((node){a[i]+1, 0, 2});
		vec.push_back((node){a[i]+h+1, 0, -1});
		vec.push_back((node){a[i]+lim-h+1, 0, -1});
		vec.push_back((node){h-a[i], 1, 1});
		vec.push_back((node){lim-h-a[i], 1, 1});
		vec.push_back((node){lim-a[i], 1, -2});
	}
	sort(vec.begin(), vec.end());
	__int128 sum=0, lst=0, lsum=0, rsum=0;
	for(int i=0; i+1<(int)vec.size(); ++i){
		if(vec[i].tp==0){
			lsum+=vec[i].cnt;
		}
		else{
			rsum+=vec[i].cnt;
		}
		if(vec[i].x==vec[i+1].x) continue;
		if(lsum>=rsum){
			sum+=rsum*(vec[i+1].x-vec[i].x);
			lst+=(lsum-rsum)*(vec[i+1].x-vec[i].x);
		}
		else{
			lst+=lsum*(vec[i+1].x-vec[i].x);
			__int128 cur=min(rsum*(vec[i+1].x-vec[i].x), lst);
			sum+=cur; lst-=cur;
		}
		if(sum>=m) return true;
	}
	return false;
}
void solve(){
	read(n); read(m); m*=2;
	for(int i=1; i<=n; ++i) read(a[i]);
	ll L=1, R=1e15, mid, ret=0;
	while(L<=R){
		mid=(L+R)/2;
		if(check(mid)){
			ret=mid; R=mid-1;
		}
		else{
			L=mid+1;
		}
	}
	printf("%lld\n", ret);
}
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 3rd Universal Cup Semifinals L Bot Friends 2

link

非常好图题。

鉴于暂时没有官方题解,本文的部分结论可能存在问题,若能指正将感激不尽。

第一步转化

我们建立一张有向完全图,定义一条有向边 \((u, v)\) 的权值为两点在原图上的最短路加上 \(a_v\),其意义是 bot \(u\)\(v\) 节点参与合并。

于是我们只需要求这个图的最小定根内向生成树,需要做 \(n\) 遍 DMST。

这显然是不能接受的,我们需要分析性质。

第二步转化

考虑能不能变成无向图 MST,发现居然是能的。

具体的,我们建立一张无向完全图,定义一条无向边 \((u,v)\) 的权值为两点在原图上的最短路加上 \(a_u+a_v\),求出来 MST 后,我们需要减去所有的 \(a_i\),把每个 bot 的来源点的 \(a\) 去掉。

这个时候,我们需要定根,不难发现直接定在 \(a\) 最小的点即可。

这个做法的正确性在于任意一颗生成树都需要经过上面的删来源和定根的操作,所以偏移值是相同的。

于是只需要跑 MST,简化了很多,但还是不足以通过。

划分等效块

不难通过最短路算法求出每个点的最优合并点,这里最优合并点定义为最短路加上这个点的 \(a\) 最小。

发现整个图会划分为若干个连通块,每个连通块内的最优合并点相同。可以用反证法证明,即考虑存在一些点把某个连通块切开,与最短路的前提矛盾。

我们不难证明对于最优合并点不是自己的点,在上述的无向图中这个边一定是优先合并的,换句话说,我们可以把(最优合并点不是自己)的点先并到最优合并点上。

相当于我们先求了一些连通块的生成树,这些生成树都是菊花图,再加一些边把这片菊花森林连通。

那么怎么找这些菊花根之间的距离关系呢?

最后一步

我一开始想的是用 Boruvka 做,但没实现出来。

回顾图的形态,所有最优合并点相同的点形成一个连通块,也就是感性上只有相邻连通块之间的边会参与构成 MST。

证明大概考虑反证,即如果 MST 中某条边是某个连通块的根跨过若干连通块,连向一个和这个连通块不相邻的连通块的根,那么可以把这条路径拆开,拆散到途径的连通块内。

严谨证明等官方题解吧,作者实力有限 qwq。

于是只需要加入相邻连通块的根之间的边,跑 MST 即可。

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

代码
#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;
int T, n, m;
ll a[N], dis[N]; int fr[N];
vector<pii> e[N];
bool v[N];
struct node{
	int x, y; ll z;
};
vector<node> vec;
int fa[N];
int get(int x){
	if(x==fa[x]) return x;
	return fa[x]=get(fa[x]);
}
bool merge(int x, int y){
	x=get(x); y=get(y);
	if(x==y) return false;
	fa[x]=y; return true;
}
void solve(){
	read(n); read(m);
	priority_queue<pair<ll, int> > pq;
	for(int i=1; i<=n; ++i) {
		read(dis[i]); fr[i]=i; fa[i]=i; v[i]=0;
		a[i]=dis[i];
		e[i].clear();
		pq.push(mapa(-dis[i], i));
	}
	for(int i=1, x, y, z; i<=m; ++i){
		read(x); read(y); read(z);
		e[x].ep(y, z); e[y].ep(x, z);
	}
	while(!pq.empty()){
		int x=pq.top().se; pq.pop();
		if(v[x]) continue;
		v[x]=1;
		for(auto edg:e[x]){
			int y=edg.fi, z=edg.se;
			if(dis[y]>dis[x]+z){
				fr[y]=fr[x];
				dis[y]=dis[x]+z;
				pq.push(mapa(-dis[y], y));
			}
		}
	}
	ll ans=0;
	for(int i=1; i<=n; ++i){
		if(fr[i]!=i){
			ans+=dis[i]+a[i];
			assert(merge(i, fr[i]));
		}
	}
	vec.clear();
	for(int x=1; x<=n; ++x){
		for(auto edg:e[x]) {
			int y=edg.fi;
			if(x>y) continue;
			if(fr[x]!=fr[y]){
				vec.ep((node){fr[x], fr[y], dis[x]+dis[y]+edg.se});
			}
		}
	}
	sort(vec.begin(), vec.end(), [&](node x, node y){return x.z<y.z;});
	for(auto t:vec){
		if(merge(t.x, t.y)){
			ans+=t.z;
		}
	}
	a[0]=a[1];
	for(int i=1; i<=n; ++i){
		ans-=a[i];
		a[0]=min(a[0], a[i]);
	}
	ans+=a[0];
	printf("%lld\n", ans);
}
int main(){
	// freopen("D:\\nya\\acm\\B\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\B\\test.out","w",stdout);
	read(T);
	while(T--){
		solve();
	}
	return 0;
}

The 4th Universal Cup. Stage 1: Grand Prix of Korolyov G Cyclic Topsort

link

非常感谢 hungry123 提供的帮助。

题目转化为求一个起点使得拓扑序后继点数量最多。

容易发现按某个起点 \(x\) 跑完后,所有 \(x\) 的后继作为起点的答案不会优于 \(x\)

于是随机化搜索顺序即可。

考虑时间复杂度期望证明,以下证明思路来源于 hungry123

将这个起点定为其所有拓扑后继点的父亲,整个图可以抽出来一棵树。

于是复杂度期望为对 (每个点的子树大小除以这个点的深度) 求和。

摊到每个点上,每个点的期望贡献为调和级数。

于是复杂度期望为 \(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;
int T, n, m;
vector<int> e[N];
int deg[N];
bool del[N], vis[N];
int p[N];
mt19937 rnd(time(0));
int q[N], hh, tt;
int calc(int st){
    q[hh=tt=1]=st;
    int ret=0;
    while(hh<=tt){
        int x=q[hh++];
        vis[x]=1;
        ++ret;
        for(auto y:e[x]){
            if(!(--deg[y])&&!del[y]&&y!=st){
                q[++tt]=y;
            }
        }
    }
    for(int i=1; i<=tt; ++i) {
        for(auto y:e[q[i]]) if(!del[y]) ++deg[y];
    }
    return ret;
}
void solve(){
    read(n); read(m);
    for(int i=1; i<=n; ++i) p[i]=i;
    shuffle(p+1, p+n+1, rnd);
    for(int i=1, x, y; i<=m; ++i){
        read(x); read(y);
        e[x].ep(y); ++deg[y];
    }
    int dlt=0;
    queue<int> que;
    for(int i=1; i<=n; ++i) if(!deg[i]){
        que.push(i);
    }
    while(!que.empty()){
        int x=que.front(); que.pop();
        ++dlt; del[x]=1;
        for(auto y:e[x]){
            if(!(--deg[y])){
                que.push(y);
            }
        }
    }
    int ans=0;
    for(int i=1; i<=n; ++i) if(!del[p[i]]&&!vis[p[i]]){
        ans=max(ans, calc(p[i]));
    }
    printf("%d\n", ans+dlt);
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	solve();
	return 0;
}

QOJ6273 马克思么克斯

link

记录一个 trick。

将区间 mex 列在平面上,可以划分为 \(O(n)\) 个矩形。

利用 ODT 和在线求区间 mex 即可求出这些矩形。

剩下的就是区间 mex,单点求值。

写的比较丑,是 $2\log $ 的,存在单 \(\log\) 的实现。

代码
#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 T, n, m;
int a[N];
int rt[N], idx, tr[N*70], ls[N*70], rs[N*70];
inline void cpy(int p, int q){
	tr[p]=tr[q]; ls[p]=ls[q]; rs[p]=rs[q];
}
void add(int &p, int q, int l, int r, int x, int v){
	p=++idx;
	cpy(p, q);
	if(l==r){
		tr[p]=v;
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid) add(ls[p], ls[q], l, mid, x, v);
	else add(rs[p], rs[q], mid+1, r, x, v);
	tr[p]=min(tr[ls[p]], tr[rs[p]]);
}
int fnd(int p, int l, int r, int lim){
	if(l==r){
		if(tr[p]>=lim) return l+1;
		else return l;
	}
	int mid=(l+r)>>1;
	if(tr[ls[p]]<lim) return fnd(ls[p], l, mid, lim);
	return fnd(rs[p], mid+1, r, lim);
}
unordered_map<int, int> h[N];
inline int qry(int l, int r){
	if(h[r].find(l)!=h[r].end()) return h[r][l];
	return h[r][l]=fnd(rt[r], 0, n, l);
}
struct node{
	int u, d, l, v;
	inline bool operator <(const node &t)const{
		return v>t.v;
	}
};
int ans[N<<2];
void apply(int p, int l, int r, int L, int R, int v){
	// if(p==1){
	// 	cout<<"add:"<<L<<' '<<R<<' '<<v<<endl;
	// }
	if(L>R) return ;
	if(L<=l&&r<=R){
		ans[p]=max(ans[p], v);
		return ;
	}
	int mid=(l+r)>>1;
	if(L<=mid) apply(p<<1, l, mid, L, R, v);
	if(R>mid) apply(p<<1|1, mid+1, r, L, R, v);
}
int opt[N];
void dfs(int p, int l, int r){
	if(l==r){
		opt[l]=ans[p];
		return ;
	}
	int mid=(l+r)>>1;
	ans[p<<1]=max(ans[p<<1], ans[p]);
	ans[p<<1|1]=max(ans[p<<1|1], ans[p]);
	dfs(p<<1, l, mid); dfs(p<<1|1, mid+1, r);
}
int lg[N];
void solve(){
	read(n);
	for(int i=1; i<=n; ++i) read(a[i]);
	for(int i=1; i<=n; ++i){
		add(rt[i], rt[i-1], 0, n, a[i], i);
	}
	for(int i=2; i<=n; ++i) lg[i]=lg[i>>1]+1;
	set<node> st;
	for(int i=1; i<=n; ++i){
		node cur=(node){i, i, i, (a[i]==0)?1:0};
		set<node>::iterator it=st.lower_bound((node){0, 0, 0, a[i]});
		if(it!=st.end()&&(*it).v==a[i]){
			vector<node> bin;
			node w=(*it);
			for(int x=w.u; x>=w.d; ){
				int curu=x;
				int curv=qry(curu, i);
				int curd=x;
				for(int k=lg[x-w.d]; k>=0; --k){
					if(curd-(1<<k)<w.d) continue;
					if(qry(curd-(1<<k), i)==curv){
						curd-=1<<k;
					}
				}
				apply(1, 1, n, max(1, w.l-w.u+1), min(n, i-1-w.d+1), w.v);
				if(curd!=w.d){
					bin.ep((node){curu, curd, i, curv});
				}
				else{
					if(it!=st.begin()){
						set<node>::iterator it2=it;
						--it2;
						if((*it2).v==curv){
							node tem=(*it2);
							apply(1, 1, n, max(1, tem.l-tem.u+1), min(n, i-1-tem.d+1), tem.v);
							st.erase(it2);
							tem.l=i; tem.u=curu;
							bin.ep(tem);
						}
						else{
							bin.ep((node){curu, curd, i, curv});
						}
					}
					else{
						bin.ep((node){curu, curd, i, curv});
					}
				}
				x=curd-1;
			}
			st.erase(it);
			for(auto t:bin) st.insert(t);
		}
		if(!st.empty()&&(*st.rbegin()).v==cur.v){
			node w=(*st.rbegin());
			st.erase(--st.end());
			apply(1, 1, n, max(1, w.l-w.u+1), min(n, i-1-w.d+1), w.v);
			w.u=i;
			st.insert(w);
		}
		else{
			st.insert(cur);
		}
		// for(auto t:st){
		// 	cout<<t.l<<' '<<i<<' '<<t.d<<' '<<t.u<<' '<<t.v<<endl;
		// }
		// cout<<"-----------------"<<endl;
	}
	for(auto t:st){
		apply(1, 1, n, max(1, t.l-t.u+1), min(n, n-t.d+1), t.v);
	}
	dfs(1, 1, n);
	read(m);
	while(m--){
		int x; read(x);
		printf("%d\n", opt[x]);
	}
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	solve();
	return 0;
}

Petrozavodsk Winter 2022. Day 6. ICPC Camp Day 1 A Soccer Match

link

如果能求出一个大小为 \(\frac{m}{2}\) 的二分图,后续只需不断删掉度数小于等于 \(k\) 的点即可。

官方给出的做法是先将点随机染色,如果图中存在邻居中同色多于异色的点,则不断反色直到没有,此时就会得到一个至少 \(\frac{m}{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;
int T, n, m, k;
vector<int> e[N];
int col[N];
mt19937 rnd(1234);
int deg[N];
int c[2];
void solve(){
	read(n); read(m); read(k);
	for(int i=1; i<=n; ++i) {
		col[i]=rnd()&1; deg[i]=0;
		e[i].clear();
	}
	for(int i=1, x, y; i<=m; ++i){
		read(x); read(y);
		e[x].ep(y); e[y].ep(x);
	}
	while(true){
		int id=0;
		for(int i=1; i<=n; ++i){
			c[0]=c[1]=0;
			for(auto j:e[i]) {
				c[col[j]]++;
			}
			if(c[col[i]]>c[col[i]^1]){
				id=i; break;
			}
		}
		if(id==0) break;
		col[id]^=1;
	}
	set<pii> st;
	for(int i=1; i<=n; ++i){
		for(auto j:e[i]) if(col[i]!=col[j]&&i<j){
			++deg[i]; ++deg[j];
		}
	}
	for(int i=1; i<=n; ++i) st.insert(mapa(deg[i], i));
	while((*st.begin()).fi<=k){
		int id=(*st.begin()).se; st.erase(st.begin());
		for(auto x:e[id]) if(col[x]!=-1&&col[x]!=col[id]){
			st.erase(st.find(mapa(deg[x], x)));
			--deg[x];
			st.insert(mapa(deg[x], x));
		}
		col[id]=-1;
	}
	vector<int> vec[2];
	for(int i=1; i<=n; ++i) if(col[i]!=-1) vec[col[i]].ep(i);
	printf("%d ", (int)vec[0].size());
	for(auto t:vec[0]) printf("%d ", t);
	putchar('\n');
	printf("%d ", (int)vec[1].size());
	for(auto t:vec[1]) printf("%d ", t);
	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;
}

The 3rd Universal Cup. Stage 18: Southeastern Europe M Enchanted Lawns Quest

link

先二分简化问题为判定。

场上的想法是可以设计子树的 DP,显然有凸性。但这里的合并是 \((\min, \max)\) 合并,这是困难的。

场上也观察到了增加边权一定是增加到叶子上,但限于时间没想到怎么应用。

正解考虑找出最终树的中心(即最大距离最小的心)。那么就要求这个点到所有叶子的距离不超过 \(\frac{\lim}{2}\)\(lim\) 是我们二分的界。

枚举中心所在的位置,容易发现能(利用叶子最多能增加的边权)是关于(中心到边的端点的距离)的一次函数,只需要带入最大和最小可能距离。

对于所有的边,可以利用换根 DP 求出。

一个细节是 \(lim\) 为奇数的情况,这时允许一侧每个叶子多放一个,这个需要根据距离是否为 \(0\) 讨论。

代码
#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;
int T, n; ll m;
vector<pii> e[N];
int rt;
ll lim;
vector<ll> pre[N], suf[N];
vector<int> pre2[N], suf2[N];
int cnt_lf[N], mxcnt[N]; ll sum_dis[N], mx_dis[N];
int rk[N], sz[N], isleaf[N];
void dfs0(int x, int fa){
	pre[x].ep(0); suf[x].ep(0);
	pre2[x].ep(0); suf2[x].ep(0);
	for(auto [y, z]:e[x]) if(y^fa){
		dfs0(y, x);
		pre[x].ep(0); suf[x].ep(0);
		pre2[x].ep(0); suf2[x].ep(0);
		rk[y]=++sz[x];
	}
	pre[x].ep(0); suf[x].ep(0);
	pre2[x].ep(0); suf2[x].ep(0);
	if(sz[x]==0){
		isleaf[x]=1;
	}
}
void dfs(int x, int fa){
	cnt_lf[x]=0; sum_dis[x]=0; mx_dis[x]=0; mxcnt[x]=0;
	if(isleaf[x]) {
		cnt_lf[x]=1;
		mxcnt[x]=1;
		return ;
	}
	for(auto [y, z]:e[x]) if(y^fa){
		dfs(y, x);
		cnt_lf[x]+=cnt_lf[y];
		sum_dis[x]+=sum_dis[y]+(ll)z*cnt_lf[y];
		mx_dis[x]=max(mx_dis[x], mx_dis[y]+z);
		pre[x][rk[y]]=suf[x][rk[y]]=mx_dis[y]+z;
		mxcnt[x]=max(mxcnt[x], cnt_lf[y]);
		pre2[x][rk[y]]=suf2[x][rk[y]]=cnt_lf[y];
	}
	for(int i=1; i<=sz[x]; ++i) pre[x][i]=max(pre[x][i], pre[x][i-1]);
	for(int i=sz[x]; i>=1; --i) suf[x][i]=max(suf[x][i], suf[x][i+1]);
	for(int i=1; i<=sz[x]; ++i) pre2[x][i]=max(pre2[x][i], pre2[x][i-1]);
	for(int i=sz[x]; i>=1; --i) suf2[x][i]=max(suf2[x][i], suf2[x][i+1]);
}
ll mx;
void dfs2(int x, int fa, int cntf, ll sumf, ll mxf, int mxcntf){
	for(auto [y, z]:e[x]) if(y^fa){
		int cnta=cntf+cnt_lf[x]-cnt_lf[y];
		ll suma=sumf+sum_dis[x]-sum_dis[y]-(ll)z*cnt_lf[y];
		ll mxa=max(mxf, max(pre[x][rk[y]-1], suf[x][rk[y]+1]));
		int cnt2a=max(mxcntf, max(pre2[x][rk[y]-1], suf2[x][rk[y]+1]));
		int cntb=cnt_lf[y];
		ll sumb=sum_dis[y];
		ll mxb=mx_dis[y];
		int cnt2b=mxcnt[y];
		if(mxb+mxa+z<=lim){
			ll mnd=max(0ll, z+mxb-lim/2);
			ll mxd=min((ll)z, lim/2-mxa);
			if(mnd<=mxd){
				ll tem=lim/2*(cnta+cntb)-suma-sumb-(ll)z*cntb+(ll)mxd*(cntb-cnta);
				if(lim&1){
					if(mxd==0) tem+=max(cnt2a, cntb);
					else if(mxd==z) tem+=max(cnta, cnt2b);
					else tem+=max(cnta, cntb);
				}
				mx=max(mx, tem);	
				tem=lim/2*(cnta+cntb)-suma-sumb-(ll)z*cntb+(ll)mnd*(cntb-cnta);
				if(lim&1){
					if(mnd==0) tem+=max(cnt2a, cntb);
					else if(mnd==z) tem+=max(cnta, cnt2b);
					else tem+=max(cnta, cntb);
				}
				mx=max(mx, tem);
				ll c=mnd+1;
				if(c<=mxd){
					tem=lim/2*(cnta+cntb)-suma-sumb-(ll)z*cntb+(ll)c*(cntb-cnta);
					if(lim&1){
						if(c==0) tem+=max(cnt2a, cntb);
						else if(c==z) tem+=max(cnta, cnt2b);
						else tem+=max(cnta, cntb);
					}
					mx=max(mx, tem);
				}
				c=mxd-1;
				if(c>=mnd){
					tem=lim/2*(cnta+cntb)-suma-sumb-(ll)z*cntb+(ll)c*(cntb-cnta);
					if(lim&1){
						if(c==0) tem+=max(cnt2a, cntb);
						else if(c==z) tem+=max(cnta, cnt2b);
						else tem+=max(cnta, cntb);
					}
					mx=max(mx, tem);
				}
			}
			if(lim&1){
				ll c=mnd-1, tem;
				if(c>=0&&c<=mxd){
					tem=lim/2*(cnta+cntb)-suma-sumb-(ll)z*cntb+(ll)c*(cntb-cnta);
					tem+=cntb;
					mx=max(mx, tem);
				}
				c=mxd+1;
				if(c<=z&&c>=mnd){
					tem=lim/2*(cnta+cntb)-suma-sumb-(ll)z*cntb+(ll)c*(cntb-cnta);
					tem+=cnta;
					mx=max(mx, tem);
				}
			}
		}
		if(!isleaf[y]){
			int cnt_nxt=cntf+cnt_lf[x]-cnt_lf[y];
			ll sum_nxt=sumf+sum_dis[x]-sum_dis[y]-(ll)z*cnt_lf[y]+(ll)z*cnt_nxt;
			ll mx_nxt=max(mxf, max(pre[x][rk[y]-1], suf[x][rk[y]+1]))+z;
			dfs2(y, x, cnt_nxt, sum_nxt, mx_nxt, cnt_nxt);
		}
	}
}
bool check(){
	mx=0;
	dfs2(rt, 0, 0, 0, 0, 0);
	return mx>=m;
}
void solve(){
	read(n); read(m);
	for(int i=1, x, y, z; i<n; ++i){
		read(x); read(y); read(z);
		e[x].ep(y, z); e[y].ep(x, z);
	}
	if(n==2){
		printf("%lld\n", m+e[1][0].se);
		return ;
	}
	for(int i=1; i<=n; ++i) if(e[i].size()>e[rt].size()) rt=i;
	dfs0(rt, 0);
	dfs(rt, 0);
	ll L=1, R=1e13, mid, ret=0;
	while(L<=R){
		mid=(L+R)>>1;
		lim=mid;
		if(check()){
			ret=mid; R=mid-1;
		}
		else{
			L=mid+1;
		}
	}
	printf("%lld\n", ret);
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	solve();
	return 0;
}

The 2nd Universal Cup. Stage 17: Jinan F Say Hello to the Future

link

单纯求方案数是非常简单半在线卷积问题,难点在于修改。

如果利用 cdq 做这个半在线卷积,我们需要处理左右各自到分治中心的最大值。

这个值只有当区间最大值唯一时才会在最大值变为 1 时发生变化,所以每个分治区间只会贡献 \(O(区间长度)\) 次变化。

进一步可以发现发生变化后只影响了一个维度的偏序,于是可以用带修数据结构处理二维偏序,树状数组即可。

复杂度为 \(O(n\log^2 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, mod=998244353;
inline void plu(int &x, int y){
	x+=y; 
	if(x>=mod) x-=mod;
}
inline void sub(int &x, int y){
	x-=y;
	if(x<0) x+=mod;
}
int n;
int a[N];
int dp[N], preans[N], sufans[N], ans[N];
struct fenwick{
	const int eps=4e5+5;
	int tr[N];
	void add(int x, int v){
		for(x+=eps, v=(v+mod)%mod; x<N; x+=(x&-x)) tr[x]=(tr[x]+v)%mod;
	}
	int get(int x){
		int ret=0;
		for(x+=eps; x>0; x-=(x&-x)) ret=(ret+tr[x])%mod;
		return ret;
	}
}T;
int p[N], q[N], rk[N];
void calc(int l, int r){
	if(l==r){
		return ;
	}
	int mid=(l+r)>>1;
	calc(l, mid);
	for(int i=l; i<=r; ++i) rk[i]=i, p[i]=0, q[i]=0;
	for(int i=mid+1; i<=r; ++i) p[i]=max(p[i-1], a[i]);
	for(int i=mid-1; i>=l; --i) q[i]=max(q[i+1], a[i+1]);
	for(int i=l; i<=r; ++i) {
		p[i]-=i; q[i]+=i;
	}
	sort(rk+l, rk+mid+1, [&](int x, int y){return q[x]<q[y];});
	int it=l;
	for(int i=mid+1; i<=r; ++i){
		while(it<=mid&&q[rk[it]]<=q[i]) {
			T.add(-p[rk[it]], dp[rk[it]]);
			++it;
		}
		dp[i]=(dp[i]+T.get(-p[i]))%mod;
	}
	for(int i=l; i<it; ++i) T.add(-p[rk[i]], -dp[rk[i]]);
	calc(mid+1, r);
}
void calc2(int l, int r){
	if(l==r){
		return ;
	}
	int mid=(l+r)>>1;
	calc2(l, mid);
	calc2(mid+1, r);
	
	//x \in [l+1, mid]
	for(int i=l; i<=r; ++i) rk[i]=i, p[i]=0, q[i]=0;
	for(int i=mid+1; i<=r; ++i) p[i]=max(p[i-1], a[i]);
	for(int i=l; i<=r; ++i) p[i]-=i;
	sort(rk+mid+1, rk+r+1, [&](int x, int y){return p[x]<p[y];});
	int mx=0, mx2=0;
	int it=mid+1;
	for(int i=mid-1; i>=l; --i){
		while(it<=r&&p[rk[it]]<=p[i]){
			T.add(-rk[it], sufans[rk[it]+1]);
			++it;
		}
		if(a[i+1]>a[mx]){
			mx2=mx; mx=i+1;
		}
		else if(a[i+1]>a[mx2]){
			mx2=i+1;
		}
		if(a[mx]!=a[mx2]){
			sub(ans[mx], 1ll*T.get(-(a[mx]+i))*preans[i]%mod);
			plu(ans[mx], 1ll*T.get(-(a[mx2]+i))*preans[i]%mod);
		}
	}
	for(int i=mid+1; i<it; ++i) T.add(-rk[i], -sufans[rk[i]+1]);
	
	
	
	//x \in [mid+1, r]
	for(int i=l; i<=r; ++i) rk[i]=i, p[i]=0, q[i]=0;
	for(int i=mid-1; i>=l; --i) q[i]=max(q[i+1], a[i+1]);
	for(int i=l; i<=r; ++i) q[i]+=i;
	sort(rk+l, rk+mid+1, [&](int x, int y){return q[x]<q[y];});
	mx=0; mx2=0;
	it=l;
	for(int i=mid+1; i<=r; ++i){
		while(it<=mid&&q[rk[it]]<=q[i]){
			T.add(rk[it], preans[rk[it]]);
			++it;
		}
		if(a[i]>a[mx]){
			mx2=mx; mx=i;
		}
		else if(a[i]>a[mx2]){
			mx2=i;
		}
		if(a[mx]!=a[mx2]){
			sub(ans[mx], 1ll*T.get(-(a[mx]-i))*sufans[i+1]%mod);
			plu(ans[mx], 1ll*T.get(-(a[mx2]-i))*sufans[i+1]%mod);
		}
	}
	for(int i=l; i<it; ++i) T.add(rk[i], -preans[rk[i]]);
	
}
void solve(){
	a[0]=1; a[n+1]=1;
    read(n);
	for(int i=1; i<=n; ++i) read(a[i]);
	dp[0]=1;
	calc(0, n);
	for(int i=0; i<=n; ++i) preans[i]=dp[i], dp[i]=0;
	dp[0]=1;
	reverse(a+1, a+n+1);
	calc(0, n);
	for(int i=1; i<=n+1; ++i) sufans[i]=dp[n+1-i], dp[n+1-i]=0;
	reverse(a+1, a+n+1);
	for(int i=1; i<=n; ++i) ans[i]=preans[n];
	calc2(0, n);
	for(int i=1; i<=n; ++i) printf("%d ", ans[i]);
}
int main(){
	// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
	solve();
	return 0;
}
posted @ 2025-10-10 22:47  Displace  阅读(66)  评论(0)    收藏  举报