2023.6 做题笔记

【集训队互测 2022】森林游戏

He_Ren orz

把得分重新定义:先手选一个数,增加得分,后手减小得分,先手想最大化得分,后手想最小化得分。

先考虑一个特殊情况:森林中的每一棵树都是一条链,且每条链从前往后不增。两个人的策略都是选择能选的点中权值最大的,也就是说这个森林等价于将所有权值归并起来形成的一条链。

再考虑在一条不增的链的最前面加上一个比较小的数 \(x\) 会发生什么:设原链头(最大值)为 \(y(x \lt y)\),有如下两种情况:

  • 新的链只有 \(x,y\) 两个数,那么在最优策略下玩家都不希望选 \(x\),即这两个点可以看做对答案有 \((-1)^{n} (x-y)\) 的贡献,我们可以将贡献加到答案里并删去这两个节点。
  • \(y\) 后面还有一个数,令这个数为 \(z\),玩家会主动去选 \(x\),当且仅当他希望得到 \(z\),也就是说,\(x \rightarrow y \rightarrow z\) 这条链等价于一个权值为 \(x-y+z\) 的节点。

我们可以利用这几条结论解决原问题,总体思想是将树转化成一条不增的链,具体的,在处理节点 \(u\) 的时候先将它的每个儿子的子树变成一条不增的链,然后将这些链启发式合并起来,再尝试加入 \(A_u\),不断和链首和链的第二个节点合并,复杂度 \(O(n \log^2 n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n;
int a[200005];
vector <int> g[200005];
priority_queue <int> pq[200005];
int ans2;
void dfs(int u,int fa)
{
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(v==fa) continue;
		dfs(v,u);
		if(pq[u].size()<pq[v].size()) swap(pq[u],pq[v]);
		while(pq[v].size()) pq[u].push(pq[v].top()),pq[v].pop();
	}
	bool in=0;
	while(1)
	{
		if(!pq[u].size()||a[u]>=pq[u].top()) 
		{
			pq[u].push(a[u]);
			in=1;
			break;
		}
		if(pq[u].size()<2) break;
		int x=pq[u].top();
		pq[u].pop();
		int y=pq[u].top();
		pq[u].pop();
		a[u]=a[u]+y-x;
	}
	if(!in)
	{
		if(n%2==0) ans2+=a[u],ans2-=pq[u].top();
		else ans2-=a[u],ans2+=pq[u].top();
		pq[u].pop();
	}
}
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	int sum=0;
	for(int i=1;i<=n;i++) sum+=a[i];
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].pb(v),g[v].pb(u);
	}
	dfs(1,-1);
	int op=0;
	while(pq[1].size())
	{
		if(!op) ans2+=pq[1].top();
		else ans2-=pq[1].top();
		op^=1;
		pq[1].pop();
	}
	cout<<(sum+ans2)/2;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【GDCPC 2023】Swapping Operation

把令前缀 \(\&\) 减小的位置 \(B=\{b_1,b_2,\cdots, b_{|B|}\}\) 拿出来,令后缀 \(\&\) 减小的位置 \(C=\{c_1,c_2,\cdots ,c_{|C|}\}\) 拿出来,枚举 \(F(A)\) 中的分界点 \(k\),此时只有在左右两边各选择一个才会对答案有影响,令在左边选择的位置为 \(i\),右边选择的位置为 \(j\),分如下几种情况讨论。

  • \(i \in B, j \in C\),令 \(V\) 是值域,由于 \(|B|,|C| \le \log V\),这部分可以暴力枚举计算。
  • \(i \notin B,j \notin C\),这样交换会使得前一半的 \(\&\) 不增,后一半也不增,可以不考虑。
  • \(i \in B, j \notin C\),事实上这种情况,无论从右面拿什么数出来,后面的 \(\&\) 都不会改变,令 \(g(l,r)\) 表示 \([l,r]\)\(\&\),我们要在后面选出一个不在 \(C\) 里面的数 \(x\),最大化 \(g(1,i-1) \& g(i+1,k) \& x\),这个通过 Trie 等常见套路不方便维护。观察到固定 \(i\) 后,也只会有 \(O(\log V)\)\(k\)\(g(1,i-1) \& g(i+1,k)\) 改变,改变的时候暴力重算,或者暴力更新这 \(O(\log^2 V)\) 个可能的 \(g(1,i-1) \& g(i+1,k)\) 即可。
  • \(i \notin B,j \in C\),和上面是对称的情况,不再赘述。

用 st 表维护 \(g(l,r)\) 可以做到 \(O(n \log^2 V)\)

Code
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#include<bits/stdc++.h>
using namespace __gnu_pbds;
using namespace std;
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,a[100005];
int Lg[100005],st[17][100005];
void build()
{
	Lg[1]=0,Lg[2]=1;
	for(int i=3;i<=n;i++) Lg[i]=Lg[i/2]+1;
	for(int i=1;i<=n;i++) st[0][i]=a[i];
	for(int k=1;k<17;k++) for(int i=1;i+(1<<k)-1<=n;i++) 
		st[k][i]=(st[k-1][i]&st[k-1][i+(1<<(k-1))]);
}
int query(int l,int r)
{
	if(l>r) return (1<<30)-1;
	int s=Lg[r-l+1];
	return (st[s][l]&st[s][r-(1<<s)+1]);
}
gp_hash_table <int,int> ma;
vector <int> pre,suf,vals;
bool isp[100005],iss[100005];
int ans=0;
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],isp[i]=iss[i]=0;
	ans=0;
	build();
	ma.clear(),pre.clear(),suf.clear(),vals.clear();
	pre.pb(1),isp[1]=1;
	for(int i=2;i<=n;i++) if(query(1,i)!=query(1,i-1)) pre.pb(i),isp[i]=1;
	suf.pb(n),iss[n]=1;
	for(int i=n-1;i>=1;i--) if(query(i,n)!=query(i+1,n)) suf.pb(i),iss[i]=1;
	reverse(suf.begin(),suf.end());
	for(int i=0;i<pre.size();i++) 
	{
		int x=pre[i];
		vals.pb(query(1,x-1));
		for(int j=x+1;j<=x;j++) if((query(1,x-1)&query(x+1,j))!=(query(1,x-1)&query(x+1,j-1))) vals.pb(query(1,x-1)&query(x+1,j));
	}
	for(int k=1;k<n;k++)
	{
		ans=max(ans,query(1,k)+query(k+1,n));
		for(int i=0;i<pre.size()&&pre[i]<=k;i++) for(int j=suf.size()-1;j>=0&&suf[j]>k;j--)
		{
			int x=pre[i],y=suf[j];
		//	cout<<"try: "<<k<<" "<<x<<" "<<y<<endl;
		//	cout<<(query(1,x-1)&query(x+1,k)&a[y])<<" "<<(query(k+1,y-1)&query(y+1,n)&a[x])<<endl;
			ans=max(ans,(query(1,x-1)&query(x+1,k)&a[y])+(query(k+1,y-1)&query(y+1,n)&a[x]));
		}
	}
//	for(int i=1;i<=n;i++) cout<<isp[i]<<" "<<iss[i]<<"\n";
	for(int k=n-1,flg=0;k>=1;k--)
	{
		if(!iss[k+1]) 
		{
			flg=1;
			for(int i=0;i<vals.size();i++) ma[vals[i]]=max(ma[vals[i]],(vals[i]&a[k+1]));
		}
		if(flg)
		{
			for(int i=0;i<pre.size()&&pre[i]<=k;i++)
			{
				int x=pre[i];
				int v=(query(1,x-1)&query(x+1,k));
				ans=max(ans,(v&ma[v])+(query(k+1,n)&a[x]));
			}
		}
	}
	reverse(a+1,a+1+n);
	for(int i=1;i<=n;i++) isp[i]=iss[i]=0;
	build();
	ma.clear(),pre.clear(),suf.clear(),vals.clear();
	pre.pb(1),isp[1]=1;
	for(int i=2;i<=n;i++) if(query(1,i)!=query(1,i-1)) pre.pb(i),isp[i]=1;
	suf.pb(n),iss[n]=1;
	for(int i=n-1;i>=1;i--) if(query(i,n)!=query(i+1,n)) suf.pb(i),iss[i]=1;
	reverse(suf.begin(),suf.end());
	for(int i=0;i<pre.size();i++) 
	{
		int x=pre[i];
		vals.pb(query(1,x-1));
		for(int j=x+1;j<=x;j++) if((query(1,x-1)&query(x+1,j))!=(query(1,x-1)&query(x+1,j-1))) vals.pb(query(1,x-1)&query(x+1,j));
	}
	for(int k=n-1,flg=0;k>=1;k--)
	{
		if(!iss[k+1]) 
		{
			flg=1;
			for(int i=0;i<vals.size();i++) ma[vals[i]]=max(ma[vals[i]],(vals[i]&a[k+1]));
		}
		if(flg)
		{
			for(int i=0;i<pre.size()&&pre[i]<=k;i++)
			{
				int x=pre[i];
				int v=(query(1,x-1)&query(x+1,k));
				ans=max(ans,(v&ma[v])+(query(k+1,n)&a[x]));
			}
		}
	}
	cout<<ans<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
	cin>>_;
	while(_--) solve();
	return 0;
}

【GDCPC 2023】Classic Problem

相较于 \(n\) 的范围,图中只有很少一部分点拥有一条特殊边权的邻边,将这些点称为”特殊点“,将其余点称为”一般点“。

只有 \(2m\) 个特殊点和 \(2m+1\) 个一般点的连续段,我们可以将一个一般点的连续段缩成一个点(正确性证明:Kruskal),即得到了一个点数为 \(O(m)\) 的图,可以接受。

本来我想用 Kruskal 继续做下去的,只要快速找到某两个联通块之间长度为某个数的一条边即可,实际上一个连通块会包含多个连续段,导致两个联通块的结构会特别多,找边会变的异常复杂甚至无法维护。

考虑 Boruvka 算法,对于一般点找到左右两边第一个不在同一连通块内的一般点,这个比较好维护,暴力扫一遍即可,对于特殊点需要暴力枚举特殊边,还需要找到左右两边第一个没有特殊边的点,事实上这个也可以暴力跳,因为总共只会经过 \(O(m)\) 个有特殊边的点。

复杂度 \(O(m \log n)\),有一些细节。

Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
struct DSU
{
	int fa[400015],L[400015],R[400015];
	int find(int x)
	{
		if(x==fa[x]) return x;
		return fa[x]=find(fa[x]);
	}
	void merge(int x,int y)
	{
		int xx=find(x),yy=find(y);
		if(xx!=yy)
		{
			fa[xx]=yy;
			L[yy]=min(L[yy],L[xx]),R[yy]=max(R[yy],R[xx]);
		}
	}
	void init(int n)
	{
		for(int i=0;i<n;i++) fa[i]=L[i]=R[i]=i;
	}
}d1,d2;
int n,m;
int U[100005],V[100005],W[100005];
vector <pii > vec;
vector <int> bad;
int getid(int u)
{
	return upper_bound(vec.begin(),vec.end(),mp(u+1,-1))-vec.begin()-1;
}
int calcd_p(int x,int y)
{
	return abs(x-y);
}
int calcd(int u,int v)
{
	return min(min(calcd_p(vec[u].fi,vec[v].fi),calcd_p(vec[u].fi,vec[v].se)),min(calcd_p(vec[u].se,vec[v].fi),calcd_p(vec[u].se,vec[v].se)));
}
int pre[400015],nxt[400015];
int to[400015],val[400015];
vector <pii > g[400015];
map <pii,int> has;
void solve()
{
	cin>>n>>m;
	
	if(!m)
	{
		cout<<n-1<<"\n";
		return;
	}
	has.clear();
	for(int i=1;i<=m;i++) cin>>U[i]>>V[i]>>W[i];
	vec.clear(),bad.clear();
	for(int i=1;i<=m;i++) bad.pb(U[i]),bad.pb(V[i]);
	sort(bad.begin(),bad.end());
	bad.resize(unique(bad.begin(),bad.end())-bad.begin());
	if(bad[0]>1) vec.pb(mp(1,bad[0]-1));
	ll ans=0;
	for(int i=0;i<bad.size();i++)
	{
		vec.pb(mp(bad[i],bad[i]));
		if(i+1<bad.size()&&bad[i]+1<=bad[i+1]-1) vec.pb(mp(bad[i]+1,bad[i+1]-1));
		if(i+1==bad.size()&&bad[i]+1<=n) vec.pb(mp(bad[i]+1,n));
	}
	d1.init(vec.size()),d2.init(vec.size());
	for(int i=0;i<vec.size();i++) ans+=vec[i].se-vec[i].fi,g[i].clear();
	for(int i=1;i<=m;i++)
	{
		int u=getid(U[i]),v=getid(V[i]);
		has[mp(u,v)]=has[mp(v,u)]=1;
//		cout<<"ban: "<<u<<" "<<v<<"\n";
		g[u].pb(mp(v,W[i])),g[v].pb(mp(u,W[i]));
	}
	int N=vec.size();
//	for(int i=0;i<N;i++) sort(g[i].begin(),g[i].end()),cout<<vec[i].fi<<" "<<vec[i].se<<" "<<g[i].size()<<"\n"; 
	while(1)
	{
		bool ok=1;
		for(int i=0;i<N;i++) if(d1.find(i)!=d1.find(0)) 
		{
			ok=0;
			break;
		}
		for(int i=0;i<N;i++) pre[i]=nxt[i]=val[i]=to[i]=-1,val[i]=inf;
		if(ok) break;
		for(int i=0;i<N;i++)
		{
			int j=i;
			while(1)
			{
				if(d1.find(j)!=d1.find((j+1)%N)) 
				{
					nxt[j]=(j+1)%N;
					break;
				}
				j=(j+1)%N;
				if(nxt[j]!=-1) break;
			}
			for(int l=i;l!=j;l=(l+1)%N) nxt[l]=nxt[j];
		//	i=j;
		//	cout<<i<<"\n";
		}
		for(int i=N-1;i>=0;i--)
		{
			int j=i;
			while(1)
			{
				if(d1.find(j)!=d1.find((j+N-1)%N)) 
				{
					pre[j]=(j+N-1)%N;
					break;
				}
				j=(j+N-1)%N;
				if(pre[j]!=-1) break;
			}
			for(int l=i;l!=j;l=(l+N-1)%N) pre[l]=pre[j];
		//	i=j;
		//	cout<<i<<"\n";
		}
		for(int i=0;i<N;i++) if(!g[i].size()) 
		{
			if(calcd(i,nxt[i])<calcd(i,pre[i])) to[i]=nxt[i],val[i]=calcd(i,nxt[i]);
			else to[i]=pre[i],val[i]=calcd(i,pre[i]);
		}
		for(int i=0;i<N;i++) if(g[i].size()) 
		{
			int u=(i+N-1)%N;
			pre[i]=nxt[i]=-1;
			int cnt=0;
			while(1)
			{
		//		cout<<cnt<<"\n";
				while(d1.find(u)==d1.find(i))
				{
					int v=d2.L[d2.find(u)];
					u=(v+N-1)%N;
				}
				cnt++;
				if(cnt>g[i].size()+1) break;
				if(has[mp(i,u)]) 
				{
					u=(u+N-1)%N;
					continue;
				}
				else
				{
					pre[i]=u;
					break;
				}
				
			}
		//	puts("...");
			cnt=0;
			u=(i+1)%N;
			while(1)
			{
				while(d1.find(u)==d1.find(i))
				{
					int v=d2.R[d2.find(u)];
					u=(v+1)%N;
				}
				cnt++;
				if(cnt>g[i].size()+1) break;
				if(has[mp(i,u)]) 
				{
					u=(u+1)%N;
					continue;
				}
				else
				{
					nxt[i]=u;
					break;
				}
				
			}
//			cout<<"... "<<i<<" "<<pre[i]<<" "<<nxt[i]<<"\n";
			val[i]=inf;
			if(pre[i]!=-1&&calcd(pre[i],i)<val[i]) val[i]=calcd(pre[i],i),to[i]=pre[i];
			if(nxt[i]!=-1&&calcd(nxt[i],i)<val[i]) val[i]=calcd(nxt[i],i),to[i]=nxt[i];
			for(int j=0;j<g[i].size();j++)
			{
				int v=g[i][j].fi,w=g[i][j].se;
				if(w<val[i]&&d1.find(i)!=d1.find(v)) val[i]=w,to[i]=v;
			}
		}
		for(int i=0;i<N;i++) if(val[i]<val[d1.find(i)]) val[d1.find(i)]=val[i],to[d1.find(i)]=to[i];
		vector <array<int,3> > ed;
		ed.clear();
		for(int i=0;i<N;i++) if(i==d1.find(i)) ed.pb({i,to[i],val[i]});
		for(int i=0;i<ed.size();i++) if(d1.find(ed[i][0])!=d1.find(ed[i][1])) ans+=1LL*ed[i][2],d1.merge(ed[i][0],ed[i][1]); 
		d2.init(N);
		for(int i=0;i+1<N;i++) if(d1.find(i)==d1.find(i+1)) d2.merge(i,i+1);
	}
	cout<<ans<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
	cin>>_;
	while(_--) solve();
	return 0;
}

【CF280E】Sequence Transformation

这题做法是 dp 导出的!令 \(f_i(x)\) 表示,填了 \([1,i]\)\(y_i\) 填的是 \(x\) 的最小代价,转移:\(f_i(x)=(x-x_i)^2+\min_{y \in [x-b,x-a]} f_{i-1}(y)\)

\(f_1(x)\) 的图像很简单,就是抛物线 \(y=(x-x_1)^2\)\(f_2(x)\) 的图像可以看做将 \(f_1(x)\) 的图像平移,从最低点切开,将右边的部分再平移,中间用一个平台相连,然后整体加上 \((x-x_2)^2\)

