[练习记录]优化基础技巧

二分系列

对于凸函数的二分

略。

分数规划

[USACO18OPEN]Talent Show G

这类带限制的分数规划直接按其限制\(dp\)即可。

[USACO18OPEN]Talent Show G
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
#define N 255
#define M 1005

int n;
int w[N],t[N];
int W;

struct P{
	int wi;
	double key;
}e[N];

bool operator < (P a,P b){
	return a.key > b.key;
}

double f[N][M];

inline bool check(double mid){
	for(int i = 1;i <= n;++i)
	e[i].wi = w[i],e[i].key = 1.0 * t[i] - mid * w[i];
	std::sort(e + 1,e + n + 1);
	int noww = 0;
	double nowt = 0;
	int k = 0;
	for(int i = 1;i <= n;++i)
	if(e[i].key > 0){
		noww += e[i].wi;
		nowt += e[i].key;
	}else{
		k = i;
		break;
	}
//	std::cout<<mid<<std::endl;
//	std::cout<<noww<<" "<<nowt<<std::endl;
	if(noww >= W)
	return 1;
	std::memset(f,-0x7f,sizeof(f));
	f[k - 1][0] = 0;
	for(int i = k;i <= n;++i)
	for(int j = 1;j <= W;++j){
		f[i][j] = f[i - 1][j];
		f[i][j] = std::max(f[i][j],f[i - 1][std::max(0,j - e[i].wi)] + e[i].key);
	}
	for(int j = W - 1;j >= 1;--j)
	f[n][j] = std::max(f[n][j],f[n][j + 1]);
//	std::cout<<mid<<std::endl;
//	std::cout<<noww<<" "<<nowt<<" "<<std::endl;
	return (f[n][W - noww] + nowt) >= 0;
}

int main(){
	scanf("%d%d",&n,&W);
	for(int i = 1;i <= n;++i)
	scanf("%d%d",&w[i],&t[i]);
	double l = 0.0,r = 1000.0;
	double ans = 0;
	#define mid ((l + r) * .5)
	for(int i = 1;i <= 500;++i){
	if(check(mid))
	ans = mid,l = mid;
	else
	r = mid;
	}
//	std::cout<<check(1.06)<<std::endl;
	std::cout<<(int)(1000 * ans)<<std::endl;
}
[JSOI2016]最佳团体

树上\(dp\)处理检查。

[JSOI2016]最佳团体
#include<algorithm>
#include<cstdio>
#define FIO "team"
using namespace std;
int n,k,tot,s[2510],p[2510],r[2510],head[2510],nxt[2510],to[2510],size[2510];
double L,R=3.0,mid,eps=1e-5,f[2510][2510],F[2510];
void add(int x)
{
    nxt[++tot]=head[r[x]],head[r[x]]=tot,to[tot]=x;
}
void dfs(int x)
{
    size[x]=1,f[x][1]=p[x]-s[x]*mid;
    for(int i=head[x];i;i=nxt[i]){
        dfs(to[i]);
        for(int j=1;j<=size[x]+size[to[i]];j++) F[j]=-1e9;
        for(int j=1;j<=size[x];j++) for(int k=0;k<=size[to[i]];k++) F[j+k]=max(F[j+k],f[x][j]+f[to[i]][k]);
        for(int j=1;j<=size[x]+size[to[i]];j++) f[x][j]=F[j];
        size[x]+=size[to[i]];
    }
}
int pd()
{
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) f[i][j]=-1e9;
    dfs(0);
    return f[0][k+1]<=0;
}
int main()
{
	//freopen(FIO".in","r",stdin);
	//freopen(FIO".out","w",stdout);
    scanf("%d%d",&k,&n);
    for(int i=1;i<=n;i++) scanf("%d%d%d",&s[i],&p[i],&r[i]),add(i);
    while(L+eps<R){
        mid=(L+R)/2.0;
        if(pd()) R=mid;
        else L=mid+eps;
    }
    printf("%.3lf",L);
}
[SDOI2017]新生舞会

发现我们二分之后。
变成了选取\(n\)对不交集合,权值最大。
费用流经典问题,要注意的是实数二分不用跑太多次,不然会喜提和暴力哥一样的成绩。

[SDOI2017]新生舞会
#include<bits/stdc++.h>
#define ll long long 
#define N 210

int a[N][N];
int b[N][N];

int n;

struct P{
	int to,next,v;
	double c;
}e[N * N * 2];

int s,t;

int cnt = 1,head[N << 1];

inline void add(int x,int y,int v,double c){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	e[cnt].v = v;
	e[cnt].c = c;
	head[x] = cnt;
}

bool vis[N];
double minc[N];
int minv[N],pre[N];

std::queue<int>QWQ;

inline bool spfa(){
	for(int i = 1;i <= (n << 1) + 2;++i)
	minc[i] = minv[i] = 0x3f3f3f3f,vis[i] = 0,pre[i] = 0;
	QWQ.push(s);
	vis[s] = 1;
	minc[s] = 0;
	while(!QWQ.empty()){
		int u = QWQ.front();
//		std::cout<<u<<" "<<pre[u]<<" "<<e[pre[u] ^ 1].to<<std::endl;
		QWQ.pop();
		vis[u] = 0;
		for(int i = head[u];i;i = e[i].next){
			ll v = e[i].to;
//			std::cout<<"->"<<v<<std::endl;
			if(e[i].v && minc[v] > minc[u] + e[i].c){
				minv[v] = std::min(minv[u],e[i].v);
				minc[v] = minc[u] + e[i].c;
				pre[v] = i;
				if(!vis[v])
				vis[v] = 1,QWQ.push(v);
			}
		}
	}
	return minc[t] != 0x3f3f3f3f;
}

int ansv;
double ansc;

inline void EK(){
	ansv = 0;
	ansc = 0;
	while(spfa()){
		ansv += minv[t];
		ansc += minc[t] * minv[t];
		int now = t;
		while(now != s){
			e[pre[now]].v -= minv[t];
			e[pre[now] ^ 1].v += minv[t];
			now = e[pre[now] ^ 1].to;
		}
	}
}

inline bool check(double mid){
	cnt = 1;
	for(int i = 1;i <= (n << 1) + 10;++i)
	head[i] = 0;
	for(int i = 1;i <= n;++i)
	for(int j = 1;j <= n;++j){
		double k = a[i][j] * 1.0 - b[i][j] * mid;
		add(i,j + n,1,-k);
		add(j + n,i,0,k);
	}
	for(int i = 1;i <= n;++i)
	add(s,i,1,0),add(i,s,0,0);
	for(int i = n + 1;i <= n << 1;++i)
	add(i,t,1,0),add(t,i,0,0);
	EK();
	return ansc <= 0;
}

int main(){  
	scanf("%d",&n);
	s = n * 2 + 1;
	t = n * 2 + 2;	
	for(int i = 1;i <= n;++i)
	for(int j = 1;j <= n;++j)
	scanf("%d",&a[i][j]);
	for(int i = 1;i <= n;++i)
	for(int j = 1;j <= n;++j)
	scanf("%d",&b[i][j]);
	double l = 0.0,r = 1e6;
	double ans = 0;
	#define mid ((l + r) * .5)
	for(int i = 1;i <= 80;++i){
//		std::cout<<l<<" "<<r<<std::endl;		
		if(check(mid))
		ans = mid,l = mid;
		else
		r = mid; 
	}
	printf("%.6f",ans);
}

凸优化

CF739E Gosha is hunting

被卡精度了./fn

考虑我们先做一个硬的三维\(f_{i,j,k}\)其转移显然。

此时我们可以发现固定后两维的一维,其对另一维是下凸函数,然后我们优化一维为凸优化,复杂度\(O(n^2log)\)

CF739E Gosha is hunting
#include<bits/stdc++.h>
#define ll long long
#define N 2005

int n,a,b;

double p[N],q[N];

struct P{double exp;int cnt;P(){exp = cnt = 0;}};//期望,使用第一种求的数量。 

P f[N][N];

inline P cmax(P a,P b){
	return a.exp < b.exp ? b : a; 
} 

inline void check(double mid){
	for(int i = 1;i <= n;++i)
	for(int j = 0;j <= std::min(i,b);++j){
		f[i][j].exp = 0;
		f[i][j].cnt = 0;
		f[i][j] = f[i - 1][j];
		P to;//转移 	
		if(j > 0){
		to.exp = f[i - 1][j - 1].exp + q[i],to.cnt = f[i - 1][j - 1].cnt;
//		std::cout<<"case2 "<<to.exp<<" "<<to.cnt<<std::endl; 		
		f[i][j] = cmax(f[i][j],to);		
		to.exp = f[i - 1][j - 1].exp + q[i] + p[i] - mid - p[i] * q[i],to.cnt = f[i - 1][j - 1].cnt + 1;
//		std::cout<<"case3 "<<to.exp<<" "<<to.cnt<<std::endl; 		
		f[i][j] = cmax(f[i][j],to);			
		}		
		to.exp = f[i - 1][j].exp + p[i] - mid,to.cnt = f[i - 1][j].cnt + 1;
//		std::cout<<"case1 "<<to.exp<<" "<<to.cnt<<std::endl; 
		f[i][j] = cmax(f[i][j],to);			
//		std::cout<<i<<" "<<j<<" "<<f[i][j].exp<<" "<<f[i][j].cnt<<std::endl;			
	}
}

int main(){
	scanf("%d%d%d",&n,&a,&b);
	for(int i = 1;i <= n;++i)
	scanf("%lf",&p[i]);
	for(int i = 1;i <= n;++i)
	scanf("%lf",&q[i]);
//	for(int i = 1;i <= n;++i)
//	std::cout<<p[i]<<" "<<q[i]<<std::endl;
	double l = 0.0,r = 1;
	#define mid ((l + r) * .5)
//	check(0.375);
	double fans = 0;
	for(int i = 1;i <= 200;++i){
//		std::cout<<l<<" "<<r<<std::endl;
		check(mid);
//		std::cout<<"!"<<mid<<" "<<f[n][b].exp<<" "<<f[n][b].cnt<<" "<<f[n][b].exp + f[n][b].cnt * mid<<std::endl;
		if(f[n][b].cnt > a)
		l = mid;
		else
		fans = std::max(f[n][b].exp + f[n][b].cnt * mid,fans),r = mid;
	}
	printf("%.12lf",fans);
}
[八省联考 2018] 林克卡特树

简要题意:在树上选取\(k + 1\)条链的权值总和最大。

该类树上选链的可以使用\(dp\),我们设\(f_{u,j,0/1/2}\)\(u\)中有\(j\)条完整链,\(0\)表示\(u\)\(0\)在子树背包合并时的点中表示其没被占据,子树背包合并结束时其意义改变为经过\(u\)的那条链在内部消化,或者\(u\)没被用,\(1\)表示其有一条链向父亲连,\(2\)表示子树背包有一条经过\(u\)的完整的内部消化链。

转移显然。

先给一个\(60\)分的直接\(dp\)代码。

其为\(O(n^2k)\)

[八省联考 2018] 林克卡特树 sub1
#include<bits/stdc++.h>
#define ll long long
#define N 300005
#define K 200

int n,k;
int f[N][K][3];

struct P{
	int to,next,v;
}e[N << 1];

int head[N],cnt;

void add(ll x,ll y,ll v){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	e[cnt].v = v;
	head[x] = cnt;
}

void dfs(int u,int fa){
	f[u][0][0] = f[u][0][1] = f[u][1][2] = 0;
	for(int i = head[u];i;i = e[i].next){
		int v = e[i].to;
		if(v == fa)
		continue;
		dfs(v,u);
		for(int j = k;j;--j){
			f[u][j][1] = std::max(f[u][j][1],f[u][j][0] + f[v][0][1] + e[i].v);
			for(int l = j - 1;~l;--l){
				f[u][j][0] = std::max(f[u][j][0],f[u][l][0] + f[v][j - l][0]);
				f[u][j][1] = std::max(f[u][j][1],std::max(f[u][l][1] + f[v][j - l][0],f[u][l][0] + f[v][j - l][1] + e[i].v));
				f[u][j][2] = std::max(f[u][j][2],std::max(f[u][l][2] + f[v][j - l][0],f[u][l][1] + f[v][j - l - 1][1] + e[i].v));
			}
		}
		f[u][0][1] = std::max(f[u][0][1],f[v][0][1] + e[i].v);
	}
	for(int i = 1;i <= k;++i)
	f[u][i][0] = std::max(f[u][i][0],std::max(f[u][i - 1][1],f[u][i][2]));
}

int main(){
	scanf("%d%d",&n,&k),++k;
	for(int i = 1;i < n;++i){
		ll x,y,v;
		scanf("%lld%lld%lld",&x,&y,&v);
		add(x,y,v);
		add(y,x,v);
	}
	memset(f,~0x3f,sizeof(f));dfs(1,0);
	std::cout<<f[1][k][0]<<std::endl;
}

然后猜想其对\(k\)有凸性,凸优化一下,但是好像没人证明这个结论,\(O(nlogk)\)

分治系列

CDQ分治

平面最近点对(加强版)

考虑我们按x分治。

每次按中位数分成两块,然后递归处理。

设两边答案的最小值为\(d\),我们把\(x \in[x - d,x + d]\)的点拉出来按\(y\)排序。

我们枚举每个点,往后暴力枚举,当两点距离\(>d\)时撤销。

证明请见

平面最近点对(加强版)
#include<bits/stdc++.h>
#define N 2000005
#define ll long long

struct P{
	int x,y;
}a[N],b[N];

bool cmp1(P ai,P b){
	return ai.x < b.x;
}

bool cmp2(P ai,P b){
	return ai.y < b.y;
}

const long long inf=5e18;
long long sqr(int x){return (long long)x*x;}
long long dis(int l,int r){return sqr(a[l].x-a[r].x)+sqr(a[l].y-a[r].y);}
ll dd(P ai,P b){return sqr(ai.x - b.x) + sqr(ai.y - b.y);}

inline ll solve(int l,int r){
	if(l >= r)return inf;
	if(l + 1 == r)return dis(l,r);
	#define mid ((l + r) >> 1)
	int cnt = 0;
	int t = a[mid].x;
	ll d = std::min(solve(l,mid),solve(mid + 1,r));
	for(int i = l;i <= r;++i)
	if(sqr(a[i].x - t) < d)
	b[++cnt] = a[i];
	std::sort(b + 1,b + cnt + 1,cmp2);
	for(int i = 1;i <= cnt;++i)
	for(int j = i + 1;j <= cnt && sqr(b[j].y - b[i].y) < d;++j)
	d = std::min(d,dd(b[i],b[j]));
	return d;
}

int n;

int main(){
	scanf("%d",&n);
	for(int i = 1;i <= n;++i)
	scanf("%d%d",&a[i].x,&a[i].y);
	std::sort(a + 1,a + n + 1,cmp1);
	printf("%.4lf",sqrt(solve(1,n)));
}

CF1442D Sum

有结论:一定只会有一个未全部取完的组。
贪心易证。
我们直接分治到最低节点钦定其为未取完的即可,然后处理背包问题。

CF1442D Sum
#include<bits/stdc++.h>
#define ll long long
#define N 3005

int n,k;

ll ans;

ll vi[N];

std::vector<int>a[N];

std::vector<ll>dp(N,0);

inline void sol(int l,int r){
	if(l == r){
//		std::cout<<l<<std::endl;
//		for(int i = 0;i <= k;++i)
//		std::cout<<dp[i]<<" ";
//		puts("");
		ll w = 0,v = 0;
		ans = std::max(ans,dp[k]);
		for(int i = 0;i < a[l].size();++i)
		++w,v += a[l][i],ans = std::max(ans,k - w >= 0 ? dp[k - w] + v : 0ll);
		return ;
	}
	#define mid ((l + r) >> 1)
	std::vector<ll> t = dp;
	for(int i = l;i <= mid;++i)
	for(int j = k;j >= (int)a[i].size();--j)
	dp[j] = std::max(dp[j],dp[j - (int)a[i].size()] + vi[i]);
	sol(mid + 1,r);
	dp = t;
	for(int i = mid + 1;i <= r;++i)
	for(int j = k;j >= (int)a[i].size();--j)
	dp[j] = std::max(dp[j],dp[j - (int)a[i].size()] + vi[i]);
	sol(l,mid);	
}