\(f_i(x)\) 的图像我们可以看做 \(O(i)\) 段开口向上的抛物线拼接成的下凹函数,证明就是转移的过程,可以利用类似 slope trick 的方法维护转移,暴力的复杂度是 \(O(n^2)\),用平衡树可以做到 \(O(n \log n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define double long double
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const double inf=1000000000000.0;
struct Quad
{
	double l,r,a,b,c;
	double gettop()
	{
		double x=-b/a/2.0;
		if(x<l) return l;
		if(x>r) return r;
		return x;
	}	
	double calc(double x)
	{
		return a*x*x+b*x+c;
	}
	double calcmin()
	{
		return calc(gettop());
	}
	void shft(double d)
	{
		l+=d,r+=d;
		double tb=b,tc=c;
		b=tb-2.0*a*d;
		c=tc-tb*d+a*d*d;
	}
};
Quad quad(double l,double r,double a,double b,double c)
{
	Quad res;
	res.l=l,res.r=r,res.a=a,res.b=b,res.c=c;
	return res;
}
void splt(Quad &x,double dl,double dr)
{
	x.l=max(x.l,dl),x.r=min(x.r,dr);
}
void add(Quad &x,Quad y)
{
	x.a+=y.a,x.b+=y.b,x.c+=y.c;
}
int sz[2];
double tp[6005];
Quad dp[2][20005];
int n;
double A,B,m;
double X[6005],Y[6005];
void solve()
{
	cin>>n>>m;
	cin>>A>>B;
	for(int i=1;i<=n;i++) cin>>X[i];
	int now=0;
	dp[0][0]=quad(1.0,m,1.0,-2.0*X[1],X[1]*X[1]);
	sz[0]=1;
	//cout<<dp[0][0].gettop()<<"\n";
	for(int i=2;i<=n;i++)
	{
		now^=1;
		sz[now]=0;
		double x=-1,y=-1;
		for(int j=0;j<sz[now^1];j++) 
		{
			double t=dp[now^1][j].calcmin();
			if(t<y||y==-1) y=t,x=dp[now^1][j].gettop();
		}
//		cout<<x<<" "<<y<<" "<<endl;
		tp[i]=x;
	//	dp[now^1][0].l=-inf,dp[now^1][sz[now^1]-1].r=inf;
		for(int j=0;j<sz[now^1];j++) 
		{
			Quad tmp=dp[now^1][j];
			tmp.shft(A);
			if(tmp.l<=x+A) 
			{
				dp[now][sz[now]]=tmp,splt(dp[now][sz[now]],1.0,x+A);
				splt(dp[now][sz[now]],1.0,m);
				if(dp[now][sz[now]].l<=dp[now][sz[now]].r) sz[now]++;
			}
		}
		dp[now][sz[now]]=quad(x+A,x+B,0.0,0.0,y);
		splt(dp[now][sz[now]],1.0,m);
		if(dp[now][sz[now]].l<=dp[now][sz[now]].r) sz[now]++;
		for(int j=0;j<sz[now^1];j++) 
		{
			Quad tmp=dp[now^1][j];
			tmp.shft(B);
			if(tmp.r>=x+B) 
			{
				dp[now][sz[now]]=tmp,splt(dp[now][sz[now]],x+B,m);
				splt(dp[now][sz[now]],1.0,m);
				if(dp[now][sz[now]].l<=dp[now][sz[now]].r) sz[now]++;
			}
			
		}
		Quad del=quad(1.0,m,1.0,-2.0*X[i],X[i]*X[i]);
		for(int j=0;j<sz[now];j++) add(dp[now][j],del);//,cout<<dp[now][j].l<<" "<<dp[now][j].r<<" "<<dp[now][j].a<<" "<<dp[now][j].b<<" "<<dp[now][j].c<<"\n";
//		system("pause");
	}
	double x=-1,y=-1;
	for(int j=0;j<sz[now];j++) 
	{
		double t=dp[now][j].calcmin();
		if(t<y||y==-1) y=t,x=dp[now][j].gettop();
	}
	double ans=y;
	Y[n]=x;
	for(int i=n;i>=2;i--)
	{
		if(x<=tp[i]+A) x-=A;
		else if(x<=tp[i]+B) x=tp[i];
		else x-=B;
		Y[i-1]=x;
	}
	for(int i=1;i<=n;i++) printf("%.12Lf ",Y[i]);
	cout<<"\n";
	printf("%.12Lf\n",ans);
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【CF1229D】Wojtek and Card Tricks

一个很符合直觉的事情,固定 \(l\)\(f(l,r)\) 改变的次数不多,证明实际上不需要任何群论:

令目前可以构造出的集合是 \(S\),我们只需要证明,加入一个置换 \(R(R \notin S)\)\(S\) 的大小至少乘以 \(2\)。我们只需要证明:

  • 对于任意 \(P,Q \in S,P \neq Q\),有 \(P \times R \neq Q \times R\)
  • 对于任意 \(P \in S\)\(P \times R \notin S\)

第一个可以反证:假设 \(P \times R = Q \times R\),令 \(P \times R = T\),而我们只能找到一个置换使其 \(\times R=T\),与 \(P \neq Q\) 矛盾。

第二个还是反证:假设 \(P \times R \in S\),令 \(P \times R = T\),由于 \(P,T \in S\),可以得到 \(R \in S\),这与 \(R \notin S\) 矛盾。

枚举 \(l\),找到 \(R\) 后暴力 bfs 即可,复杂度 \(O(nk \space k!\log(k!))\)

Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n,K;
vector <int> A[200005];
vector <int> shuf(vector <int> P,vector <int> Q)
{
	vector <int> R;
	R.clear();
	for(int i=0;i<Q.size();i++) R.pb(P[Q[i]]);
	return R;
}
int ha[1000005];
vector <int> rl[1000005];
int gethash(vector <int> P)
{
	int k=0;
	for(int i=0;i<P.size();i++) k*=10,k+=P[i];
	return ha[k];
}
bool vis[125];
int now[6];
int to[125][125],a[200005];
queue <int> pos[200005];
void solve()
{
	cin>>n>>K;
	for(int i=1;i<=K;i++) now[i]=i;
	int idx=0;
	do
	{
		int k=0;
		for(int i=1;i<=K;i++) k*=10,k+=now[i]-1;
		ha[k]=idx;
	//	cout<<k<<" --- "<<idx<<endl;
		for(int i=1;i<=K;i++) rl[idx].pb(now[i]-1);
		idx++;
	}while(next_permutation(now+1,now+1+K));
	for(int i=0;i<idx;i++) for(int j=0;j<idx;j++) to[i][j]=gethash(shuf(rl[i],rl[j]));
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<K;j++)
		{
			int x;
			cin>>x;
			x--;
			A[i].pb(x);
		}
		pos[gethash(A[i])].push(i);
		a[i]=gethash(A[i]);
	}
//	for(int j=0;j<idx;j++) cout<<pos[j].size()<<"\n";
	ll ans=0;
	for(int l=1;l<=n;l++)
	{
		for(int j=0;j<idx;j++) if(pos[j].size()&&pos[j].front()<l) pos[j].pop();
		memset(vis,0,sizeof(vis));
		int u=0;
		vis[0]=1;
		for(int j=0;j<6;j++) u=to[u][a[l]],vis[u]=1;
		int lst=l;
		vector <int> nw;
		nw.clear();
		nw.pb(a[l]);
		while(1)
		{
			int nxt=n+1;
			for(int j=0;j<idx;j++) if(!vis[j]&&pos[j].size()&&pos[j].front()<nxt) nxt=pos[j].front();
			int c=0;
			for(int j=0;j<idx;j++) if(vis[j]) c++;
			ans+=1LL*(nxt-lst)*c;
		//	cout<<l<<" "<<lst<<" "<<nxt<<" "<<c<<endl;
		//	for(int j=0;j<idx;j++) cout<<pos[j].size()<<"\n";
			if(nxt==n+1) break;
			nw.pb(a[nxt]);
			lst=nxt;
			queue <int> q;
			while(q.size()) q.pop();
			for(int j=0;j<idx;j++) if(vis[j]) q.push(j);
			while(q.size())
			{
				int v=q.front();
				q.pop();
				for(int j=0;j<nw.size();j++) if(!vis[to[v][nw[j]]]) vis[to[v][nw[j]]]=1,q.push(to[v][nw[j]]);
			}
		}
	}
	cout<<ans<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UOJ Round 25】设计草图

先考虑一个区间怎么判断合法性:将左括号看成 \(+1\),右括号看成 \(-1\),则合法括号串的条件可以写成:所有前缀和 \(\ge 0\) 且和为 \(0\)。一个等价的表述方法是:所有前缀和 \(\ge 0\) 且所有后缀和 \(\le 0\)。贪心的替换方法是将前面若干个问号替换成左括号,后面的若干个问号替换成右括号,那么带问号的合法条件可以写成:

  • 将问号和左括号看成 \(+1\),右括号看成 \(-1\),所有前缀和 \(\ge 0\)
  • 将问号和右括号看成 \(+1\),左括号看成 \(-1\),所有后缀和 \(\ge 0\)

对于第一个条件,固定左端点之后,满足条件的右端点是一段前缀,同理对于第二个条件,固定右端点之后满足条件的左端点是一段后缀,确定这个边界可以用 ST 表 + 二分,做完之后发现答案是一个二维数点的形式,用 BIT 维护即可,复杂度 \(O(n \log n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
char s[1000005];
int a[1000005],b[1000005];
int n;
int st[20][1000005],Lg[1000005];

struct BIT
{
	int t[1000005];
	int lowbit(int x)
{
	return x&(-x);
}
void update(int x,int y)
{
	for(int i=x;i<=n;i+=lowbit(i)) t[i]+=y;
}
int query(int x)
{
	int res=0;
	for(int i=x;i>=1;i-=lowbit(i)) res+=t[i];
	return res;
}
}t[2];
void build()
{
	for(int k=1;k<20;k++) for(int i=1;i+(1<<k)-1<=n;i++) st[k][i]=min(st[k-1][i],st[k-1][i+(1<<(k-1))]);
}
int query(int l,int r)
{
	int ss=Lg[r-l+1];
	return min(st[ss][l],st[ss][r-(1<<ss)+1]);
}
vector <int> dc[1000005];
void solve()
{
	cin>>(s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;i++) a[i]=a[i-1]+(s[i]!=')'?1:-1),st[0][i]=a[i];
	build();
	for(int i=n;i>=1;i--) b[i]=b[i+1]+(s[i]!='('?1:-1);
	Lg[1]=0,Lg[2]=1;
	for(int i=3;i<=n;i++) Lg[i]=Lg[i/2]+1;
	for(int i=1;i<=n;i++)
	{
		int L=i,R=n,res=n+1;
		while(L<=R)
		{
			int mid=(L+R)>>1;
			if(query(i,mid)<a[i-1]) res=mid,R=mid-1;
			else L=mid+1;
		}
		dc[res].pb(i);
	//	cout<<i<<" "<<res<<endl;
	}
	for(int i=1;i<=n;i++) st[0][i]=b[i];
	build();
	ll ans=0;
	for(int i=1;i<=n;i++)
	{
		t[i&1].update(i,1);
		for(int j=0;j<dc[i].size();j++) t[dc[i][j]&1].update(dc[i][j],-1);
		int L=1,R=i,res=0;
		while(L<=R)
		{
			int mid=(L+R)>>1;
	//		cout<<mid<<" "<<i<<" "<<query(mid,i)<<"\n";
			if(query(mid,i)<b[i+1]) res=mid,L=mid+1;
			else R=mid-1;
		}
	//	cout<<res<<" "<<i<<endl;
		ans+=1LL*(t[(i&1)^1].query(i)-t[(i&1)^1].query(res));
	}
	cout<<ans;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UOJ Round 25】见贤思齐

这里写一种比较简单的倍增做法。

\(p(i)\) 表示题目中的 \(p_i\)\(dp(i,j)\) 表示经过 \(i\) 时刻,\(a_j\) 的值,\(dp(i,j)\) 可以由 \(dp(i-1,p(i))\) 确定,具体的,\(dp(i,j)=\max\{a_i,\min\{a_i+j,dp(i-1,p(i))+1\}\}\),对每个 \(k\),令 \(a_0,a_1,\cdots,a_{2^k-1}\) 表示 \(i,p(i),p^2(i),\cdots,p^{2^k-1}(i)\),设 \(g(k,i)\) 表示,\(\min_{0 \le j \le 2^k-1}\{a_{p^{j}(i)}+2^k\}\)\(h(k,i)\) 表示 \(\max_{0 \le j \le 2^k-1}\{a_{p^j(i)}+j\}\),这两个可以很轻松的利用倍增在 \(O(n \log n)\) 内算出来。

这里我们可以看成,\(g(k,i)\) 表示的是 \(dp(2^k,i)\) 的上界,\(h(k,i)\) 表示的是 \(dp(2^k,i)\) 的下界,我们可以在倍增表上很轻松的找出一段最长的,下界小于等于上界的路径,超出这条路径的部分是无用的。得到路径之后,我们不需要考虑上界,算答案变的很容易,复杂度 \(O((n+q)\log d)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=2e9;
int n,Q;
int a[200005],P[200005];
int F[20][200005],G[20][200005],H[20][200005];
void solve()
{
	cin>>n>>Q;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>P[i],F[0][i]=P[i],G[0][i]=H[0][i]=a[i];
	for(int k=1;k<20;k++) for(int i=1;i<=n;i++)
		F[k][i]=F[k-1][F[k-1][i]],G[k][i]=min(G[k-1][i],G[k-1][F[k-1][i]]),H[k][i]=max(H[k-1][i],H[k-1][F[k-1][i]]+(1<<(k-1)));
	while(Q--)
	{
		int u,d;
		cin>>u>>d;
		int l=0,r=inf,now=0;
		for(int k=19;k>=0;k--) if((1<<k)+now<=d)
		{
			int ll=max(l,H[k][u]+now),rr=min(r,G[k][u]+d);
			if(ll>rr) continue;
			l=ll,r=rr,now+=(1<<k),u=F[k][u];
		}
		l=max(l,min(r,a[u]+d));
		cout<<l<<"\n";
	}
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UOJ Round 25】装配序列

首先可以发现,\(x \ge n^2\) 时答案一定是 \(n\)

有一个贪心的想法,假设我们有 \(k\) 个不增子序列,子序列末尾依次为 \(b_1,b_2, \cdots, b_k\),新加入一个数 \(x\) 的时候我们找到一个最小的 \(\ge x\)\(b_i\),将这个 \(b_i\) 改为 \(x\),若找不到则新建一个序列,只需要记录哪些时刻新建了就可以求出答案。观察这个过程,由于 \(k \le n\),而每个数最多从 \(n\) 变成 \(1\),也就是说,我们开桶记录 \(b\),找的时候暴力找,可以做到 \(O(n^2)\) 的复杂度。

这个做法很没有前途,最小不增序列划分等价于最长上升子序列,考虑从最长上升子序列的角度解决这个问题。

刚刚那个暴力实际上在 \(O(n+A^2)\) 的复杂度解决了值域大小为 \(A\) 的 LIS 问题。

我们记录令 LIS 增加的一些位置,观察到对于每个值,这个值被保存的位置一定是一段前缀中的所有位置(证明可以归纳,感性理解就是越往后令 LIS 增加的难度越大),令值 \(x\) 被保留了 \(cnt_x\) 个,按值从小往大考虑 \(x\):从 \(x\) 在排列中出现的位置 \(pos_x\),开始做如下过程:

\(p\) 表示排列,初始设 \(cnt_x=0\),从 \(pos_x\) 往后扫,扫到第一个 \(y\) 使得 \(cnt_{p_y} \gt cnt_x\),则交换 \(cnt_x,cnt_{p_y}\),原因是这些 \(p_y\) 的位置可以用一个更大的更靠前的数来代替,且这样交换会令答案更优(可以看做后面一些令答案增加的点往前挪了);如果找不到,则将 \(cnt_x\) 增加 \(1\) 并从 \(1\) 开始扫,进行类似的过程直到扫描到 \(pos_x\) 为止,理由类似。

暴力做还是 \(O(n^2)\) 的,但是我们可以用线段树二分找到 \(y\),由于一次交换操作 \(cnt_x\) 至少会增大 \(1\),即最多只会交换 \(O(\sqrt n)\) 次,复杂度 \(O(n \sqrt n \log n)\)

upd:被叉掉了(悲

Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,Q;
int a[200005],pos[200005];
int t[800005],b[200005];
void update(int id,int l,int r,int x,int d)
{
	if(l==r)
	{
		t[id]=d;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(id<<1,l,mid,x,d);
	else update(id<<1|1,mid+1,r,x,d);
	t[id]=max(t[id<<1],t[id<<1|1]);
}
int findnxt(int id,int l,int r,int x,int y)
{
	if(t[id]<=y) return -1;
	if(l==r) return l;
	int mid=(l+r)>>1;
	if(x<=mid) 
	{
		int L=findnxt(id<<1,l,mid,x,y);
		if(L!=-1) return L;
		return findnxt(id<<1|1,mid+1,r,x,y);
	}
	return findnxt(id<<1|1,mid+1,r,x,y);
}

void solve()
{
	cin>>n>>Q;
	for(int i=1;i<=n;i++) cin>>a[i],pos[a[i]]=i;
	for(int i=1;i<=n;i++)
	{
		int u=pos[i],s=u;
		while(1)
		{
			int v=findnxt(1,1,n,u,b[s]);
	//		cout<<u<<" -- "<<v<<"\n";
			if(u<s&&v>s) break;
			if(v!=-1) swap(b[s],b[v]),update(1,1,n,s,b[s]),update(1,1,n,v,b[v]);
			else
			{
				if(u>=s&&s>1) b[s]++,update(1,1,n,s,b[s]),u=1;
				else
				{
					if(s==1) b[s]++,update(1,1,n,s,b[s]);
					break;
				}
			}
		}
	//	for(int j=1;j<=n;j++) cout<<b[j]<<" ";
	//	cout<<"\n";
//		system("pause");
	}
	vector <ll> ans;
	ans.clear();
	for(int i=1;i<=n;i++) for(int j=0;j<b[i];j++) ans.pb(1LL*i+1LL*j*n);
	sort(ans.begin(),ans.end());
	while(Q--)
	{
		ll x;
		cin>>x;
		int r=upper_bound(ans.begin(),ans.end(),x)-ans.begin();
		cout<<r<<"\n";
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【CF1500F】Cupboards Jumps

做差分:令 \(a_i\) 为答案,\(d_i=a_{i+1}-a_i(1 \le i \lt n)\),则有 \(\max\{|d_i|,|d_{i+1}|,|d_i+d_{i+1}|\}=w_i\)

考虑 dp,令 \(dp(i,j)\) 表示确定了 \(d\) 的前 \(i\) 项,\(d_i=j\) 是否可行,转移方程是容易的,考虑优化:

  • \(dp(i,j)\) 写成集合的形式,令 \(S_i\) 表示 \(dp(i,j)=1\)\(j\) 的集合。
  • 首先将 \(S_i\)\([-w_i,w_i]\) 之外的数删掉。
  • 如果 \(w_i \in S_i\),则 \(S_{i+1}=[-w_i,w_i]\)
  • 否则 \(S_{i+1}\) 可以看做,将 \(S_i\) 的所有数 \(x\) 变成 \(w_i-x\),然后将 \(w_i\) 插入 \(S_{i+1}\)

由这个过程可以看出来,我们只需要关心 \(S\) 中每个数的绝对值,正负是对称的,对于 \(w_i-x\) 的变换,可以通过打标记的 trick 实现,而每一轮只会加入一个数,直接用 set 维护是 \(O(n \log n)\) 的。

考虑还原方案,我们对于每一个位置随便记录一个合法的值,然后从后往前还原:若 \(|d_i| \lt w_i\),则 \(d_{i-1}\) 可以唯一确定,若 \(|d_i|=w_i\),则将 \(d_{i-1}\) 放上记录的值,根据 \(d_i\) 确定正负号。

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n,B;
int d[1000005];
set <int> S;
int vL,vR;
int tag1,tag2;
int dec(int x)
{
	if(!tag1) return x+tag2;
	return -x+tag2;
}
int enc(int x)
{
	if(!tag1) return x-tag2;
	return -x+tag2;
}
int ok[1000005],ok2[1000005],ans[1000005];
void solve()
{
	cin>>n>>B;
	for(int i=2;i<n;i++) cin>>d[i];
	vL=0,vR=d[2];
	for(int i=2;i<n;i++)
	{
		if(!S.size()&&vL>vR)
		{
			cout<<"NO\n";
			return;
		}
		ok[i]=dec(min((vL<=vR?vL:INF),(S.size()?(*S.begin()):INF)));
		ok2[i]=dec(max((vL<=vR?vR:INF),(S.size()?(*prev(S.end())):-INF)));
		if(vL<=enc(d[i])&&enc(d[i])<=vR) S.clear(),tag1=tag2=0,vL=0,vR=d[i],ok[i]=d[i];
		else if(S.count(enc(d[i]))) S.clear(),tag1=tag2=0,vL=0,vR=d[i],ok[i]=d[i];
		else
		{
			tag1^=1,tag2=d[i]-tag2;
			S.insert(enc(d[i]));
		}
		if(i==n-1) break;
		int L=enc(0),R=enc(d[i+1]);
		if(L>R) swap(L,R);
		vL=max(vL,L),vR=min(vR,R);
		while(S.size()&&(*S.begin())<L) S.erase(S.begin());
		while(S.size()&&(*prev(S.end()))>R) S.erase(prev(S.end()));
	}
	if(!S.size()&&vL>vR)
	{
		cout<<"NO\n";
		return;
	}
	ok[n]=dec(min((vL<=vR?vL:INF),(S.size()?(*S.begin()):INF)));
	ok2[n]=dec(max((vL<=vR?vR:INF),(S.size()?(*prev(S.end())):-INF)));
	ans[n]=ok[n];
	for(int i=n-1;i>=2;i--)
	{
		if(abs(ans[i+1])!=d[i]&&abs(ok[i])!=d[i])
		{
			ans[i]=abs(d[i])-abs(ans[i+1]);
			if(ans[i+1]<0) ans[i]*=-1;
		} 
		else
		{
			ans[i]=ok[i];
			if((ans[i]<0&&ans[i+1]<0)||(ans[i]>0&&ans[i+1]>0)) ans[i]*=-1;
		}
	}
//	for(int i=2;i<=n;i++) cout<<ans[i]<<" "<<ok[i]<<" "<<ok2[i]<<"\n";
//	cout<<"\n";
	for(int i=2;i<=n;i++) ans[i]+=ans[i-1];
	int minn=0;
	for(int i=1;i<=n;i++) minn=min(minn,ans[i]);
	cout<<"YES\n";
	for(int i=1;i<=n;i++) ans[i]-=minn,cout<<ans[i]<<" ";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【神秘题】

题意:给定 \(n\) 个点,坐标为 \(x_i\),你需要将每个点往左或往右移动 \(d\) 个长度,不能不移,可以重合,移动完后你要放置一些闭区间覆盖所有点,放置 \([l,r]\) 的代价为 \(a+b(r-l)\)\(1 \le n,d,x_i \le 150,1 \le a,b \le 10^6\)

问题等价于,所有点留在原位或向右移动 \(2d\) 个长度,为方便起见下文中用 \(d\) 表示 \(2d\)\(d\) 比较小的情况下可以直接状压,\(d\) 大的话考虑若干条这样的链:

\(1,d+1,2d+1,\cdots\)

\(2,d+2,2d+2,\cdots\)

\(3,d+3,3d+3,\cdots\)

不同链之间在判断方案的合法性的时候是独立的,而 \(d\) 比较大的话链长比较小,可以按照 \(1,d+1,2d+1,\cdots,2,d+2,2d+2,\cdots\) 的顺序 dp,状态记录的内容类似轮廓线 dp,注意 \(d,d+1\) 等位置是连续的,所以需要枚举第一行的覆盖状态,注意特判 \(a \lt b\)

两个做法拼起来就可以通过了,我设置的阈值是 \(d \le 18\) 选用状压,否则选用轮廓线 dp。

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,d,A,B;
int has[1005];
int f[2][1<<18];
void solve()
{
	cin>>n>>d>>A>>B;
	B=min(A,B);
	d*=2;
	int N=0;
	for(int i=1;i<=n;i++)
	{
		int x;
		cin>>x;
		has[x]=1;
		N=max(N,x+d);
	}
	if(d<=18) // debuging
	{
		memset(f,0x3f,sizeof(f));
		f[0][0]=0;
		int nw=0;
		for(int i=1;i<=N;i++)
		{
			nw^=1;
			for(int mask=0;mask<(1<<d);mask++) f[nw][mask]=inf;
			for(int mask=0;mask<(1<<d);mask++) if(f[nw^1][mask]<inf)
			{
				if((i-d<=0||!has[i-d])||(mask&1)) f[nw][mask>>1]=min(f[nw][mask>>1],f[nw^1][mask]);
				f[nw][(mask>>1)|(1<<(d-1))]=min(f[nw][(mask>>1)|(1<<(d-1))],f[nw^1][mask]+((mask&(1<<(d-1)))?B:A));
			}
		}
		int ans=inf;
		for(int mask=0;mask<(1<<d);mask++) ans=min(ans,f[nw][mask]);
		cout<<ans;
		return;
	}
	int len=N/d;
//	for(int i=1;i<=d;i++) for(int j=0;j*d+i<=N;j++) if(has[j*d+i]) msk[i]|=(1<<(j+1));
//	cout<<N<<" "<<len+1<<"\n";
//	int f[2][1<<(len+1)];
	int ans=inf;
	for(int m0=0;m0<(1<<(len+1));m0++)
	{
		bool valid=1;
		for(int j=0;j*d+1<=N;j++) if(has[j*d+1]&&(!(m0&(1<<j)))&&(!(m0&(1<<(j+1)))))
		{
			valid=0;
			break;
		}
		if(!valid) continue;
		for(int mask=0;mask<(1<<(len+1));mask++) f[0][mask]=f[1][mask]=inf;
		int nw=0;
		f[0][m0]=__builtin_popcount(m0)*A;
	//	cout<<m0<<"\n";
		for(int i=2;i<=d;i++) for(int j=0;j<=len;j++)
		{
			int pos=(j-1)*d+i;
			nw^=1;
			for(int mask=0;mask<(1<<(len+1));mask++) f[nw][mask]=inf;
			for(int mask=0;mask<(1<<(len+1));mask++) if(f[nw^1][mask]<inf)
			{
				for(int t=0;t<2;t++)
				{
					int mask2=mask;
					mask2&=((1<<(len+1))-1)^(1<<j);
					mask2|=(t<<j);
					if(j&&has[pos]&&(!(mask2&(1<<j)))&&(!(mask2&(1<<(j-1))))) continue;
					int con=f[nw^1][mask];
					if(t) 
					{
						if(mask&(1<<j)) con+=B;
						else con+=A;
						if(i==d&&j<len&&(m0&(1<<(j+1)))) con-=A-B;  
					}
					f[nw][mask2]=min(f[nw][mask2],con);
				}
			}
		}
		for(int mask=0;mask<(1<<(len+1));mask++) ans=min(ans,f[nw][mask]);
	}
	cout<<ans;
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【THUSCH2017】换桌

显然的一个最大权匹配的模型,由于给的是区间,考虑用线段树或 ST 表优化建图,我们对于所有 \(m\) 种位置拿出来,每一种位置考虑向左走和向右走,总共建立 \(2m\) 个 ST 表,拆费用有些细节。

实际上可以做到线性点和线性边,考虑分块,块长 \(O(\log n)\),块内前后缀优化建图,块之间建立 ST 表,由于数据随机,两个端点落在同一块内的概率是 \(O(\frac{\log n}{n})\) 级别的,这个可以暴力,其余情况连两个前后缀和一段 ST 表的区间,这个做法虽然是线性的点或边,但常数特别大。

蚌的是直接 \(O(n^2m)\) 条边的做法可以直接过去:对于桌子间暴力连,对于同桌的座位间连两个环,跑的速度也还行。

模拟赛把时限开到 1s,我不好评价,这样做没有任何意义,很难将大常数线性,带 \(\log\)\(n^2m\) 区分开,还会因为费用流跑的慢被卡出现随机区分,放过裸的费用流实现应该是出题人最基本的素质。

Code
/*
Things to notice:
1. do not calculate useless values
2. do not use similar names
 
Things to check:
1. submit the correct file
2. time (it is log^2 or log)
3. memory
4. prove your naive thoughts 
5. long long
6. corner case like n=0,1,inf or n=m
7. check if there is a mistake in the ds or other tools you use
8. fileio in some oi-contest

9. module on time 
10. the number of a same divisor in a math problem
11. multi-information and queries for dp and ds problems
*/
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
template <class Cap, class Cost> struct mcf_graph {
  public:
    mcf_graph() {}
    mcf_graph(int n) : _n(n), g(n) {}

    int add_edge(int from, int to, Cap cap, Cost cost) {
        assert(0 <= from && from < _n);
        assert(0 <= to && to < _n);
        
        int m = (pos.size());
        pos.push_back({from, (g[from].size())});
        int from_id = (g[from].size());
        int to_id = (g[to].size());
        if (from == to) to_id++;
        g[from].push_back(_edge{to, to_id, cap, cost});
        g[to].push_back(_edge{from, from_id, 0, -cost});
        return m;
    }

    struct edge {
        int from, to;
        Cap cap, flow;
        Cost cost;
    };

    edge get_edge(int i) {
        int m = (pos.size());
        assert(0 <= i && i < m);
        auto _e = g[pos[i].first][pos[i].second];
        auto _re = g[_e.to][_e.rev];
        return edge{
            pos[i].first, _e.to, _e.cap + _re.cap, _re.cap, _e.cost,
        };
    }
    std::vector<edge> edges() {
        int m = (pos.size());
        std::vector<edge> result(m);
        for (int i = 0; i < m; i++) {
            result[i] = get_edge(i);
        }
        return result;
    }

    std::pair<Cap, Cost> flow(int s, int t) {
        return flow(s, t, std::numeric_limits<Cap>::max());
    }
    std::pair<Cap, Cost> flow(int s, int t, Cap flow_limit) {
        return slope(s, t, flow_limit).back();
    }
    std::vector<std::pair<Cap, Cost>> slope(int s, int t) {
        return slope(s, t, std::numeric_limits<Cap>::max());
    }
    std::vector<std::pair<Cap, Cost>> slope(int s, int t, Cap flow_limit) {
        assert(0 <= s && s < _n);
        assert(0 <= t && t < _n);
        assert(s != t);
        std::vector<Cost> dual(_n, 0), dist(_n);
        std::vector<int> pv(_n), pe(_n);
        std::vector<bool> vis(_n);
        auto dual_ref = [&]() {
            std::fill(dist.begin(), dist.end(),
                      std::numeric_limits<Cost>::max());
            std::fill(pv.begin(), pv.end(), -1);
            std::fill(pe.begin(), pe.end(), -1);
            std::fill(vis.begin(), vis.end(), false);
            struct Q {
                Cost key;
                int to;
                bool operator<(Q r) const { return key > r.key; }
            };
            std::priority_queue<Q> que;
            dist[s] = 0;
            que.push(Q{0, s});
            while (!que.empty()) {
                int v = que.top().to;
                que.pop();
                if (vis[v]) continue;
                vis[v] = true;
                if (v == t) break;
                for (int i = 0; i < (g[v].size()); i++) {
                    auto e = g[v][i];
                    if (vis[e.to] || !e.cap) continue;
                    Cost cost = e.cost - dual[e.to] + dual[v];
                    if (dist[e.to] - dist[v] > cost) {
                        dist[e.to] = dist[v] + cost;
                        pv[e.to] = v;
                        pe[e.to] = i;
                        que.push(Q{dist[e.to], e.to});
                    }
                }
            }
            if (!vis[t]) {
                return false;
            }

            for (int v = 0; v < _n; v++) {
                if (!vis[v]) continue;
                dual[v] -= dist[t] - dist[v];
            }
            return true;
        };
        Cap flow = 0;
        Cost cost = 0, prev_cost_per_flow = -1;
        std::vector<std::pair<Cap, Cost>> result;
        result.push_back({flow, cost});
        while (flow < flow_limit) {
            if (!dual_ref()) break;
            Cap c = flow_limit - flow;
            for (int v = t; v != s; v = pv[v]) {
                c = std::min(c, g[pv[v]][pe[v]].cap);
            }
            for (int v = t; v != s; v = pv[v]) {
                auto& e = g[pv[v]][pe[v]];
                e.cap -= c;
                g[v][e.rev].cap += c;
            }
            Cost d = -dual[s];
            flow += c;
            cost += c * d;
            if (prev_cost_per_flow == d) {
                result.pop_back();
            }
            result.push_back({flow, cost});
            prev_cost_per_flow = d;
        }
        return result;
    }

  private:
    int _n;

    struct _edge {
        int to, rev;
        Cap cap;
        Cost cost;
    };

    std::vector<std::pair<int, int>> pos;
    std::vector<std::vector<_edge>> g;
};

mcf_graph<int,int> mf(40005);
int n,m;
const int B=8;
int L[305][15],R[305][15],id[305][15];
int st[9][305],st2[9][305],Lg[305],idx;
int pre[305],suf[305],cnt[305];
void build(int pos,int op)
{
//	cout<<pos<<" "<<op<<"\n";
	Lg[1]=0,Lg[2]=1;
	for(int i=3;i<=n;i++) Lg[i]=Lg[i/2]+1;
	for(int i=1;i<=n;i+=B)
	{
		for(int j=i;j<=min(n,i+B-1);j++)
		{
			pre[j]=++idx;
			if(op) mf.add_edge(pre[j],id[j][pos],1,2*(j-i));
			else mf.add_edge(pre[j],id[j][pos],1,0);
		}
		cnt[i/B+1]=st2[0][i/B+1]=min(n,i+B-1)-i+1;
		if(op) for(int j=i;j<min(n,i+B-1);j++) mf.add_edge(pre[j+1],pre[j],inf,0),st[0][i/B+1]=pre[min(n,i+B-1)];
		else for(int j=min(n,i+B-1);j>i;j--) mf.add_edge(pre[j],pre[j-1],inf,2),st[0][i/B+1]=pre[min(n,i+B-1)];
		for(int j=i;j<=min(n,i+B-1);j++)
		{
			suf[j]=++idx;
			if(op) mf.add_edge(suf[j],id[j][pos],1,0);
			else mf.add_edge(suf[j],id[j][pos],1,2*(min(n,i+B-1)-j));
		}
		if(!op) for(int j=i;j<min(n,i+B-1);j++) mf.add_edge(suf[j],suf[j+1],inf,0);
		else for(int j=i;j<min(n,i+B-1);j++) mf.add_edge(suf[j],suf[j+1],inf,2);
	}
	for(int k=1;k<9;k++) for(int i=1;i+(1<<k)-1<=(n-1)/B+1;i++)
	{
		st2[k][i]=st2[k-1][i]+st2[k-1][i+(1<<(k-1))];
		st[k][i]=++idx;
		mf.add_edge(st[k][i],st[k-1][i],inf,(op^1)*st2[k-1][i+(1<<(k-1))]*2);
		mf.add_edge(st[k][i],st[k-1][i+(1<<(k-1))],inf,op*st2[k-1][i]*2); 
	}
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
	{
		int l=L[i][j],r=R[i][j];
		int u=(i-1)*m+j;
		if(op&&max(l,i)<=r) 
		{
			l=max(l,i);
			int bl=(l-1)/B+1,br=(r-1)/B+1;
			if(bl==br)
			{
				for(int k=l;k<=r;k++) mf.add_edge(u,id[k][pos],inf,abs(i-k)*2+min(m-abs(j-pos),abs(j-pos)));
				continue;
			}
			int del=min(m-abs(j-pos),abs(j-pos));
			mf.add_edge(u,suf[l],inf,2*(l-i)+del),bl++;
			mf.add_edge(u,pre[r],inf,2*((br-1)*B+1-i)+del),br--;
			if(bl>br) continue;
			int ss=Lg[br-bl+1];
			mf.add_edge(u,st[ss][bl],inf,del+((bl-1)*B+1-i)*2);
			mf.add_edge(u,st[ss][br-(1<<ss)+1],inf,del+((br-(1<<ss)+1-1)*B+1-i)*2);
		}
		if(!op&&l<=min(r,i)) 
		{
			r=min(r,i);
			int bl=(l-1)/B+1,br=(r-1)/B+1;
			if(bl==br)
			{
				for(int k=l;k<=r;k++) mf.add_edge(u,id[k][pos],inf,abs(i-k)*2+min(m-abs(j-pos),abs(j-pos)));
				continue;
			}
			int del=min(m-abs(j-pos),abs(j-pos));
			mf.add_edge(u,suf[l],inf,2*(i-(bl*B))+del),bl++;
			mf.add_edge(u,pre[r],inf,2*(i-r)+del),br--;
			if(bl>br) continue;
			int ss=Lg[br-bl+1];
			int tmp=bl+(1<<ss)-1;
			mf.add_edge(u,st[ss][bl],inf,del+(i-tmp*B)*2);
			tmp=br;
			mf.add_edge(u,st[ss][br-(1<<ss)+1],inf,del+(i-tmp*B)*2);
		}
	}
	
}
void solve()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>L[i][j],L[i][j]++;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>R[i][j],R[i][j]++;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) idx++,mf.add_edge(0,idx,1,0);
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) id[i][j]=++idx;
	for(int i=1;i<=m;i++) build(i,0),build(i,1);
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) mf.add_edge(id[i][j],idx+1,1,0);
//	cout<<F::eid<<" "<<idx<<"\n";
	pii r=mf.flow(0,idx+1);
//	cout<<r.fi<<" "<<r.se<<"\n";
	
	if(r.fi!=n*m) cout<<"no solution\n";
	else cout<<r.se<<"\n";
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【LibreOJ β Round #3】绯色 IOI(悬念)

吐槽:这个 trick(二分图匹配状物,一侧的点和另一侧侧至多两个点连边),四月(省选),五月(ccpc final),六月(这个题),见过三次了。

对于左侧每个点,会向右边 \(1\)\(2\) 个点连边,建立新图,在这 \(2\) 个点之间连一条无向边(如果只有 \(1\) 个点就连一个自环),给新图中每条无向边,每个方向都有一个代价,要求每个点入度均 \(\le 1\),最大化代价之和。

由于原图中存在最大匹配使得左侧点都被匹配完,所以新图中每个连通分量都是树或基环树。

对于基环树,情况比较简单,环上的边有顺时针或逆时针两种定向方法,对于环外面的边只能朝外面定向,总共只有两种方法,维护是容易的,注意特判自环和二元环。

对于树比较复杂,由于是 \(n-1\) 条边选择 \(n\) 个点,也就是说有一个点不选,确定这个不选的点后每条边都朝着这个点外面定向。我们维护每个点不选带来的代价:随便定个根,一条边 \((u,v)\),令 \(u\)\(v\) 的父亲,若定向 \(u \rightarrow v\),则会给 \(v\) 子树外面的点带来贡献,若定向 \(v \rightarrow u\),则会给 \(v\) 子树里面的点带来贡献,在 dfs 序上用线段树维护即可,复杂度 \(O((n+q) \log n)\)

Code
/*
Things to notice:
1. do not calculate useless values
2. do not use similar names
 
Things to check:
1. submit the correct file
2. time (it is log^2 or log)
3. memory
4. prove your naive thoughts 
5. long long
6. corner case like n=0,1,inf or n=m
7. check if there is a mistake in the ds or other tools you use
8. fileio in some oi-contest

9. module on time 
10. the number of a same divisor in a math problem
11. multi-information and queries for dp and ds problems
*/
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int Wei[1000005];
array<int,3> bl[1000005];
struct SolveTree
{
	vector<vector<int> > g;
	vector <int> dfn,R,t,lz,par,dep;
	int n,uid,tid,clk;
	map <int,int> ma;
	void dfs0(int u,int fa)
	{
		dfn[u]=R[u]=++clk,par[u]=fa;
		
		for(int i=0;i<g[u].size();i++)
		{
			int v=g[u][i];
			if(v==fa) continue;
			dep[v]=dep[u]+1;
			dfs0(v,u);
			R[u]=R[v];
		}
	}
	void pushdown(int id)
	{
		if(lz[id])
		{
			lz[id<<1]+=lz[id],lz[id<<1|1]+=lz[id];
			t[id<<1]+=lz[id],t[id<<1|1]+=lz[id];
			lz[id]=0;
		}
	}
	void update(int id,int l,int r,int x,int y,int d)
	{
		if(x<=l&&r<=y) 
		{
			t[id]+=d,lz[id]+=d;
			return;
		}
		pushdown(id);
		int mid=(l+r)>>1;
		if(x<=mid) update(id<<1,l,mid,x,y,d);
		if(y>mid) update(id<<1|1,mid+1,r,x,y,d);
		t[id]=max(t[id<<1],t[id<<1|1]);
	}
	int query(int id,int l,int r,int x,int y)
	{
		if(x<=l&&r<=y) return t[id];
		pushdown(id);
		int mid=(l+r)>>1,res=0;
		if(x<=mid) res=max(res,query(id<<1,l,mid,x,y));
		if(y>mid) res=max(res,query(id<<1|1,mid+1,r,x,y));
		return res;
	}
	void init(vector <array<int,4> > es)
	{
		n=es.size()+1;
		g.resize(n+5);
		t.resize(4*n+5),lz.resize(4*n+5);
		dfn.resize(n+5),R.resize(n+5),par.resize(n+5),dep.resize(n+5);
		for(int i=0;i<es.size();i++)
		{
			int u=es[i][0],v=es[i][1];
			if(!ma[u]) ma[u]=++uid;
			if(!ma[v]) ma[v]=++uid;
			u=ma[u],v=ma[v];
			g[u].pb(v);
			g[v].pb(u);
	//		cout<<es[i][0]<<" --- "<<es[i][1]<<" "<<u<<" --- "<<v<<"\n";
	//		cout<<"tr: "<<tid<<" "<<u<<" "<<v<<" "<<es[i][2]<<" "<<es[i][3]<<"\n";
			bl[es[i][2]]={tid,v,u},bl[es[i][3]]={tid,u,v};
		}
		dfs0(1,-1);
	//	cout<<"...\n";
	//	for(int i=1;i<=n;i++) cout<<dfn[i]<<" "<<R[i]<<" "<<dep[i]<<"\n";
	//	cout<<"...\n";
		for(int i=0;i<es.size();i++)
		{
			int u=es[i][0],v=es[i][1];
			u=ma[u],v=ma[v];
			if(dep[u]>dep[v]) swap(u,v),swap(es[i][2],es[i][3]);
			update(1,1,n,dfn[v],R[v],Wei[es[i][2]]);
			if(dfn[v]>1) update(1,1,n,1,dfn[v]-1,Wei[es[i][3]]);
			if(R[v]<n) update(1,1,n,R[v]+1,n,Wei[es[i][3]]);
		}
	}	
	void upd(int eid,int val)
	{
		int u=bl[eid][1],v=bl[eid][2];
		if(dep[u]>dep[v]) update(1,1,n,dfn[u],R[u],val-Wei[eid]),Wei[eid]=val;
		else
		{
			if(dfn[v]>1) update(1,1,n,1,dfn[v]-1,val-Wei[eid]);
			if(R[v]<n) update(1,1,n,R[v]+1,n,val-Wei[eid]);
			Wei[eid]=val;
		}
	}
	int calc()
	{
		return query(1,1,n,1,n);
	}
}tr[500005];
struct SolveCycle
{
	vector <vector<array<int,3> > > g;
	vector <int> deg;
	int n,uid,cid;
	map <int,int> ma;
	int ans=0,sa=0,sb=0,cyclen=0;
	void init(vector <array<int,4> > es)
	{
		n=es.size()+1;
		g.resize(n+5);
		deg.resize(n+5);
		for(int i=0;i<es.size();i++)
		{
			int u=es[i][0],v=es[i][1];
			if(!ma[u]) ma[u]=++uid;
			if(!ma[v]) ma[v]=++uid;
			u=ma[u],v=ma[v];
			g[u].pb({v,es[i][2],es[i][3]});
			g[v].pb({u,es[i][3],es[i][2]});
		//	cout<<u<<" --- "<<v<<" "<<es[i][2]<<" "<<es[i][3]<<"\n";
			bl[es[i][2]]={-cid,-1,-1},bl[es[i][3]]={-cid,-1,-1};
		}
		for(int i=1;i<=n;i++) deg[i]=g[i].size();
		queue <int> q;
		while(q.size()) q.pop();
		for(int i=1;i<=n;i++) if(deg[i]==1) q.push(i);
		while(q.size())
		{
			int u=q.front();
			deg[u]=-1;
			q.pop();
			for(int i=0;i<g[u].size();i++)
			{
				int v=g[u][i][0];
				if(deg[v]<0) continue;
				deg[v]--;
				if(deg[v]==1) q.push(v);
				ans+=Wei[g[u][i][1]];
				bl[g[u][i][1]]={-cid,0,0};
			}
		}
		for(int s=1;s<=n;s++) if(deg[s]==2) cyclen++;//,cout<<s<<" ";
	//	cout<<"\n"; 
		for(int s=1;s<=n;s++) if(deg[s]==2)
		{
			int tmps=s,lst=-1;
			while(1)
			{
				bool Flg=0;
				for(int i=0;i<g[s].size();i++) if(deg[g[s][i][0]]==2&&g[s][i][1]!=lst&&g[s][i][2]!=lst)
				{
					int v=g[s][i][0];
			//		cout<<s<<" --> "<<v<<" "<<g[s][i][1]<<" "<<g[s][i][2]<<"\n";
					sa+=Wei[g[s][i][1]];
					bl[g[s][i][1]]={-cid,1,1};
					if(cyclen==1)
					{
						Flg=1;
						break;
					}
					sb+=Wei[g[s][i][2]];
					bl[g[s][i][2]]={-cid,2,2};
					if(v==tmps) Flg=1;
					lst=g[s][i][2],s=v;
					break;
				} 
				if(Flg) break;
			}
			break;
		}
	//	cout<<ans<<" "<<sa<<" "<<sb<<"\n";
	//	system("pause");
	}
	void upd(int eid,int val)
	{
//		cout<<"before: "<<ans<<" "<<sa<<" "<<sb<<"\n";
		if(bl[eid][1]==0) ans+=val-Wei[eid];
		if(bl[eid][1]==1) sa+=val-Wei[eid];
		if(bl[eid][1]==2) sb+=val-Wei[eid];
		Wei[eid]=val;
//		cout<<"updated: "<<ans<<" "<<sa<<" "<<sb<<"\n";
	}
	int calc()
	{
		return ans+max(sa,sb);
	}
}cy[500005];
int n,m,tp;
int A[500005],B[500005];
vector <array<int,3> > g[500005];
bool vis[500005],vise[1000005];
vector <array<int,4> > nw;
int cntu=0;
void dfs2(int u)
{
	vis[u]=1;
	cntu++;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i][0];
		if(!vis[v]) dfs2(v);
		if(!vise[min(g[u][i][1],g[u][i][2])]) vise[min(g[u][i][1],g[u][i][2])]=1,nw.pb({u,v,g[u][i][1],g[u][i][2]});
	}
}
int ntr=0,ncy=0;
int query()
{
	int ans=0;
	for(int i=1;i<=ntr;i++) ans+=tr[i].calc();//,cout<<ans<<"...\n";
	for(int i=1;i<=ncy;i++) ans+=cy[i].calc();//,cout<<ans<<"\n";
	return ans;
}
void solve()
{
	cin>>m>>n>>tp;
	for(int i=1;i<=m;i++) cin>>A[i];
	int cnt=0;
	for(int i=1;i<=m;i++) 
	{
		cin>>B[i];
		int u=(A[i]-B[i]+n)%n;
		int v=(A[i]+B[i])%n;
		if(u>v) swap(u,v);
		u++,v++;
	//	cout<<u<<" "<<v<<"\n";
		if(u==v) cnt++,g[u].pb({v,cnt,cnt}),g[v].pb({u,cnt,cnt});
		else g[u].pb({v,cnt+1,cnt+2}),g[v].pb({u,cnt+2,cnt+1}),cnt+=2;
	}
	for(int i=1;i<=cnt;i++) cin>>Wei[i];
	for(int i=1;i<=n;i++) if(!vis[i]) 
	{
		nw.clear(),cntu=0;
		dfs2(i);
//		for(int j=0;j<nw.size();j++) cout<<nw[j][0]<<" "<<nw[j][1]<<" "<<nw[j][2]<<" "<<nw[j][3]<<"\n";
		
		if(nw.size()+1==cntu) ntr++,tr[ntr].tid=ntr,tr[ntr].init(nw);//,cout<<"tr\n";
		else ncy++,cy[ncy].cid=ncy,cy[ncy].init(nw);//,cout<<"cy\n";
	//	system("pause");
	}
//	for(int i=1;i<=cnt;i++) cout<<bl[i][0]<<" "<<bl[i][1]<<" "<<bl[i][2]<<"\n";
	int lstans=query();
	cout<<lstans<<"\n";
	int q;
	cin>>q;
	while(q--)
	{
		int x,v;
		cin>>x>>v;
		x-=lstans*tp,v-=lstans*tp;
	//	cout<<x<<" "<<v<<" "<<bl[x][0]<<"\n";
		if(bl[x][0]>=1) lstans-=tr[bl[x][0]].calc(),tr[bl[x][0]].upd(x,v),lstans+=tr[bl[x][0]].calc();
		else lstans-=cy[-bl[x][0]].calc(),cy[-bl[x][0]].upd(x,v),lstans+=cy[-bl[x][0]].calc();
		cout<<lstans<<"\n";
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【CF1186F】Vus the Cossack and a Graph

duel 时冲的一个随机化做法。

先把每个点的邻接表随机打乱。

算法类似一个 bfs 的过程,取出队首,依次枚举邻边,并尝试删除这条边,然后将所有邻居入队。

这样就已经能过了,但可以做到更好:类似 spfa,不仅将未访问过的邻居入队,还将被删除的边的另一个端点也入队;也可以不断重复这个过程直到没有新删除的边。

正确性不会证,感觉能卡掉的。

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,m;
vector <pii > g[1000005];
int deg[1000005],lim[1000005];
int U[1000005],V[1000005];
bool vis[1000005];
bool inq[1000005];
void bfs(int s)
{
	vis[s]=1;
	queue <int> q;
	q.push(s);
	while(q.size())
	{
		int u=q.front();
		q.pop();
		for(int i=0;i<g[u].size();i++)
		{
			int v=g[u][i].fi;
			if(!inq[g[u][i].se]&&deg[u]-1>=lim[u]&&deg[v]-1>=lim[v]) deg[u]--,deg[v]--,inq[g[u][i].se]=1;
			if(!vis[v]) q.push(v),vis[v]=1;
		}
	}
}
mt19937 rnd(time(0));
void solve()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>U[i]>>V[i],g[U[i]].pb(mp(V[i],i)),g[V[i]].pb(mp(U[i],i)),deg[U[i]]++,deg[V[i]]++; 
	for(int i=1;i<=n;i++)
	{
		for(int j=(int)g[i].size()-1;j>=1;j--) 
		{
			int p=rnd()%j;
			swap(g[i][j],g[i][p]);
		}
	}
	for(int i=1;i<=n;i++) lim[i]=(deg[i]+1)/2;
	for(int i=1;i<=n;i++) if(!vis[i]) bfs(i);
	int cnt=0;
	for(int i=1;i<=m;i++) if(!inq[i]) cnt++;
	cout<<cnt<<"\n";
	for(int i=1;i<=m;i++) if(!inq[i]) cout<<U[i]<<" "<<V[i]<<"\n"; 
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【SNOI2020】水池

先观察下操作,操作 \(0\) 可以看成对一段区间赋值成 \(h\),操作 \(1\) 可以看成,对于一段区间赋值成到某个点所经过的挡板高度的最大值,这两个观察是符合直觉的。

对于操作 \(0\),考虑维护区间挡板最大值,这样就可以二分出影响到的区间,此处暴力二分即可,不需要线段树二分。

对于操作 \(1\),维护出区间挡板最大值并支持单点查询水位高度的话也可以很轻松的找到影响的区间,然后将这段区间的数改为到 \(x\) 所经过的挡板的最大值。

这两个操作是需要打区间懒标记的,此题需要可持久化,我们可以将每个点最近被修改的时间记录进懒标记,并同时记录操作 \(0\)\(h\) 或者操作 \(1\)\(x\),单点求值的时候直接到历史版本去查一下。

复杂度 \(O(n \log^2 n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int ls[5000005],rs[5000005],H[5000005],h[200005],rt[200005];
int tag_t[5000005],tag[5000005];
int idx,n,q;
int cpynode(int x)
{
	idx++;
	ls[idx]=ls[x],rs[idx]=rs[x],H[idx]=H[x],tag_t[idx]=tag_t[x],tag[idx]=tag[x];
	return idx;
}
int build(int l,int r)
{
	int id=++idx;
	if(l==r)
	{
		H[id]=h[l];
		return id;
	}
	int mid=(l+r)>>1;
	ls[id]=build(l,mid),rs[id]=build(mid+1,r);
	H[id]=max(H[ls[id]],H[rs[id]]);
//	cout<<"build "<<l<<" "<<r<<" "<<H[id]<<"\n";
	return id;
}

int queryH(int id,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return H[id];
	int mid=(l+r)>>1,res=0;
	if(x<=mid) res=max(res,queryH(ls[id],l,mid,x,y));
	if(y>mid) res=max(res,queryH(rs[id],mid+1,r,x,y));
	return res; 
}
pii queryT(int id,int l,int r,int x)
{
	if(l==r) return mp(tag_t[id],tag[id]);
	int mid=(l+r)>>1;
	pii res=mp(tag_t[id],tag[id]);
	pii nxt=(x<=mid?queryT(ls[id],l,mid,x):queryT(rs[id],mid+1,r,x));
	if(nxt.fi>=res.fi&&(nxt.fi||nxt.se)) res=nxt;
	return res;
}
int updateH(int id,int l,int r,int x,int d)
{
	int u=cpynode(id);
	if(l==r)
	{
		H[u]=d;
		return u;
	}
	int mid=(l+r)>>1;
	if(x<=mid) ls[u]=updateH(ls[id],l,mid,x,d);
	else rs[u]=updateH(rs[id],mid+1,r,x,d);
	H[u]=max(H[ls[u]],H[rs[u]]);
	return u;
}
int updateT(int id,int l,int r,int x,int y,int tt,int t)
{
	int u=cpynode(id);
	if(x<=l&&r<=y) 
	{
		tag_t[u]=tt,tag[u]=t;
		return u;
	}
	int mid=(l+r)>>1;
	if(x<=mid) ls[u]=updateT(ls[id],l,mid,x,y,tt,t);
	if(y>mid) rs[u]=updateT(rs[id],mid+1,r,x,y,tt,t);
	return u;
}
int queryHH(int r,int x)
{
	pii t=queryT(rt[r],1,n,x);
	if(t.se>=0) return t.se;
	if(-t.se<x) return queryH(rt[t.fi],1,n,-t.se,x-1);
	else if(-t.se>x) return queryH(rt[t.fi],1,n,x,-t.se-1);
	return 0;
}
void solve()
{
	cin>>n>>q;
	for(int i=1;i<n;i++) cin>>h[i];
	h[0]=h[n]=inf;
	rt[0]=build(1,n);
	for(int ti=1;ti<=q;ti++)
	{
		int op;
		cin>>op;
		if(op==0)
		{
			int i,x,d;
			cin>>i>>x>>d;
			int L=1,R=x-1,l=0;
		//	for(int j=1;j<x;j++) cout<<queryH(rt[i],1,n,j,x-1)<<"\n";
			while(L<=R)
			{
				int mid=(L+R)>>1;
				if(queryH(rt[i],1,n,mid,x-1)>=d) l=mid,L=mid+1;
				else R=mid-1;
			}
			l++;
			int r=n;
			L=x,R=n;
			while(L<=R)
			{
				int mid=(L+R)>>1;
				if(queryH(rt[i],1,n,x,mid)>=d) r=mid,R=mid-1;
				else L=mid+1;
			}
		//	cout<<l<<" "<<r<<" "<<d<<"\n";
			if(queryHH(i,x)>=d) 
			{
				rt[ti]=rt[i];
				continue;
			}
			else rt[ti]=updateT(rt[i],1,n,l,r,i,d);
		}
		if(op==1)
		{
			
			int i,x;
			cin>>i>>x;
			int L=1,R=x-1,l=x;
			while(L<=R)
			{
				int mid=(L+R)>>1;
				if(queryHH(i,mid)>queryH(rt[i],1,n,mid,x-1)) l=mid,R=mid-1;
				else L=mid+1;
			}
			int r=x;
			L=x+1,R=n; 
			while(L<=R)
			{
				int mid=(L+R)>>1;
				if(queryHH(i,mid)>queryH(rt[i],1,n,x,mid-1)) r=mid,L=mid+1;
				else R=mid-1;
			}
			rt[ti]=updateT(rt[i],1,n,l,r,i,-x);
		}
		if(op==2)
		{
			int i,x,d;
			cin>>i>>x>>d;
			rt[ti]=updateH(rt[i],1,n,x,d);
		}
		if(op==3)
		{
			int i,x;
			cin>>i>>x;
			rt[ti]=rt[i];
			cout<<queryHH(i,x)<<"\n";
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【LibreOJ β Round #7】网格图

此题 \(n,m\) 非常大,\(k\) 相对较小,考虑以某种方式“离散化”。

如果一段连续的行中都没有障碍,可以缩成一行,如果一段连续的列中都没有障碍,可以缩成一列,这样就得到了一个 \(O(k) \times O(k)\) 的网格图,注意特判一下起点和终点在同一个大格子的情况,这种答案可能不是 \(0\)

一个比较显然的 dp:\(dp(i,j,0/1/2/3)\),表示在 \((i,j)\) 朝向哪个方向,的最小旋转次数,可以 bfs 求解,然而我们可以只记录 \(0/1\),也就是横向还是纵向,是等价的。

我们考虑再进行缩点,对于同一行连续的若干个空位,我们也可以缩成一个点,列同理,这样状态是 \(O(k)\) 的。转移的话,对于一横一竖两个节点,如果有交点的话,就连一条长度为 \(1\) 的边,这个可以扫描线 + 主席树优化建图。

有一点细节:主席树中是要连单向边,需要横竖各做一次或者维护两棵树,一棵连父亲到儿子,一棵连儿子到父亲。

复杂度大常数 \(O(k \log k)\),恶心的一点是,此题不能瞎开 vector,会爆空间。

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,m,k,Q;
int X[100005],Y[100005];
int Lx[100005],Rx[100005],Ly[100005],Ry[100005];
int getx(int x)
{
	return upper_bound(Lx+1,Lx+1+n,x)-Lx-1;
}
int gety(int x)
{
	return upper_bound(Ly+1,Ly+1+m,x)-Ly-1;
}
vector <int> hasX[100005],hasY[100005];
vector <array<int,3> > vX[100005],vY[100005],op[100005]; 
struct Graph
{
	int p[16000005],to[16000005],nxt[16000005],eid;
	void addedge(int u,int v)
	{
		eid++;
		to[eid]=v,nxt[eid]=p[u],p[u]=eid; 
	}
}g0,g1;
int cnt_e=0;
void add(int u,int v,int w)
{
	if(!w) g0.addedge(u,v);
	else g1.addedge(u,v);
}
int ls[8000005],rs[8000005],uidx,dis[8000005];
void print(int id,int l,int r)
{
//	cout<<id<<" "<<l<<" "<<r<<" "<<ls[id]<<" "<<rs[id]<<"\n";
	if(l==r) return;
	if(ls[id]) print(ls[id],l,(l+r)>>1);
	if(rs[id]) print(rs[id],((l+r)>>1)+1,r);
}
int build(int l,int r)
{
	int u=++uidx;
	if(l==r) return u;
	int mid=(l+r)>>1;
	ls[u]=build(l,mid),rs[u]=build(mid+1,r);
	add(u,ls[u],0),add(u,rs[u],0);
	return u;
}
int update(int id,int l,int r,int x,int d)
{
//	cout<<"update: "<<id<<" "<<l<<" "<<r<<" "<<x<<" "<<d<<" "<<ls[id]<<" "<<rs[id]<<"\n";
//	system("pause");
	int u=++uidx;
	ls[u]=ls[id],rs[u]=rs[id];
	if(l==r)
	{
		if(d) add(u,d,0);
		return u;
	}
	int mid=(l+r)>>1;
	if(x<=mid) ls[u]=update(ls[id],l,mid,x,d),add(u,ls[u],0),add(u,rs[u],0);
	else rs[u]=update(rs[id],mid+1,r,x,d),add(u,rs[u],0),add(u,ls[u],0);
//	cout<<"updated: "<<u<<" "<<l<<" "<<r<<" "<<ls[u]<<" "<<rs[u]<<"\n";
//	system("pause");
	return u;
}
void lnk(int id,int l,int r,int x,int y,int d)
{
	if(x<=l&&r<=y)
	{
		add(d,id,1);
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) lnk(ls[id],l,mid,x,y,d);
	if(y>mid) lnk(rs[id],mid+1,r,x,y,d);
}
void add2(int u,int v,int w)
{
	swap(u,v);
	if(!w) g0.addedge(u,v);
	else g1.addedge(u,v);
}
int build2(int l,int r)
{
	int u=++uidx;
	if(l==r) return u;
	int mid=(l+r)>>1;
	ls[u]=build2(l,mid),rs[u]=build2(mid+1,r);
	add2(u,ls[u],0),add2(u,rs[u],0);
	return u;
}

int update2(int id,int l,int r,int x,int d)
{
//	cout<<"update: "<<id<<" "<<l<<" "<<r<<" "<<x<<" "<<d<<" "<<ls[id]<<" "<<rs[id]<<"\n";
//	system("pause");
	int u=++uidx;
	ls[u]=ls[id],rs[u]=rs[id];
	if(l==r)
	{
		if(d) add2(u,d,0);
		return u;
	}
	int mid=(l+r)>>1;
	if(x<=mid) ls[u]=update2(ls[id],l,mid,x,d),add2(u,ls[u],0),add2(u,rs[u],0);
	else rs[u]=update2(rs[id],mid+1,r,x,d),add2(u,rs[u],0),add2(u,ls[u],0);
//	cout<<"updated: "<<u<<" "<<l<<" "<<r<<" "<<ls[u]<<" "<<rs[u]<<"\n";
//	system("pause");
	return u;
}
void lnk2(int id,int l,int r,int x,int y,int d)
{
	if(x<=l&&r<=y)
	{
		add2(d,id,1);
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) lnk2(ls[id],l,mid,x,y,d);
	if(y>mid) lnk2(rs[id],mid+1,r,x,y,d);
}
pii get_onseg(int x,int y)
{
	array<int,3> Tmp={y+1,-1,-1};
	int pos=lower_bound(vX[x].begin(),vX[x].end(),Tmp)-vX[x].begin();
	pos--;
	if(pos<0||pos>=vX[x].size()) return mp(-1,-1);
	pii res;
	res.fi=vX[x][pos][2];
	Tmp={x+1,-1,-1};
	pos=lower_bound(vY[y].begin(),vY[y].end(),Tmp)-vY[y].begin();
	pos--;
	res.se=vY[y][pos][2];
	return res;
}
void solve()
{
	cin>>n>>m>>k>>Q;
	vector <int> vx,vy;
	vx.pb(1),vx.pb(n),vy.pb(1),vy.pb(m);
	for(int i=1;i<=k;i++) cin>>X[i]>>Y[i],vx.pb(X[i]),vy.pb(Y[i]);
	sort(vx.begin(),vx.end()),vx.resize(unique(vx.begin(),vx.end())-vx.begin());
	n=0,m=0;
	for(int i=0;i<vx.size();i++)
	{
		Lx[++n]=vx[i],Rx[n]=vx[i];
		if(i+1<vx.size()&&vx[i]+1<=vx[i+1]-1) Lx[++n]=vx[i]+1,Rx[n]=vx[i+1]-1;
	}
	sort(vy.begin(),vy.end()),vy.resize(unique(vy.begin(),vy.end())-vy.begin());
	for(int i=0;i<vy.size();i++)
	{
		Ly[++m]=vy[i],Ry[m]=vy[i];
		if(i+1<vy.size()&&vy[i]+1<=vy[i+1]-1) Ly[++m]=vy[i]+1,Ry[m]=vy[i+1]-1;
	}
	for(int i=1;i<=k;i++) X[i]=getx(X[i]),Y[i]=gety(Y[i]),hasX[X[i]].pb(Y[i]),hasY[Y[i]].pb(X[i]);
	for(int i=1;i<=n;i++)
	{
		if(!hasX[i].size()) vX[i].pb({1,m,++uidx});
		else
		{
			sort(hasX[i].begin(),hasX[i].end());
			if(hasX[i][0]>1) vX[i].pb({1,hasX[i][0]-1,++uidx});
			for(int j=0;j+1<hasX[i].size();j++) if(hasX[i][j]+1<=hasX[i][j+1]-1) vX[i].pb({hasX[i][j]+1,hasX[i][j+1]-1,++uidx});
			if(hasX[i][hasX[i].size()-1]<m) vX[i].pb({hasX[i][hasX[i].size()-1]+1,m,++uidx});
		}
	}
	for(int i=1;i<=m;i++)
	{
		if(!hasY[i].size()) vY[i].pb({1,n,++uidx});
		else
		{
			sort(hasY[i].begin(),hasY[i].end());
			if(hasY[i][0]>1) vY[i].pb({1,hasY[i][0]-1,++uidx});
			for(int j=0;j+1<hasY[i].size();j++) if(hasY[i][j]+1<=hasY[i][j+1]-1) vY[i].pb({hasY[i][j]+1,hasY[i][j+1]-1,++uidx});
			if(hasY[i][hasY[i].size()-1]<n) vY[i].pb({hasY[i][hasY[i].size()-1]+1,n,++uidx});
		}
	}
	for(int i=1;i<=m;i++) for(int j=0;j<vY[i].size();j++) op[vY[i][j][0]].pb({-1,i,vY[i][j][2]}),op[vY[i][j][1]+1].pb({-2,i,vY[i][j][2]});
	for(int i=1;i<=n;i++) for(int j=0;j<vX[i].size();j++) op[i].pb({vX[i][j][2],vX[i][j][0],vX[i][j][1]});
	int rt=build(1,m);
	int rt2=build2(1,m); 
	for(int i=1;i<=n;i++) for(int j=0;j<op[i].size();j++)
	{
		if(op[i][j][0]==-1) rt=update(rt,1,m,op[i][j][1],op[i][j][2]),rt2=update2(rt2,1,m,op[i][j][1],op[i][j][2]);//,cout<<"+ "<<op[i][j][1]<<" "<<op[i][j][2]<<"\n",print(rt,1,m),system("pause");
		else if(op[i][j][0]==-2) rt=update(rt,1,m,op[i][j][1],0),rt2=update2(rt2,1,m,op[i][j][1],0);//,cout<<"- "<<op[i][j][1]<<"\n";
		else lnk(rt,1,m,op[i][j][1],op[i][j][2],op[i][j][0]),lnk2(rt2,1,m,op[i][j][1],op[i][j][2],op[i][j][0]);//,cout<<"link "<<op[i][j][1]<<" "<<op[i][j][2]<<" "<<op[i][j][0]<<"\n";
	//	print(rt,1,m),system("pause");
	}
	int sx,sy,tmp_sx,tmp_sy;
	cin>>sx>>sy;
	tmp_sx=sx,tmp_sy=sy;
	sx=getx(sx),sy=gety(sy);
	memset(dis,0x3f,sizeof(dis));
	vector <int> q;
	q.clear();
	pii r=get_onseg(sx,sy);
	q.pb(r.fi),q.pb(r.se);
//	cout<<"start: "<<r.fi<<" "<<r.se<<"\n";
	dis[r.fi]=dis[r.se]=0;
	while(q.size())
	{
		for(int i=0;i<q.size();i++)
		{
			int u=q[i];
			for(int j=g0.p[u];j;j=g0.nxt[j]) if(dis[g0.to[j]]==inf) 
				dis[g0.to[j]]=dis[u],q.pb(g0.to[j]);
		}
		vector <int> q2;
		q2.clear();
		for(int i=0;i<q.size();i++)
		{
			int u=q[i];
			for(int j=g1.p[u];j;j=g1.nxt[j]) if(dis[g1.to[j]]==inf) 
				dis[g1.to[j]]=dis[u]+1,q2.pb(g1.to[j]);
		}
		swap(q,q2);
	}
//	cerr<<uidx<<" "<<cnt_e<<"\n";
	while(Q--)
	{
		int x,y,tmp_x,tmp_y;
		cin>>x>>y;
		tmp_x=x,tmp_y=y;
		x=getx(x),y=gety(y);
		r=get_onseg(x,y);
		//cout<<"query: "<<r.fi<<" "<<r.se<<"\n";
		if(r.fi==-1) cout<<"-1\n";
		else 
		{
			int ans=min(dis[r.fi],dis[r.se]);
			if(tmp_x!=tmp_sx&&tmp_y!=tmp_sy) ans=max(1,ans);
			if(ans==inf) ans=-1; 
			cout<<ans<<"\n";
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【集训队互测 2021】蜘蛛爬树

njwrz orz

我们只需要关心每个询问两个端点在树上的位置 \(u,v\) 和两棵树之间的距离 \(d\),令连接两棵不同的树的边叫做“横边”,两个点最短路一定只经过一条横边,假设横边的权值为 \(a_x\)\(dist(u,v)\) 表示同一棵树上 \(u,v\) 的路径长度,则最短路可以表示为 \(\min_x\{dist(u,x)+dist(x,v)+da_x\}\)

考虑点分树,观察点分树上的 \(lca(u,x)\)\(lca(v,x)\),这两个点是具有祖先关系的,而且一个询问中,对它有贡献的 lca 点对的数量级是 \(O(\log n)\),可以通过暴力跳父亲的方式把这些点对找出来并挂在深度较深的点上。

然后我们将点分树上的每一棵子树拿出来,并枚举 \(lca(u,x),lca(v,x)\),令其为 \(f,g\),其中有一个点为子树的根。子树内的每个点 \(i\) 作为 \(x\) 的贡献可以看做一条斜率为 \(a_i\),截距为 \(dist(i,f)+dist(i,g)\) 的直线,对于一个询问,你需要找出一条直线,最小化直线在两棵树之间的距离 \(d\) 处的取值,把这个最小值加上 \(dist(u,f)+dist(v,g)\) 并贡献到答案里,可以用凸包或李超树算最小值,这里需要根据 \(u,v\) 是否在当前子树内分讨一下哪个端点作为 \(u\),哪个端点作为 \(v\)

复杂度 \(O(n \log^2 n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n,m,q;
int a[200005];
vector <pii > g[200005];
int ans[200005];
int qu[200005],qv[200005],qd[200005];
struct LCA
{
	int dep[200005],dfn[200005],times;
	int dist[200005];
	int st[20][400005],Lg[400005];
	void dfs(int u,int fa)
	{
		dfn[u]=++times,st[0][times]=u;
		for(int i=0;i<g[u].size();i++)
		{
			int v=g[u][i].fi;
			if(v==fa) continue;
			dep[v]=dep[u]+1,dist[v]=dist[u]+1LL*g[u][i].se,dfs(v,u);
			st[0][++times]=u;
		}
	}
	int mindep(int x,int y)
	{
		if(dep[x]<dep[y]) return x;
		return y;
	}
	void build()
	{
		times=0,dep[1]=dist[1]=0;
		dfs(1,-1);
		Lg[1]=0,Lg[2]=1;
		for(int i=3;i<=times;i++) Lg[i]=Lg[i/2]+1;
		for(int k=1;k<20;k++) for(int i=1;i+(1<<k)-1<=times;i++) 
			st[k][i]=mindep(st[k-1][i],st[k-1][i+(1<<(k-1))]);
	}
	int getlca(int u,int v)
	{
		u=dfn[u],v=dfn[v];
		if(u>v) swap(u,v);
		int s=Lg[v-u+1];
		return mindep(st[s][u],st[s][v-(1<<s)+1]);
	}
	int getdis(int u,int v)
	{
		int l=getlca(u,v);
		return dist[u]+dist[v]-2LL*dist[l];	
	}
}Lca;
int sz[200005],msz[200005],tot,rt;
bool vis[200005];
void getsz(int u,int fa)
{
	sz[u]=1,msz[u]=0;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i].fi;
		if(v==fa||vis[v]) continue;
		getsz(v,u),sz[u]+=sz[v];
	}
}
void getrt(int u,int fa)
{
	msz[u]=0;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i].fi;
		if(v==fa||vis[v]) continue;
		getrt(v,u),msz[u]=max(msz[u],sz[v]);
	}
	msz[u]=max(msz[u],tot-sz[u]);
	if(msz[u]<msz[rt]) rt=u;
}
int par[200005][21],dep[200005];
vector <int> T[200005];
int R;
void dfs_build(int u,int fa)
{
	getsz(u,-1),rt=0,tot=sz[u],getrt(u,-1);
	u=rt;
	par[u][0]=fa;
	if(fa!=-1) T[fa].pb(u),dep[u]=dep[fa]+1;
	else R=rt;
	vis[u]=1;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i].fi;
		if(vis[v]) continue;
		dfs_build(v,u);
	}
}
vector <int> has[200005][21];
vector <int> vs;
bool insub[200005];

void dfs1(int u,int fa)
{
	vs.pb(u),insub[u]=1;
	for(int i=0;i<T[u].size();i++)
	{
		int v=T[u][i];
		if(v==fa||vis[v]) continue;
		dfs1(v,u);
	}
}
bool cmp_a(int x,int y)
{
	return a[x]>a[y];
}
double getslope(pii x,pii y)
{
	int p=(x.se-y.se),q=(x.fi-y.fi);
	return (double)(p)/(double)(q);
}
int calcval(pii line,int x)
{
	return line.fi*x+line.se;
}
void dfs0(int u)
{
	vis[u]=1;
	dfs1(u,-1);
	sort(vs.begin(),vs.end(),cmp_a);
//	cout<<"calc: "<<u<<" "<<dep[u]<<"\n";
//	for(int i=0;i<vs.size();i++) cout<<vs[i]<<" ";
//	cout<<"\n";
	for(int i=0;i<=dep[u];i++) if(has[u][i].size())
	{
		int lca1=u,lca2=u;
		if(dep[u]-i) lca1=par[u][dep[u]-i-1];
		vector <pii > conv;
		conv.clear();
	//	cout<<u<<" "<<i<<" "<<lca1<<" "<<lca2<<": \n";
		for(int j=0;j<vs.size();j++)
		{
			int l=j,minn=INF;
			while(l<vs.size()&&a[vs[l]]==a[vs[j]]) minn=min(minn,Lca.getdis(vs[l],lca1)+Lca.getdis(vs[l],lca2)),l++;
			l--;
			pii line=mp(a[vs[j]],minn);
	//		cout<<a[vs[j]]<<" "<<minn<<"\n";
			while(conv.size()>=2)
			{
				pii x=conv[conv.size()-2],y=conv[conv.size()-1];
				if(getslope(x,line)<getslope(y,line)) conv.pop_back();
				else break;
			}
			j=l;
			conv.pb(line);
		}
	//	system("pause");
	//	for(int j=0;j<conv.size();j++) cout<<conv[j].fi<<" "<<conv[j].se<<"\n";
	//	system("pause");
		int l=0;
		for(int j=0;j<has[u][i].size();j++)
		{
			int id=has[u][i][j];
			while(l+1<conv.size()&&calcval(conv[l],qd[id])>calcval(conv[l+1],qd[id])) l++;
			int now=calcval(conv[l],qd[id]);
			
			if(!insub[qu[id]]) swap(qu[id],qv[id]);
	//		cout<<id<<" "<<now<<" "<<Lca.getdis(qu[id],lca2)+Lca.getdis(qv[id],lca1)<<"\n";
			ans[id]=min(ans[id],now+Lca.getdis(qu[id],lca2)+Lca.getdis(qv[id],lca1));
		}
	//	system("pause");
	}
	for(int i=0;i<vs.size();i++) insub[vs[i]]=0;
	vs.clear();
	for(int i=0;i<T[u].size();i++) dfs0(T[u][i]);
}
bool cmp_qd(int x,int y)
{
	return qd[x]<qd[y];
}
void solve()
{
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		g[u].pb(mp(v,w)),g[v].pb(mp(u,w));
	}
	Lca.build();
	msz[0]=inf,rt=0,tot=n;
	getsz(1,-1),getrt(1,-1);
//	for(int i=1;i<=n;i++) cout<<sz[i]<<" "<<msz[i]<<"\n";
	
	dfs_build(1,-1);
	for(int k=1;k<20;k++) for(int i=1;i<=n;i++) par[i][k]=par[par[i][0]][k-1];
//	for(int i=1;i<=n;i++) cout<<par[i][0]<<" "<<dep[i]<<"\n";
//	cout<<"\n";
	for(int i=1;i<=q;i++)
	{
		int u,v;
		cin>>u>>v;
		int x=(u-1)/n,y=(v-1)/n;
		u-=x*n,v-=y*n;
		qu[i]=u,qv[i]=v,qd[i]=abs(x-y);
		int tu=u,tv=v;
	//	cout<<u<<" "<<v<<"\n";
		while(tu!=tv)
		{
			if(dep[tu]<dep[tv]) swap(tu,tv);
			tu=par[tu][0];
		}
		int L=tu;
	//	cout<<L<<"\n";
		while(u!=L) has[u][dep[L]].pb(i),u=par[u][0];
		while(v!=L) has[v][dep[L]].pb(i),v=par[v][0];
		while(L!=-1) has[L][dep[L]].pb(i),L=par[L][0];
	}
	for(int i=1;i<=n;i++) for(int j=0;j<20;j++) sort(has[i][j].begin(),has[i][j].end(),cmp_qd);
	
	memset(vis,0,sizeof(vis));
	memset(ans,0x3f,sizeof(ans));
	dfs0(R);
	for(int i=1;i<=q;i++) cout<<ans[i]<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【JOISC 2020】扫除

先考虑只有一个维度的操作,观察到同一个维度的操作顺序无关,我们只需要关心长度的集合即可,因为区间 checkmax 是有交换律的。

此题的重点在于将两个维度分离:假设有一个长度为 \(l_1\) 的 H 操作,那么对于一些长度 \(\ge n-l_1\) 的 V 操作,它们的影响范围可以看成 \([n-l_1,n]\) 而不是 \([1,n]\)

考虑线段树分治,我们的问题就变成了静态的问题:先进行若干个扫动操作,然后查询若干个点的位置,经过上面的处理之后,两维可以分离,按某一维坐标排序之后,用区间 checkmax 的线段树维护即可,复杂度 \(O(n \log^2 n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,m,Q;
const int N=1500005;
int X[1500005],Y[1500005],ti[1500005],ansX[1500005],ansY[1500005],qL[1500005],qR[1500005],qid;
int op[1500005],Len[1500005],uid;
struct Segtree
{
	int ls[20000005],rs[20000005],val[20000005],rt,idx;
	void update(int &id,int l,int r,int x,int y,int d)
	{
		if(x>y) return;
		if(!id) id=++idx,ls[id]=rs[id]=val[id]=0;
		if(x<=l&&r<=y) 
		{
			val[id]=max(val[id],d);
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid) update(ls[id],l,mid,x,y,d);
		if(y>mid) update(rs[id],mid+1,r,x,y,d); 
	}
	int query(int id,int l,int r,int x)
	{
		if(!id) return 0;
		if(l==r) return val[id];
		int res=val[id],mid=(l+r)>>1;
		if(x<=mid) res=max(res,query(ls[id],l,mid,x));
		else res=max(res,query(rs[id],mid+1,r,x));
		return res;
	}
}st[2];
bool cmp1(int x,int y)
{
	return ansX[x]>ansX[y];
}
bool cmp2(int x,int y)
{
	return ansY[x]>ansY[y];
}
void divide(int L,int R,vector <int> qs)
{
	if(L>R||!qs.size()) return;
	vector <pii > us[2];
	st[0].rt=st[0].idx=st[1].rt=st[1].idx=0;
	for(int i=L;i<=R;i++)
	{
		int p=st[op[i]].query(st[op[i]].rt,0,n,Len[i]);
		if(p>n-Len[i]) continue;
		us[op[i]].pb(mp(Len[i],p));
		st[op[i]^1].update(st[op[i]^1].rt,0,n,p,n-Len[i]-1,Len[i]+1);
	}
	sort(us[0].begin(),us[0].end()),reverse(us[0].begin(),us[0].end());
	st[0].rt=st[0].idx=st[1].rt=st[1].idx=0;
	vector <int> now;
	now.clear();
	for(int i=0;i<qs.size();i++) 
	{
		int u=qs[i];
		if(qL[u]<=L&&R<=qR[u]) now.pb(u);
	}
	sort(now.begin(),now.end(),cmp2);
	for(int i=0,j=0;i<now.size();i++)
	{
		while(j<us[0].size()&&us[0][j].fi>=ansY[now[i]]) st[0].update(st[0].rt,0,n,us[0][j].se,n-us[0][j].fi,n-us[0][j].fi),j++;
		ansX[now[i]]=max(ansX[now[i]],st[0].query(st[0].rt,0,n,ansX[now[i]]));
	}
	sort(us[1].begin(),us[1].end()),reverse(us[1].begin(),us[1].end());
	sort(now.begin(),now.end(),cmp1);
	for(int i=0,j=0;i<now.size();i++)
	{
		while(j<us[1].size()&&us[1][j].fi>=ansX[now[i]]) st[1].update(st[1].rt,0,n,us[1][j].se,n-us[1][j].fi,n-us[1][j].fi),j++;
		ansY[now[i]]=max(ansY[now[i]],st[1].query(st[1].rt,0,n,ansY[now[i]]));
	}
	vector <int> qls,qrs;
	qls.clear(),qrs.clear();
	int mid=(L+R)>>1;
	for(int i=0;i<qs.size();i++)
	{
		int u=qs[i];
		if(qL[u]<=L&&R<=qR[u]) continue;
		if(qL[u]<=mid) qls.pb(u);
		if(qR[u]>mid) qrs.pb(u);
	}
	divide(L,mid,qls),divide(mid+1,R,qrs);
} 
void solve()
{
	cin>>n>>m>>Q;
	for(int i=1;i<=m;i++) cin>>X[i]>>Y[i];
	for(int i=1;i<=Q;i++)
	{
		int tp,x;
		cin>>tp>>x;
		if(tp==1) qid++,qL[qid]=ti[x]+1,qR[qid]=uid,ansX[qid]=X[x],ansY[qid]=Y[x];
		if(tp==2||tp==3) uid++,op[uid]=tp-2,Len[uid]=x;
		if(tp==4) 
		{
			int y;
			cin>>y;
			m++,X[m]=x,Y[m]=y,ti[m]=uid;
		}
	}
	vector <int> vec;
	vec.clear();
	for(int i=1;i<=qid;i++) if(qL[i]<=qR[i]) vec.pb(i);
	divide(1,uid,vec);
	for(int i=1;i<=qid;i++) cout<<ansX[i]<<" "<<ansY[i]<<"\n";
} 
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【CEOI 2020】象棋世界

吐槽这种大缝合怪!

P 很 trivial,只需要判断是否在同一列,是的话答案为 \(1,1\),否则为 \(0,0\)

R 也很 trivial,还是只需要判断是否在同一列,是的话答案为 \(1,1\),否则因为可以先走横或先走竖,答案为 \(2,2\)

Q 需要一点点的分讨,先判断是否可以一步到达,是的话答案为 \(1,1\),否则,由于 Q 是包含 R 的,所以最短距离为 \(2\),方案数的话,首先有 R 的 \(2\) 个方案,走一竖一斜也有 \(2\) 个方案,一横一斜的方案存在当且仅当 \(C=R\) 且起点在一个角上,两斜的话,众所周知有黑象和白象,将棋盘黑白染色之后把不在同色格子的情况判掉,然后可以算出往一边延伸的距离,判断是否走出去即可。暴力一点的方法是线段求交。

B 先特判黑白染色不同色(不可达)。最短距离可以贪心求出,先指定一个方向,走到边界再停止,找到第一个 \(x \ge C\)\(y=c_R\) 的点,此时可能会多出来一段,但是多出来的一段可以通过在某个拐点缩进去,某个拐点缩一个单位会将多出来的那一段的长度减去 \(2\)。假设有 \(x\) 个拐点,多出来的距离为 \(2y\),则第一问答案为 \(x+1\),第二问的答案可以看做将 \(y\) 个球放到 \(x\) 个盒子里面,球没区别盒子有区别,可以为空,方案为 \(\binom{x+y-1}{x-1}\)

关于找路径:观察到横坐标有着长度为 \(2(C-1)\) 的循环节,可以快速的算出中间的那一段的贡献。

K,由于 \(C \le R\),第一问答案为 \(R-1\),此时可以把棋子削弱一下:每一步只能走到左前方,正前方或右前方,可以矩阵乘法:\(f(i,j)\) 表示走了 \(i\) 行目前在第 \(j\) 列的方案数,我们可以看做对于这个矩阵:

1 1 0 0 0 0
1 1 1 0 0 0
0 1 1 1 0 0
0 0 1 1 1 0
0 0 0 1 1 1
0 0 0 0 1 1

求出 \(R-1\) 次幂,每次询问可以看做询问矩阵的一个位置,打表观察发现如下几个结论:

  • 主副对角线对称。
  • 观察第二行,每一个数等于左上和右上的数之和。
  • 观察后面的行,多往右上看一眼,就可以发现是左上的一个数加上不断往右上延伸到第一行的那个数,也就是 \(a_{i,j}=a_{i-1,j-1}+a_{1,j+i-1}\)

我们只需要暴力乘第一行即可,后面都可以通过式子或对称性推出来,复杂度 \(O(C^2 \log R)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int fpow(int x,int b){
	if(x==0) return 0;
	if(b==0) return 1;
	int res=1;
	while(b>0){
		if(b&1)	res=1LL*res*x%mod;
		x=1LL*x*x%mod;
		b>>=1;
	}
	return res;
}
int N,M;
struct Matrix
{
	int a[1005][1005];
};
Matrix mul(Matrix A,Matrix B)
{
 	Matrix C;
 	for(int i=1;i<=1;i++) for(int j=1;j<=N;j++)
 	{
 		C.a[i][j]=0;
 		for(int k=1;k<=N;k++) C.a[i][j]=(C.a[i][j]+A.a[i][k]*B.a[k][j])%mod; 
 		C.a[j][i]=C.a[i][j];
	 }
	 for(int i=2;i<=N;i++) for(int j=2;j<=N-i+1;j++) C.a[i][j]=(C.a[i-1][j-1]+C.a[1][j+i-1])%mod;
	for(int i=1;i<=N;i++) for(int j=1;j<=N-i+1;j++) C.a[N-j+1][N-i+1]=C.a[i][j];
	 return C;
}
int Comb(int x,int y)
{
	int res=1;
	for(int i=y;i>=1;i--) res=res*(x-i+1)%mod,res=res*fpow(i,mod-2)%mod;
	return res;
}
Matrix fpow_mat(Matrix x,int b){
	Matrix res=x;
	b--;
	while(b>0){
		if(b&1)	res=mul(res,x);
		x=mul(x,x);
		b>>=1;
	}
	return res;
}
void solve()
{
	cin>>M>>N;
	Matrix A,B;
	for(int i=1;i<=N;i++) 
	{
		A.a[i][i]=1;
		if(i) A.a[i-1][i]=1;
		if(i+1<=N) A.a[i+1][i]=1;
	}
	A=fpow_mat(A,M-1);
	int Q;
	cin>>Q;
	while(Q--)
	{
		char op;
		int u,v;
		cin>>op;
		cin>>u>>v;
		if(op=='P')
		{
			if(u==v) cout<<M-1<<" "<<1<<"\n";
			else cout<<0<<" "<<0<<"\n";
		}
		if(op=='R')
		{
			if(u==v) cout<<1<<" "<<1<<"\n";
			else cout<<2<<" "<<2<<"\n";
		}
		if(op=='Q')
		{
			if(u==v||abs(u-v)==M-1) cout<<1<<" "<<1<<"\n";
			else
			{
				int ans=4;
				if(u%2==((v%2)^((M-1)%2)))
				{
					int d=M-1-abs(u-v);
					d/=2;
					if(min(u,v)-d>=1) ans++;
					if(max(u,v)+d<=N) ans++;
				}
				if(N==M&&(min(u,v)==1||max(u,v)==N)) ans++;
				cout<<2<<" "<<ans<<"\n";
			}
		}
		if(op=='B')
		{
			
			if(u%2!=((v%2)^((M-1)%2))) 
			{
				cout<<0<<" "<<0<<"\n";
				continue;
			}
			
			int ans1=inf,ans2=0;
			// left
		//	cout<<"... "<<u<<" "<<v<<"\n";
			int cnt=0,x=1;
		//	cout<<u<<"\n";
			int y=u;
		//	cout<<u<<" "<<y<<"\n";
			while(y>1) y--,x++;//,cout<<x<<" "<<y<<endl;
			cnt++;
		//	cout<<x<<" "<<y<<"...\n";
			int d=(M-x)/(2*(N-1));
			cnt+=d*2;
			x+=d*2*(N-1);
			int del=1;
			if(x<M||y!=v) cnt++; 
			while(x<M||y!=v)
			{
				if((del==1&&y==N)||(del==-1&&y==1)) cnt++,del*=-1;
				x++,y+=del;
		//		cout<<x<<" "<<M<<" "<<y<<" "<<v<<"\n";
		//		system("pause");
			}
			int ex=(x-M)/2;
			int ways=Comb(cnt-1+ex-1,ex);
			if(cnt<ans1) ans1=cnt,ans2=ways;
			else ans2=(ans2+ways)%mod;
			//right
			cnt=0,ways=0;
			x=1,y=u;
			while(y<N) y++,x++;
			cnt++;
			d=(M-x)/(2*(N-1));
			cnt+=d*2;
			x+=d*2*(N-1);
			del=-1;
			if(x<M||y!=v) cnt++; 
			while(x<M||y!=v)
			{
				if((del==1&&y==N)||(del==-1&&y==1)) cnt++,del*=-1;
				x++,y+=del;
			}
			ex=(x-M)/2;
			ways=Comb(cnt-1+ex-1,ex);
			if(cnt<ans1) ans1=cnt,ans2=ways;
			else if(cnt==ans1) ans2=(ans2+ways)%mod;
			cout<<ans1<<" "<<ans2<<"\n";
		}
		if(op=='K')
		{
		//	cout<<u<<" "<<v<<"\n";
			cout<<M-1<<" "<<A.a[u][v]<<"\n";
		}
	}
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【EC-Final 2018】Mysterious … Host

若两个排列连续区间的集合相同,则我们称这两个排列属于同一个等价类,我们要对于等价类计数。

如果熟悉析合树,可以发现就是数析合树个数。

析点儿子个数 \(\ge 4\),合点儿子个数 \(\ge 2\),令一个析合树的权值为 \(2^{deg \ge 4 节点的个数}\),对所有析合树的权值求和。令 \(dp(i,j)\) 表示,\(i\) 阶排列的析合树,根有 \(j\) 个儿子的权值和。可以令 \(j \le 4\),原因是,\(j \gt 4\) 的一个状态和 \(j=4\) 的状态等价,根节点都可以作为析点或合点。

复杂度 \(O(n^2)\),可以用多项式做到 \(O(n \log^2 n)\),据说写出生成函数发现可以整式递推搞到线性,这个太神了没搞懂。

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int dp[5005][5];
void solve()
{
	int n;
	cin>>n>>mod;
	dp[1][1]=1;
	cout<<"1\n";
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<=4;j++) for(int k=1;k<i;k++)
		{
			dp[i][min(4LL,j+1)]=(dp[i][min(4LL,j+1)]+dp[k][1]*dp[i-k][j]%mod*(j==3?2LL:1LL)%mod)%mod;
		}
		dp[i][1]=(dp[i][2]+dp[i][3]+dp[i][4])%mod;
		cout<<dp[i][1]<<"\n";
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【JOISC 2018】路网服务

说一下我做这个题的过程:

我一开始想的是,加入一条令总距离减少最多的边,但这样实在是太慢了,于是我就随了几条边取最优,这个表现并不是很优秀,在 case 2 只得到了 \(18\%\) 的分数。

打算给 case 1 写一个暴力,算量是 \(\binom{n(n-1)/2}{k}n^2\),在一个不错的电脑上能 \(10\) 分钟内出解,发现连出来的是一个菊花图,想了想发现菊花图确实很对,因为菊花图内部的路径非常短,每个点到菊花图上的路径长度也很小。

直觉告诉我根选择重心,选叶子也比较麻烦,因为算一次代价是 \(O(n^2)\) 的,随机几条边,这样能在几分钟内在 case 2 跑出 \(60\%\)\(70\%\),调用全家电脑去跑枚举所有边的程序,得到了所有点的 \(80\%\)\(90\%\)

题目中的代价不好维护,我想了一个估价函数,就是每个点到菊花的最短路径之和。这时候我意识到重心可能并不是很优秀。先贪心,每次选令估价函数减少最多的点,选出一个集合,枚举集合内哪个作为根,可以在 \(O(kn^2)\) 的时间内运行完毕,而且非常优秀,能在所有点得到 \(90\%\)\(95\%\)

先贪心得到比较好的叶子集合,对叶子退火(我是把根确定了),每次扰动选择一个叶子和菊花图外的点交换并估价,每个点跑五六分钟就可以得到满分。

此题还有别的做法,一种是不退火,每次扰动随机一个子集换出,这个子集大小需要调一调;还有一种是爬山,发现爬不动了就进行一个较大的扰动;还有一种是最小化估价函数的确定性做法,类似 NOI2008 奥运物流的二维树上背包,实现的好可以在一秒内出解,很厉害。

下面的代码是我的退火代码,它在 i7-1260P 2.10GHz 16GB 的电脑上,能在一分钟的时间对 case 6 处理完毕并获得所有数据(case 1不知道表现如何,没测)的满分。

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
mt19937 rnd(time(0));
vector <int> g[1005];
int dis[1005][1005];
int n,k,W0; 
int chk(int u,int v)
{
	g[u].pb(v),g[v].pb(u);
	int tu=u,tv=v;
	memset(dis,-1,sizeof(dis));
	for(int i=1;i<=n;i++)
	{
		queue <int> q;
		q.push(i),dis[i][i]=0;
		while(q.size())
		{
			u=q.front();
			q.pop();
			for(int j=0;j<g[u].size();j++)
			{
				v=g[u][j];
				if(dis[i][v]!=-1) continue;
				dis[i][v]=dis[i][u]+1;
				q.push(v); 
			}
		}
	}
	int W=0;
	for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) W+=dis[i][j];
	g[tu].pop_back(),g[tv].pop_back();
	return W;
}
vector <int> vs;
int calc_dist(int i)
{
	memset(dis[i],-1,sizeof(dis[i]));
	queue <int> q;
	q.push(i),dis[i][i]=0;
	for(int j=0;j<vs.size();j++) dis[i][vs[j]]=0,q.push(vs[j]);
	while(q.size())
	{
		int u=q.front();
		q.pop();
		for(int j=0;j<g[u].size();j++)
		{
			int v=g[u][j];
			if(dis[i][v]!=-1) continue;
			dis[i][v]=dis[i][u]+1;
			q.push(v); 
		}
	}
	int W=0;
	for(int j=1;j<=n;j++) W+=dis[i][j];
	return W;
}
int maxT=100000000;
int T=100000000;
void solve()
{
	cin>>n>>k>>W0;
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].pb(v),g[v].pb(u);
	}
	for(int i=1;i<=k+1;i++)
	{
		int bst=inf,bu=-1;
		for(int j=1;j<=n;j++)
		{
			int t=calc_dist(j);
			if(t<bst) bst=t,bu=j;
		}
		vs.pb(bu);
	}
	int bst=inf;
	int bu=-1;
	for(int i=0;i<k+1;i++)
	{
		for(int j=0;j<k+1;j++) g[vs[j]].pb(vs[i]),g[vs[i]].pb(vs[j]);
		int t=chk(0,0);
		if(t<bst) bst=t,bu=i;
		for(int j=0;j<k+1;j++) g[vs[j]].pop_back(),g[vs[i]].pop_back();
	}
	bst=calc_dist(vs[0]);
	int rlbst=bst;
	vector <int> rlvs=vs;
	for(int _=1;_<=10000000;_++)
	{
		int u=rnd()%(k+1),v=1+rnd()%n;//,w=rnd()%(k+1),x=1+rnd()%n;
		while(vs[u]==bu) u=rnd()%(k+1),v=1+rnd()%n;
		vector <int> vs2=vs;
		vs.clear();
		for(int j=0;j<vs2.size();j++) if(j!=u) vs.pb(vs2[j]);
		vs.pb(v);//,vs.pb(x);
		int t=calc_dist(v);
		double Tmp=(double)(t)*0.97;
		T=(int)(Tmp);
		if(t<rlbst) rlvs=vs,rlbst=t;
		if(t<bst||(rnd()%maxT<=T)) bst=t;
		else vs=vs2;
		if(_%10000==0) cerr<<_/10000<<" "<<bst<<"\n";
	}
	vs=rlvs;
	bst=inf;
	bu=-1;
	for(int i=0;i<k+1;i++)
	{
		for(int j=0;j<k+1;j++) g[vs[j]].pb(vs[i]),g[vs[i]].pb(vs[j]);
		int t=chk(0,0);
		if(t<bst) bst=t,bu=i;
		for(int j=0;j<k+1;j++) g[vs[j]].pop_back(),g[vs[i]].pop_back();
	}
	cerr<<bst<<"\n";
	for(int i=0;i<k+1;i++) if(i!=bu) cout<<vs[i]<<" "<<vs[bu]<<"\n";
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【JOISC 2019】聚会

先考虑链上的情况:把链拉平,假设我们知道了 \(x\)\(y\) 的左边,询问 \((u,x,y)\):如果返回 \(x\),说明 \(u\)\(x\) 左边;返回 \(u\),说明 \(u\)\(x,y\) 中间;返回 \(y\),说明 \(u\)\(y\) 的右边。

这启发我们从 \(x,y\) 入手,容易找出子问题。每次随机一个 \(x,y\),想象一下:把 \(x,y\) 路径构成的链拉平,链上的每个点下面挂了一个子树。对于每个节点 \(u\),询问 \((u,x,y)\) 就可以得到 \(u\) 被挂在哪个子树上。每个子树是一个子问题,\(x\)\(y\) 这条链也是一个子问题,对于这些子问题递归求解,边界是简单的,只有两个节点时直接连边。

题目中保证了度数 \(\le 18\),不用担心被菊花图卡,表现很优秀。

复杂度不会证,反正能过。

Code
#include "meetings.h"
#include <bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
using namespace std;
mt19937 rnd(time(0));
void divide(vector <int> vec)
{
	if(vec.size()<=1) return;
	if(vec.size()<=2)
	{
		if(vec[0]>vec[1]) swap(vec[0],vec[1]);
		Bridge(vec[0],vec[1]);
		return;
	}
	int x=rnd()%vec.size(),y=rnd()%vec.size();
	while(x==y) x=rnd()%vec.size(),y=rnd()%vec.size();
	vector <pii > que;
	que.clear();
	que.pb(mp(vec[x],vec[x])),que.pb(mp(vec[y],vec[y]));
	for(int i=0;i<vec.size();i++)
	{
		if(i==x||i==y) continue;
		int t=Query(vec[x],vec[y],vec[i]);
		que.pb(mp(t,vec[i]));
	}
	sort(que.begin(),que.end());
	vector <int> pa;
	pa.clear();
	for(int i=0;i<que.size();i++)
	{
		int j=i;
		vector <int> nxt;
		nxt.clear();
		while(j<que.size()&&que[i].fi==que[j].fi) nxt.pb(que[j].se),j++;
		j--;
		pa.pb(que[i].fi);
		i=j;
		divide(nxt);
	}
	divide(pa);
}
void Solve(int N)
{
	vector <int> vec;
	vec.clear();
	for(int i=0;i<N;i++) vec.pb(i);
	divide(vec);
}

【USACO2021 P】Minimizing Edges

\(f(u)\) 表示 \(1\)\(u\) 的奇数长度最短路;\(g(u)\) 表示 \(1\)\(u\) 的偶数长度最短路,我们只需要保证 \(f,g\) 相同。

先特判两种边界:

  • 图是二分图,此时 \(1\) 每个点只有奇数或偶数长度的路径,构造最短路树即可,答案为 \(n-1\)
  • \(1\) 有自环,此时构造最短路树和一个自环即可,答案为 \(n\)

然后考虑构造:我们记一个点 \(u\) 的状态为 \((x,y)\)\(x=\min\{f(u),g(u)\},y=\max\{f(u),g(u)\}\),这个状态可以由如下几种方式构造出来:

  • 存在边 \((u,v)\),且 \(v\) 的状态为 \((x-1,y-1)\)
  • 存在边 \((u,v),(u,w)\),且 \(x+1\lt y\)\(v\) 的状态为 \((x-1,y+1)\)\(w\) 的状态为 \((x+1,y-1)\)
  • 存在边 \((u,v),(u,w)\),且 \(x+1=y\)\(v\) 的状态为 \((x,y)\)\(w\) 的状态为 \((x-1,y+1)\)

观察上面的三种情况,我们可以按 \(x+y\) 为第一关键字,\(x\) 为第二关键字从小到大排序后贪心。

如果 \((x-1,y+1)\) 存在,我们可以先消耗剩余的 \((x-1,y+1)\) 连向 \((x,y)\) 的边,然后连向 \((x+1,y-1)\),这样代价为 \(1\) 且能多一条边。

否则如果存在 \((x-1,y-1)\) 则用 \(1\) 的代价连上,不多任何边,对应上述第一种情况。

否则用代价 \(2\) 连到另外两个点,对应上述第二种情况。

std::map 维护剩余边数和点数,注意特判 \(x+1=y\),复杂度 \(O(n \log n)\)

Code
/*
Things to notice:
1. do not calculate useless values
2. do not use similar names
 
Things to check:
1. submit the correct file
2. time (it is log^2 or log)
3. memory
4. prove your naive thoughts 
5. long long
6. corner case like n=0,1,inf or n=m
7. check if there is a mistake in the ds or other tools you use
8. fileio in some oi-contest

9. module on time 
10. the number of a same divisor in a math problem
11. multi-information and queries for dp and ds problems
*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n,m;
vector <int> g[100005];
int dis[100005][2];
map <pii,int> has,res;
bool cmp(pii x,pii y)
{
	if(x.fi+x.se!=y.fi+y.se) return x.fi+x.se<y.fi+y.se;
	return x.fi<y.fi;
}
void solve()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) g[i].clear(),dis[i][0]=dis[i][1]=-1;
	bool hascir=0;
	while(m--)
	{
		int u,v;
		cin>>u>>v;
		if(u==1&&v==1) hascir=1;
		g[u].pb(v),g[v].pb(u);
	}
	if(hascir)
	{
		cout<<n<<"\n";
		return;
	}
	queue <pii > q;
	q.push(mp(1,0)),dis[1][0]=0;
	while(q.size())
	{
		int u=q.front().fi,op=q.front().se;
		q.pop();
		for(int i=0;i<g[u].size();i++)
		{
			int v=g[u][i];
			if(dis[v][op^1]!=-1) continue;
			dis[v][op^1]=dis[u][op]+1,q.push(mp(v,op^1));
		}
	}
	if(dis[1][1]==-1)
	{
		cout<<n-1<<"\n";
		return;
	}
	has.clear(),res.clear();
	vector <pii > vec;
	for(int i=1;i<=n;i++) 
	{
		pii t=mp(min(dis[i][0],dis[i][1]),max(dis[i][0],dis[i][1]));
		if(!has[t]) vec.pb(t);
		has[t]++;
	}
	sort(vec.begin(),vec.end(),cmp);
	int ans=0;
	for(int i=0;i<vec.size();i++)
	{
		int x=vec[i].fi,y=vec[i].se;
		//cout<<x<<" "<<y<<" "<<has[mp(x,y)]<<"\n";
		if(has[mp(x-1,y+1)]&&has[mp(x-1,y-1)])
		{
			int d=min(res[mp(x-1,y+1)],has[mp(x,y)]);
			if(x+1==y) ans+=(d+1)/2;
			else ans+=d,res[mp(x,y)]+=d;
			ans+=max(0LL,has[mp(x,y)]-res[mp(x-1,y+1)]);
			continue;
		}
		if(has[mp(x-1,y-1)]) ans+=has[mp(x,y)];
		else if(has[mp(x-1,y+1)])
		{
			ans+=max(0LL,has[mp(x,y)]-res[mp(x-1,y+1)]);
			if(x+1==y) ans+=(has[mp(x,y)]+1)/2;
			else 
			{
				int d=has[mp(x,y)];
				ans+=d,res[mp(x,y)]+=d;
			}
		}
	//	cout<<ans<<"\n";
	}
	cout<<ans<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
	cin>>_;
	while(_--) solve();
	return 0;
}

【ABC306H】Balance Scale

= 看成无向边,> 看做一条 \(u \rightarrow v\) 的有向边,< 看做一条 \(v \rightarrow u\) 的有向边,问题转化成求这样定向并将无向边构成的每个连通分量缩点后,形成 DAG 的方案数。

\(dp(S)\) 表示仅考虑点集 \(S\) 的方案数,转移枚举一个真子集 \(T\),将 \(T,S-T\) 两个集合之间的所有边定向,容易写出式子\(dp(S)=\sum_T dp(T)\),但这样有一个问题,即在 \(S-T\) 有多个联通分量时会被多算,所以需要将式子改为 \(dp(S)=\sum_Tdp(T)(-1)^{cnt(S-T)-1}\),其中 \(cnt(X)\) 表示点集 \(X\) 导出子图的连通分量个数,复杂度 \(O(3^n)\)

挂一个链接:https://www.cnblogs.com/Mackerel-Pike/p/16395436.html 找个时间看看。

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int N,M;
int dp[1<<17],coef[1<<17];
vector <int> g[17];
bool vis[17];
void dfs(int u,int mask)
{
	vis[u]=1;
	for(int i=0;i<g[u].size();i++) 
		if(!vis[g[u][i]]&&(mask&(1<<g[u][i]))) dfs(g[u][i],mask);
}
void solve()
{
	cin>>N>>M;
	while(M--)
	{
		int u,v;
		cin>>u>>v;
		g[u-1].pb(v-1),g[v-1].pb(u-1);
	}
	for(int mask=0;mask<(1<<N);mask++)
	{
		memset(vis,0,sizeof(vis)),coef[mask]=-1;
		for(int i=0;i<N;i++) if(!vis[i]&&(mask&(1<<i))) coef[mask]*=-1,dfs(i,mask);
	}
	dp[0]=1;
	for(int S=1;S<(1<<N);S++)
	{
		dp[S]=(coef[S]+mod)%mod;
		for(int T=(S-1)&S;T;T=(T-1)&S)
			dp[S]=(dp[S]+dp[T]*coef[S^T]%mod+mod)%mod;
	}
	cout<<dp[(1<<N)-1];
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【ARC162E】Strange Constraints

说的直白点,数 \(x\) 的出现次数不能超过 \(\min\{A_x,x所在所有位置的最小 A_i\}\)

考虑按出现次数从大到小加入,令 \(dp(i,j,k)\) 表示,目前出现次数最少的是 \(i\) 次,已经加入了 \(j\) 个数,占用 \(k\) 个位置,转移枚举出现 \(i-1\) 次的有多少个数,你会发现合法的候选数和候选位置都能被表示出来。

这样看上去是 \(O(n^3 \log n)\) 的,但实际上转移的数量是 \(O(\sum_{i=1}^{n} \frac{n^3}{i^2})\)\(\sum_{i=1}^n \frac{1}{i^2} = 1+\sum_{i=2}^{n} \frac{1}{(i+1)^2} \le 1+\sum_{i=2}^{n} \frac{1}{i(i+1)}=1+\sum_{i=2}^n \frac{1}{i}-\frac{1}{i+1}=\frac{3}{2}-\frac{1}{n+1}\) ,故 \(O(\sum_{i=1}^n \frac{1}{i^2})=O(1)\),能轻松通过。

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n;
int has[505];
int fpow(int x,int b){
	if(x==0) return 0;
	if(b==0) return 1;
	int res=1;
	while(b>0){
		if(b&1)	res=1LL*res*x%mod;
		x=1LL*x*x%mod;
		b>>=1;
	}
	return res;
}
int fac[300005],ifac[300005];
int C(int x,int y)
{
	if(y>x) return 0;
	if(x==y||y==0) return 1;
	return 1LL*fac[x]*ifac[x-y]%mod*ifac[y]%mod;
}
void init(int x)
{
	fac[0]=fac[1]=1;
	for(int i=2;i<=x;i++) fac[i]=1LL*fac[i-1]*i%mod;
	ifac[x]=fpow(fac[x],mod-2);
	for(int i=x-1;i>=0;i--) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
int dp[505][505][505];
/*
dp[i][j][k]: 考虑了出现次数[i,n] 的数,填了 j 个数,k个位置 
*/
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int x;
		cin>>x;
		has[x]++;
	}
	for(int i=n;i>=0;i--) has[i]+=has[i+1];
	dp[n+1][0][0]=1;
	init(1000);
	for(int i=n+1;i>=1;i--) for(int j=0;j<=n;j++) for(int k=0;k<=n;k++) if(dp[i][j][k])
	{
		for(int l=0,coef=1;j+l<=has[i-1]&&k+l*(i-1)<=has[i-1];l++)
		{
			dp[i-1][j+l][k+l*(i-1)]=(dp[i-1][j+l][k+l*(i-1)]+dp[i][j][k]*C(has[i-1]-j,l)%mod*C(has[i-1]-k,l*(i-1))%mod*coef%mod)%mod;
		//	cout<<i<<" "<<j<<" "<<k<<" --> "<<i-1<<" "<<j+l<<" "<<k+l*(i-1)<<" = "<<dp[i][j][k]*C(has[i-1]-j,l)%mod*C(n-k,l*(i+1))%mod*coef%mod<<"\n";
			coef=coef*C((l+1)*(i-1),i-1)%mod;
		}
	}
	cout<<dp[0][n][n]<<"\n";
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【ARC162F】Montage

题目中的条件可以写成,对于任意 \(a \lt c,b \lt d\),若 \(A_{a,b},A_{c,d}\) 都为 \(1\),则 \(A_{a,d},A_{c,b}\) 都为 \(1\)

想象一下,假设我们将一个数变为 \(1\),如果左上角也有一个为 \(1\),则这两个位置构成的子矩形的另外两个顶点也会被迫变成 \(1\)。随便手玩几组就会发现,一个合法的矩阵满足:去掉所有全 \(0\) 行和全 \(0\) 列,每一行的 \(1\) 是一个区间,且从上到下,区间的左端点不增,右端点不增。

\(f(i,j,k)\) 表示,一个 \(i \times j\) 的没有全 \(0\) 行和全 \(0\) 列的矩阵,最后一行前 \(k\) 个数是 \(1\),且每一行 \(1\) 构成区间,从上到下区间左端点不增,右端点不增的方案数。观察从 \(f(i-1,* ,*)\) 转移到 \(f(i,*,*)\) 的过程:每一个 \(f(i,j,k)\) 对应的都是 \(f(i-1,*,*)\) 这个矩阵的一块阶梯状的和,这个是容易预处理的。

计算出 \(f\) 之后,枚举 \(x,y\),令 \(S=\sum_{k=1}^{y} f(x,y,k)\),将 \(\binom{N}{x}\binom{M}{y}S\) 加入到答案里,复杂度 \(O(NM^2)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int f[405][405][405];
int n,m;
int fpow(int x,int b){
	if(x==0) return 0;
	if(b==0) return 1;
	int res=1;
	while(b>0){
		if(b&1)	res=1LL*res*x%mod;
		x=1LL*x*x%mod;
		b>>=1;
	}
	return res;
}
int fac[300005],ifac[300005];
int C(int x,int y)
{
	if(y>x) return 0;
	if(x==y||y==0) return 1;
	return 1LL*fac[x]*ifac[x-y]%mod*ifac[y]%mod;
}
void init(int x)
{
	fac[0]=fac[1]=1;
	for(int i=2;i<=x;i++) fac[i]=1LL*fac[i-1]*i%mod;
	ifac[x]=fpow(fac[x],mod-2);
	for(int i=x-1;i>=0;i--) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
int sum[405][405],g[405][405];
int g2[405][405];
void solve()
{
	cin>>n>>m;
	init(1000);
	for(int j=1;j<=m;j++) f[1][j][j]=1;
	for(int i=2;i<=n;i++)
	{
		memset(sum,0,sizeof(sum)),memset(g,0,sizeof(g)),memset(g2,0,sizeof(g2));
		for(int j=1;j<=m;j++) for(int k=m;k>=1;k--) sum[j][k]=(sum[j][k+1]+f[i-1][j][k])%mod;
		for(int j=1;j<=m;j++) for(int k=1;k<=m;k++) g[j][k]=(g[j][k-1]+sum[k][min(m+1,j+k-1)])%mod;
		for(int j=0;j>=-m;j--) for(int k=1;k<=m;k++) g2[-j][k]=(g2[-j][k-1]+sum[k][max(1LL,min(m+1,j+k-1))])%mod;
	/*		for(int j=1;j<=m;j++)
		{
			for(int k=1;k<=m;k++) cout<<f[i-1][j][k]<<" ";
			cout<<"\n";
		 } 
		 cout<<"\n";
		for(int j=1;j<=m;j++)
		{
			for(int k=1;k<=m;k++) cout<<sum[j][k]<<" ";
			cout<<"\n";
		 } 
		 cout<<"\n";
		 for(int j=1;j<=m;j++)
		 {
		 	for(int k=1;k<=m;k++) cout<<g[j][k]<<" ";
		 	cout<<"\n";
		 }
		 cout<<"\n";
		 for(int j=1;j<=m;j++)
		 {
		 	for(int k=1;k<=m;k++) cout<<g2[j][k]<<" ";
		 	cout<<"\n";
		 }*/
		for(int j=1;j<=m;j++) for(int k=1;k<=m;k++) 
		{
			f[i][j][k]=(k+1-j>0?g[k+1-j][j]:g2[-(k+1-j)][j]);
			if(j-k-1>=0)
			{
				f[i][j][k]-=(k+1-j>0?g[k+1-j][j-k-1]:g2[-(k+1-j)][j-k-1]);
				f[i][j][k]=(f[i][j][k]+mod)%mod;
			}
		}
	}
	int ans=1;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
	{
		int coef=0;
		for(int k=1;k<=m;k++) coef=(coef+f[i][j][k])%mod;//,cout<<i<<" "<<j<<" "<<k<<" "<<f[i][j][k]<<"\n";
		ans=(ans+coef*C(n,i)%mod*C(m,j)%mod)%mod;
	}
	cout<<ans<<"\n";
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【SDOI2019】世界地图

题目中让你求的是一段前缀和后缀构成的图的最小生成树,我们需要实现两段区间的生成树的合并。

回想一下往生成树里面加边的过程,加入一条边 \((u,v,w)\) 的时候,找到 \((u,v)\) 路径上的最大值 \(w'\),如果 \(w \lt w'\),则加入 \(w\) 并删除 \(w'\)。这个可以用 LCT 维护,但是 LCT 不能可持久化,也就是说我们无法保留每个前缀和后缀的结果。

\(n\) 的限制范围很小,合并过程中,新加入的边也只会在 \(O(n)\) 个点之间连,也就是只涉及这 \(O(n)\) 个点的虚树上的点。考虑只保留这 \(O(n)\) 个点(最左边和最右边的 \(n\) 个点)在最小生成树上的虚树。虚树的边权是原树上两点路径上边权的最大值,合并的时候把这些点和边拿出来暴力算一遍即可,复杂度 \(O((m+q)n\log n)\)

Code
#include<bits/stdc++.h>
using namespace std;
unsigned int SA, SB, SC;int lim;
int getweight() {
SA ^= SA << 16;
SA ^= SA >> 5;
SA ^= SA << 1;
unsigned int t = SA;
SA = SB;
SB = SC;
SC ^= t ^ SA;
return SC % lim + 1;
}
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
struct Tree
{
	int n,sumw;
	vector <array<int,3> > g;	
}pre[10005],suf[10005];
struct DSU
{
	int fa[1005];
	void init(int n)
	{
		for(int i=1;i<=n;i++) fa[i]=i;
	}
	int find(int x)
	{
		if(fa[x]==x) return x;
		return fa[x]=find(fa[x]);
	}
	void merge(int x,int y)
	{
		int xx=find(x),yy=find(y);
		if(xx!=yy) fa[xx]=yy;
	}
}dsu;
vector <pii > g[1005];
Tree res;
bool vt[1005],has[1005];
int id[1005];
void dfs_getvt(int u,int fa)
{
	int cnt=0;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i].fi;
		if(v==fa) continue;
		dfs_getvt(v,u);
		if(has[v]) cnt++,has[u]=1;
	}
	if(cnt>=2) vt[u]=1;
}
void dfs_lnk(int u,int fa,int top,int w)
{
	if(vt[u]&&top!=-1) 
	{
		res.sumw-=w;
		res.g.pb({id[u],id[top],w});
	}
	if(vt[u]) top=u,w=-1;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i].fi;
		if(v==fa) continue;
		dfs_lnk(v,u,top,max(w,g[u][i].se));
	}
}
bool cmp(array<int,3> X,array<int,3> Y)
{
	return X[2]<Y[2];
}
int N,M;
void merge_tree(Tree A,Tree B,vector <int> w)
{
	vector <array<int,3> > es;
	es.clear();
	for(int i=1;i<=A.n+B.n;i++) g[i].clear();
	dsu.init(A.n+B.n);
	int sum=A.sumw+B.sumw;
	for(int i=0;i<A.g.size();i++) es.pb(A.g[i]);//,sum+=A.g[i][2];
	for(int i=0;i<B.g.size();i++) es.pb({B.g[i][0]+A.n,B.g[i][1]+A.n,B.g[i][2]});//,sum+=B.g[i][2];
//	cout<<A.n<<" "<<B.n<<"\n";
//	
	for(int i=1,j=A.n-w.size()+1;i<=w.size();i++,j++) es.pb({j,i+A.n,w[i-1]});
	sort(es.begin(),es.end(),cmp);
//	for(int i=0;i<es.size();i++) cout<<es[i][0]<<" "<<es[i][1]<<" "<<es[i][2]<<"\n";
	for(int i=0;i<es.size();i++) if(dsu.find(es[i][0])!=dsu.find(es[i][1]))
	{
		
		dsu.merge(es[i][0],es[i][1]),g[es[i][0]].pb(mp(es[i][1],es[i][2])),g[es[i][1]].pb(mp(es[i][0],es[i][2]));
		sum+=es[i][2];
	//	if(es[i][1]<=A.n&&es[i][0]>A.n) sum+=es[i][2];
	}
//	cout<<sum<<"\n";
	for(int i=1;i<=A.n+B.n;i++) vt[i]=has[i]=0;
	for(int i=1;i<=N;i++) vt[i]=has[i]=1;
	for(int i=A.n+B.n;i>=A.n+B.n-N+1;i--) vt[i]=has[i]=1;
	dfs_getvt(1,-1);
	int j=1;
	for(int i=1;i<=A.n+B.n;i++) if(vt[i]) id[i]=j,j++;
	res.n=j-1,res.sumw=sum,res.g.clear();
//	cout<<sum<<"\n";
	dfs_lnk(1,-1,-1,-1);
}
int a[105][10005],b[105][10005];
void solve()
{
	cin>>N>>M>>SA>>SB>>SC>>lim;
	for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) a[i][j]=getweight();
	for(int i=1;i<N;i++) for(int j=1;j<=M;j++) b[i][j]=getweight();
	for(int i=1;i<=M;i++) 
	{
		pre[i].n=suf[i].n=N;
		for(int j=1;j<N;j++) pre[i].g.pb({j,j+1,b[j][i]}),suf[i].g.pb({j,j+1,b[j][i]});
	}
	for(int i=2;i<=M;i++)
	{
		vector <int> w;
		for(int j=1;j<=N;j++) w.pb(a[j][i-1]);
		merge_tree(pre[i-1],pre[i],w);
		pre[i]=res;
	//	cerr<<i<<" "<<pre[i].n<<"\n";
	}
	for(int i=M-1;i>=1;i--)
	{
		vector <int> w;
		for(int j=1;j<=N;j++) w.pb(a[j][i]);
		merge_tree(suf[i],suf[i+1],w);
		suf[i]=res;
	}
	int q;
	cin>>q;
	while(q--)
	{
		int l,r;
		cin>>l>>r;
		vector <int> w;
		for(int j=1;j<=N;j++) w.pb(a[j][M]);
		merge_tree(suf[r+1],pre[l-1],w);
		int ans=res.sumw;
		for(int i=0;i<res.g.size();i++) ans+=res.g[i][2];
//		cout<<res.n<<" "<<res.sumw<<"\n";
//		for(int j=0;j<res.g.size();j++) cout<<res.g[j][0]<<" "<<res.g[j][1]<<" "<<res.g[j][2]<<"\n";
//		system("pause");
//		res=suf[r+1];
//		cout<<res.n<<" "<<res.sumw<<"\n";
//		for(int j=0;j<res.g.size();j++) cout<<res.g[j][0]<<" "<<res.g[j][1]<<" "<<res.g[j][2]<<"\n";
//		system("pause");
//		res=pre[l-1];
//		cout<<res.n<<" "<<res.sumw<<"\n";
//		for(int j=0;j<res.g.size();j++) cout<<res.g[j][0]<<" "<<res.g[j][1]<<" "<<res.g[j][2]<<"\n";
//		system("pause");
		cout<<ans<<"\n";
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【LibreOJ Round #10】yanQval 的生成树

除法默认下取整。

离差这个东西看上去很奇怪,首先我们可以发现,\(\mu\) 一定取 \(a_i\) 的中位数,我们将 \(a_i\) 升序排列,则离差为 \(\sum_{i=1}^n |a_i-a_{(n+1)/2}|\)。将 \(a_1,a_n\) 配对,\(a_2,a_{n-1}\) 配对,……,离差还可以写成 \(\sum_{i=1}^{n/2} a_{n-i+1}-a_i\)

也就是说,我们要选择一棵生成树/森林(对于奇数 \(n\) 选生成树,对于偶数 \(n\)\(n-2\) 条边的生成森林,因为中间那条边对答案没有贡献),对生成树上每条边给一个 \(+1\)\(-1\) 的系数,要求每种系数的数量都为 \((n-1)/2\)

感觉是凸的,考虑 wqs 二分,对所有选择 \(-1\) 的边加上一个额外的贡献值 \(C\),由于 \(-1\) 都是权值小的边,\(+1\) 都是权值大的边,可以按边权升序排列后用两个指针从两边往里扫。二分这个贡献值 \(C\) 去找到一个系数数量合适的方案,最后答案减去 \(C\),复杂度 \(O(n \log w)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define i128 __int128
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;

struct DSU
{
	int fa[200005];
	void init(int n)
	{
		for(int i=1;i<=n;i++) fa[i]=i;
	}
	int find(int x)
	{
		if(fa[x]==x) return x;
		return fa[x]=find(fa[x]);
	}
	void merge(int x,int y)
	{
		int xx=find(x),yy=find(y);
		if(xx!=yy) fa[xx]=yy;
	}
}dsu;
int n,m;
array<int,3> g[500005]; 
pii chk(int cost)
{
	dsu.init(n);
	int sumw=0,cntL=0,d=0;
	vector <int> vec;
	for(int l=1,r=m,i=0;l<=r;)
	{
		if(cost-g[l][0]>g[r][0])
		{
			if(dsu.find(g[l][1])!=dsu.find(g[l][2])) dsu.merge(g[l][1],g[l][2]),sumw-=g[l][0],i++,cntL++;//,cout<<"- "<<g[l][0]<<" "<<g[l][1]<<" "<<g[l][2]<<" "<<"\n";
			l++;
		 } 
		 else
		 {
		 	if(dsu.find(g[r][1])!=dsu.find(g[r][2])) 
		 	{
		 		dsu.merge(g[r][1],g[r][2]),i++;
		 		sumw+=g[r][0];
		 	//	if(cost-g[l][0]==g[r][0]) d++,vec.pb(-2*g[r][0]);//,cout<<"... "<<g[r][0]<<" "<<g[r][1]<<" "<<g[r][2]<<"\n";
		 	//	else cout<<"+ "<<g[r][0]<<" "<<g[r][1]<<" "<<g[r][2]<<"\n"; 
			 }
		 	r--;
		 }
		 if(n%2==0&&i==n-2) break;
	}
//	sort(vec.begin(),vec.end());
//	reverse(vec.begin(),vec.end());
//	cout<<cost<<" "<<cntL<<" "<<sumw<<"\n";
//	for(int i=(int)vec.size()-1;i>=0&&cntL<(n-1)/2;i--) cntL++,sumw+=vec[i];
//	cout<<cost<<" "<<cntL<<" "<<sumw<<"\n";
	return mp(sumw,cntL);
}
void solve()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>g[i][1]>>g[i][2]>>g[i][0];
	sort(g+1,g+1+m);
	int L=0,R=INF;
	int ans=0;
	while(L<=R)
	{
		int mid=(L+R)>>1;
		pii res=chk(mid);
		if(res.se==(n-1)/2)
		{
			cout<<res.fi;
			return;
		}
		
		if(res.se<(n-1)/2) ans=res.fi-mid*((n-1)/2-res.se),L=mid+1;
		else R=mid-1;
	}
	cout<<ans<<"\n";
}
signed main()
{
	
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UNR #6】面基之路

考察最后一个面基的位置,我们可以让所有网友都在这个位置面基,只需要让提前面基的网友跟随 hehe 行动,时间不会增多。

有两种情况,一种是汇合点在节点上,还有一种是汇合点在边上。第一种情况是简单的,对于第二种情况,枚举一条边 \((u,v)\),一些人从 \(u\) 进入,另一些人从 \(v\) 进入。令第 \(i\) 个人到达 \(u\) 的时间为 \(a_i\),到达 \(v\) 的时间为 \(b_i\),将所有人按 \(a_i\) 排序,由于时间取决于最大值,所以一定是一段前缀走到 \(u\),一个后缀走到 \(v\),枚举这个前缀算答案即可。

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

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n,m;
vector <pii > g[200005];
int dist[22][200005];
bool vis[200005];
int k,pos[22],U[200005],V[200005],W[200005];
void solve()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		U[i]=u,V[i]=v,W[i]=w;
		g[u].pb(mp(v,w)),g[v].pb(mp(u,w));
	}
	cin>>k;
	k++;
	for(int i=1;i<=k;i++) 
	{
		if(i==1) pos[i]=1;
		else cin>>pos[i];
		memset(dist[i],0x3f,sizeof(dist[i]));
		memset(vis,0,sizeof(vis));
		dist[i][pos[i]]=0;
		priority_queue <pii > pq;
		pq.push(mp(0,pos[i]));
		while(pq.size())
		{
			int u=pq.top().se;
			pq.pop();
			if(vis[u]) continue;
			vis[u]=1;
			for(int j=0;j<g[u].size();j++)
			{
				int v=g[u][j].fi,w=g[u][j].se;
				if(dist[i][u]+w<dist[i][v]) dist[i][v]=dist[i][u]+w,pq.push(mp(-dist[i][v],v));
			}
		}
	}
	int ans=INF;
	for(int i=1;i<=n;i++)
	{
		int nw=0;
		for(int j=1;j<=k;j++) nw=max(nw,2LL*dist[j][i]);
		ans=min(ans,nw);
	}
	for(int i=1;i<=m;i++)
	{
		int u=U[i],v=V[i],t=W[i];
		vector <pii > vec;
		vec.clear();
		for(int j=1;j<=k;j++) vec.pb(mp(dist[j][u],dist[j][v]));
		sort(vec.begin(),vec.end());
		for(int j=vec.size()-1,maxx=0;j>=0;j--)
		{
			int d1=vec[j].fi,d2=maxx;
			if(d1>d2) swap(d1,d2);
			if(d1+t<=d2) ans=min(ans,2LL*d2);
			else ans=min(ans,2LL*d2+t-d2+d1);
			maxx=max(maxx,vec[j].se);
		}
	}
	cout<<ans<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UNR #6】稳健型选手

若卡牌总数是奇数,则最后一张牌一定被先手拿走,可以把这张牌的贡献加上去,变成偶数的情况。下文默认总数为偶数。

观察先手的选择,容易发现先手可以拿走的卡牌集合满足:前 \(2i\) 张牌最多拿走 \(i\) 个。

可以贪心:维护被选上的集合,从前往后做,每一轮加入两个数,由于此时会超出限制,所以需要将一个最小的选上的数移除,可以用 pq 维护。

也可以反过来做:前 \(2i\) 张牌最多拿走 \(i\) 个,等价于后 \(2i\) 张牌至少拿走 \(i\) 个,维护没选的集合,从后往前做,每一轮加入两个数,选择一个数,还是可以用 pq 维护。

对于多个区间,可以考虑分治,计算出右半部分每个前缀的没被选择的集合,和左半部分每个后缀被选择的集合,两个区间合并会发生:左边一些数从选变成不选,右边一些数从不选变成选。用主席树维护集合,二分一个最大的数 \(x\),满足左边集合内的第 \(x\)\(\le\) 有右边集合内的第 \(x\) 大。

复杂度 \(O((n+q)\log^2 n)\)

喜提倒数第二劣解!

Code
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#include<bits/stdc++.h>
using namespace __gnu_pbds;
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
	return x*f;
}
void print(long long x) {
    if(x>9) print(x/10);
    *O++=x%10+'0';
}
int rl[200005],V;
gp_hash_table <int,int> rk;
struct Tree
{
	int ls[8000005],rs[8000005],sz[8000005],sum[8000005],idx;
	void init()
	{
		for(int i=0;i<=idx;i++) ls[i]=rs[i]=sz[i]=sum[i]=0;
		idx=0;
	}
	int update(int id,int l,int r,int x,int d)
	{
		int u=++idx;
		if(id) ls[u]=ls[id],rs[u]=rs[id],sz[u]=sz[id],sum[u]=sum[id];
		if(l==r)
		{
			sz[u]+=d,sum[u]+=d*rl[l];
			return u;
		}
		int mid=(l+r)>>1;
		if(x<=mid) ls[u]=update(ls[id],l,mid,x,d);
		else rs[u]=update(rs[id],mid+1,r,x,d);
		sz[u]=sz[ls[u]]+sz[rs[u]];
		sum[u]=sum[ls[u]]+sum[rs[u]];
		return u;
	}
	int kth(int id,int l,int r,int k)
	{
		if(l==r) return rl[l];
		int mid=(l+r)>>1;
		if(ls[id]&&k<=sz[ls[id]]) return kth(ls[id],l,mid,k);
		return kth(rs[id],mid+1,r,k-sz[ls[id]]);
	}
	int kthsum(int id,int l,int r,int k)
	{
		if(l==r) return k*rl[l];
		int mid=(l+r)>>1;
		if(ls[id]&&k<=sz[ls[id]]) return kthsum(ls[id],l,mid,k);
		return sum[ls[id]]+kthsum(rs[id],mid+1,r,k-sz[ls[id]]);
	}
}t1,t2;
int n,q;
int a[200005],b[200005],ans[200005],rt[200005],sum[200005];
void divide(int dl,int dr,vector <array<int,3> > qs)
{
//	if(dr-dl>=10000) cerr<<dl<<" "<<dr<<" "<<qs.size()<<"\n";
	if(dl>=dr) return;
	if(dl+1==dr)
	{
		for(int i=0;i<qs.size();i++) ans[qs[i][0]]+=max(a[dr],a[dl]);
		return;
	}
	vector <array<int,3> > qL,qR,qnow;
	int mid=(dl+dr)>>1;
	for(int i=0;i<qs.size();i++)
	{
		if(qs[i][2]<=mid) qL.pb(qs[i]);
		else if(qs[i][1]>mid) qR.pb(qs[i]);
		else qnow.pb(qs[i]);
	}
	divide(dl,mid,qL),divide(mid+1,dr,qR);
	t1.init(),t2.init();
	
	priority_queue <int> pq;
	while(pq.size()) pq.pop();
	for(int i=mid-1,r=0,s=0;i>=dl;i-=2)
	{
		pq.push(a[i]),pq.push(a[i+1]);
		int x=pq.top();
		pq.pop();
		s+=x,sum[i]=s;
		r=t1.update(r,1,V,rk[x],1),rt[i]=r;
	}
	while(pq.size()) pq.pop();
	for(int i=mid+2,r=0,s=0;i<=dr;i+=2)
	{
		pq.push(-a[i]),pq.push(-a[i-1]);
		s+=a[i],s+=a[i-1];
		int x=-pq.top();
		pq.pop();
		s-=x,sum[i]=s;
		r=t2.update(r,1,V,rk[x],1),rt[i]=r; 
	}
//	if(qs.size())
//	{
//		for(int i=dl;i<=dr;i++) cout<<sum[i]<<" ";
//		cout<<"\n";
//	}
//	for(int i=dl;i<=dr;i++) cout<<sum[i]<<" ";
//	cout<<"\n";
	qs=qnow;
//	cout<<dl<<" "<<dr<<"\n"; 
	for(int i=0;i<qs.size();i++)
	{
		int l=qs[i][1],r=qs[i][2],id=qs[i][0];
		if((mid-l+1)%2==1) continue;
		ans[id]+=sum[l]+sum[r];
	//	cout<<id<<" "<<sum[l]<<" "<<sum[r]<<"\n";
		int L=1,R=min(t1.sz[rt[l]],t2.sz[rt[r]]),res=0;
//		cout<<id<<" 1\n";
		while(L<=R)
		{
			int mi=(L+R)>>1;
			int u=t1.kth(rt[l],1,V,mi),v=t2.kth(rt[r],1,V,t2.sz[rt[r]]-mi+1);
			if(u<v) res=mi,L=mi+1;
			else R=mi-1;
		}
//		cout<<t1.kthsum(rt[l],1,V,res)<<"\n";
//		cout<<t2.kthsum(rt[r],1,V,t2.sz[rt[r]]-res)<<"\n";
		ans[id]-=t1.kthsum(rt[l],1,V,res);
		ans[id]+=t2.sum[rt[r]]-t2.kthsum(rt[r],1,V,t2.sz[rt[r]]-res);
	}
	
	
	mid--;
	t1.init(),t2.init();
	while(pq.size()) pq.pop();
	for(int i=mid-1,r=0,s=0;i>=dl;i-=2)
	{
		pq.push(a[i]),pq.push(a[i+1]);
		int x=pq.top();
		pq.pop();
		s+=x,sum[i]=s;
		r=t1.update(r,1,V,rk[x],1),rt[i]=r;
	}
	while(pq.size()) pq.pop();
	for(int i=mid+2,r=0,s=0;i<=dr;i+=2)
	{
		pq.push(-a[i]),pq.push(-a[i-1]);
		s+=a[i],s+=a[i-1];
		int x=-pq.top();
		pq.pop();
		s-=x,sum[i]=s;
		r=t2.update(r,1,V,rk[x],1),rt[i]=r; 
	}
	for(int i=0;i<qs.size();i++)
	{
		int l=qs[i][1],r=qs[i][2],id=qs[i][0];
		if((mid-l+1)%2==1) continue;
		if(l-1==mid) 
		{
			ans[id]+=sum[r];
			continue;
		}
		ans[id]+=sum[l]+sum[r];
//		cout<<id<<" 2\n";
		int L=1,R=min(t1.sz[rt[l]],t2.sz[rt[r]]),res=0;
		while(L<=R)
		{
			int mi=(L+R)>>1;
			int u=t1.kth(rt[l],1,V,mi),v=t2.kth(rt[r],1,V,t2.sz[rt[r]]-mi+1);
			if(u<v) res=mi,L=mi+1;
			else R=mi-1;
		}
		ans[id]-=t1.kthsum(rt[l],1,V,res);
		ans[id]+=t2.sum[rt[r]]-t2.kthsum(rt[r],1,V,t2.sz[rt[r]]-res);
	}
//	cout<<dl<<" "<<dr<<"\n";
//	for(int i=1;i<=q;i++) cout<<ans[i]<<" ";
//	cout<<"\n";
//	system("pause");
}
void solve()
{
	n=read(),q=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++) rl[i]=a[i];
	sort(rl+1,rl+1+n),V=unique(rl+1,rl+1+n)-rl-1;
	for(int i=1;i<=V;i++) rk[rl[i]]=i;
	for(int i=1;i<=n;i++) b[i]=lower_bound(rl+1,rl+1+V,a[i])-rl;
	vector <array<int,3> > qs;
	for(int i=1;i<=q;i++)
	{
		int l=read(),r=read();
		if((r-l+1)%2==1) ans[i]+=a[r],r--;
		if(l<=r) qs.pb({i,l,r});
	}
	divide(1,n,qs);
	for(int i=1;i<=q;i++) print(ans[i]),*O++=10;
	fwrite(obuf,O-obuf,1,stdout);
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【ROI 2019】无人驾驶出租车

一次询问中,可以走到的格子可以看成一段修改操作的后缀所对应的行列集合,称这些行列合法。

一种情况是可以走曼哈顿距离:当且仅当两点之间每行或每列均合法,或者起点所在行终点所在列合法,起点所在列终点所在行合法。

第二种可能需要绕行,找出起点左右最近的合法列,上下最近的合法行,可以用线段树二分找,根据这些行列稍微分讨一下。

复杂度 \(O(q\log n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
struct Segtree
{
	int maxx[4000005],minn[4000005];
	void update(int id,int l,int r,int x,int y)
	{
		if(l==r)
		{
			maxx[id]=minn[id]=y;
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid) update(id<<1,l,mid,x,y);
		else update(id<<1|1,mid+1,r,x,y);
		maxx[id]=max(maxx[id<<1],maxx[id<<1|1]);
		minn[id]=min(minn[id<<1],minn[id<<1|1]);
	}
	int querymin(int id,int l,int r,int x,int y)
	{
		if(x<=l&&r<=y) return minn[id];
		int mid=(l+r)>>1,res=inf;
		if(x<=mid) res=min(res,querymin(id<<1,l,mid,x,y));
		if(y>mid) res=min(res,querymin(id<<1|1,mid+1,r,x,y));
		return res;
	}
	int find_next(int id,int l,int r,int x,int lim)
	{
		if(maxx[id]<lim) return -1;
		if(l==r) return l;
		int mid=(l+r)>>1;
		if(x<=mid) 
		{
			int t=find_next(id<<1,l,mid,x,lim);
			if(t!=-1) return t;
			return find_next(id<<1|1,mid+1,r,x,lim); 
		}
		else return find_next(id<<1|1,mid+1,r,x,lim); 
	}
	int find_prev(int id,int l,int r,int x,int lim)
	{
	//	cout<<id<<" "<<l<<" "<<r<<" "<<x<<" "<<lim<<" "<<maxx[id]<<"\n";
		if(maxx[id]<lim) return -1;
		if(l==r) return l;
		int mid=(l+r)>>1;
	//	cout<<mid<<"\n";
		if(x>mid) 
		{
			int t=find_prev(id<<1|1,mid+1,r,x,lim);
			if(t!=-1) return t;
			return find_prev(id<<1,l,mid,x,lim); 
		}
		else return find_prev(id<<1,l,mid,x,lim); 
	}
}t1,t2;
int n,m,q;
void solve()
{
	cin>>n>>m>>q;
	for(int clk=1;clk<=q;clk++)
	{
		int op;
		cin>>op;
		if(op==1) 
		{
			int x;
			cin>>x;
			t1.update(1,1,n,x,clk);
		}
		else if(op==2)
		{
			int x;
			cin>>x;
			t2.update(1,1,m,x,clk);
		}
		else
		{
			int x1,y1,x2,y2,lim;
			cin>>x1>>y1>>x2>>y2>>lim;
			
			if(lim>=clk||t1.querymin(1,1,n,min(x1,x2),max(x1,x2))>=clk-lim||t2.querymin(1,1,m,min(y1,y2),max(y1,y2))>=clk-lim)
			{
				cout<<abs(x1-x2)+abs(y1-y2)<<"\n";
				continue;
			}
			lim=clk-lim;
			int a=t1.find_next(1,1,n,x1,lim),b=t1.find_prev(1,1,n,x1,lim);
			int c=t2.find_next(1,1,m,y1,lim),d=t2.find_prev(1,1,m,y1,lim);
		//	system("pause");
			bool okx=(t1.find_next(1,1,n,x2,lim)==x2);
			bool oky=(t2.find_next(1,1,m,y2,lim)==y2);
		//	cout<<a<<" "<<b<<" "<<c<<" "<<d<<" "<<okx<<" "<<oky<<"\n";
			int ans=inf;
			if((a==x1&&oky)||(c==y1&&okx))
			{
				cout<<abs(x1-x2)+abs(y1-y2)<<"\n";
				continue;
			}
			if(okx&&a==x1)
			{
				if(c!=-1) ans=min(ans,abs(x1-x2)+abs(y1-c)+abs(c-y2));
				if(d!=-1) ans=min(ans,abs(x1-x2)+abs(y1-d)+abs(d-y2));
			}
			if(oky&&c==y1)
			{
				if(a!=-1) ans=min(ans,abs(y1-y2)+abs(x1-a)+abs(a-x2));
				if(b!=-1) ans=min(ans,abs(y1-y2)+abs(x1-b)+abs(b-x2));
			}
			if(ans==inf) ans=-1;
			cout<<ans<<"\n";
		}
	 } 
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【LOJ6289】花朵

先想链和菊花:菊花可以看做若干个 \(B_ix+1\) 的多项式乘积的 \(x^M\) 项系数,可以分治 + NTT。链可以令 \(f_{i,j},g_{i,j}\) 分别表示前 \(i\) 个点选 \(j\) 个,第 \(i\) 个点选或不选的答案,令 \(F_i(x),G_i(x)\) 分别表示 \(f_{i,j},g_{i,j}\) 的生成函数,它们可以通过算一些每个元素为一次多项式的 \(2 \times 2\) 的矩阵乘积求出,也可以分治 + NTT。

树上的情况可以考虑树链剖分,考察一条重链,先将重链上所有点挂的轻子树用菊花的方法合并,然后把重链拿下来用链的方法合并。

复杂度,好像是 \(O(n \log^3 n)\) 的,反正能过,不管了。

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int fpow(int x,int b){
	if(x==0) return 0;
	if(b==0) return 1;
	int res=1;
	while(b>0){
		if(b&1)	res=1LL*res*x%mod;
		x=1LL*x*x%mod;
		b>>=1;
	}
	return res;
}
struct Mypoly
{
	int n,m;
	int a[2200005],b[2200005],p[2200005],c[2200005];
	int N=1,lg;
	// ntt
	void ntt(int *A)
	{
		for(int i=0;i<N;i++) if(i<p[i]) swap(A[i],A[p[i]]);
		for(int mid=1;mid<N;mid*=2)
		{
			int v=fpow(3,(mod-1)/(mid*2));
			for(int j=0;j<N;j+=mid*2)
			{
				int w=1;
				for(int k=0;k<mid;k++,w=(w*v)%mod)
				{
					int t1=A[j+k],t2=(w*A[j+k+mid])%mod;
					A[j+k]=(t1+t2)%mod,A[j+k+mid]=(t1-t2+mod)%mod;
				}
			}
		}
	}
	void intt(int *A)
	{
		for(int i=0;i<N;i++) if(i<p[i]) swap(A[i],A[p[i]]);
		for(int mid=1;mid<N;mid*=2)
		{
			int v=fpow(fpow(3,mod-2),(mod-1)/(mid*2));
			for(int j=0;j<N;j+=mid*2)
			{
				int w=1;
				for(int k=0;k<mid;k++,w=(w*v)%mod)
				{
					int t1=A[j+k],t2=(w*A[j+k+mid])%mod;
					A[j+k]=(t1+t2)%mod,A[j+k+mid]=(t1-t2+mod)%mod;
				}
			}
		}
		int inv=fpow(N,mod-2);
		for(int i=0;i<=n+m;i++) A[i]=A[i]*inv%mod;
	}
	void init_butterfly(int x)
	{
		N=1,lg=0;
		while(N<x) N*=2,lg++;
		for(int i=1;i<N;i++) p[i]=(p[i>>1]>>1)|((i&1)<<(lg-1));
	} 
	// poly mul 
	vector <int> mul(vector <int> P,vector <int> Q)
	{
	//	memset(a,0,sizeof(a)),memset(b,0,sizeof(b)),memset(p,0,sizeof(p));
		n=P.size()-1,m=Q.size()-1;
		for(int i=0;i<=n;i++) a[i]=P[i];
		for(int i=0;i<=m;i++) b[i]=Q[i];
		init_butterfly(n+m+1);
		ntt(a),ntt(b);
		for(int i=0;i<N;i++) a[i]=(a[i]*b[i])%mod;
		intt(a);
		vector <int> res;
		res.clear();
		for(int i=0;i<=n+m;i++) res.pb(a[i]);
		for(int i=0;i<=2*N;i++) a[i]=b[i]=p[i]=0;
		return res;
	}
	// poly inv
	void work_getinv(int deg)
	{
		if(deg==1) 
		{
			b[0]=fpow(a[0],mod-2);
			return;
		}
		work_getinv((deg+1)>>1);
		init_butterfly(deg<<1);
		for(int i=0;i<deg;i++) c[i]=a[i];
		for(int i=deg;i<N;i++) c[i]=0;
		ntt(c),ntt(b);
		for(int i=0;i<N;i++) b[i]=1LL*(2-1LL*c[i]*b[i]%mod+mod)%mod*b[i]%mod;
		intt(b);
		for(int i=deg;i<N;i++) b[i]=0;
	}
	vector <int> getinv(vector <int> P)
	{
		n=P.size();
	//	memset(a,0,sizeof(a)),memset(b,0,sizeof(b)),memset(p,0,sizeof(p));
		for(int i=0;i<n;i++) a[i]=P[i];
		work_getinv(n);
		vector <int> Q;
		for(int i=0;i<n;i++) Q.pb(b[i]);
		for(int i=0;i<=2*N;i++) a[i]=b[i]=p[i]=0;
		return Q;
	}
	vector <int> add(vector <int> P,vector <int> Q)
	{
		if(P.size()>Q.size()) swap(P,Q);
		for(int i=0;i<P.size();i++) Q[i]=(Q[i]+P[i])%mod;
		return Q;
	}
}poly;
struct PolyMatrix
{
	vector <int> a[2][2];	
};
PolyMatrix mul(PolyMatrix A,PolyMatrix B)
{
	PolyMatrix C;
	for(int i=0;i<2;i++) for(int j=0;j<2;j++)
	{
		C.a[i][j].clear();
		for(int k=0;k<2;k++) C.a[i][j]=poly.add(C.a[i][j],poly.mul(A.a[i][k],B.a[k][j]));
	}
	return C;
}
vector <int> F[80005],G[80005];
int n,m;
int w[80005];
vector <int> g[80005];
int sz[80005],son[80005],par[80005];
PolyMatrix now[80005];
int idx;
PolyMatrix mul_now(int l,int r)
{
	if(l==r) return now[l];
	if(l+1==r) return mul(now[l],now[r]);
	int mid=(l+r)>>1;
	PolyMatrix L=mul_now(l,mid),R=mul_now(mid+1,r);
	return mul(L,R);
}
vector <int> Now[80005];
vector <int> mul_Now(int l,int r)
{
	if(l==r) return Now[l];
	if(l+1==r) return poly.mul(Now[l],Now[r]);
	int mid=(l+r)>>1;
	vector<int> L=mul_Now(l,mid),R=mul_Now(mid+1,r);
	return poly.mul(L,R);
}
void dfs1(int u,int fa)
{
	par[u]=fa,sz[u]=1;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(v==fa) continue;
		dfs1(v,u),sz[u]+=sz[v];
		if(!son[u]||sz[son[u]]<sz[v]) son[u]=v;
	}
//	son[u]=0;
}
void dfs2(int u)
{
	vector <int> vec;
	vec.clear();
	for(int i=u;i;i=son[i])
	{
		vec.pb(i);
		vector <int> ss;
		ss.clear();
		for(int j=0;j<g[i].size();j++)
		{
			int v=g[i][j];
			if(v==son[i]||v==par[i]) continue;
			dfs2(v);
			ss.pb(v);
		}
		idx=0;
		for(int j=0;j<ss.size();j++)
		{
			int v=ss[j];
			Now[++idx]=G[v];
		}
		idx++;
		Now[idx].clear();
		Now[idx].pb(0),Now[idx].pb(w[i]);
		F[i]=mul_Now(1,idx);
		idx=0;
		for(int j=0;j<ss.size();j++)
		{
			int v=ss[j];
			Now[++idx]=poly.add(F[v],G[v]);
		}
		if(!ss.size()) G[i].pb(1);
		else G[i]=mul_Now(1,idx);
//		cout<<"... "<<i<<"\n";
//		for(int j=0;j<F[i].size();j++) cout<<F[i][j]<<" ";
//		cout<<"\n";
//		for(int j=0;j<G[i].size();j++) cout<<G[i][j]<<" ";
//		cout<<"\n";
	}
	if(vec.size()==1) return;
	idx=0;
//	cout<<"...... ";
//	for(int i=0;i<vec.size();i++) cout<<vec[i]<<" ";
//	cout<<"\n";
	for(int i=vec.size()-1;i>=0;i--)
	{
		idx++;
		now[idx].a[0][0].clear(),now[idx].a[0][1]=G[vec[i]];
		now[idx].a[1][0]=F[vec[i]],now[idx].a[1][1]=G[vec[i]];
	}
	PolyMatrix t=mul_now(1,idx);
	F[u]=t.a[1][0],G[u]=t.a[1][1];
//	cout<<u<<"\n";
//	for(int i=0;i<F[u].size();i++) cout<<F[u][i]<<" ";
//	cout<<"\n";
//	for(int i=0;i<G[u].size();i++) cout<<G[u][i]<<" ";
//	cout<<"\n";
}
void solve()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i];
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].pb(v),g[v].pb(u);
	}
	dfs1(1,-1),dfs2(1);
	F[1]=poly.add(F[1],G[1]);
	if(m<F[1].size()) cout<<F[1][m]<<"\n";
	else cout<<0<<"\n"; 
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【CF303E】Random Ranking

沉舟题!

把所有端点离散化得到序列 \(b_1,b_2,\cdots,b_m\),将数轴按照 \([b_i,b_{i+1})\) 的形式分成 \(m-1\) 块。如果一个块里面有 \(k\) 个人,那么这 \(k!\) 种排名是等概率出现的。

枚举每个人和每个人得分所属的块,令 \(dp(i,j,k)\) 表示,考虑了其它人中的前 \(i\) 个,有 \(j\) 个在所属块前面,有 \(k\) 个同块,这样做是 \(O(n^5)\) 的。

事实上,我们可以预处理每一个人的得分所属每一块的概率,转移可以通过合理安排顺序,做到从 \(dp(i,*,*)\) 转移到 \(dp(i+1,*,*)\) 只需要刷一遍表即可。

然后,就过了,跑的比 \(O(n^4 \log n)\) 甚至要快。

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n;
int L[81],R[81],v[161];
double dp[81][81];
double ans[81];
double le[81][161],on[81][161],ri[81][161];
const double eps=1e-9;
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>L[i]>>R[i],v[2*i-1]=L[i],v[2*i]=R[i];
	sort(v+1,v+1+2*n);
	int m=unique(v+1,v+1+2*n)-v-1;
	for(int i=1;i<=n;i++) for(int j=1;j<m;j++)
	{
		int len=max(0,min(R[i],v[j+1])-max(L[i],v[j]));
		on[i][j]=(double)(len)/(double)(R[i]-L[i]);
		len=max(0,min(v[j],R[i])-L[i]);
		le[i][j]=(double)(len)/(double)(R[i]-L[i]);
		len=max(0,R[i]-max(L[i],v[j+1]));
		ri[i][j]=(double)(len)/(double)(R[i]-L[i]);
//		cout<<i<<" "<<j<<" "<<le[i][j]<<" "<<on[i][j]<<" "<<ri[i][j]<<"\n";
	}
	for(int u=1;u<=n;u++) 
	{
		for(int i=0;i<n;i++) ans[i]=0.0;
		for(int bl=1;bl<m;bl++) if(on[u][bl]>0)
		{
			memset(dp,0,sizeof(dp));
			dp[0][0]=1.0;
			for(int i=0;i<n;i++) 
			{
				if(i+1!=u)
				{
					for(int k=i+1;k>=0;k--) for(int l=i+1-k;l>=0;l--) 
					{
						dp[k][l]=(k?dp[k-1][l]*le[i+1][bl]:0)+(l?dp[k][l-1]*on[i+1][bl]:0)+dp[k][l]*ri[i+1][bl];
					}
				}
			}
			for(int k=0;k<=n;k++) for(int l=0;l<=n-k;l++) 
			{
				double coef=dp[k][l]*on[u][bl]/(double)(l+1);
				ans[k]+=coef,ans[k+l+1]-=coef;
			}
		}
		for(int i=0;i<n;i++) ans[i]+=(i?ans[i-1]:0.0),printf("%.10lf ",ans[i]);
		cout<<"\n";
	}
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【icpc2021 上海】Kaiji!

左右手是无关紧要的,因为最优策略一定是随机选一个数查询。

你需要对每个数设定三个参数:\(x_i,y_i,z_i\),表示知道这个数,猜 \(\lt,=,\gt\) 三种的概率。

将数升序排列并离散化,限制有两种形式:对于所有 \(i\)\(x_i+y_{i+1}\) 和对于出现 \(\ge 2\) 次的所有 \(j\)\(z_j\),最大化求这些式子的最小值。

二分并贪心检验是容易的,但是这个题需要取模,我们需要写出一个比较优美的形式。

构造一个新的序列 \(b\),如果 \(x\) 只出现了 \(1\) 次,则\(b_x=2\),否则 \(b_x=3\),你需要找到一个区间 \([l,r]\),最小化 \(\frac{r-l+1}{(\sum_{i=l}^r b_i)-2}\)

感性理解:只出现 \(1\) 次需要把总概率平均分到 \(2\) 个变量,出现 \(\ge 2\) 次需要平均分到 \(3\) 个变量,而最小的不会猜 \(\gt\),最大的不会猜 \(\lt\),所以要减去 \(2\)

可以去二分概率,尝试找到是否存在一个区间满足 \(\frac{r-l+1}{(\sum_{i=l}^r b_i)-2}\) \(\lt\) 概率,展开这个式子发现是最大子段和,可以 \(O(n)\) 检验。

二分若干次找到一个区间,由于数据随机,这个区间大概率就是最小值。

复杂度 \(O(n \log eps^{-1})\)

Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int fpow(int x,int b)
{
	if(x==0) return 0;
	if(b==0) return 1;
	int res=1;
	while(b>0)
	{
		if(b&1)	res=1LL*res*x%mod;
		x=1LL*x*x%mod;
		b>>=1;
	}
	return res;
}
int n,a0,A,B,C,a[10000005],M;
int cnt[10000005],b[10000005],minn[10000005]; 
double c[10000005];
int idx;
pii calc(double k)
{
	double ans=-10000000000.0;
	pii ret=mp(-1,-1);
	for(int i=1;i<=idx;i++) c[i]=(double)(b[i]-b[i-1])*k-1.0,c[i]+=c[i-1];//,cout<<c[i]<<" ";
//	cout<<"\n";
	double minn=10000000000.0;
	int j=-1;
	minn=0,j=0;
	for(int i=1;i<=idx;i++)
	{
		if(c[i]-minn>ans) ans=c[i]-minn,ret=mp(j+1,i);
		if(c[i]<minn)  minn=c[i],j=i;
	}
//	cout<<ans<<"\n";
	return ret;
}
void solve()
{
	cin>>n>>a0>>A>>B>>C>>M;
	for(int i=1;i<=n;i++) cnt[i]=0;
	for(int i=1,j=a0;i<=n;i++) j=(ll)(1LL*A*j%M*j%M+1LL*B*j%M+C)%M+1,a[i]=j,cnt[a[i]]++;//,cout<<a[i]<<"\n";
	idx=0;
	for(int i=1;i<=n;i++) if(cnt[i])
	{
		if(cnt[i]==1) b[++idx]=2;
		else b[++idx]=3;
	}
	for(int i=2;i<=idx;i++) b[i]+=b[i-1];
//	for(int i=1;i<=idx;i++) cout<<b[i]<<" ";
//	cout<<"\n";
	if(idx==1)
	{
		cout<<1<<"\n";
		return;
	}
	for(int i=1;i<=idx;i++) minn[i]=-1;
	double L=0.33,R=1.00;
	pii r=mp(-1,-1);
	for(int t=1;t<=20;t++)
	{
		double mid=(L+R)/2.0;
		pii res=calc(mid);
		if(mid*(double)(b[res.se]-b[res.fi-1])-(res.se-res.fi+1)>=2.0*mid) R=mid,r=res;//,cout<<"< "<<mid<<"\n";
		else L=mid,r=res;//,cout<<"> "<<mid<<"\n";
		if(t==20) 
		{
			res=r;
			int ans=1LL*(res.se-res.fi+1)*fpow(b[res.se]-b[res.fi-1]-2,mod-2)%mod;
			cout<<ans<<"\n";
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
	cin>>_;
	while(_--) solve();
	return 0;
}

神秘题

题意:给定一棵 \(n\) 个节点的二叉树,根为 \(1\)\(q\) 次操作:单点修改权值 \(a_u\);询问子树 \(v\) 内有多少个点的子树是二叉搜索树,\(1 \le n,q \le 2 \times 10^5,1 \le a_i \le 10^9\)

先把中序遍历的序列搞出来,一个节点的子树是二叉搜索树,对应到序列上是一段不降区间;对应到树上是,左右子树都是二叉搜索树,且左子树 \(\max\) \(\le\) 权值 $\le $ 右子树 \(\min\)

单点修改 \(u\) 的影响,在树上看是把 \(1\)\(u\) 路径上所有点设置成不合法,然后把 \(u\) 到某个祖先 \(f\) 的路径上所有点设置成合法。在序列上看是尝试将所在连续段分裂,尝试将所在连续段和左右两个连续段合并。可以用 set 维护出连续段。

维护出连续段后,可以用树上倍增找出 \(f\),用树剖维护路径赋 \(0\) 和赋 \(1\) 和子树查询。

复杂度 \(O(q \log^2 n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,ls[200005],rs[200005];
vector <int> g[200005];
struct Segtree
{
	int tag[800005],sum[800005];
	void pushdown(int id,int l,int r)
	{
		if(tag[id]!=-1)
		{
			tag[id<<1]=tag[id],tag[id<<1|1]=tag[id];
			int mid=(l+r)>>1;
			sum[id<<1]=(mid-l+1)*tag[id],sum[id<<1|1]=(r-mid)*tag[id];
			tag[id]=-1;
		}
	}	
	void update(int id,int l,int r,int x,int y,int d)
	{
		if(x<=l&&r<=y)
		{
			sum[id]=d*(r-l+1),tag[id]=d;
			return;
		}
		pushdown(id,l,r);
		int mid=(l+r)>>1;
		if(x<=mid) update(id<<1,l,mid,x,y,d);
		if(y>mid) update(id<<1|1,mid+1,r,x,y,d);
		sum[id]=sum[id<<1]+sum[id<<1|1];
//for(int i=x;i<=y;i++) sum[i]=d;
	}
	int query(int id,int l,int r,int x,int y)
	{
		if(x<=l&&r<=y) return sum[id];
		pushdown(id,l,r);
		int mid=(l+r)>>1,res=0;
		if(x<=mid) res+=query(id<<1,l,mid,x,y);
		if(y>mid) res+=query(id<<1|1,mid+1,r,x,y);
		return res;
//int res=0;
//for(int i=x;i<=y;i++) res+=sum[i];
//return res;
	}
	int par[200005],son[200005],sz[200005],dep[200005];
int top[200005],dfn[200005],dfnR[200005],clk;
void pre(int u,int p)
{
    par[u]=p,son[u]=0,sz[u]=1,dep[u]=dep[p]+1;
    int mx=0;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(p==v) continue;
        pre(v,u);
        sz[u]+=sz[v];
        if(sz[v]>mx) mx=sz[v],son[u]=v;
    }
}
void dfs(int u,int p)
{
    top[u]=p,dfn[u]=++clk;
    dfnR[u]=dfn[u];
    if(son[u]) dfs(son[u],p),dfnR[u]=dfnR[son[u]];
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==son[u]||v==par[u]) continue;
        dfs(v,v);
        dfnR[u]=dfnR[v];
    }
}
void path_update(int u,int v,int d)
{
	int cnt_t=0;
//    int ans=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        update(1,1,n,dfn[top[u]],dfn[u],d);
        u=par[top[u]];
        cnt_t++;
    }
//    cout<<cnt_t<<"\n";
    if(dep[u]>dep[v]) swap(u,v);
    update(1,1,n,dfn[u],dfn[v],d);
}
int query_sub(int x)
{
	return query(1,1,n,dfn[x],dfnR[x]);
}
}st;
int a[200005],fa[20][200005];
vector <int> vec;
set <pii > S; 
int pos[200005],vL[200005],vR[200005];
void get_seq(int u)
{
	vL[u]=vec.size();
	if(ls[u]) get_seq(ls[u]);
	pos[u]=vec.size(),vec.pb(a[u]);
	if(rs[u]) get_seq(rs[u]);
	vR[u]=vec.size()-1;
}
bool chk(int i)
{
	auto it=prev(S.lower_bound(mp(vL[i]+1,-1)));
	if((*it).se>=vR[i]) return 1;
	return 0;
}
void merge_next(int x)
{
	auto it=prev(S.lower_bound(mp(x+1,-1)));
	pii t=(*it);
	pii t2=(*next(it));
	S.erase(t),S.erase(t2),S.insert(mp(t.fi,t2.se));
}
void merge_prev(int x)
{
	auto it=prev(S.lower_bound(mp(x+1,-1)));
	pii t=(*it);
	pii t2=(*prev(it));
	S.erase(t),S.erase(t2),S.insert(mp(t2.fi,t.se));
}
void solve()
{
	int q,dirt;
	cin>>n>>q>>dirt;
	memset(st.tag,-1,sizeof(st.tag));
	memset(fa,-1,sizeof(fa));
	for(int i=1;i<=n;i++) 
	{
		cin>>ls[i]>>rs[i];
		if(ls[i]) g[i].pb(ls[i]),fa[0][ls[i]]=i;
		if(rs[i]) g[i].pb(rs[i]),fa[0][rs[i]]=i;
	}	
	for(int k=1;k<20;k++) for(int i=1;i<=n;i++) if(fa[k-1][i]!=-1) fa[k][i]=fa[k-1][fa[k-1][i]];
	for(int i=1;i<=n;i++) cin>>a[i];
	vec.pb(-1);
	get_seq(1);
	st.pre(1,1),st.dfs(1,1);
//	for(int i=1;i<=n;i++) cout<<st.top[i]<<"\n";
	for(int i=1;i<=n;i++)
	{
		int j=i;
		while(j+1<=n&&vec[j]<=vec[j+1]) j++;
		S.insert(mp(i,j));
		i=j;
	}
	for(int i=1;i<=n;i++) st.path_update(i,i,chk(i));
	while(q--)
	{
		int op;
		cin>>op;
		if(op==1)
		{
			int x,y;
			cin>>x>>y;
			
//			continue;
			pii t=*prev(S.lower_bound(mp(pos[x]+1,-1)));
			S.erase(t);
			if(pos[x]>t.fi) S.insert(mp(t.fi,pos[x]-1));
			S.insert(mp(pos[x],pos[x]));
			if(pos[x]<t.se) S.insert(mp(pos[x]+1,t.se));
			vec[pos[x]]=y;
			if(pos[x]<n&&vec[pos[x]]<=vec[pos[x]+1]) merge_next(pos[x]);
			if(pos[x]>1&&vec[pos[x]-1]<=vec[pos[x]]) merge_prev(pos[x]);
//			cout<<"... "<<chk(x)<<" "<<st.top[x]<<"\n";
			if(!chk(x)) st.path_update(1,x,0);
			else 
			{
				st.path_update(1,x,0);
				t=*prev(S.lower_bound(mp(pos[x]+1,-1)));
				int u=x;
				for(int k=19;k>=0;k--) if(fa[k][u]!=-1&&t.fi<=vL[fa[k][u]]&&vR[fa[k][u]]<=t.se) u=fa[k][u];
				st.path_update(u,x,1);
			}
		}
		else
		{
			int x;
			cin>>x;
			cout<<st.query_sub(x)<<"\n";
		}
//		if(q%1000==0) cerr<<q<<"\n";
//		for(auto it=S.begin();it!=S.end();it++) cout<<(*it).fi<<" "<<(*it).se<<"\n";
//		cout<<"...\n";
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【CCO 2023】Flip it and Stick it

又是大缝合怪!但每种情况想起来都蛮有意思的。

  • \(|T|=1\)。只需要判断 \(|S|\) 中是否有对应字符。
  • \(|T|=2,T_1 \neq T_2\)。令 \(T=01\),目标串一定是,所有 \(1\) 构成一个前缀,所有 \(0\) 构成一个后缀,答案为,所有不为前缀的 \(1\) 构成的极长连续段数量(每次操作将一个连续段移到前面)。
  • \(|T|=2,T_1 \neq T_2\)。令 \(T=00\),意味着不能出现相邻的 \(0\),先特判 \(0\) 的个数超出 \(\lceil n/2\rceil\),在有解的情况下,令 \(l_1,l_2,\cdots,l_k\) 是所有 \(0\) 构成的极长连续段长度,每次操作可以看做把一个 \(1\) 插到两个 \(0\) 中间,则答案为 \(\sum_{i=1}^{k} (l_i-1)\)
  • \(|T|=3,T_1 \neq T_3\)。此时 \(T\)\(S\) 中所有出现位置均不交,一次操作可以破坏一个出现位置,故答案为 \(T\)\(S\) 中的出现次数。
  • \(|T|=3,T_1 \neq T_2\)。令 \(T=010\),目标串不能出现一个长度为 \(1\) 的,不为前后缀的,\(1\) 构成的极长连续段。一次操作可以合并两个长度为 \(1\) 的连续段,或者将一个长度为 \(1\) 的连续段和更长的段合并,令 \(cnt\) 表示长度为 \(1\) 的,不为前后缀的,\(1\) 构成的极长连续段的数量,则答案为 \(\lceil cnt/2 \rceil\)
  • \(|T|=3\) 的其余情况。令 \(T=000\),目标串不能出现一个长度 \(\ge 3\)\(0\) 连续段,先特判 \(0\) 的个数超出 \(\lfloor n/3 \rfloor+n \bmod 3\)。观察一次操作之后连续段长度会发生什么变化:即删除两个长度为 \(x,y\) 的连续段,加入两个 \(x',y'(0 \le x',y' \le x+y,x'+y'=x+y)\) 的连续段。初始状态可以看做,若干个连续段可以增加 \(1\)\(2\) 个长度,若干连续段需要减少若干个长度。我们需要最大化“一次增减 \(2\) 个长度”的操作次数,贪心即可。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
char s[200005],t[5];
int n,m;
int calc_occ()
{
	int cnt=0;
	for(int i=1;i<=n-m+1;i++) 
	{
		bool flg=1;
		for(int j=1;j<=m;j++) if(s[i+j-1]!=t[j]) flg=0;
		if(flg) cnt++; 
	}
	return cnt;
}
void solve()
{
	cin>>(s+1)>>(t+1);
	n=strlen(s+1),m=strlen(t+1);
	if(m==1)
	{
		cout<<(calc_occ()?-1:0);
		return;
	}
	if(m==2&&t[1]!=t[2])
	{
		if(t[1]=='0') for(int i=1;i<=n;i++) s[i]=(s[i]=='1'?'0':'1');
		int ans=0;
		for(int i=1;i<=n;i++) if(s[i]=='0')
		{
			int j=i;
			while(j<=n&&s[j]=='0') j++;
			j--;
			if(i!=1) ans++;
			i=j;
		}
		cout<<ans<<"\n";
		return;
	} 
	if(m==2&&t[1]==t[2])
	{
		if(t[1]=='0') for(int i=1;i<=n;i++) s[i]=(s[i]=='1'?'0':'1');
		int ans=0,occ=0;
		for(int i=1;i<=n;i++) if(s[i]=='1')
		{
			int j=i;
			while(j<=n&&s[j]=='1') j++;
			j--;
			ans+=j-i;
			occ+=j-i+1;
			i=j;
		}
		if(occ>(n+1)/2) ans=-1;
		cout<<ans<<"\n";
		return;
	}
	if(m==3&&t[1]!=t[3])
	{
		cout<<calc_occ()<<"\n";
		return;
	}
	if(m==3&&t[1]!=t[2]&&t[2]!=t[3])
	{
		if(t[1]=='0') for(int i=1;i<=n;i++) s[i]=(s[i]=='1'?'0':'1');
		vector <int> vec;
		vec.clear();
		for(int i=1;i<=n;i++) if(s[i]=='0')
		{
			int j=i;
			while(j<=n&&s[j]=='0') j++;
			j--;
			if(i>1&&j<n) vec.pb((j-i+1==1?0:1));
			i=j;
		}
		int ans=0;
		for(int i=0;i<vec.size();i++) if(vec[i]==0) ans++;
		cout<<(ans+1)/2<<"\n";
		return;
	}
	if(m==3&&t[1]==t[2]&&t[2]==t[3])
	{
		if(t[1]=='0')  for(int i=1;i<=n;i++) s[i]=(s[i]=='1'?'0':'1');
		int ans=0,occ=0;
		vector <int> vec,vec2;
		vec.clear(),vec2.clear();
		vec.pb(0);
		for(int i=1;i<=n;i++)
		{
			if(s[i]=='0') vec.pb(i);
			else occ++;
		}
		vec.pb(n+1);
		if(occ>n/3*2+n%3)
		{
			cout<<-1<<"\n";
			return;
		}
		int c1=0,c2=0;
		for(int i=0;i+1<vec.size();i++)
		{
			int l=vec[i+1]-vec[i]-1;
			if(l==0) c2++;
			else if(l==1) c1++;
			else vec2.pb(l-2);
		}
		for(int i=0;i<vec2.size();i++)
		{
			while(c2&&vec2[i]>=2) vec2[i]-=2,c2--,ans++;
			while(vec2[i]) vec2[i]--,c1--,ans++;
		}
		cout<<ans<<"\n";
	}
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【CCO 2023】Triangle Collection

假设我们钦定要组成 \(x\) 个等腰三角形,可以贪心的凑出最大的 \(x\) 对腰。检查是否合法是一个二分图匹配状物,考虑 Hall 定理,令 \(a_x\) 表示长度 \(\le x\) 的腰选了几对,\(b_x\) 表示长度为 \(x\) 的腰总共能选出多少个合法的底,则对于所有 \(x\)\(a_x \le b_x\)

二分 \(x\) 特别不好做,查询 \(a_x,b_x\) 很麻烦,我们有更好的解法。

考虑这样一个过程:先不管是否合法,配出最多的腰,然后拆散一些腰作为底使其合法。

用支持区间加,全局 max 的线段树维护 \(a_x-b_x\),查询时找到最大值,拆散一对腰会使得最大的 \(a_x-b_x\) 减去 \(3\)\(a_x\) 减去 \(1\)\(b_x\) 加上 \(2\)),复杂度 \(O((n+q) \log n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int t[800005],lz[800005];
void pushdown(int id)
{
	if(lz[id])
	{
		t[id<<1]+=lz[id],t[id<<1|1]+=lz[id];
		lz[id<<1]+=lz[id],lz[id<<1|1]+=lz[id];
		lz[id]=0;
	}
}
void update(int id,int l,int r,int x,int y,int d)
{
	if(x<=l&&r<=y)
	{
		lz[id]+=d,t[id]+=d;
		return;
	}
	pushdown(id);
	int mid=(l+r)>>1;
	if(x<=mid) update(id<<1,l,mid,x,y,d);
	if(y>mid) update(id<<1|1,mid+1,r,x,y,d);
	t[id]=max(t[id<<1],t[id<<1|1]);
}
int a[200005],n,q; 
void solve()
{
	cin>>n>>q;
	int s=0;
	for(int i=1;i<=n;i++) cin>>a[i],update(1,1,n,i,n,a[i]/2),update(1,1,n,i/2+1,n,-(a[i]%2)),s+=a[i]/2;
	while(q--)
	{
		int x,y;
		cin>>x>>y;
		update(1,1,n,x,n,-(a[x]/2)),update(1,1,n,x/2+1,n,(a[x]%2)),s-=a[x]/2;
		a[x]+=y;
		update(1,1,n,x,n,a[x]/2),update(1,1,n,x/2+1,n,-(a[x]%2)),s+=a[x]/2;
		cout<<s-(max(0LL,t[1])+2)/3<<"\n";
		
	 } 
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【USACO2020.1 P】Falling Portals

\(N\) 条直线 \(y=-ix+A_i(x \ge 0)\) 画到坐标系里,问题即为求出从每条线 \(i\) 起点往下走,走到 \(Q_i\) 的最小 \(x\) 坐标。

\(A_{Q_i} \lt A_i\),即我们需要往下走,越快越好。也就是说,如果遇到了一条斜率绝对值更大的线,走到这条线上一定更优。我们还可以发现,只有 \(A_j \gt A_i\) 的线 \(j\) 是有用的(若 \(A_j \lt A_i\)\(j \lt i\) 没用,\(j \gt i\) 大追不上)。按 \(A_i\) 从大往小考虑, 每次可以看做往凸包里面插入一条直线,并求直线 \(Q_i\) 和凸包的交点,求交点可以二分。

\(A_{Q_i} \gt A_i\) 同理,复杂度 \(O(n \log n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n,A[200005],Q[200005],ord[200005];
bool cmp(int x,int y)
{
	return A[x]<A[y];
}
int ansp[200005],ansq[200005],stk[200005],top;
bool chk(int x,int y,int z)
{
	return (A[x]-A[z])*(y-z)<(A[y]-A[z])*(x-z);
}
void ins_down(int id)
{
	while((top>=2&&chk(stk[top-1],stk[top],id))||(top&&id>stk[top])) top--;
	stk[++top]=id;
	if(A[Q[id]]>A[id]||stk[1]<Q[id]) return;
	int l=2,r=top,res=1;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(stk[mid]<Q[id]) r=mid-1;
		else if(chk(stk[mid-1],stk[mid],Q[id])) r=mid-1;
		else res=mid,l=mid+1;
	}
	ansp[id]=A[stk[res]]-A[Q[id]],ansq[id]=stk[res]-Q[id]; 
}
void ins_up(int id)
{
	while((top>=2&&chk(stk[top-1],stk[top],id))||(top&&id<stk[top])) top--;
	stk[++top]=id;
//	cout<<id<<"\n";
//	for(int i=1;i<=top;i++) cout<<stk[i]<<"\n";
//	system("pause");
	if(A[Q[id]]<A[id]||stk[1]>Q[id]) return;
	int l=2,r=top,res=1;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(stk[mid]>Q[id]) r=mid-1;
		else if(chk(stk[mid-1],stk[mid],Q[id])) r=mid-1;
		else res=mid,l=mid+1;
	}
	ansp[id]=A[stk[res]]-A[Q[id]],ansq[id]=stk[res]-Q[id]; 
}
void solve()
{
	
	cin>>n;
	for(int i=1;i<=n;i++) cin>>A[i],ord[i]=i;
	for(int i=1;i<=n;i++) cin>>Q[i];
	sort(ord+1,ord+1+n,cmp);
	memset(ansp,-1,sizeof(ansp));
	for(int i=n;i>=1;i--) ins_down(ord[i]);
	memset(stk,0,sizeof(stk)),top=0;
	for(int i=1;i<=n;i++) ins_up(ord[i]);
	for(int i=1;i<=n;i++) 
	{
		if(ansp[i]==-1) cout<<"-1\n";
		else 
		{
//			int g=1;
			int g=__gcd(ansp[i],ansq[i]);
			cout<<ansp[i]/g<<"/"<<ansq[i]/g<<"\n";
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【LibreOJ Round #11】Misaka Network 与 Accelerator

一眼 2-sat,考虑树分治优化建图。

最容易想到的是点分,但由于点分存在多个子树,而由于下界 \(L\) 的存在,每个子树又不能连向自己,只能用主席树,常数特别大。

而边分的优势在于只需要考虑两个子树之间的影响,将一个子树中的点按深度排序,则另一个子树中每个点只需要和一个区间连边,用线段树优化。

复杂度 \(O(n \log^2 n)\)

由于 \(L,R\) 固定,这题其实可以做到 \(O(n \log n)\)。边分治的时候将所有点按深度排序后分块,第 \(i\) 块包含所有深度属于 \([(i-1)(R-L+1)+1,i(R-L+1)]\) 的所有点,对于每一块前后缀优化建图。这个做法常数巨大,表现甚至远远不如 \(O(n \log^2 n)\) 的做法。

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int eid,uidx;
struct TWO_SAT
{
int p[10000005],to[10000005],nxt[10000005];
void add(int u,int v)
{
//	cout<<u<<" --> "<<v<<"\n";
	eid++;
	to[eid]=v,nxt[eid]=p[u],p[u]=eid;
}
int stk[10000005],top,dfn[10000005],low[10000005],bl[10000005],clk,scccnt;
bool instk[10000005];
void tarjan(int u)
{
	dfn[u]=low[u]=++clk;
	instk[u]=1,stk[++top]=u;
	for(int i=p[u];i;i=nxt[i])
	{
		int v=to[i];
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(instk[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		scccnt++;
		while(1)
		{
			int x=stk[top];
			top--;
			bl[x]=scccnt,instk[x]=0;
			if(x==u) break;
		}
	}
}
}ts;
struct Segtree1
{
	int idx[400005];
	void build(int id,int l,int r)
	{
		idx[id]=++uidx;
		if(l==r) return;
		int mid=(l+r)>>1;
		build(id<<1,l,mid),build(id<<1|1,mid+1,r);
		ts.add(idx[id],idx[id<<1]),ts.add(idx[id],idx[id<<1|1]);
	}
	void update(int id,int l,int r,int x,int d)
	{
		if(l==r) 
		{
			ts.add(idx[id],d);
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid) update(id<<1,l,mid,x,d);
		else update(id<<1|1,mid+1,r,x,d);
	}
	void lnk(int id,int l,int r,int x,int y,int d)
	{
		if(x<=l&&r<=y) 
		{
			ts.add(d,idx[id]);
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid) lnk(id<<1,l,mid,x,y,d);
		if(y>mid) lnk(id<<1|1,mid+1,r,x,y,d);
	}
}t1[2];
struct Segtree2
{
	int idx[400005];
	void build(int id,int l,int r)
	{
		idx[id]=++uidx;
		if(l==r) return;
		int mid=(l+r)>>1;
		build(id<<1,l,mid),build(id<<1|1,mid+1,r);
		ts.add(idx[id<<1],idx[id]),ts.add(idx[id<<1|1],idx[id]);
	}
	void update(int id,int l,int r,int x,int d)
	{
		if(l==r) 
		{
			ts.add(d,idx[id]);
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid) update(id<<1,l,mid,x,d);
		else update(id<<1|1,mid+1,r,x,d);
	}
	void lnk(int id,int l,int r,int x,int y,int d)
	{
		if(x<=l&&r<=y) 
		{
			ts.add(idx[id],d);
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid) lnk(id<<1,l,mid,x,y,d);
		if(y>mid) lnk(id<<1|1,mid+1,r,x,y,d);
	}
}t2[2];
int N,M,L,R;
vector <pii > g[100005];
vector <int> hasson[50005];
bool has[50005][4];
int vidx;
bool Vis[100005];
map <int,int> ban[100005];
void rebuild(int u,int fa)
{
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i].fi;
		if(v==fa) continue;
		hasson[u].pb(v);
		rebuild(v,u);
	}
}
int T[50005],F[50005];
int sz[100005],dep[100005],par[100005],mxdep=0;
vector <int> ver;
void dfs1(int u,int fa)
{
	sz[u]=1,ver.pb(u);
	par[u]=fa;
	mxdep=max(mxdep,dep[u]);
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i].fi;
		if(v==fa) continue;
		dep[v]=dep[u]+g[u][i].se,dfs1(v,u);
		sz[u]+=sz[v];
	}
}
void divide(int u)
{
	ver.clear();
	dfs1(u,-1);
//	for(int i=0;i<ver.size();i++) cout<<ver[i]<<" ";
//	cout<<"\n";
	if(sz[u]==1) return;
	int maxx=inf,res=-1;
	for(int i=0;i<ver.size();i++) if(ver[i]!=u&&max(sz[ver[i]],sz[u]-sz[ver[i]])<maxx) maxx=max(sz[ver[i]],sz[u]-sz[ver[i]]),res=ver[i];
	u=res;
	int v=par[u];
	int wuv=-1;
	for(int i=0;i<g[u].size();i++) if(g[u][i].fi==v) wuv=g[u][i].se,g[u].erase(g[u].begin()+i);
	for(int i=0;i<g[v].size();i++) if(g[v][i].fi==u) g[v].erase(g[v].begin()+i);
//	cout<<u<<" --- "<<v<<" "<<wuv<<"\n";
//	system("pause");
	mxdep=dep[u]=dep[v]=1;
	ver.clear();
	dfs1(u,-1);
	t1[0].build(1,1,mxdep),t1[1].build(1,1,mxdep),t2[0].build(1,1,mxdep),t2[1].build(1,1,mxdep);
	for(int i=0;i<ver.size();i++) if(ver[i]<=N) 
		t1[0].update(1,1,mxdep,dep[ver[i]],F[ver[i]]),t2[0].update(1,1,mxdep,dep[ver[i]],F[ver[i]]),
		t1[1].update(1,1,mxdep,dep[ver[i]],T[ver[i]]),t2[1].update(1,1,mxdep,dep[ver[i]],T[ver[i]]);
	int t_mxdep=mxdep;
	ver.clear();
	dfs1(v,-1);
	mxdep=t_mxdep;
	for(int i=0;i<ver.size();i++) if(ver[i]<=N)
	{
		int w=ver[i];
		int l=L-(dep[w]-1+wuv)+1,r=R-(dep[w]-1+wuv)+1;
		l=max(1,l),r=min(r,mxdep);
//		cout<<w<<" "<<l<<" "<<r<<"\n";
		if(l>r) continue;
		if(has[w][0]) t1[0].lnk(1,1,mxdep,l,r,F[w]),t2[1].lnk(1,1,mxdep,l,r,T[w]);
		if(has[w][1]) t1[1].lnk(1,1,mxdep,l,r,F[w]),t2[0].lnk(1,1,mxdep,l,r,T[w]);
		if(has[w][2]) t1[0].lnk(1,1,mxdep,l,r,T[w]),t2[1].lnk(1,1,mxdep,l,r,F[w]);
		if(has[w][3]) t1[1].lnk(1,1,mxdep,l,r,T[w]),t2[0].lnk(1,1,mxdep,l,r,F[w]);
	}
	swap(u,v);
	mxdep=dep[u]=dep[v]=1;
	ver.clear();
	dfs1(u,-1);
	t1[0].build(1,1,mxdep),t1[1].build(1,1,mxdep),t2[0].build(1,1,mxdep),t2[1].build(1,1,mxdep);
	for(int i=0;i<ver.size();i++) if(ver[i]<=N) 
		t1[0].update(1,1,mxdep,dep[ver[i]],F[ver[i]]),t2[0].update(1,1,mxdep,dep[ver[i]],F[ver[i]]),
		t1[1].update(1,1,mxdep,dep[ver[i]],T[ver[i]]),t2[1].update(1,1,mxdep,dep[ver[i]],T[ver[i]]);
	t_mxdep=mxdep;
	ver.clear();
	dfs1(v,-1);
	mxdep=t_mxdep;
	for(int i=0;i<ver.size();i++) if(ver[i]<=N)
	{
		int w=ver[i];
		int l=L-(dep[w]-1+wuv)+1,r=R-(dep[w]-1+wuv)+1;
//		cout<<w<<" "<<l<<" "<<r<<"\n";
		l=max(1,l),r=min(r,mxdep);
		if(l>r) continue;
		if(has[w][0]) t1[0].lnk(1,1,mxdep,l,r,F[w]),t2[1].lnk(1,1,mxdep,l,r,T[w]);
		if(has[w][1]) t1[1].lnk(1,1,mxdep,l,r,F[w]),t2[0].lnk(1,1,mxdep,l,r,T[w]);
		if(has[w][2]) t1[0].lnk(1,1,mxdep,l,r,T[w]),t2[1].lnk(1,1,mxdep,l,r,F[w]);
		if(has[w][3]) t1[1].lnk(1,1,mxdep,l,r,T[w]),t2[0].lnk(1,1,mxdep,l,r,F[w]);
	}
	divide(u),divide(v);
}
void solve()
{
	cin>>N>>M>>L>>R;
	vidx=N;
	for(int i=1;i<N;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].pb(mp(v,1)),g[v].pb(mp(u,1));
	}
	for(int i=1;i<=N;i++) T[i]=++uidx,F[i]=++uidx;
	for(int i=1;i<=M;i++)
	{
		int u,t;
		cin>>u>>t;
		has[u][t]=1; 
	}
	rebuild(1,-1);
	for(int i=1;i<=N;i++) g[i].clear();
	for(int i=1;i<=N;i++)
	{
		for(int j=0,nw=i;j<hasson[i].size();j++)
		{
			g[nw].pb(mp(hasson[i][j],1)),g[hasson[i][j]].pb(mp(nw,1));
			if(j+1<hasson[i].size())
			{
				int v=++vidx;
				g[nw].pb(mp(v,0)),g[v].pb(mp(nw,0));
				nw=v;
			}
		}
	}
//	for(int i=1;i<=vidx;i++)
//	{
//		cout<<i<<":\n";
//		for(int j=0;j<g[i].size();j++) cout<<g[i][j].fi<<","<<g[i][j].se<<"\n";
//		cout<<"\n";
//	}
	divide(1);
	for(int i=1;i<=uidx;i++) if(!ts.dfn[i]) ts.tarjan(i);
	for(int i=1;i<=N;i++) if(ts.bl[T[i]]==ts.bl[F[i]]) 
	{
		cout<<"NO\n";
		return;
	}
	cout<<"YES\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【COCI 2020.2】Matching

由于每一个横坐标,每一个纵坐标都只有两个点,我们可以假设所有线段都是平行于 \(x\) 轴的,尽可能多的找出匹配关系,显然这样的方案唯一。

考虑调整,维护一个队列表示目前所有没被匹配的点,取出队首,只能画平行于 \(y\) 轴的线去匹配它。但是线段不能相交,这个匹配关系会破坏一些横着的匹配,可以通过线段树套 set 找出这些关系。由于一对关系最多插入一次,删除一次,复杂度 \(O(n \log^2 n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n;
int X[100005],Y[100005];
vector <int> hasX[100005],hasY[100005];
int O[100005];
set <pii > t[400005];
void update(int id,int l,int r,int x,int y,pii d,int op)
{
	if(x<=l&&r<=y)
	{
		if(op==1) t[id].insert(d);
		else t[id].erase(d);
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(id<<1,l,mid,x,y,d,op);
	if(y>mid) update(id<<1|1,mid+1,r,x,y,d,op);
}
vector <pii > vec;
void query(int id,int l,int r,int x,int dl,int dr)
{
	for(auto it=t[id].lower_bound(mp(dl,-1));it!=t[id].end()&&(*it).fi<=dr;it++) vec.pb(*it);
	if(l==r) return;
	int mid=(l+r)>>1;
	if(x<=mid) query(id<<1,l,mid,x,dl,dr);
	else query(id<<1|1,mid+1,r,x,dl,dr);
}
const int V=100000;
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>X[i]>>Y[i],hasX[X[i]].pb(i),hasY[Y[i]].pb(i);
	memset(O,-1,sizeof(O));
	for(int i=1;i<=V;i++) if(hasY[i].size()==2) 
	{
		int u=hasY[i][0],v=hasY[i][1];
//		cout<<"matching: "<<u<<" "<<v<<"\n"; 
		O[u]=v,O[v]=u;
		update(1,1,V,min(X[u],X[v]),max(X[u],X[v]),mp(Y[u],u),1);
	}
	queue <int> q;
	while(q.size()) q.pop();
	for(int i=1;i<=n;i++) if(O[i]==-1) q.push(i);
	while(q.size())
	{
		int u=q.front();
//		cout<<u<<"\n";
		q.pop();
		if(O[u]!=-1) continue;
		if(hasX[X[u]].size()==1) 
		{
			cout<<"NE\n";
			return;
		}
		vec.clear();
		int v=(hasX[X[u]][0]==u?hasX[X[u]][1]:hasX[X[u]][0]); 
		O[u]=v,O[v]=u;
		query(1,1,V,X[u],min(Y[u],Y[v]),max(Y[u],Y[v]));
		for(int j=0;j<vec.size();j++) 
		{
			int x=vec[j].se;
			int y=(hasY[Y[x]][0]==x?hasY[Y[x]][1]:hasY[Y[x]][0]);
//			cout<<vec[j].fi<<" "<<vec[j].se<<" "<<x<<" "<<y<<"\n";
			update(1,1,V,min(X[x],X[y]),max(X[x],X[y]),vec[j],-1);
			if(x!=u&&x!=v) O[x]=-1,q.push(x);
			if(y!=u&&y!=v) O[y]=-1,q.push(y);
		}
//		for(int j=1;j<=n;j++) cout<<O[j]<<" ";
//		cout<<"\n"; 
	}
	cout<<"DA\n";
	for(int i=1;i<=n;i++) if(O[i]<i) cout<<O[i]<<" "<<i<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【JOI Open 2017】高尔夫

观察行动路线:每一段要么贴着矩形的一条边,要么过起点或终点。

将坐标离散化,我们需要对于每条边找出最远能上下/左右延伸到哪里,可以通过扫描线 + set 维护。

找出这些线段之后,对于每个线段建一个点,若两条线段有交则连一条边,答案就是最短路,可以 bfs 一下。

然后就是和 LOJ546 网格图 一模一样的主席树优化建图,两题代码编辑距离不超过 2k。

复杂度 \(O(n \log n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,m;
vector <int> hasX[200005],hasY[200005];
vector <array<int,3> > vX[200005],vY[200005],op[200005]; 
struct Graph
{
	int p[40000005],to[40000005],nxt[40000005],eid;
	void addedge(int u,int v)
	{
		eid++;
		to[eid]=v,nxt[eid]=p[u],p[u]=eid; 
	}
}g0,g1;
int cnt_e=0;
void add(int u,int v,int w)
{
	if(!w) g0.addedge(u,v);
	else g1.addedge(u,v);
}
int ls[20000005],rs[20000005],uidx,dis[20000005];
void print(int id,int l,int r)
{
//	cout<<id<<" "<<l<<" "<<r<<" "<<ls[id]<<" "<<rs[id]<<"\n";
	if(l==r) return;
	if(ls[id]) print(ls[id],l,(l+r)>>1);
	if(rs[id]) print(rs[id],((l+r)>>1)+1,r);
}
int build(int l,int r)
{
	int u=++uidx;
	if(l==r) return u;
	int mid=(l+r)>>1;
	ls[u]=build(l,mid),rs[u]=build(mid+1,r);
	add(u,ls[u],0),add(u,rs[u],0);
	return u;
}
int update(int id,int l,int r,int x,int d)
{
//	cout<<"update: "<<id<<" "<<l<<" "<<r<<" "<<x<<" "<<d<<" "<<ls[id]<<" "<<rs[id]<<"\n";
//	system("pause");
	int u=++uidx;
	ls[u]=ls[id],rs[u]=rs[id];
	if(l==r)
	{
		if(d) add(u,d,0);
		return u;
	}
	int mid=(l+r)>>1;
	if(x<=mid) ls[u]=update(ls[id],l,mid,x,d),add(u,ls[u],0),add(u,rs[u],0);
	else rs[u]=update(rs[id],mid+1,r,x,d),add(u,rs[u],0),add(u,ls[u],0);
//	cout<<"updated: "<<u<<" "<<l<<" "<<r<<" "<<ls[u]<<" "<<rs[u]<<"\n";
//	system("pause");
	return u;
}
void lnk(int id,int l,int r,int x,int y,int d)
{
	if(x<=l&&r<=y)
	{
		add(d,id,1);
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) lnk(ls[id],l,mid,x,y,d);
	if(y>mid) lnk(rs[id],mid+1,r,x,y,d);
}
void add2(int u,int v,int w)
{
	swap(u,v);
	if(!w) g0.addedge(u,v);
	else g1.addedge(u,v);
}
int build2(int l,int r)
{
	int u=++uidx;
	if(l==r) return u;
	int mid=(l+r)>>1;
	ls[u]=build2(l,mid),rs[u]=build2(mid+1,r);
	add2(u,ls[u],0),add2(u,rs[u],0);
	return u;
}

int update2(int id,int l,int r,int x,int d)
{
//	cout<<"update: "<<id<<" "<<l<<" "<<r<<" "<<x<<" "<<d<<" "<<ls[id]<<" "<<rs[id]<<"\n";
//	system("pause");
	int u=++uidx;
	ls[u]=ls[id],rs[u]=rs[id];
	if(l==r)
	{
		if(d) add2(u,d,0);
		return u;
	}
	int mid=(l+r)>>1;
	if(x<=mid) ls[u]=update2(ls[id],l,mid,x,d),add2(u,ls[u],0),add2(u,rs[u],0);
	else rs[u]=update2(rs[id],mid+1,r,x,d),add2(u,rs[u],0),add2(u,ls[u],0);
//	cout<<"updated: "<<u<<" "<<l<<" "<<r<<" "<<ls[u]<<" "<<rs[u]<<"\n";
//	system("pause");
	return u;
}
void lnk2(int id,int l,int r,int x,int y,int d)
{
	if(x<=l&&r<=y)
	{
		add2(d,id,1);
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) lnk2(ls[id],l,mid,x,y,d);
	if(y>mid) lnk2(rs[id],mid+1,r,x,y,d);
}
array<int,4> rec[100005];
vector <int> vx,vy;
int getx(int x)
{
	return lower_bound(vx.begin(),vx.end(),x)-vx.begin()+1;
}
int gety(int y)
{
	return lower_bound(vy.begin(),vy.end(),y)-vy.begin()+1;
} 
pii get_onseg(int x,int y)
{
	array<int,3> Tmp={y+1,-1,-1};
	int pos=lower_bound(vX[x].begin(),vX[x].end(),Tmp)-vX[x].begin();
	pos--;
	if(pos<0||pos>=vX[x].size()) return mp(-1,-1);
	pii res;
	res.fi=vX[x][pos][2];
	Tmp={x+1,-1,-1};
	pos=lower_bound(vY[y].begin(),vY[y].end(),Tmp)-vY[y].begin();
	pos--;
	res.se=vY[y][pos][2];
	return res;
}
void solve()
{
	int sx,sy,tx,ty;
	cin>>sx>>sy>>tx>>ty;
	
	vx.pb(sx),vx.pb(tx),vy.pb(sy),vy.pb(ty);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>rec[i][0]>>rec[i][2]>>rec[i][1]>>rec[i][3],vx.pb(rec[i][0]),vx.pb(rec[i][2]),vy.pb(rec[i][1]),vy.pb(rec[i][3]);
	sort(vx.begin(),vx.end()),vx.resize(unique(vx.begin(),vx.end())-vx.begin());
	sort(vy.begin(),vy.end()),vy.resize(unique(vy.begin(),vy.end())-vy.begin());
	sx=getx(sx),tx=getx(tx),sy=gety(sy),ty=gety(ty); 
	for(int i=1;i<=n;i++) rec[i][0]=getx(rec[i][0]),rec[i][2]=getx(rec[i][2]),rec[i][1]=gety(rec[i][1]),rec[i][3]=gety(rec[i][3]);
//	cout<<"...\n";
	set <int> S;
	S.insert(0),S.insert(vy.size()+1);
	vector <array<int,4> > vec;
	vec.clear();
	vec.pb({sx,1,sy,sy});
	vec.pb({tx,1,ty,ty});
//	cout<<vx.size()<<" "<<vy.size()<<"\n";
//	for(int i=0;i<vx.size();i++) cout<<vx[i]<<" ";
//	cout<<"\n";
//	for(int i=0;i<vy.size();i++) cout<<vy[i]<<" ";
//	cout<<"\n";
//	cout<<sx<<" "<<sy<<" "<<tx<<" "<<ty<<"\n";
//	for(int i=1;i<=n;i++) cout<<rec[i][0]<<" "<<rec[i][2]<<" "<<rec[i][1]<<" "<<rec[i][3]<<"\n";
//	system("pause");
	for(int i=1;i<=n;i++) vec.pb({rec[i][0],1,rec[i][1],rec[i][3]}),vec.pb({rec[i][2],1,rec[i][1],rec[i][3]}),vec.pb({rec[i][0]+1,-1,rec[i][1],rec[i][3]}),vec.pb({rec[i][2],0,rec[i][1],rec[i][3]});
	sort(vec.begin(),vec.end());
	for(int i=0;i<vec.size();i++)
	{
		if(vec[i][1]==-1) S.insert(vec[i][2]),S.insert(vec[i][3]);
		if(vec[i][1]==0) S.erase(vec[i][2]),S.erase(vec[i][3]);
		if(vec[i][1]==1)
		{
			auto it1=S.upper_bound(vec[i][2]),it2=prev(it1);
//			cout<<"+ "<<vec[i][0]<<" "<<(*it2)<<" "<<(*it1)<<"\n";
			vX[vec[i][0]].pb({max(1,*it2),min((int)vy.size(),*it1),++uidx});
		}
	}
	
	S.clear();
	S.insert(0),S.insert(vx.size()+1);
	vec.clear();
	vec.pb({sy,1,sx,sx});
	vec.pb({ty,1,tx,tx});
	for(int i=1;i<=n;i++) vec.pb({rec[i][1],1,rec[i][0],rec[i][2]}),vec.pb({rec[i][3],1,rec[i][0],rec[i][2]}),vec.pb({rec[i][1]+1,-1,rec[i][0],rec[i][2]}),vec.pb({rec[i][3],0,rec[i][0],rec[i][2]});
	sort(vec.begin(),vec.end());
	for(int i=0;i<vec.size();i++)
	{
//		cout<<vec[i][0]<<" "<<vec[i][1]<<" "<<vec[i][2]<<" "<<vec[i][3]<<"\n";
		if(vec[i][1]==-1) S.insert(vec[i][2]),S.insert(vec[i][3]);
		if(vec[i][1]==0) S.erase(vec[i][2]),S.erase(vec[i][3]);
		if(vec[i][1]==1)
		{
			auto it1=S.upper_bound(vec[i][2]),it2=prev(it1);
			vY[vec[i][0]].pb({max(1,*it2),min((int)vx.size(),*it1),++uidx});
		}
//		cout<<"done\n";
	}
	
//	for(int i=1;i<=vx.size();i++)
//	{
//		for(int j=0;j<vX[i].size();j++) cout<<i<<" "<<vX[i][j][0]<<" "<<vX[i][j][1]<<" "<<vX[i][j][2]<<"\n";
//		system("pause");
//	}
	
//	for(int i=1;i<=vy.size();i++)
//	{
//		for(int j=0;j<vY[i].size();j++) cout<<i<<" "<<vY[i][j][0]<<" "<<vY[i][j][1]<<" "<<vY[i][j][2]<<"\n";
//		system("pause");
//	}
	
	n=vx.size(),m=vy.size();
	
	for(int i=1;i<=m;i++) for(int j=0;j<vY[i].size();j++) op[vY[i][j][0]].pb({-1,i,vY[i][j][2]}),op[vY[i][j][1]+1].pb({-2,i,vY[i][j][2]});
	for(int i=1;i<=n;i++) for(int j=0;j<vX[i].size();j++) op[i].pb({vX[i][j][2],vX[i][j][0],vX[i][j][1]});
	int rt=build(1,m);
	int rt2=build2(1,m); 
	for(int i=1;i<=n;i++) for(int j=0;j<op[i].size();j++)
	{
		if(op[i][j][0]==-1) rt=update(rt,1,m,op[i][j][1],op[i][j][2]),rt2=update2(rt2,1,m,op[i][j][1],op[i][j][2]);//,cout<<"+ "<<op[i][j][1]<<" "<<op[i][j][2]<<"\n",print(rt,1,m),system("pause");
		else if(op[i][j][0]==-2) rt=update(rt,1,m,op[i][j][1],0),rt2=update2(rt2,1,m,op[i][j][1],0);//,cout<<"- "<<op[i][j][1]<<"\n";
		else lnk(rt,1,m,op[i][j][1],op[i][j][2],op[i][j][0]),lnk2(rt2,1,m,op[i][j][1],op[i][j][2],op[i][j][0]);//,cout<<"link "<<op[i][j][1]<<" "<<op[i][j][2]<<" "<<op[i][j][0]<<"\n";
	//	print(rt,1,m),system("pause");
	}
	
	memset(dis,0x3f,sizeof(dis));
	vector <int> q;
	q.clear();
	pii r=get_onseg(sx,sy);
	q.pb(r.fi),q.pb(r.se);
//	cout<<"start: "<<r.fi<<" "<<r.se<<"\n";
	dis[r.fi]=dis[r.se]=0;
	while(q.size())
	{
		for(int i=0;i<q.size();i++)
		{
			int u=q[i];
			for(int j=g0.p[u];j;j=g0.nxt[j]) if(dis[g0.to[j]]==inf) 
				dis[g0.to[j]]=dis[u],q.pb(g0.to[j]);
		}
		vector <int> q2;
		q2.clear();
		for(int i=0;i<q.size();i++)
		{
			int u=q[i];
			for(int j=g1.p[u];j;j=g1.nxt[j]) if(dis[g1.to[j]]==inf) 
				dis[g1.to[j]]=dis[u]+1,q2.pb(g1.to[j]);
		}
		swap(q,q2);
	}
//	cerr<<uidx<<" "<<cnt_e<<"\n";
	r=get_onseg(tx,ty);
	//cout<<"query: "<<r.fi<<" "<<r.se<<"\n";
	int ans=min(dis[r.fi],dis[r.se]);
//	cout<<uidx<<" "<<g0.eid<<" "<<g1.eid<<"\n";
	cout<<ans+1<<"\n";	
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【CEOI2005】Depot Rearrangement

对于所有 \(M\) 种物品各建立一个点,所有 \(N\) 段区间各建立一个点。考虑连出一个二分图:物品连向区间表示这个区间需要一个这种物品,区间连向物品表示这个区间多出一个这种物品(如果多出多个就连多条边),更形象的说,就是用边去刻画物品的流向。

显然每个点的入度等于出度,每一个联通分量都有欧拉回路,找出后构造是容易的。

复杂度 \(O(NM)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
vector <int> g[805];
vector <pii > ans;
vector <int> has[405][405];
int n,m;
void get_cir(vector <int> ver)
{
	if(ver.size()<=1) return;
	ans.pb(mp(ver[ver.size()-1],n*m+1));
	for(int i=ver.size()-2;i>=0;i--) ans.pb(mp(ver[i],ver[i+1]));
	ans.pb(mp(n*m+1,ver[0]));
}
int cur[805];
bool vis[805];
vector <int> P;
void dfs(int u)
{
	vis[u]=1;
	while(cur[u]<g[u].size())
	{
		int v=g[u][cur[u]];
		cur[u]++;
		dfs(v);
		P.pb(u);
	}
	
}
void solve()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
	{
		int x;
		cin>>x;
		has[i][x].pb((i-1)*m+j);
	}
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) 
	{
		if(has[i][j].size()==0) g[n+j].pb(i);//,cout<<n+j<<" "<<i<<"\n";
		for(int t=0;t<(int)(has[i][j].size())-1;t++) g[i].pb(n+j);//,cout<<i<<" "<<n+j<<"\n";
	}
	for(int i=1;i<=n;i++) if(!vis[i]) 
	{
		P.clear();
		dfs(i);
		vector <int> ver;
		ver.clear();
		reverse(P.begin(),P.end());
//		cout<<P.size()<<"\n";
//		for(int j=0;j<P.size();j++) cout<<P[j]<<"\n";
		for(int j=0;j+1<P.size();j+=2) ver.pb(has[P[j]][P[j+1]-n][0]),has[P[j]][P[j+1]-n].erase(has[P[j]][P[j+1]-n].begin());//,cout<<ver[ver.size()-1]<<" ";
//		cout<<"\n";
		get_cir(ver);
	}
	cout<<ans.size()<<"\n";
	for(int i=0;i<ans.size();i++) cout<<ans[i].fi<<" "<<ans[i].se<<"\n";
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【KOI2023】野餐

\(L,M,R\) 表示 left_num, my_num, right_num

先忽略下午和晚上,我们需要找到一个函数 \(f(x,y)\),满足对于任意 \(x,y,z(x \neq y,y \neq z)\),有 \(f(x,y) \neq f(y,z)\),每个人在上午只需要返回 \(f(M,R)\)

考虑 \(x,y\) 的二进制表示,找到任意一位 \(x,y\) 不同的二进制位,令其为第 \(d\) 位,\(x\) 在这一位上的数是 \(b\),令 \(f(x,y)=2d+b\),如果 \(f(x,y)=f(y,z)\),则 \(x\) 的第 \(d\) 位和 \(y\) 的第 \(d\) 位是相同的,与定义矛盾。

\(f(x,y)\) 的值域是 \([0,2 \lceil\log A\rceil)\),其中 \(A\)\(x,y\) 的值域。

上午下午晚上都返回 \(f(M,R)\),值域是 \(10^5 \rightarrow 34 \rightarrow 12 \rightarrow 8\)

但是我们没有用 \(L\),如果在下午晚上返回 \(f(f(L,M),f(M,R))\),值域是 \(10^5 \rightarrow 34 \rightarrow 12 \rightarrow 8 \rightarrow 6 \rightarrow 6\)

这个方法有一个明显的弊端,因为 \(2 \lceil \log 6 \rceil = 6\),有再多次的操作也无法缩小值域。

一个特别天才的想法:将每个数映射到一个 \(01\) 个数相等的串,虽然会扩大串长,但是由于一定能找到一个不同的二进制位满足 \(x\) 在这一位为 \(0\),故 \(f(x,y)\) 只需要返回 \(d\),可以突破 \(6\)

早上,值域是 \(10^5\)\(\binom{20}{10}=184,756\),可以映射到 \(20\) 位。

下午,计算 \(f(L,M),f(M,R)\) 的时候,值域是 \(20\)\(\binom{6}{3}=20\),可以映射到 \(6\) 位。

下午,计算返回值的时候,值域是 \(6\)\(\binom{4}{2}=6\),可以映射到 \(4\) 位。

晚上的想法比较简单,如果 \(M \le 2\) 直接返回 \(M\),否则返回一个不是 \(L\) 也不是 \(R\) 的数,这个数一定可以在 \(0,1,2\) 中找到,得到满分做法。

Code
#include "workshop.h"
#include<bits/stdc++.h>
using namespace std;

int ma[21][200005],idx[21];

void init() 
{
	for(int lg=1;lg<=20;lg++) for(int i=0;i<(1<<lg);i++)
	{
		int ppc=0;
		for(int l=lg-1;l>=0;l--) 
		{
			if(i&(1<<l)) ppc++;
//			if(ppc) lg++;
		}
		if(ppc*2==lg) ma[lg][idx[lg]]=i,idx[lg]++;
//		cout<<i<<" "<<j<<"\n";
	}

}

int g(int my_num, int right_num) 
{
	for(int i=0;i<20;i++) 
	{
		int t1=0,t2=0;
		if(my_num&(1<<i)) t1=1;
		if(right_num&(1<<i)) t2=1;
		if(t1==0&&t2==1) return i;
	}
//	assert(0);
}

int morning(int my_num, int right_num) 
{
	my_num=ma[20][my_num],right_num=ma[20][right_num]; 
	return g(my_num,right_num);
}

int afternoon(int left_num, int my_num, int right_num) 
{
	left_num=ma[6][left_num],my_num=ma[6][my_num],right_num=ma[6][right_num]; 
//	cout<<g(left_num,my_num)<<" "<<g(my_num,right_num)<<" "<<ma[4][g(left_num,my_num)]<<" "<<ma[4][g(my_num,right_num)]<<"\n";
	return g(ma[4][g(left_num,my_num)],ma[4][g(my_num,right_num)]);
}

int evening(int left_num, int my_num, int right_num) 
{
//	left_num=ma[left_num],my_num=ma[my_num],right_num=ma[right_num]; 
//	cout<<my_num<<"\n"; 
	if(my_num==3)
	{
		for(int i=0;i<3;i++) if(i!=left_num&&i!=right_num) return i; 
	}
	return my_num;
//	return g(ma[g(left_num,my_num)],ma[g(my_num,right_num)]);
}

【KOI2023】外环路 2

没有奇环,说明是二分图。

删掉最少的权值的边,变成二分图,可以看成在保持是二分图的情况下保留尽可能多的权值。

考虑 dp,\(dp(i,0/1,0/1,0/1)\) 表示,考虑子树 \(i\)\(i\) 填了什么颜色,\(i\) 子树内最左边和左右边的叶子填了什么颜色。合并 \(u\) 的所有儿子的时候,令 \(f(j,0/1,0/1,0/1)\) 表示,目前合并了 \(j\) 棵子树,我们即将要在 \(u\) 上填什么颜色,目前合并的子树中,最左边和最右边的叶子填了什么颜色。

复杂度 \(O(n)\)

Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const ll mod=998244353;
const ll inf=0x3f3f3f3f;
const ll INF=1e18;
vector <pii > g[100005];
ll dp[100005][2][2][2],f[100005][2][2][2];
ll L[100005],R[100005],n;
vector <ll> W;
void dfs0(ll u,ll fa)
{
	if(!g[u].size()) return;
	for(ll i=0;i<g[u].size();i++)
	{
		ll v=g[u][i].fi;
		if(v==fa) continue;
		dfs0(v,u),L[u]=min(L[u],L[v]),R[u]=max(R[u],R[v]);
	}
}
void dfs1(ll u,ll fa)
{
//	cout<<u<<" "<<L[u]<<" "<<R[u]<<"\n";
	if(!g[u].size()) 
	{
		dp[u][0][0][0]=dp[u][1][1][1]=0;
		return;
	}
	for(ll i=0;i<g[u].size();i++)
	{
		ll v=g[u][i].fi;
		if(v==fa) continue;
		dfs1(v,u);
	}
//	memset(f,-0x3f,sizeof(f));
	
	for(ll i=0;i<g[u].size();i++)
	{
		ll v=g[u][i].fi;
		ll w=g[u][i].se;
//		cout<<u<<" --> "<<v<<" "<<w<<"\n";
		if(v==fa) continue;
		if(!i)
		{
			for(ll j=0;j<2;j++) for(ll k=0;k<2;k++) for(ll l=0;l<2;l++) if(dp[v][j][k][l]>=0)
				f[0][0][k][l]=max(f[0][0][k][l],dp[v][j][k][l]+1LL*w*(j^0)),f[0][1][k][l]=max(f[0][1][k][l],dp[v][j][k][l]+1LL*w*(j^1));
		}
		else
		{
			for(ll j=0;j<2;j++) for(ll k=0;k<2;k++) for(ll l=0;l<2;l++) if(dp[v][j][k][l]>=0)
			{
				for(ll j2=0;j2<2;j2++) for(ll k2=0;k2<2;k2++) for(ll l2=0;l2<2;l2++)
				{
					ll nw=f[i-1][j2][k2][l2]+dp[v][j][k][l];
					if(l2!=k) nw+=W[R[g[u][i-1].fi]];
					if(j!=j2) nw+=1LL*w;
					f[i][j2][k2][l]=max(f[i][j2][k2][l],nw);
				}
			}
		}
//		for(ll j=0;j<2;j++) for(ll k=0;k<2;k++) for(ll l=0;l<2;l++) cout<<i<<" "<<j<<" "<<k<<" "<<l<<" "<<f[i][j][k][l]<<"\n";
//		system("pause");
	}
	
//	cout<<u<<" "<<g[u].size()<<"\n";
	for(ll j=0;j<2;j++) for(ll k=0;k<2;k++) for(ll l=0;l<2;l++) dp[u][j][k][l]=f[g[u].size()-1][j][k][l];//,cout<<u<<" "<<j<<" "<<k<<" "<<l<<" "<<dp[u][j][k][l]<<"\n";
	for(ll i=0;i<g[u].size();i++) for(ll j=0;j<2;j++) for(ll k=0;k<2;k++) for(ll l=0;l<2;l++) f[i][j][k][l]=-INF;
}
ll place_police(vector <int> P,vector <ll> C,vector <ll> _W)
{
	W=_W;
	n=P.size()+1;
	ll Sum=0;
	for(ll i=0;i<P.size();i++) g[P[i]+1].pb(mp(i+2,C[i])),Sum+=C[i];//,cout<<P[i]+1<<" "<<i+2<<"\n";
	for(ll i=0,j=1;i<W.size();i++)
	{
		Sum+=W[i];
		while(g[j].size()) j++;
		L[j]=R[j]=i;
		j++;
	}
	dfs0(1,-1);
	memset(f,-0x3f,sizeof(f)),memset(dp,-0x3f,sizeof(dp));
	dfs1(1,-1);
	ll ans=0;
	for(ll i=0;i<2;i++) for(ll j=0;j<2;j++) for(ll k=0;k<2;k++) 
	{
		ll nw=dp[1][i][j][k];
		if(j!=k) nw+=W[W.size()-1];
		ans=max(ans,nw);
	}
//	cout<<Sum<<" "<<ans<<"\n";
	return Sum-ans;
}

【RMI 2020】Peru

假设我们已经确定了要拍哪些区间和对应的力度,按照力度降序排列,令它的影响范围为目前没有被拍的所有位置,由于所有区间长度相等,这个范围也是一个区间。

将整个序列的所有影响区间抠出来,容易发现一个影响区间所需要的力度是区间内的最大值,也就是说问题转化成:将序列 \(S\) 划分成若干连续段,每一段的长度 \(\le K\),最小化每一段最大值之和。

\(dp(i)\) 表示前缀 \([1,i]\) 的答案,转移 \(dp(i)=\min_{j \in [i-K,i-1]} dp(j)+\max\{S_{j+1},\cdots,S_i\}\)

再观察这个式子,发现只需要考虑 \(i-K\) 的决策和 \(S_j\)\([j,i]\) 中的最大值的 \(j\) 的决策(理由:\(dp(i)\) 显然不降,在保证 \(\max\) 不变的时候,选取更小的 \(j\) 不劣)。把这些 \(j\) 拿出来,令其为 \(a_1,a_2,\cdots ,a_m\),则 \(dp(i)=\min_{i \lt m} dp(a_i)+S_{a_{i+1}}\)

\(i-K\) 的转移可以用滑动窗口维护;\(a\) 的变化仅有:右边 push/pop,左边 pop,可以用双栈维护,复杂度 \(O(n)\)

Code
#include "peru.h"
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=1e9+7;
const int inf=0x3f3f3f3f; 
const int INF=1e18;
int a[2500005],rgmax[2500005];
int dp[2500005];
int stk1[2500005],top1;
int min1[2500005];
int stk2[2500005],top2;
int min2[2500005];
int query()
{
	int res=INF;
	if(top1>=2) res=min(res,min1[top1]);
	if(top2>=2) res=min(res,min2[top2]);
	if(top1&&top2) res=min(res,dp[stk1[1]]+a[stk2[1]]);
	return res;
}
void rebuild()
{
	vector <int> vec;
	for(int i=top1;i>=1;i--) vec.pb(stk1[i]);
	for(int i=1;i<=top2;i++) vec.pb(stk2[i]);
	int mid=(vec.size())/2;
	top1=top2=0;
	for(int i=mid-1;i>=0;i--) stk1[++top1]=vec[i];
	for(int i=mid;i<vec.size();i++) stk2[++top2]=vec[i];
	min1[0]=min1[1]=min2[0]=min2[1]=INF;
	for(int i=2;i<=top1;i++) min1[i]=min(min1[i-1],dp[stk1[i]]+a[stk1[i-1]]);
	for(int i=2;i<=top2;i++) min2[i]=min(min2[i-1],dp[stk2[i-1]]+a[stk2[i]]);
}
void ins(int x)
{
	stk2[++top2]=x;
	if(top2>=2) min2[top2]=min(min2[top2-1],dp[stk2[top2-1]]+a[stk2[top2]]);
}
void p_back()
{
	if(top1+top2==1) 
	{
		top1=top2=0;
		return;
	}
	if(!top2) rebuild();
	top2--; 
}
void p_front()
{
	if(top1+top2==1)
	{
		top1=top2=0;
		return;
	}
	if(!top1) rebuild();
	top1--;
}
//int solve(int n,int k,int *S)

signed solve(signed n,signed k,signed *S)
{
	memset(dp,0x3f,sizeof(dp));
	rebuild();
	dp[0]=0;
	for(int i=0;i<n;i++) a[i+1]=S[i];
	deque <int> dq;
	for(int i=1;i<=n;i++)
	{
		while(dq.size()&&dq.front()<=i-k) dq.pop_front(),p_front();
		while(dq.size()&&a[i]>a[dq.back()]) dq.pop_back(),p_back();
		dq.push_back(i),ins(i);
		dp[i]=min(dp[i],dp[max(0LL,i-k)]+a[dq.front()]);
		dp[i]=min(dp[i],query());
//		cout<<i<<" "<<dp[i]<<" "<<top1<<" "<<top2<<"\n";
		
	}
	
	int res=0;
	for(int i=n,w=1;i>=1;i--)
	{
		dp[i]%=mod; 
		int nw=1LL*w*dp[i]%mod;
		res=(res+nw)%mod;
		w=23LL*w%mod;
	}
	return res;
}

【2022 克罗地亚国家队选拔】这么牛

写了大半天的随机化,卡在了 Test #52,为什么过不去后面再说。

将所有数按模 \(4\) 意义分组,令 \(S_0,S_1,S_2,S_3\) 表示四个集合。

考虑全是偶数的情况,此时一定有解,因为两个 \(S_0\) 中的元素,或两个 \(S_2\) 中的元素,对其操作之后一定产生一个偶数,不断这样操作,不能操作的时候要么已经操作完,要么 \(|S_0|=|S_2|=1\),此时将剩余两个数操作即可。全是奇数同理。

对于一般情况,先要证明一个引理:

  • \(|S_0|,|S_2| \ge 1\)\(|S_1|,|S_3| \ge 1\),则能构造出解。

证明:以 \(|S_0|,|S_2| \ge 1\) 为例,保留两个集合各一个元素 \(x_0,x_2\),将剩下的奇数和剩下的偶数随便操作,可能会得到:

  • 剩余一个奇数:此时操作 \(x_0,x_2\),得到奇数,和剩下的奇数操作。
  • 剩余一个偶数:偶数要么属于 \(|S_0|\),要么属于 \(|S_2|\),取两个属于相同集合的元素操作,得到偶数,再和剩下的偶数操作。
  • 剩余一个奇数和一个偶数:将所有数设成 \(4a+b\) 的形式:不妨设我们有四个数:\(4a_1+0,4a_2+0,4a_3+2,4a_4+1\)
    • 可以操作 \(4a_2+0,4a_3+2\),得到 \(2(a_2+a_3)+1\),然后和 \(4a_4+1\) 操作得到 \(a_2+a_3+2a_4+1\),若 \(a_2+a_3\) 为奇数,则可以和 \(4a_1+0\) 操作构造出解。
    • 若将上述方法的第一步换成操作 \(4a_1+0,4a_3+2\),则若 \(a_1+a_3\) 为奇数,则可以和 \(4a_3+0\) 构造出解。
    • \(a_1,a_2\) 奇偶性相同,操作 \(4a_1+0,4a_2+0\),得到 \(2(a_1+a_2)\),其一定属于 \(S_0\),可以用上文“剩余一个奇数”的方式处理。

如果我们能构造出一个 \(S_0\) 中的元素和一个 \(S_2\) 中的元素,或者构造出一个 \(S_1\) 中的元素和一个 \(S_3\) 中的元素,随便操作后暴力解剩下的四个数,问题得以解决。

\(S_0\)\(S_2\) 为例,找出所有偶数中最低的二进制位 \(d\) ,满足所有数在这一位上不全部相同。任意找出两个不相同的数,不妨设两个数为:\(a_12^{d+1}+b\)\(a_22^{d+1}+2^d+b\),操作这两个数,得到 \((a_1+a_2)2^d+2^{d-1}+b\),此时第 \(d-1\) 位一定改变,将 \(d\) 减去 \(1\) 继续进行同样的过程,注意先判断数够不够。

复杂度 \(O(n \log A)\)\(A\) 是值域。

随机化过不去的原因:在 Test #52 中,\(d\) 比较大,严格限定了其中很多步操作。

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
mt19937 rnd(time(0));
struct Random_array
{
	int a[2000005],in[2000005],n,has;
	void init()
	{
		n=has=0;
	}
	void rebuild()
	{
		int j=0;
		for(int i=1;i<=n;i++) if(in[i]) j++,a[j]=a[i],in[j]=1;
		n=has=j;
	}
	void add(int x)
	{
		a[++n]=x,has++,in[n]=1;
	}
	int query()
	{
		int x=1+rnd()%n;
		while(!in[x]) x=1+rnd()%n;
		has--,in[x]=0;
		int res=a[x];
		if(has<n/2) rebuild();
		return res;
	}
}O,E;
void random_small(vector <int> vec)
{
	while(1)
	{
		O.init(),E.init();
		for(int i=0;i<vec.size();i++)
		{
			if(vec[i]%2==0) E.add(vec[i]);
			else O.add(vec[i]);
		}
		vector <pii > ans;
		ans.clear();
		while(O.has+E.has>=2)
		{
			if(O.has==1&&E.has==1) break;
			int tag=-1;
			if(O.has>=2&&E.has>=2) tag=rnd()%2;
			else if(O.has>=2) tag=0;
			else tag=1;
			if(tag==0)
			{
				int u=O.query();
				int v=O.query();
				ans.pb(mp(u,v));
				if(((u+v)/2)%2==1) O.add((u+v)/2);
				else E.add((u+v)/2);
			}
			else
			{
				int u=E.query();
				int v=E.query();
				ans.pb(mp(u,v));
				if(((u+v)/2)%2==1) O.add((u+v)/2);
				else E.add((u+v)/2);
			}
		}
		if(O.has+E.has==1)
		{
			for(int i=0;i<ans.size();i++) cout<<ans[i].fi<<" "<<ans[i].se<<"\n";
			return;
		}
	}
}
int construct_same(vector <int> vec)
{
	vector <int> v[2];
	v[0].clear(),v[1].clear();
	for(int i=0;i<vec.size();i++) v[(vec[i]%4)/2].pb(vec[i]);
	while(v[0].size()+v[1].size()>=2)
	{
		if(v[0].size()>=2)
		{
			int x=v[0][v[0].size()-1];
			v[0].pop_back();
			int y=v[0][v[0].size()-1];
			v[0].pop_back();
			cout<<x<<" "<<y<<"\n";
			x=(x+y)/2;
			v[(x%4)/2].pb(x);
		}
		else if(v[1].size()>=2)
		{
			int x=v[1][v[1].size()-1];
			v[1].pop_back();
			int y=v[1][v[1].size()-1];
			v[1].pop_back();
			cout<<x<<" "<<y<<"\n";
			x=(x+y)/2;
			v[(x%4)/2].pb(x);
		}
		else 
		{
			cout<<v[0][0]<<" "<<v[1][0]<<"\n";
			return (v[0][0]+v[1][0])/2;
		}
	}
	return (v[0].size()?v[0][0]:v[1][0]);
}
pii find_diff(vector <int> v,int i)
{
	int f1=0,f2=0;
	for(int j=0;j<v.size();j++)
	{
		if(v[j]&(1LL<<i)) f1=j;
		else f2=j;
	}
	return mp(f1,f2);
}
int n,a[2500005];
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	vector <int> v[2];
	v[0].clear(),v[1].clear();
	for(int i=1;i<=n;i++) v[a[i]%2].pb(a[i]);
	if(v[0].size()==n) 
	{
		construct_same(v[0]);
		return;
	}
	if(v[1].size()==n)
	{
		construct_same(v[1]);
		return;
	}
	int min_d=inf;
	for(int i=1;i<60;i++)
	{
		int f1=0,f2=0;
		for(int j=0;j<v[0].size();j++)
		{
			if(v[0][j]&(1LL<<i)) f1=1;
			else f2=1;
		}
		if(f1&&f2)
		{
			min_d=i;
			break;
		} 
	}
//	cout<<v[0].size()<<" "<<min_d<<"\n";
	if(v[0].size()>=min_d+1)
	{
		for(int i=min_d;i>=2;i--)
		{
			pii t=find_diff(v[0],i);
			if(t.fi>t.se) swap(t.fi,t.se);
			int x=v[0][t.fi],y=v[0][t.se];
			cout<<x<<" "<<y<<"\n";
			v[0].erase(v[0].begin()+t.fi),v[0].erase(v[0].begin()+t.se-1);
			v[0].pb((x+y)/2);
		}
		pii t=find_diff(v[0],1);
		if(t.fi>t.se) swap(t.fi,t.se);
		int x=v[0][t.fi],y=v[0][t.se];
//		cout<<x<<" "<<y<<"\n";
		v[0].erase(v[0].begin()+t.fi),v[0].erase(v[0].begin()+t.se-1);
		int z=(v[0].size()?construct_same(v[0]):-1);
		int w=construct_same(v[1]);
		vector <int> vec;
		vec.clear();
		vec.pb(x),vec.pb(y),vec.pb(w);
		if(z!=-1) vec.pb(z);
		random_small(vec);
		return;
	}
	swap(v[0],v[1]);
	min_d=inf;
	for(int i=1;i<60;i++)
	{
		int f1=0,f2=0;
		for(int j=0;j<v[0].size();j++)
		{
			if(v[0][j]&(1LL<<i)) f1=1;
			else f2=1;
		}
		if(f1&&f2)
		{
			min_d=i;
			break;
		} 
	}
//	cout<<"... "<<v[0].size()<<" "<<min_d<<"\n";
	if(v[0].size()>=min_d+1)
	{
		for(int i=min_d;i>=2;i--)
		{
			pii t=find_diff(v[0],i);
			if(t.fi>t.se) swap(t.fi,t.se);
			int x=v[0][t.fi],y=v[0][t.se];
			cout<<x<<" "<<y<<"\n";
			v[0].erase(v[0].begin()+t.fi),v[0].erase(v[0].begin()+t.se-1);
			v[0].pb((x+y)/2);
		}
		pii t=find_diff(v[0],1);
		if(t.fi>t.se) swap(t.fi,t.se);
		int x=v[0][t.fi],y=v[0][t.se];
//		cout<<x<<" "<<y<<"\n";
		v[0].erase(v[0].begin()+t.fi),v[0].erase(v[0].begin()+t.se-1);
		int z=(v[0].size()?construct_same(v[0]):-1);
		int w=construct_same(v[1]);
		vector <int> vec;
		vec.clear();
		vec.pb(x),vec.pb(y),vec.pb(w);
		if(z!=-1) vec.pb(z);
		random_small(vec);
		return;
	}
	cout<<"-1\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
	cin>>_;
	while(_--) solve();
	return 0;
}

【2022 克罗地亚国家队选拔】这么不牛

将图看做 \(100\) 个点的完全图,每条边染成了黑色或白色。

先取出任意五个节点,解出这个大小为 \(5\) 的团。每三个询问一下,得到了 \(10\) 个未知数和 \(10\) 个方程,把它解出来。由于未知数不多,可以暴力枚举。

每次选一个点 \(u\) 加入团,将团中的节点随机打乱,得到排列 \(p_1,p_2,\cdots,p_k\),令 \(i=1\),询问 \((u,p_i,p_{i+1})\),将其减去边 \((p_i,p_{i+1})\) 的贡献,有两种情况。

  • \((u,p_i)\)\((u,p_{i+1})\) 同色,我们可以知道具体的颜色,令 \(i=i+2\),重复此过程。
  • \((u,p_i)\)\((u,p_{i+1})\) 异色,令 \(i=i+1\),重复此过程直到找到一对同色边,找到之后可以推出前面的一些边。

有一些细节:如果找完了都没有找到同色边,那么可以任意取一个 \(j\),询问 \((u,p_j,p_{j+2})\)\((u,p_1,p_j)\),得到 \((u,p_j)\)\((u,p_{j+2})\) 的颜色,由于 \(k \ge 5\),一定能找到一个合适的询问。

我们已经将团里面的点随机打乱了,每次问到同色的概率至少是 \(0.5\)。即,一次询问在最坏情况下,有 \(0.5\) 的概率确定两条边,有 \(0.5\) 的概率确定一条边,期望确定 \(1.5\) 条边,询问次数期望大约为 \(\frac{n^2}{2 \times 1.5}=\frac{n^2}{3}\)

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int re[6][6][6];
int ans[105][105];
mt19937 rnd(19260817);
int query(int u,int v,int w)
{
	cout<<"? "<<u<<" "<<v<<" "<<w<<"\n";
	fflush(stdout);
	int x;
	cin>>x;
	return x;
}
void solve()
{
	for(int i=1;i<=5;i++) for(int j=i+1;j<=5;j++) for(int k=j+1;k<=5;k++) 
		re[i][j][k]=query(i,j,k);
	for(int mask=0;mask<(1<<10);mask++)
	{
		int idx=0;
		for(int i=1;i<=5;i++) for(int j=i+1;j<=5;j++)
		{
			if(mask&(1<<idx)) ans[i][j]=ans[j][i]=1;
			else ans[i][j]=ans[j][i]=0;
			idx++;
		}
		bool ok=1;
		for(int i=1;i<=5;i++) for(int j=i+1;j<=5;j++) for(int k=j+1;k<=5;k++) 
			if(ans[i][j]+ans[j][k]+ans[k][i]!=re[i][j][k]) ok=0;
		if(ok) break;
	}
	for(int i=6;i<=100;i++)
	{
		vector <int> vec;
		vec.clear();
		for(int j=1;j<i;j++) vec.pb(j);
		for(int j=vec.size()-1;j>=0;j--) swap(vec[j],vec[rnd()%(j+1)]);
		for(int j=0;j<vec.size();j++)
		{
			if(j+1==vec.size())
			{
				int t=query(i,vec[j],vec[0]);
				t-=ans[vec[j]][vec[0]]+ans[i][vec[0]];
				ans[i][vec[j]]=ans[vec[j]][i]=t;
			}
			else
			{
				int k=j;
				while(k+1<vec.size())
				{
					int t=query(i,vec[k],vec[k+1]);
					t-=ans[vec[k]][vec[k+1]];
					if(t==0||t==2)
					{
						if(t==0) ans[i][vec[k]]=ans[vec[k]][i]=ans[i][vec[k+1]]=ans[vec[k+1]][i]=0;
						else ans[i][vec[k]]=ans[vec[k]][i]=ans[i][vec[k+1]]=ans[vec[k+1]][i]=1;
						for(int l=k-1;l>=j;l--) ans[i][vec[l]]=ans[vec[l]][i]=ans[i][vec[l+1]]^1;
						break;
					}
					k++;
				}
				if(k+1==vec.size())
				{
					if(j)
					{
						int t=query(i,vec[j],vec[0]);
						t-=ans[i][vec[0]]+ans[vec[j]][vec[0]];
						ans[i][vec[j]]=ans[vec[j]][i]=t;
					}
					else
					{
						int t=query(i,vec[j],vec[j+2]);
						t-=ans[vec[j]][vec[j+2]];
						if(t==0) ans[i][vec[j]]=ans[vec[j]][i]=0;
						else ans[i][vec[j]]=ans[vec[j]][i]=1;
					}
					for(int l=j+1;l<vec.size();l++) ans[i][vec[l]]=ans[vec[l]][i]=ans[i][vec[l-1]]^1;
				}
				j=k+1;
			}
		}
	}
	cout<<"!\n";
	for(int i=1;i<=100;i++)
	{
		for(int j=1;j<=100;j++) cout<<ans[i][j];
		cout<<"\n";
	}
	fflush(stdout);
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}
posted @ 2023-06-03 22:10  znstz  阅读(58)  评论(0编辑  收藏  举报