int main(){
	scanf("%d%d",&n,&k);
	for(int i = 1;i <= n;++i){
		int l;
		scanf("%d",&l);
		while(l -- ){
			int x;
			scanf("%d",&x);
			vi[i] += x;
			a[i].push_back(x);
		}
	}
	sol(1,n);
	std::cout<<ans<<std::endl;
}


【模板】三维偏序(陌上花开)

版。版。板。

【模板】三维偏序(陌上花开)
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
#define N 100005
#define lowbit(x) (x & -x)

struct P{
	ll x,y,z,id;
}e[N];

ll ans[N],unq[N],f[N];
ll t[N * 2];

ll n,k;

void add(ll x,ll p){
	for(int i = x;i <= k;i += lowbit(i))
	t[i] += p;
}

int get(ll x){
	ll ans = 0;
	for(int i = x;i;i -= lowbit(i))
	ans += t[i];
	return ans;
}

bool cmp(P t,P s){
	if(t.x != s.x)
	return t.x < s.x;
	if(t.y != s.y)
	return t.y < s.y;
	return t.z < s.z;
}

bool cmp1(P t,P s){
	if(t.y != s.y)
	return t.y < s.y;
	if(t.z != s.z)
	return t.z < s.z;	
	return t.x < s.x;
}

void cdq(ll l,ll r){
	if(l == r)
	return;
	int mid = (l + r) >> 1;
	cdq(l,mid),cdq(mid + 1,r);
	std::sort(e + l,e + r + 1,cmp1);
	for(int i = l;i <= r;++i)
	if(e[i].x <= mid)
	add(e[i].z,1);
	else
	ans[e[i].id] += get(e[i].z);
	for(int i = l;i <= r;++i){
	if(e[i].x <= mid)
	add(e[i].z,-1);
//	printf("%lld %lld\n",e[i].x,ans[e[i].id]);
	}
}

int main(){
	scanf("%lld%lld",&n,&k);
	for(int i = 1;i <= n;++i)
	scanf("%lld%lld%lld",&e[i].x,&e[i].y,&e[i].z),e[i].id = i;
	std::sort(e + 1,e + n + 1,cmp);
	for(int i = 1,j;i <= n;){
		j = i + 1;
		while(j <= n && e[j].x == e[i].x && e[j].y == e[i].y && e[j].z == e[i].z)
		j ++ ;
		while(i < j)
		unq[e[i].id] = e[j - 1].id,i ++ ;
	}
	for(int i = 1;i <= n;++i)
	e[i].x = i;
	cdq(1,n);
	for(int i = 1;i <= n;++i)
	f[ans[unq[e[i].id]]] ++ ;
	for(int i = 0;i < n;++i)
	printf("%lld\n",f[i]);
} 


[CQOI2011]动态逆序对

考虑把一个点写作\((t,w,v)\)表示其被删除的时间,以及位置,和权值。

则一个点对能贡献一定有\(t_i < t_j\),然后就变成逆序对了。

然后将答案写在\(f_{t_i}\),然后做一个后缀和。

[CQOI2011]动态逆序对
#include<bits/stdc++.h>
#define ll long long 
#define N 100005

struct P{
	int t,w,v;
}e[N];

int n;
int m;

ll f[N];
int del[N];

inline bool cmp(P a,P b){
	return a.t < b.t;
}

inline bool cmp2(P a,P b){
	return a.w < b.w;
}

int T[N];

#define lowbit(x) (x & -x)

inline void add(int x,int p){
	for(int i = x;i <= n;i += lowbit(i))
	T[i] += p;
} 

inline int find(int x){
	int ans = 0;
	for(int i = x;i;i -= lowbit(i))
	ans += T[i];
	return ans;
 }

inline void solve(int l,int r){
	#define mid ((l + r) >> 1)
	if(l == r)return ;
	solve(mid + 1,r);
	solve(l,mid);
	std::sort(e + l,e + mid + 1,cmp2);
	std::sort(e + mid + 1,e + r + 1,cmp2);
//	std::cout<<"!"<<l<<" "<<r<<" "<<mid<<std::endl;		
//	for(int i = mid + 1;i <= r;++i)
//	std::cout<<e[i].w<<" ";
//	puts("");
	int ri = mid + 1;
//	std::cout<<"!"<<l<<" "<<r<<" "<<mid<<std::endl;	
	for(int i = l;i <= mid;++i){//wx < wy vx > vy
//		std::cout<<e[i].t<<" "<<e[i].w<<" "<<e[i].v<<std::endl;;
		while(e[ri].w < e[i].w && ri <= r)
		add(e[ri].v,1),++ri;
//		std::cout<<find(n) - find(e[i].v)<<std::endl;
		f[e[i].t] += find(n) - find(e[i].v);
	}
	-- ri; 
	while(ri >= mid + 1)
	add(e[ri].v,-1),--ri;
	ri = r;//wx > wy vx < vy
	for(int i = mid;i >= l;--i){
//		std::cout<<e[i].t<<" "<<e[i].w<<" "<<e[i].v<<std::endl;;			
		while(e[ri].w > e[i].w && ri >= mid + 1)
		add(e[ri].v,1),--ri;
//		std::cout<<find(e[i].v - 1)<<std::endl;
		f[e[i].t] += find(e[i].v - 1);
	}	
	++ ri;
	while(ri <= r)
	add(e[ri].v,-1),++ri;	
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;++i)
	scanf("%d",&e[i].v),e[i].w = i;
	for(int i = 1;i <= m;++i){
		int x;
		scanf("%d",&x);
		del[x] = i;
	}
	for(int i = 1;i <= n;++i){
		if(del[e[i].v])
		e[i].t = del[e[i].v];
		else
		e[i].t = m + 1;
	}
	std::sort(e + 1,e + n + 1,cmp);
	solve(1,n);
	for(int i = m;i >= 1;--i)
	f[i] = f[i + 1] + f[i];
	for(int i = 1;i <= m;++i)
	std::cout<<f[i]<<std::endl;
}


线段树分治

CF601E A Museum Robbery

把物品打在树上,暴力背包。

CF601E A Museum Robbery
#include<bits/stdc++.h>
#define ll long long 
#define N 40007
#define K 1007
#define base ((ll)1e7 + 19)
#define mod ((ll)1e9 + 7)
using std::pair;
using std::vector;
#define pii std::pair<int,int>

#define vii std::vector<pii>::iterator 

struct P{
	int v,w,l,r;
}e[N];

int k;

std::vector<pii> seg[N << 2];

ll dp[K];

#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
#define mid ((l + r) >> 1)

inline void add(int u,int l,int r,int tl,int tr,int v,int w){
	if(tl > tr)return ;
	if(tl <= l && r <= tr){
		seg[u].push_back(std::make_pair(v,w));
		return ;
	}
	if(tl <= mid)
	add(ls(u),l,mid,tl,tr,v,w);
	if(tr > mid)
	add(rs(u),mid + 1,r,tl,tr,v,w);
}

int n,q,opt,u,v,t = 1;

inline void solve(int u,int l,int r,ll *f){
	for(vii it = seg[u].begin();it != seg[u].end();++it){
		for(int j = k;j >= it -> second;--j)
		f[j] = std::max(f[j],f[j - it -> second] + it -> first);
//,std::cout<<it -> first<<" "<<it -> second<<" "<<f[j]<<std::endl	
	}
//	std::cout<<u<<" "<<l<<" "<<r<<std::endl;
//	for(int i = 1;i <= k;++i)
//	std::cout<<f[i]<<" ";
//	puts("");		
	if(l == r){
		ll ans = 0;
		for(int i = k;i;--i)
		ans = (ans * base + f[i]) % mod;
		std::cout<<ans<<std::endl;
		return ;
	}
	ll Q[K];
	memcpy(Q,f,sizeof(Q));
	solve(ls(u),l,mid,Q);
	memcpy(Q,f,sizeof(Q));
	solve(rs(u),mid + 1,r,Q);
}

int main(){
	scanf("%d%d",&n,&k);
	for(int i = 1;i <= n;++i){
		scanf("%d%d",&u,&v);
		e[i] = (P){u,v,t,-1};
	}
	scanf("%d",&q);
	for(int i = 1;i <= q;++i){
		scanf("%d",&opt);
		if(opt == 1){
			scanf("%d%d",&u,&v);
			e[++n] = (P){u,v,t,-1};
		}else if(opt == 2){
			int x;
			scanf("%d",&x);
			e[x].r = t - 1;
		}else{
			++ t;
		}
	}
	--t;
	for(int i = 1;i <= n;++i){
		e[i].r = e[i].r == -1 ? t : e[i].r;
		add(1,1,t,e[i].l,e[i].r,e[i].v,e[i].w);
	}
	solve(1,1,t,dp);
}

CF813F Bipartite Checking二分图 /【模板】线段树分治

拆点,一个点拆成白点和黑点,一次加边如果两点同色在一个集合则其非法。

要做的就是给修改丢到线段树上,然后搞个可撤销并查集就行了。

线段树下标要对应。

二分图 /【模板】线段树分治
#include<bits/stdc++.h>
#define ll long long
#define N 200005

int n,m,k;

using std::pair;
using std::vector;

#define pii std::pair<int,int>
#define vii std::vector<pii>

vii T[N << 2];

#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
#define mid ((l + r) >> 1)

inline void add(int u,int l,int r,int tl,int tr,pii t){
	if(tl > tr)return;
	if(tl <= l && r <= tr){
		T[u].push_back(t);		
		return ;
	}
	if(tl <= mid)
	add(ls(u),l,mid,tl,tr,t);
	if(tr > mid)
	add(rs(u),mid + 1,r,tl,tr,t);
}

int fa[N << 1],top;
ll key[N << 1];
bool tag = 0;

int st[(N << 1) * 2];

inline int find(int u){return (u == fa[u]) ? u : find(fa[u]);}

inline void merge(int x,int y){
	int fx = find(x),fy = find(y);
	if(key[fx] >= key[fy])
	std::swap(fx,fy);
	st[++top] = fx;
	fa[fx] = fy;		
}

inline void del(int now){
	while(top > now){
		fa[st[top]] = st[top];
		-- top;
	}
}

inline void solve(int u,int l,int r){
	int las = top;
	tag = 0;
	for(int i = 0;i < T[u].size();++i){
		pii a = T[u][i];
		if(find(a.first) == find(a.second)){
			tag = 1;
			for(int j = l;j <= r;++j)
			puts("No");
			break;
		}else{
			merge(a.first + n,a.second);
			merge(a.first,a.second + n);
		}
	}
	if(!tag){
		if(l == r)
		puts("Yes");
		else{
			solve(ls(u),l,mid);
			solve(rs(u),mid + 1,r);
		}
	}
	del(las);
}

#define fhq_treap time(0)

int main(){
	srand(fhq_treap);
	scanf("%d%d%d",&n,&m,&k);
	for(int i = 1;i <= m;++i){
		int x,y,l,r;
		scanf("%d%d%d%d",&x,&y,&l,&r);
		if(l == r)continue;
		add(1,1,k,l + 1,r,std::make_pair(x,y));
	}
	for(int i = 1;i <= n << 1;++i)
	fa[i] = i,key[i] = rand() << 15 | rand(); 
	solve(1,1,k);
}

CF938G Shortest Path Queries

前置知识:[WC2011]最大XOR和路径[学习笔记] 线性基 ,后者年久失修。

考虑这题实际上是动态问题。

我们维护可撤销线性基以及可撤销并查集表示树结构即可。

和上题代码差不太多,咕咕咕了。

CF576E Painting Edges

考虑这题的难点是我们并不知道一条影响是否会改变下一条

我们不妨假定其染色成功。

我们发现如果染色成功等同于其影响后延,那么我们不撤销操作,否则我们撤销操作。

然后和前面的题一样,不同的是维护\(k\)的并查集即可。

代码咕咕咕。

CF603E Pastoral Oddities

有如下结论:
一定是所有联通块均为偶数点,且加边不影响答案。

考虑一个静态问题:
有一个固定边集我们如何操作。
我们一定是从小到大能加就加。
因为对奇数联通块数量来说加一条边一定不劣。

考虑动态问题。

我们先思考为什么我们不能直接把每条边打在区间上然后操作:
因为不满足最优子结构,我这里选了这条边显然底下会出问题。

但是我们发现:一个最优答案的子结构一定满足最优化(最优化定义为按可选边权依次加入并查集的边。)

我们发现加边不好做,我们不妨思考倒序处理。

那么我们从后往前处理最优化结构:
我们可以确定其中的边集内的边的属于最优结构的范围为\([begin,\max T (i \in best\ when\ in\ T]\)
那么我们就确定了其中的范围可以在树上打标记了。
很好的题。

CF603E Pastoral Oddities
#include<bits/stdc++.h>
#define N 300005

int n,m,pos,ans[N],odd;

struct P{
	int x,y,v,id;
}e[N];

bool operator < (P a,P b){
	return a.v < b.v;
}

struct Last{int x,y,dlt;}st[N];//可撤销

int fa[N],siz[N],top;

inline int find(int x){return fa[x] == x ? x : find(fa[x]);}

std::vector<P>vec[N << 2];

#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
#define mid ((l + r) >> 1) 

inline void cover(int u,int l,int r,int tl,int tr,P v){
	if(tl > tr)return;
	if(tl <= l && r <= tr){
		vec[u].push_back(v);
		return ;	
	}
	if(tl <= mid)
	cover(ls(u),l,mid,tl,tr,v);
	if(tr > mid)
	cover(rs(u),mid + 1,r,tl,tr,v);
} 

inline void merge(int x,int y){
	int fx = find(x),fy = find(y);
	if(fx == fy)return;
	if(siz[fx] < siz[fy])std::swap(fx,fy);
	st[++top] = (Last){fx,fy,0};
	if(siz[fx] % 2 && siz[fy] % 2)
	odd -= 2,st[top].dlt += 2;
	siz[fx] += siz[fy],fa[fy] = fx;
}

inline void solve(int u,int l,int r){
	int las = top;
	for(int i = 0;i < vec[u].size();++i)
	merge(vec[u][i].x,vec[u][i].y);
	if(l == r){
		while(1){
			if(odd == 0 || pos == m)break;
			if(e[pos + 1].id <= l){
				merge(e[pos + 1].x,e[pos + 1].y);
				cover(1,1,m,e[pos + 1].id,l - 1,e[pos + 1]);
			}
			++ pos;
		}
		if(odd == 0)ans[l] = e[pos].v;
		else ans[l] = -1;
	}else{
		solve(rs(u),mid + 1,r);
		solve(ls(u),l,mid);
	}
	while(top != las){
		int x = st[top].x,y = st[top].y,dlt = st[top].dlt;
		siz[x] -= siz[y],fa[y] = y;
		odd += dlt; 
		-- top;
	}
}

int main(){
	scanf("%d%d",&n,&m);
	odd = n;
	for(int i = 1;i <= n;++i)
	fa[i] = i,siz[i] = 1;
	for(int i = 1;i <= m;++i)
	scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].v),e[i].id = i;
	std::sort(e + 1,e + m + 1);
	solve(1,1,m);
	for(int i = 1;i <= m;++i)
	std::cout<<ans[i]<<std::endl;
}

二维分治

CF364E Empty Rectangles

考虑横竖交替分治。

维护\(k\)个指针表示:在分治线上和线下的和为\(x\)的边界。

image

我们枚举矩形的左边界 \(i\),然后把右边界当成指针一直往右扫,过程中维护"橙线",也就是达到 \(x\)\(1\) 的边界。显然随着右边界的移动上面橙线只会下移,下面的橙线只会上移,这个是具有单调性的,可以维护 \(k\) 个指针,算答案的时候还是简单乘法原理。

CF364E Empty Rectangles
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 2505;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,a[M][M],b[M][M],up[2][10];ll ans;
int cal(int xl,int xr,int yl,int yr)
{
	return b[xr][yr]-b[xr][yl]-b[xl][yr]+b[xl][yl];
}
void cdq(int xl,int xr,int yl,int yr,int ty)
{
	if(xl==xr || yl==yr) return ;
	if(xl+1==xr && yl+1==yr)
	{
		ans+=(cal(xl,xr,yl,yr)==k);
		return ;
	}
	if(ty==0)
	{
		int mid=(xl+xr)>>1;
		cdq(xl,mid,yl,yr,1);
		cdq(mid,xr,yl,yr,1);
		for(int i=yl;i<=yr;i++)
		{
			up[0][0]=up[1][0]=mid;
			for(int j=1;j<=k+1;j++)
				up[0][j]=xl,up[1][j]=xr;
			for(int j=i+1;j<=yr;j++)
			{
				for(int p=1;p<=k+1;p++)
				{
					while(cal(up[0][p],mid,i,j)>=p)
						up[0][p]++;
					while(cal(mid,up[1][p],i,j)>=p)
						up[1][p]--;
				}
				for(int p=0;p<=k;p++)
					ans+=(up[0][p]-up[0][p+1])
					*(up[1][k-p+1]-up[1][k-p]);
			}
		}
	}
	else
	{
		int mid=(yl+yr)>>1;
		cdq(xl,xr,yl,mid,0);
		cdq(xl,xr,mid,yr,0);
		for(int i=xl;i<=xr;i++)
		{
			up[0][0]=up[1][0]=mid;
			for(int j=1;j<=k+1;j++)
				up[0][j]=yl,up[1][j]=yr;
			for(int j=i+1;j<=xr;j++)
			{
				for(int p=1;p<=k+1;p++)
				{
					while(cal(i,j,up[0][p],mid)>=p)
						up[0][p]++;
					while(cal(i,j,mid,up[1][p])>=p)
						up[1][p]--;
				}
				for(int p=0;p<=k;p++)
					ans+=(up[0][p]-up[0][p+1])
					*(up[1][k-p+1]-up[1][k-p]);
			}
		}
	}
}
signed main()
{
	n=read();m=read();k=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
			scanf("%1d",&a[i][j]);b[i][j]+=a[i][j];
		}
	cdq(0,n,0,m,1);
	printf("%lld\n",ans);
}


整体二分

[POI2011]MET-Meteors

考虑其二分是非常简单的。
贡献的时候拆环就行了。
然后可以相当于是个整体二分板子。
同时其也利用了分治中心移动距离\(O(nlog)\)的操作保证了复杂度。

[POI2011]MET-Meteors
#include<bits/stdc++.h>
#define ll unsigned long long 
#define N 300010
#define int ll 

int n,m,belong[N],ls[N],rs[N],target[N],k;
int val[N],now,cont[N],lt[N],rt[N],ans[N];

ll tree[N],tc[N];

using std::vector;

std::vector<int>T[N];

#define lowbit(x) (x & -x)

inline void add(int x,int p){
	for(int i = x;i <= m;i += lowbit(i))
	tree[i] += p; 
} 

inline ll find(int x){
	ll ans = 0;
	for(int i = x;i;i -= lowbit(i))
	ans += tree[i];
	return ans;
}

inline void change(int l,int r,int x){
	if(l <= r){
		add(l,x);
		add(r + 1,-x);
	}else{
		add(l,x),add(m + 1,-x);
		add(1,x),add(r + 1,-x);
	}
}

int t;

#define mid ((l + r) >> 1)

inline void query(int l,int r,int L,int R){
	if(l > r)return;if(L > R)return ;
//	std::cout<<l<<" "<<r<<std::endl; 
//	for(int i = L;i <= R;++i)
//	std::cout<<cont[i]<<" ";
//	puts("");
	if(l == r){
		for(int i = L;i <= R;++i)
		ans[cont[i]] = l;
		return ;
	}
	while(now < mid)++now,change(ls[now],rs[now],val[now]);
	while(now > mid)change(ls[now],rs[now],-val[now]),--now;
	for(int i = L;i <= R;++i){
		tc[cont[i]] = 0;
		for(int j =0;j < T[cont[i]].size();++j)
		tc[cont[i]] += find(T[cont[i]][j]);
	}
	int lct = 0,rct = 0;
	for(int i = L;i <= R;++i){
		if(tc[cont[i]] >= target[cont[i]])
		lt[++lct] = cont[i];
		else
		rt[++rct] = cont[i];
	}
	int split = lct;
//	puts("LC");
//	for(int i = 1;i <= lct;++i)
//	std::cout<<lt[i]<<" ";
//	puts("");
//	puts("RC");
//	for(int i = 1;i <= rct;++i)
//	std::cout<<rt[i]<<" ";
//	puts("");	 
	for(int i = 1;i <= lct;++i)
	cont[i + L - 1] = lt[i];
	for(int i = 1;i <= rct;++i)
	cont[L + lct + i - 1] = rt[i];
	query(l,mid,L,L + split - 1);
	query(mid + 1,r,L + split,R); 
}

signed main(){
	scanf("%llu%llu",&n,&m);
	for(int i = 1;i <= m;++i){
		scanf("%llu",&belong[i]);
		T[belong[i]].push_back(i);
	}
	for(int i = 1;i <= n;++i){
		scanf("%llu",&target[i]);
		cont[i] = i;
	}
	scanf("%llu",&k);
	for(int i = 1;i <= k;++i){
		scanf("%llu%llu%llu",&ls[i],&rs[i],&val[i]);
	}
	++ k;
	ls[k] = 1;
	rs[k] = m;
	val[k] = 0x3f3f3f3f;
	now = 0;
	query(1,k,1,n);
	for(int i = 1;i <= n;++i){
		if(ans[i] == k)
		puts("NIE");
		else
		std::cout<<ans[i]<<std::endl;
	}
	return 0;
}

[国家集训队]矩阵乘法

整体二分板板。

[国家集训队]矩阵乘法
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int maxn = 3e5 + 50, INF = 0x3f3f3f3f;

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

int n, q, len, c[maxn], a[505][505], tree[505][505], ans[maxn], mp[maxn];
vector <int> res;
vector <pair <int, int> > pos[maxn];

struct Node {
	int x1, y1, x2, y2, k;
} b[maxn];

inline void Insert (register int x, register int y) {
	for (register int i = x; i <= n; i += i & -i) 
		for (register int j = y; j <= n; j += j & -j) 
			tree[i][j] ++;
}

inline void Delete (register int x, register int y) {
	for (register int i = x; i <= n; i += i & -i) 
		for (register int j = y; j <= n; j += j & -j) 
			tree[i][j] --;
}

inline int Query (register int x, register int y, register int ans = 0) {
	for (register int i = x; i; i -= i & -i) 
		for (register int j = y; j; j -= j & -j)
			ans += tree[i][j];
	return ans;
}

inline void Binary (register int l, register int r, vector <int> vec) {	
	if (! vec.size ()) return;
	if (l == r) {
		for (register int i = 0; i < vec.size (); i ++) ans[vec[i]] = mp[l];
		return;
	}
	register int mid = (l + r) >> 1;
	vector <int> vec0, vec1;
	for (register int x = l; x <= mid; x ++) 
		for (register int i = 0; i < pos[x].size (); i ++) 
			Insert (pos[x][i].first, pos[x][i].second);			
	for (register int i = 0; i < vec.size (); i ++) {
		register int id = vec[i], x1 = b[id].x1, y1 = b[id].y1, x2 = b[id].x2, y2 = b[id].y2;
		register int sum = Query (x2, y2) - Query (x1 - 1, y2) - Query (x2, y1 - 1) + Query (x1 - 1, y1 - 1);
		if (sum >= b[id].k) vec0.push_back (id);
		else b[id].k -= sum, vec1.push_back (id);
	}
	for (register int x = l; x <= mid; x ++) 
		for (register int i = 0; i < pos[x].size (); i ++) 
			Delete (pos[x][i].first, pos[x][i].second);			
	Binary (l, mid, vec0), Binary (mid + 1, r, vec1);
}

int main () {
	n = read(), q = read();
	for (register int i = 1; i <= n; i ++) 
		for (register int j = 1; j <= n; j ++) 
			a[i][j] = c[++ len] = read();
	sort (c + 1, c + len + 1), len = unique (c + 1, c + len + 1) - c - 1;
	for (register int i = 1; i <= n; i ++) {
		for (register int j = 1; j <= n; j ++) {
			register int res = lower_bound (c + 1, c + len + 1, a[i][j]) - c;
			mp[res] = a[i][j], a[i][j] = res, pos[res].push_back (make_pair (i, j));
		}
	}
	for (register int i = 1; i <= q; i ++) 
		b[i].x1 = read(), b[i].y1 = read(), b[i].x2 = read(), b[i].y2 = read(), b[i].k = read(), res.push_back (i);
	Binary (1, len, res);
	for (register int i = 1; i <= q; i ++) printf ("%d\n", ans[i]);
	return 0;
}

决策单调性分治

CF868F Yet Another Minimization Problem

待会写个四边形不等式的博。

这是一个 2D/1D 动态规划问题。

每次对一维通过上一维dp过来。

这是一个 1D/1D 动态规划问题。

前文我们提到,1D/1D问题可以考虑CDQ分治,但我们需要在合并两段区间时也能做到对右子区间做到\(O(n)\)

但是这题的贡献并不满足这个条件。

我们转而思考,这类区间贡献的方式是否拥有决策单调性。

下面证明该问题具有决策单调性:

即证明\(\forall i_1 < i_2\),假设其转移点为\(x_1,x_2\),那么有\(x_1\leq x_2\)

假设若有\(x_1 > x_2\),则有\(f_{x_1} + w_{x_1 + 1,i1} \leq f_{x_2} + w_{x_2 + 1,i2}\),\(f_{x_1} + w_{x_2 + 1,i1} \leq f_{x_2} + w_{x_1 + 1,i2}\)

若两等号均成立知其不影响证明。

若有一个取了小于:不妨假设其为\(f_{x_1} + w_{x1 + 1,i1} \leq f_{x_2} + w_{x2 + 1,i2}\).\(f_{x_1} + w_{x_2 + 1,i1} < f_{x_2} + w_{x_1 + 1,i2}\)

整理可得\(w_{x_1 + 1,i1} - w_{x_2 + 1,i1} < w_{x_1 + 1,i2} - w_{x_2 + 1,i2}\)

\(i1 < i2\)知其错误。

则结论得证。

可分治解决该问题。

具体可以看代码。

其中利用了两个指针在分治过程移动距离不过\(O(nlogn)\)

CF868F Yet Another Minimization Problem
#include<bits/stdc++.h>
#define ll long long 
#define N 100005

int n,k;

int buc[N],L,R,a[N];

ll ans;

inline void up(int c,int d){ans += d * buc[c] * 1ll * (buc[c] - 1) / 2;}

inline ll cal(int l,int r){
	while(L < l)up(a[L],-1),buc[a[L]] -- ,up(a[L],1),++L;
	while(L > l)L -- ,up(a[L],-1),buc[a[L]] ++ ,up(a[L],1);
	while(R > r)up(a[R],-1),buc[a[R]] -- ,up(a[R],1),R --;
	while(R < r)R ++ ,up(a[R],-1),buc[a[R]] ++ ,up(a[R],1);
	return ans;	
} 

ll dp[22][N];
int cur;

inline void solve(int lb,int rb,int l,int r){
	if(lb > rb || l > r)return ;
	int d = 0;
	ll res = 1e18;
	#define mid ((l + r) >> 1)
	for(int i = lb;i <= rb;++i){
		ll tmp = cal(i + 1,mid);
		if(res > dp[cur - 1][i] + tmp) res = dp[cur - 1][i] + tmp,d = i;
	}
	dp[cur][mid] = res;
	solve(lb,d,l,mid - 1),solve(d,rb,mid + 1,r);
}

int main(){
	std::memset(dp,0x3f,sizeof(dp));
	dp[0][0] = 0;
	scanf("%d%d",&n,&k);
	for(int i = 1;i <= n;++i)
	scanf("%d",&a[i]);
	buc[a[1]] ++ ,L = R = 1;
	for(cur = 1;cur <= k;++cur)
	solve(0,n - 1,1,n);
	std::cout<<dp[k][n]<<std::endl;
}

树分治

点分治
CF150E Freezing with Style

二分答案。

树上启发式合并单调队列。

淀粉质上去就行了。

CF150E Freezing with Style
#include <cstdio>
#include <vector>
#include <algorithm>

const int Inf = 0x3f3f3f3f;
const int MN = 100005;

int N, L, R, Ans = -1, AnsU, AnsV;
int uv[MN], w[MN];
std::vector<int> G[MN];
int dw[MN], M;

int vis[MN], siz[MN], tsiz, rsiz, Root;
void GetRoot(int u, int fz) {
	siz[u] = 1;
	int nsiz = 0;
	for (auto i : G[u]) {
		int v = uv[i] ^ u;
		if (v == fz || vis[v]) continue;
		GetRoot(v, u), siz[u] += siz[v];
		if (nsiz < siz[v]) nsiz = siz[v];
	}
	if (nsiz < tsiz - siz[u]) nsiz = tsiz - siz[u];
	if (rsiz > nsiz) rsiz = nsiz, Root = u;
}
int stk[MN], tp, _U;
inline bool cmp(int i, int j) {
	return siz[uv[i] ^ _U] < siz[uv[j] ^ _U];
}
int seq[MN], sequ[MN], odep, tmp[MN], tmpu[MN], ndep;
void DFS(int u, int fz, int d, int x, int y) {
	if (tmp[d] < x) tmp[d] = x, tmpu[d] = u;
	if (ndep < d) ndep = d;
	for (auto i : G[u]) {
		int v = uv[i] ^ u;
		if (v == fz || vis[v]) continue;
		DFS(v, u, d + 1, x + (w[i] >= y ? 1 : -1), y);
	}
}
int ucal, vcal;
bool Calc(int u, int x) {
	static int que[MN];
	seq[odep = 0] = 0, sequ[0] = u;
	for (int i = 1; i <= tp; ++i) {
		int v = uv[stk[i]] ^ u;
		for (int j = 1; j <= siz[v]; ++j) tmp[j] = -Inf;
		ndep = 0, DFS(v, u, 1, w[stk[i]] >= x ? 1 : -1, x);
		int l = 1, r = 0, lb = odep, rb = odep + 1;
		for (int j = 1; j <= ndep; ++j) {
			while (rb > 0 && rb > L - j) {
				--rb;
				while (l <= r && seq[que[r]] < seq[rb]) --r;
				que[++r] = rb;
			}
			while (lb >= 0 && lb > R - j) {
				--lb;
				while (l <= r && que[l] > lb) ++l;
			}
			if (l <= r && seq[que[l]] + tmp[j] >= 0) {
				ucal = sequ[que[l]], vcal = tmpu[j];
				return 1;
			}
		}
		while (odep < ndep) seq[++odep] = -Inf;
		for (int j = 1; j <= ndep; ++j)
			if (seq[j] < tmp[j])
				seq[j] = tmp[j], sequ[j] = tmpu[j];
	}
	return 0;
}
void Solve(int u) {
	int nsiz = tsiz;
	tp = 0;
	for (auto i : G[u]) {
		int v = uv[i] ^ u;
		if (vis[v]) continue;
		siz[v] = siz[v] > siz[u] ? nsiz - siz[u] : siz[v];
		stk[++tp] = i;
	}
	_U = u, std::sort(stk + 1, stk + tp + 1, cmp);
	int lb = 1, rb = M, mid, ans = 0, ansu = 0, ansv = 0;
	while (lb <= rb) {
		mid = (lb + rb) >> 1;
		if (Calc(u, dw[mid])) {
			ans = mid;
			ansu = ucal, ansv = vcal;
			lb = mid + 1;
		}
		else rb = mid - 1;
	}
	if (Ans < dw[ans]) {
		Ans = dw[ans];
		AnsU = ansu, AnsV = ansv;
	}
	vis[u] = 1;
	for (auto i : G[u]) {
		int v = uv[i] ^ u;
		if (vis[v]) continue;
		rsiz = tsiz = siz[v], GetRoot(v, 0), Solve(Root);
	}
}

int main() {
	scanf("%d%d%d", &N, &L, &R);
	for (int i = 1; i < N; ++i) {
		int x, y;
		scanf("%d%d%d", &x, &y, &w[i]);
		uv[i] = x ^ y;
		G[x].push_back(i);
		G[y].push_back(i);
		dw[i] = w[i];
	}
	std::sort(dw + 1, dw + N);
	M = std::unique(dw + 1, dw + N) - dw - 1;
	rsiz = tsiz = N, GetRoot(1, 0), Solve(Root);
	printf("%d %d\n", AnsU, AnsV);
	return 0;
}

淀粉树

写吐了兄弟。

【模板】点分树 | 震波

建出点分树。

然后用两个BIT来询问的时候容斥。

【模板】点分树 | 震波
#include <vector>
#include <cstdio>
#include <iostream>
#include <algorithm>

const int N = 4e5 + 5;

int n, m, val[N];

struct Edge { int to, next; } e[N << 1];

int head[N], cnt = 0;

void Add(int x, int y) { 
	e[++cnt].next = head[x]; 
	e[cnt].to = y;
	head[x] = cnt;
}

int dep[N], siz[N], fa[N];
int pos[N], lg[N << 1], st[N << 1][21], scnt = 0;

void Dfs1(int u, int f) {
	st[++cnt][0] = u;
	pos[u] = cnt;
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].to;
		if (v == f)
			continue;
		dep[v] = dep[u] + 1;
		Dfs1(v, u);
		st[++cnt][0] = u;
	}
}

int GetMin(int x, int y) {
	return dep[x] < dep[y] ? x : y;
}

void GetST() 
{
	lg[1] = 0;
	for(int i = 2; i <= cnt; ++i) 
		lg[i] = lg[i >> 1] + 1;
	for(int j = 1; 1 << j <= cnt; j++)
		for(int i = 1; i + (1 << j) <= cnt; i++)
			st[i][j] = GetMin(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}

int GetDis(int u, int v) {
	if (pos[u] > pos[v])
		std :: swap(u, v);
	int pu = pos[u], pv = pos[v];
	int len = pv - pu +1;
	return dep[u] + dep[v] - 2 * dep[GetMin(st[pu][lg[len]], st[pv - (1 << lg[len]) + 1][lg[len]])];
}

std :: vector <int> tr[2][N];

void Modify(int u, int opt, int x, int v) {
//	printf("siz%d\n", siz[u]);
	x++;
	for (int i = x; i <= siz[u]; i += i & -i)
		tr[opt][u][i] += v;
}

int Query(int u, int opt, int x) {
	int res = 0;
	x = std :: min(x + 1, siz[u]);
	for (int i = x; i; i -= i & -i)
		res += tr[opt][u][i];
	return res;
}

int tot, rt, vis[N << 1], mn;

void FindRt(int u, int f) {
	siz[u] = 1;
	int res = 0;
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].to;
		if (v == f || vis[v])
			continue;
		FindRt(v, u);
		siz[u] += siz[v];
		res = std :: max(res, siz[v]); 
	}
	res = std :: max(res, tot - siz[u]);
	if (res < mn)
		mn = res, rt = u;
}

void Dfs2(int u) {
	vis[u] = 1;
	siz[u] = tot + 1;
	tr[0][u].resize(siz[u] + 1);
	tr[1][u].resize(siz[u] + 1);
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].to;
		if (vis[v])
			continue;
		tot = siz[v];
		rt = 0;
		mn = 1e9;
		FindRt(v, 0);
		fa[rt] = u;
		Dfs2(rt);
	} 
}

void Calc(int u ,int w) {
	for (int i = u; i; i = fa[i])
//		printf("dis%d\n", GetDis(u, i)),
		Modify(i, 0, GetDis(u, i), w);
	for (int i = u; fa[i]; i = fa[i])
//		printf("dis%d\n", GetDis(u, fa[i])),
		Modify(i, 1, GetDis(u, fa[i]), w);
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
		scanf("%d", &val[i]);
	for (int i = 1; i < n; i++) {
		int u, v;
		scanf("%d%d", &u, &v);
		Add(u, v);
		Add(v, u);
	}
	Dfs1(1, 0);
	GetST();
	tot = n;
	mn = 1e9;
	FindRt(1, 0);
	Dfs2(rt);
	for (int i = 1; i <= n; i++)
		Calc(i, val[i]);
	int lst = 0;
//	for (int i = 1; i <= n; i++)
//		printf("%d ", GetDis(i, i + 2));	
//	puts("");
	while (m--) {
		int opt, x, y;
		scanf("%d%d%d", &opt, &x, &y);
		x ^= lst, y ^= lst;
		if (opt == 0) {
			lst = 0;
			lst += Query(x, 0, y);
			for (int i = x; fa[i]; i = fa[i])
				if (y >= GetDis(x, fa[i]))
					lst += Query(fa[i], 0, y - GetDis(x, fa[i])) - Query(i, 1, y - GetDis(x, fa[i]));
			printf("%d\n", lst);
		}
		else {
			Calc(x, y - val[x]);
			val[x] = y;
		}
	}
}

[HNOI2015]开店

和上面那个题差不多。

真写不动了。/tuu。

为了提升分治操作,准备找一天挑战紫荆花之恋。

终于了结束了啊,分治专题。

启发式合并

长链启发式合并

CF1009F Dominant Indices

板板题。

没有采用指针方式,而是使用了\(vector\)

CF1009F Dominant Indices
#include<bits/stdc++.h>
#define ll long long
#define N 1000005

std::vector<int>e[N],f[N];

int fa[N],len[N],son[N];

int ans[N];

int n;

inline void dfs1(int u,int f){//find_len_rt
//	std::cout<<u<<" "<<f<<std::endl;
	fa[u] = f;
	for(auto v : e[u]){
		if(v == f)continue;
		dfs1(v,u);		
		if(len[v] >= len[son[u]])
		son[u] = v,len[u] = len[v] + 1;
	}
}

inline void dfs2(int u){
	if(son[u]){
		dfs2(son[u]);
		std::swap(f[u],f[son[u]]);
		f[u].push_back(1);
		ans[u] = ans[son[u]];
		if(f[u][ans[u]] == 1)ans[u] = len[u];
		for(auto v : e[u]){
//			std::cout<<v<<std::endl;
			if(v == fa[u] || v == son[u])continue;
			dfs2(v);
			for(int i = len[v];i >= 0;--i){
				int tmp = i + len[u] - len[v] - 1;
				f[u][tmp] += f[v][i];
				if(f[u][tmp] > f[u][ans[u]])
				ans[u] = tmp;
				else
				if(f[u][tmp] == f[u][ans[u]] && tmp > ans[u])
				ans[u] = tmp;
			} 
		}
	}else{
		f[u].push_back(1);
		ans[u] = 0;
	}
//	std::cout<<u<<std::endl;
//	for(int i = len[u];i >= 0;--i)
//	std::cout<<f[u][i]<<" ";
//	puts("");
}

int main(){
	scanf("%d",&n);
	for(int i = 1;i < n;++i){
		int x,y;
		scanf("%d%d",&x,&y);
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs1(1,0);
	dfs2(1);
	for(int i = 1;i <= n;++i)
	printf("%d\n",len[i] - ans[i]);
}

CF1499F Diameter Cuts

考虑\(dp\)

\(f_{u,i}\)\(u\)子树里能到达\(u\)的最大深度为\(i\),且子树合法的方案数。

有断边和不断边两种转移

\(f_{u,max(i,j + 1)} = f_{u,i} * f_{v,j} (i + j + 1 < k)\)
\(f_{u,i} = f_{u,i} * f_{v,j} (j < k)\)

套用转移即可。

这题甚至\(O(n^2)\)可过。

倍增

[SCOI2015]国旗计划

如果他不是环。
那简直是典中典题。
考虑记录\(f_{i,j}\)即从\(i\)处使用了\(2^j\)个区间达到的最右边的处。
环就直接扩展\(2\)倍。

[SCOI2015]国旗计划
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
using namespace std;
int n, m, res[200005];
struct soldier {
	int id, l, r;
} s[400005];
int cmp(soldier a, soldier b)
{
	return a.l < b.l; 
}

int go[400005][20];

void pre()
{
	for(int i = 1, p = i; i <= 2 * n; i++) {
		while(p <= 2 * n && s[p].l <= s[i].r)
			p++;
		int pos = p - 1;
		go[i][0] = pos;
	}
	for(int i = 1; i < 20; i++) {
		for(int j = 1; j <= 2 * n; j++) {
			go[j][i] = go[go[j][i - 1]][i - 1];
		}
	}
}
void search(int k)
{
	int lmt = s[k].l + m, ans = 1, p = k;
	for(int i = 19; i >= 0; i--) {
		if(go[k][i] != 0 && s[go[k][i]].r < lmt) {
			ans += (1 << i);
			k = go[k][i];
		}
	}
	res[s[p].id] = ans + 1;
}
int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i++) {
		cin >> s[i].l >> s[i].r;
		if(s[i].r < s[i].l)
			s[i].r += m;
		s[i].id = i;
	}
	sort(s + 1, s + 1 + n, cmp);
	for(int i = 1; i <= n; i++) {
		s[i + n] = s[i];
		s[i + n].l = s[i].l + m;
		s[i + n].r = s[i].r + m;
	}
	pre();
	for(int i = 1; i <= n; i++)
		search(i);
	for(int i = 1; i <= n; i++)
		cout << res[i] << " ";
	return 0;
}

扫描线

略。(题单上来的时候扫描线已经全做完了)

分块

数论分块

略。该技巧在某些反演题中反复出现。

操作分块

[APIO2019]桥梁

考虑如果没有修改操作,我们显然排序并查集可做。

但是有了修改操作。

我们发现无法快速维护,这个时候我们用到操作分块。

我们把操作分为\(S\)块,我们依次处理块内问题。

我们依次处理每个块,把每个块前的修改都修改。

然后将询问与修改按权排序,枚举询问,先把在块内未被修改过的操作给他永久加入。

然后我们对询问将满足他条件的加入,处理完这个询问之后就暴力撤销。

其一个块的复杂度为\(O(mlogm + S^2logn)\)

调一调块长,取块长为\(750 ~ 780\)好像能跑的飞快。

贡献卡常记录。
image

image

[APIO2019]桥梁
#include<bits/stdc++.h>
#define ll long long 
#define N 100005

int n,m;

inline int read(){
	int ans = 0,f = 1;
	char a = getchar();
	while(a != '-' && !(a <= '9' && a >= '0'))
	a = getchar();
	while(a <= '9' && a >= '0')
	ans = (ans << 1) + (ans << 3) + (a - '0'),a = getchar();
	return f * ans; 
} 

struct P{
	int x,y,p;// x <-> y color : p
	int id;
}e[N],D[N];//have_done_edge 

inline bool cmp1(P a,P b){
	return a.p > b.p;
}

inline bool cmp2(P a,P b){
	return a.id < b.id;
}

struct All{
	int x,y,tim;//b_x -> y or form x up with color y
}C[N],Q[N];

inline bool cmp(All a,All b){
	return a.y > b.y;
}

inline bool cmp3(All a,All b){
	return a.tim > b.tim;
}

inline bool cmp5(All a,All b){
	return a.tim < b.tim;
}

int fa[N],sz[N];

int stk[N],top;//can _ del _ DSU
int stk2[N];

int q;
int now = 1;
int B = 750;

int ccnt,qcnt;

int fans[N];

int rk[N];

int siz[N];

bool vis[N],used[N];

inline void cancel(int las){
	while(top != las){
//		std::cout<<"del"<<stk[top]<<" "<<stk2[top]<<std::endl;
		fa[stk[top]] = stk[top];
		siz[stk2[top]] -= siz[stk[top]];
		-- top;
	}
}

inline int find(int x){return fa[x] == x ? x : find(fa[x]);} 

inline void merge(int x,int y){
	int fx = find(x),fy = find(y);
//	std::cout<<x<<" "<<y<<std::endl;	
	if(fx == fy)return;
	if(rk[fx] > rk[fy])std::swap(fx,fy);
	fa[fx] = fy;
	siz[fy] += siz[fx]; 
	stk[++top] = fx;
	stk2[top] = fy;
}

inline void del(){
	int R = std::min(now + B,q);
	int L = now;
	ccnt = qcnt = 0;
	for(int i = 1;i <= m;++i)
	e[i] = D[i];
	for(int i = 1;i <= n;++i)
	fa[i] = i,siz[i] = 1;
	top = 0;
	for(;now <= R;++now){
		int opt = read(),x = read(),y = read();
//		scanf("%d%d%d",&opt,&x,&y);
//		std::cout<<now<<std::endl;
//		std::cout<<opt<<" "<<x<<" "<<y<<std::endl;		
		if(opt == 1){
			C[++ccnt].x = x;
			C[ccnt].y = y;
			C[ccnt].tim = now;
		}else{
			Q[++qcnt].x = x;
			Q[qcnt].y = y;
			Q[qcnt].tim = now;
		}
	}
	for(int i = 1;i <= m;++i)
	vis[i] = 0;
	for(int i = 1;i <= ccnt;++i)
	vis[C[i].x] = 1;
	for(int i = 1;i <= m;++i)
	if(vis[i])
	C[++ccnt].x = i,C[ccnt].y = e[i].p,C[ccnt].tim = 0;
	std::sort(e + 1,e + m + 1,cmp1);
	std::sort(Q + 1,Q + qcnt + 1,cmp);
	std::sort(C + 1,C + ccnt + 1,cmp3);
//	puts("Stand edge");
//	for(int i = 1;i <= m;++i)
//	std::cout<<e[i].x<<" "<<e[i].y<<" "<<e[i].p<<" "<<vis[i]<<std::endl;
	int done = 1;
	for(int i = 1;i <= qcnt;++i){
//		puts("Query");		
//		std::cout<<Q[i].x<<" "<<Q[i].y<<" "<<Q[i].tim<<std::endl;
//		std::cout<<"match"<<" "<<e[done].p<<std::endl;
		while(e[done].p >= Q[i].y && done <= m){
			if(!vis[e[done].id]){
				merge(e[done].x,e[done].y);
			}
			++ done;
		}
//		puts("Change");
		int las = top;
		for(int j = 1;j <= ccnt;++j){
//			std::cout<<C[j].x<<" "<<C[j].y<<" "<<C[j].tim<<" "<<used[C[j].x]<<std::endl;
			if(C[j].tim <= Q[i].tim && !used[C[j].x]){
				used[C[j].x] = 1;
				if(C[j].y >= Q[i].y)
				merge(D[C[j].x].x,D[C[j].x].y); 
			}
		}
		for(int j = 1;j <= ccnt;++j)
		used[C[j].x] = 0;
		fans[Q[i].tim] = siz[find(Q[i].x)];
//		std::cout<<"getans"<<find(Q[i].x)<<" "<<siz[find(Q[i].x)]<<std::endl;
		cancel(las);//回退到las 
//		puts("cancel_done");
//		for(int j = 1;j <= n;++j) 
//		std::cout<<fa[j]<<" "<<siz[j]<<std::endl; 
	}
//	std::sort(e + 1,e + m + 1,cmp2);
//	std::sort(C + 1,C + ccnt + 1,cmp5);	
	for(int i = ccnt;i >= 1;--i){
//		std::cout<<C[i].x<<" "<<C[i].y<<std::endl;
		D[C[i].x].p = C[i].y;
	}
}

template <typename T>  
void write(T x)    
{    
    if(x < 0) {    
        putchar('-');    
        x = -x;    
    }    
    if(x > 9)    
        write(x/10);    
    putchar(x % 10 + '0');    
    return;    
}    

int main(){
	std::srand(time(0));
	n = read(),m = read();
	for(int i = 1;i <= n;++i)
	rk[i] = rand() << 15 | rand(),siz[i] = 1;
	for(int i = 1;i <= m;++i){
		int x,y,c;
		x = read();
		y = read();
		c = read(); 
		e[i].x = x,e[i].y = y,e[i].p = c,e[i].id = i;
		D[i] = e[i];
	}
	q = read();
	while(now <= q){
		del();//del with the block
	}
	for(int i = 1;i <= q;++i)
	if(fans[i])
	write(fans[i]),puts("");
}
/*
7 8
1 2 5
1 6 5
2 3 5
2 7 5
3 4 5
4 5 5
5 6 5
6 7 5
5
1 1 1
1 2 3
1 5 2
1 3 1
2 4 2
*/
CF1588F Jumping Through the Array

考虑操作分块。

考虑每个块只会有\(B\)个操作。

那么我们把操作的点作为黑点。

我们发现其黑点的个数只有\(O(B)\)个。

那么我们把一串白色的点给他全合并,则其是静态的。

其这样的白色链也为\(O(B)\)

操作二,我们直接\(O(B)\)暴力更改其所在环的白链的\(\Delta\)

操作三,我们直接\(O(1)\)交换两黑点所在的置换环的答案。

查询时我们直接查询每个白链其在二分\([l,r]\)的答案以及暴力枚举黑点。

其复杂度为\(O(\frac{q}{B} * n + q * (B + B log n) )\)

原文地址:https://www.cnblogs.com/dixiao/p/15683390.html