2023.7 做题笔记

【CCO 2021】Travelling Merchant

\(dp(u)\) 表示从节点 \(u\) 出发,最少需要多少的起始资金才能无限行走下去。转移 \(dp(u)=\min_{(u,v,r,p) \in G}\{\max\{r,dp(v)-p\}\}\)

图中有环,不好处理。先考虑不断将所有出度为 \(0\) 的点删掉,这个可以在反图上用拓扑排序解决。然后我们得到的图满足,只要有足够的钱,在任意节点都可以无限走下去。找出 \(r\) 最大的一条边 \((u,v,r,p)\),用 \(r\) 去更新 \(dp(u)\),并把这条边删去。此时可能会出现出度为 \(0\) 的点,继续在反图上拓扑排序更新 \(dp\)

复杂度除排序外 \(O(n+m)\)

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,deg[200005],dp[200005];
vector <array<int,3> > g[200005];
array<int,4> es[200005];
queue <int> q;
void topo()
{
	while(q.size())
	{
		int u=q.front();
		q.pop();
		for(int i=0;i<g[u].size();i++)
		{
			int v=g[u][i][0],r=g[u][i][1],p=g[u][i][2];
			dp[v]=min(dp[v],max(r,dp[u]-p));
			deg[v]--;
			if(!deg[v]) q.push(v);
		}
	}
}
bool cmp(array<int,4> x,array<int,4> y)
{
	return x[2]>y[2];
}
void solve()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>es[i][0]>>es[i][1]>>es[i][2]>>es[i][3];
	sort(es+1,es+1+m,cmp);
	for(int i=m;i>=1;i--) g[es[i][1]].pb({es[i][0],es[i][2],es[i][3]}),deg[es[i][0]]++;
	for(int i=1;i<=n;i++) if(!deg[i]) q.push(i);
	memset(dp,0x3f,sizeof(dp));
	topo();
	for(int i=1;i<=m;i++)
	{
		int u=es[i][0],v=es[i][1],p=es[i][2],w=es[i][3];
		if(!deg[v]) continue;
		g[v].pop_back();
//		cout<<u<<" "<<v<<" "<<p<<" "<<w<<"\n"; 
		dp[u]=min(dp[u],p);
		deg[u]--;
		if(!deg[u]) q.push(u);
		topo();
	}
	for(int i=1;i<=n;i++) cout<<(dp[i]>1000000000?-1:dp[i])<<" "; 
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【CCO 2023】Real Mountains

称一个数在山谷里,当且仅当在左边和右边都有严格比它大的数。

观察这样一个过程,将所有在山谷里的数的最小值整体 \(+1\)

每次操作 \((i,j,k)\)\(h_j\) 带来的代价是固定的,我们需要尽可能最小化 \(h_i,h_k\) 带来的代价。先将这些数中最左边和最右边的数 \(+1\),然后操作中间的数就可以令 \(h_i,h_k=h_j+1\),非常划算。为了最小化最左边和最右边的数操作的代价,可以算出先左后右,先右后左的代价。这是一个求一段前缀/后缀中某个数后继的问题,可以选用自己喜欢的数据结构维护。

然而 \(h\) 的值域非常大,令 \(x\) 为目前的山谷里的最小值,\(y\)\(x\) 在所有 \(h_i\) 中的后继,将所有山谷里的最小值从 \(x\) 加到 \(y\) 的过程中,由于需要改变的数的集合不变,可以整体处理,而这个集合只会改变 \(O(n)\) 次,故复杂度是 \(O(n \log n)\) 的。

Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#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=1e6+3;
const int inf=0x3f3f3f3f;
const int INF=1e18;
struct Segtree
{
	int rt=1,uidx;
	int val[30000005],ls[30000005],rs[30000005];
	int build(int l,int r)
	{
		int id=++uidx;
		val[id]=0;
		if(l==r) return id;
		int mid=(l+r)>>1;
		ls[id]=build(l,mid),rs[id]=build(mid+1,r);
		return id;
	}
	int update(int id,int l,int r,int x,int d)
	{
		int u=++uidx;
		val[u]=val[id],ls[u]=ls[id],rs[u]=rs[id];
		if(l==r)
		{
			val[u]+=d;
			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);
		val[u]=val[ls[u]]+val[rs[u]];
		return u;
	}
	int find_next(int id,int l,int r,int x)
	{
		if(!val[id]) return -1;
		if(l==r)
		{
			if(l>=x) return l;
			return -1;
		}
		int mid=(l+r)>>1;
		if(x<=mid)
		{
			int t=find_next(ls[id],l,mid,x);
			if(t!=-1) return t;
		}
		return find_next(rs[id],mid+1,r,x);
	}
}st1,st2;
int n;
int calc(int l,int r)
{
	return 1LL*(1LL*(l+r)*(r-l+1)/2)%mod;
}
int h[1000005],R1[1000005],R2[1000005],b[1000005],pmax[1000005],smax[1000005]; 
pii c[1000005];
void solve()
{
	cin>>n;
	int m;
	for(int i=1;i<=n;i++) cin>>h[i],b[i]=h[i];
	sort(b+1,b+1+n),m=unique(b+1,b+1+n)-b-1;
	R1[0]=R2[n+1]=1;
	st1.build(1,m),st2.build(1,m);
	for(int i=1;i<=n;i++) h[i]=lower_bound(b+1,b+1+m,h[i])-b,R1[i]=st1.update(R1[i-1],1,m,h[i],1),pmax[i]=max(pmax[i-1],h[i]),c[i]=mp(h[i],i);
	for(int i=n;i>=1;i--) R2[i]=st2.update(R2[i+1],1,m,h[i],1),smax[i]=max(smax[i+1],h[i]);
	ll ans=0;
	sort(c+1,c+1+n);
	set <int> S;
	for(int s=1,j=1;s<=m;s++)
	{
		while(c[j].fi==s) S.insert(c[j].se),j++;
		while(S.size())
		{
			int u=(*S.begin());
			if(s>=min(pmax[u-1],smax[u+1])) S.erase(u);
			else break;
		}
		while(S.size())
		{
			int u=(*prev(S.end()));
			if(s>=min(pmax[u-1],smax[u+1])) S.erase(u);
			else break;
		}
//		cout<<s<<" "<<S.size()<<"\n";
		if(S.size()==1)
		{
			int u=*S.begin();
			int t1=st1.find_next(R1[u-1],1,m,s+1),t2=st2.find_next(R2[u+1],1,m,s+1);
//			cout<<u<<" "<<b[t1]<<" "<<b[t2]<<"\n";
			ans+=1LL*(b[s+1]-b[s])%mod*(b[t1]+b[t2])%mod,ans%=mod;
			ans+=1LL*calc(b[s],b[s+1]-1),ans%=mod;
		}
		if(S.size()>=2)
		{
			ans+=1LL*calc(b[s],b[s+1]-1)*(ll)(S.size())%mod,ans%=mod;
//			cout<<S.size()<<" "<<b[s]<<" "<<b[s+1]-1<<" "<<ans<<"\n";
			int coef=2*S.size()-3;
			ans+=1LL*coef*calc(b[s]+1,b[s+1])%mod,ans%=mod;
//			cout<<ans<<"\n";
			int u=(*S.begin()),v=(*prev(S.end()));
			int t1=st1.find_next(R1[u-1],1,m,s+1),t2=st2.find_next(R2[u+1],1,m,s+1),t3=st2.find_next(R2[v+1],1,m,s+1);
			int tt1=st1.find_next(R1[u-1],1,m,s+1),tt2=st1.find_next(R1[v-1],1,m,s+1),tt3=st2.find_next(R2[v+1],1,m,s+1);
//			cout<<u<<" "<<v<<" "<<b[t1]<<" "<<b[t2]<<" "<<b[t3]<<" "<<b[tt1]<<" "<<b[tt2]<<" "<<b[tt3]<<"\n";
			ll r=min(1LL*b[t1]+1LL*b[t2]+1LL*b[t3],1LL*b[tt1]+1LL*b[tt2]+1LL*b[tt3])%mod;
			r=1LL*r*(b[s+1]-b[s])%mod;
			ans+=r,ans%=mod;
		}
//		cout<<ans<<"\n";
	}
	cout<<ans<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【CCO 2023】Line Town

部分分在暗示做法。

最关键的一个性质:考虑第 \(i\) 个数 \(h_i\) 在最后被换到了 \(p_i\) 的位置,那么最终状态下,第 \(p_i\) 个数是 \((-1)^{i-p_i}h_i\),且操作次数是 \(p_i\) 的逆序对数。

先考虑 \(|h_i| \neq |h_j|\) 的部分分,按绝对值从大往小枚举,当前枚举到的数,要么丢到末尾且符号为正,要么丢到开头且符号为负。令 \(dp(i,0/1)\) 表示,已经枚举了前 \(i\) 个数,开头的数模 \(2\) 是什么。转移只需要用 BIT 优化一下计算逆序对。

再考虑 \(|h_i| \le 1\),由于符号翻转很烦,需要一点点的转化去掉它。令初始状态为 \(a_1,\cdots,a_n\),目标状态为 \(b_1,\cdots,b_n\),对于所有奇数(偶数也行)的 \(i\),将 \(a_i,b_i\) 取相反数,操作就变成了“交换相邻数,没有符号翻转”(理由:转化前后对于符号的要求均为,若 \(|i-p_i|\) 为偶数,需要 \(a_i,b_{p_i}\) 符号相同;若 \(|i-p_i|\) 为奇数,需要 \(a_i,b_{p_i}\) 符号相反)。

在转化后的问题上,需要对于一个 \(-1,0,1\) 的序列求出,变成某个序列(中间一段为 \(0\),左右两段 \(+1,-1\) 交替排列)所需要的最小交换次数。

先做单组询问:给最终状态用 \([1,n]\) 顺次标号,给初始状态中的标号满足:对于所有 \(x,y\),第 \(x\) 个数 \(y\) 在两个状态中的标号相同。操作次数就是初始状态标号的逆序对。

对于多组询问需要一点分讨,一个稍微简单的做法是,分 \(0\) 左边的数有奇数还是偶数两种情况。对于同种情况,将 \(0\) 的区间往右移动两步后,标号序列的变化也仅仅是将两个数的标号移到中间一段 \(0\) 的前面,很容易用 BIT 计算逆序对的变化量。

考虑满分做法:\(dp\) 状态一样,按绝对值 \(x\) 从大到小枚举的时候,将 \(+x\) 看成 \(+1\)\(-x\) 看成 \(-1\),绝对值小于 \(x\) 的看成 \(0\)。有时候 \(0\) 会很多,但是计算逆序对的时候只需要分别计算:\((-1,0),(1,0),(-1,1)\) 之间的贡献,额外用 BIT 维护 \(0\) 的位置即可。

复杂度 \(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[500005],b[500005],P[500005],Q[500005];
struct BIT
{
	int t[500005];
int lowbit(int x)
{
	return x&(-x);
}
void update(int x,int d)
{
	for(int i=x;i<=n;i+=lowbit(i)) t[i]+=d;
}
int query(int x)
{
	int res=0;
	for(int i=x;i>=1;i-=lowbit(i)) res+=t[i];
	return res;
}
}bt;
pii calc(vector <int> vec,int tp)
{
	pii res=mp(INF,INF);
	vector <int> v[2];
	int c0=bt.query(n);
	for(int i=0;i<vec.size();i++)
	{
		int flg=(a[vec[i]]>0?1:0);
		v[flg].pb(vec[i]);
//		cout<<"flg: "<<flg<<" "<<vec[i]<<"\n";
	}
	int m=c0+vec.size();
	vector <int> seq;
	seq.clear();
	for(int i=m,j=(int)(v[1].size())-1,k=(int)(v[0].size())-1;i>c0;i--) 
	{
		int flg=(i%2==tp?0:1);
		if(flg==1&&j>=0) seq.pb(v[flg][j]),j--;
		if(flg==0&&k>=0) seq.pb(v[flg][k]),k--;  
	}
	if(seq.size()==v[0].size()+v[1].size())
	{
		reverse(seq.begin(),seq.end());
		int nw=0;
		for(int i=0;i<seq.size();i++) nw+=bt.query(n)-bt.query(seq[i]),bt.update(seq[i],1);
		res.fi=min(res.fi,nw);
		for(int i=0;i<seq.size();i++) bt.update(seq[i],-1);
		for(int i=0;i+1<seq.size();i+=2)
		{
			nw-=bt.query(n)-2*bt.query(seq[i]);
			nw-=bt.query(n)-2*bt.query(seq[i+1]);
			if(c0%2==0) 
			{
				int tmp=(seq[i]>seq[i+1]);
				if(tmp) nw--;
				else nw++;
			}
			res.fi=min(res.fi,nw);
		}
	}
	if(!v[tp].size()) return res;
	seq.clear();
	int nw=bt.query(v[tp][0]);
	int del=0;
	for(int i=0;i<v[tp^1].size();i++) if(v[tp^1][i]<v[tp][0]) del++;
	for(int i=m,j=(int)(v[1].size())-1,k=(int)(v[0].size())-1;i>c0+1;i--) 
	{
		int flg=(i%2==tp?0:1);
		if(flg==1&&j>=(int)(tp==1)) seq.pb(v[flg][j]),j--;
		if(flg==0&&k>=(int)(tp==0)) seq.pb(v[flg][k]),k--;  
	}
	if(seq.size()==v[0].size()+v[1].size()-1)
	{
		reverse(seq.begin(),seq.end());
		for(int i=0;i<seq.size();i++) nw+=bt.query(n)-bt.query(seq[i]),bt.update(seq[i],1);
		res.se=min(res.se,nw);
		for(int i=0;i<seq.size();i++) bt.update(seq[i],-1);
		for(int i=0;i+1<seq.size();i+=2)
		{
			nw-=bt.query(n)-2*bt.query(seq[i]);
			nw-=bt.query(n)-2*bt.query(seq[i+1]);
			if(c0%2==0) 
			{
				int tmp=(seq[i]>seq[i+1]);
				if(tmp) nw--;
				else nw++;
			}
			res.se=min(res.se,nw);
		}
	}
	res.se+=del;
	return res;
} 
int dp[500005][2];
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++) 
	{
		cin>>a[i];
		if(i%2==0) a[i]*=-1;
	}
	memset(dp,0x3f,sizeof(dp));
	vector <pii > vec;
	vec.clear();
	for(int i=1;i<=n;i++) vec.pb(mp(abs(a[i]),i));
	sort(vec.begin(),vec.end()),reverse(vec.begin(),vec.end());
	dp[0][0]=0;
	int j=0;
	for(int i=1;i<=n;i++) bt.update(i,1);
	for(int i=0;i<n;i++)
	{
		int l=i;
		while(l<n&&vec[l].fi==vec[i].fi) l++;
		l--;
		if(vec[l].fi==0) break;
		vector <int> nw;
		nw.clear();
		for(int _=i;_<=l;_++) nw.pb(vec[_].se),bt.update(vec[_].se,-1);
		sort(nw.begin(),nw.end());
		for(int tp=0;tp<2;tp++) if(dp[j][tp]<1e12)
		{
			pii tmp=calc(nw,tp);
//			cout<<j<<" "<<tp<<" "<<dp[j][tp]<<"\n";
//			for(int _=0;_<nw.size();_++) cout<<nw[_]<<" ";
//			cout<<"\n";
//			cout<<tp<<" "<<tmp.fi<<" "<<tmp.se<<"\n";
//			system("pause");
			dp[j+1][tp]=min(dp[j+1][tp],dp[j][tp]+tmp.fi);
			dp[j+1][tp^1]=min(dp[j+1][tp^1],dp[j][tp]+tmp.se);
		}
		i=l,j++;
	}
	int ans=min(dp[j][0],dp[j][1]);
	cout<<(ans>1e12?-1:ans);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【IzhO 2017】Hard route

\(u,v\) 为我们选的两个叶子,在点 \(w\) 取到所有点到 \(u,v\) 路径距离的最大值(即取到题目中 \(H\) 的最大值),令 \(x\)\(u,v\) 路径上到 \(w\) 距离最小的点。

枚举 \(x\),将 \(x\) 的所有子树(注意定根后不要漏掉上面的那棵子树)按子树内最深叶子的深度从大到小排序。取出前三个子树中最深的叶子,令深度为 \(a,b,c(a \le b \le c)\),最大化 \(hardness\) 一定是令 \(u=a,v=b,w=c\),此时 \(hardness=c(a+b)\)(理由:\(hardness\) 可以写成 \(ab+ac+bc-(作为u,v的点的乘积)\),最小化减去的部分即可)。

计算方案数有点麻烦,先换根 dp 预处理出每个节点子树内和子树外,距离最远的叶子和叶子个数。计数的时候数出 \(a,b,c\) 有多少个可选方案,分 \(a,b\) 是否相等,\(b,c\) 是否相等讨论。

幸运的是,计算取到最大值的方案时不会被重复计算。证明只需要证对于任意取到最大值的 \(u,v\),只有一个合法的 \(w\)。若有多个,则将 \(u,v\) 其中之一替换成另一个 \(w\),能变的更优。

复杂度 \(O(n)\)\(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;
vector <int> g[500005];
int rt;
int n;
pii dep1[500005],dep2[500005];
void upd(pii &x,pii y)
{
	if(!y.se) return;
	if(y.fi==x.fi) x.se+=y.se;
	else if(y.fi>x.fi) x=y;
}
pii ans=mp(0,0);
void dfs1(int u,int fa)
{
	dep1[u]=mp(1,1);
	for(int i=0;i<g[u].size();i++) 
	{
		int v=g[u][i];
		if(v==fa) continue;
		dfs1(v,u),upd(dep1[u],mp(dep1[v].fi+1,dep1[v].se));
	}
}
void dfs2(int u,int fa)
{
	int max1=0,max2=0;
	max1=dep2[u].fi+1;
	for(int i=0;i<g[u].size();i++) 
	{
		int v=g[u][i];
		if(v==fa) continue;
		int t=dep1[v].fi+1;
		if(t>max1) max2=max1,max1=t;
		else if(t>max2) max2=t;
	} 
	int cnt1=0,cnt2=0;
	if(dep2[u].fi+1==max1) cnt1+=dep2[u].se;
	if(dep2[u].fi+1==max2) cnt2+=dep2[u].se;
	for(int i=0;i<g[u].size();i++) 
	{
		int v=g[u][i];
		if(v==fa) continue;
		int t=dep1[v].fi+1;
		if(t==max1) cnt1+=dep1[v].se;
		if(t==max2) cnt2+=dep1[v].se;
	} 
//	cout<<u<<" "<<" "<<max1<<" "<<max2<<" "<<cnt1<<" "<<cnt2<<"\n";
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(v==fa) continue;
		int t=dep1[v].fi+1;
		if(t==max1) cnt1-=dep1[v].se;
		if(t==max2) cnt2-=dep1[v].se;
		if(cnt1) dep2[v]=mp(max1,cnt1);
		else dep2[v]=mp(max2,cnt2);
		dfs2(v,u);
		if(t==max1) cnt1+=dep1[v].se;
		if(t==max2) cnt2+=dep1[v].se;
	}
}
void dfs3(int u,int fa)
{
	vector <pii > vec;
	if(u!=rt) vec.pb(dep2[u]);
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(v==fa) continue;
		vec.pb(dep1[v]);
	}
	sort(vec.begin(),vec.end()),reverse(vec.begin(),vec.end());
	if(vec.size()>=3)
	{
		int x=vec[0].fi,y=vec[1].fi,z=vec[2].fi;
		if(y==z)
		{
			int cnt=0,ws=0;
			if(dep2[u].fi==y) cnt=dep2[u].se;
			for(int i=0;i<g[u].size();i++)
			{
				int v=g[u][i];
				if(v==fa||dep1[v].fi!=y) continue;
//				cout<<v<<" "<<dep1[v].se<<" "<<cnt<<" "<<"ok\n";
				ws+=dep1[v].se*cnt,cnt+=dep1[v].se;
			}
//			cout<<u<<" "<<x<<" "<<y<<" "<<z<<" "<<ws<<"\n";
			upd(ans,mp(x*(y+z),ws));
		}
		else
		{
			int cnt=0,cnt1=0;
			if(x==y) cnt1+=vec[0].se,cnt1+=vec[1].se;
			else cnt1=vec[1].se;
			if(dep2[u].fi==z) cnt=dep2[u].se;
			for(int i=0;i<g[u].size();i++)
			{
				int v=g[u][i];
				if(v==fa||dep1[v].fi!=z) continue;
				cnt+=dep1[v].se;
			}
//			cout<<u<<" "<<x<<" "<<y<<" "<<z<<" "<<cnt*cnt1<<"\n";
			upd(ans,mp(x*(y+z),cnt*cnt1));
		}
	}
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(v==fa) continue;
		dfs3(v,u);
	}
}
void solve()
{
	cin>>n;
	if(n==2)
	{
		cout<<0<<" "<<1<<"\n";
		return;
	}
	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<=n;i++) if(g[i].size()>=2) 
	{
		rt=i;
		break;
	}
//	cout<<rt<<"\n";
	dfs1(rt,-1),dep2[rt]=mp(0,1);
	dfs2(rt,-1),dfs3(rt,-1);
//	for(int i=1;i<=n;i++) cout<<dep1[i].fi<<" "<<dep1[i].se<<" "<<dep2[i].fi<<" "<<dep2[i].se<<"\n";
	if(!ans.fi) ans.se=1;
	cout<<ans.fi<<" "<<ans.se<<"\n";
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【IOI2009 中国国家队选拔】\(N^2\) 数码游戏

对于测试点 \(1\),可以爆搜,复杂度 \(O((N^2)!poly(N))\)

对于测试点 \(2,6\)可以剪十六个小纸片玩,观察发现前若干次操作一定是转矩形的最外面一圈,类似于 UUULLLDDDRRRUUULLLDDDRRR... 的过程,当把 \(1,2,3,4\) 转到最上面的时候,剩余部分可以暴力或者用下文方法解。

考虑 bfs,设计一个估价函数,每一层只保留一些可能比较优的状态,可以试试如下几个。

  • 每个数到终点的曼哈顿距离,的和/平方和/立方和/平方根和。
  • 重新定义距离:\((x_1,y_1),(x_2,y_2)\) 的距离为 \((|x_2-x_1|+1)(|y_2-y_1|+1)\),算和/平方和/立方和/平方根和。
  • 将距离乘上 \(0\) 到这个数的距离,算和/平方和/立方和/平方根和。

实测和/平方根和比较优秀。

这样子会出现一个现象,目前队列中估价函数最低的会在两个数中反复横跳,每次扰动一步显然是不够的。可以在模 \(2/3/4=0\) 的某一轮中删减状态。

需要调一调删除的频率和保留的状态数。

当然估价函数并不是特别合理的,比如有两个相邻但位置相反的数,空格又隔得特别远。其实这种状态是很劣的,但这几种估价都会认为这种状态很优秀。或者说多个路径交错在一起,也是特别劣的,但很难设计函数把它们区分开,欢迎各位来交流更好的估价函数。


题外话:关于多项式复杂度的构造方法,也是我的赛时做法:

  • 大概思路是按照 \(1,2,\cdots,N^2\) 的顺序归位,已经归位的不去动它。
  • 对于前 \(N-2\) 行的前 \(N-2\) 列,记录状态 \((ux,uy,ex,ey)\),表示目前需要归位的数和空格分别在什么位置,爆搜就行。
  • 对于前 \(N-2\) 行每一行的最后两列,并不能保证上一条能跑出解,但是我们一定可以把棋盘变成下面的形状之一(. 表示空格,? 表示没被归位的数字):
1 2 . 3
? ? ? 4
1 2 4 .
? ? 3 ?

所以记录状态时记录两个数的位置和空格的位置,也一定能得到解。

  • 对于后两行,由于最后一行极有可能顺序不对,上述方法不能使用。从左往右枚举列,同时归位这一列的两个数,假设 \(N=4\),目前归位 \(9,13\),只需要在第三行令 \(13\)\(9\) 挨在一起,然后转下来就行,也可以记录三个坐标爆搜。
1 2 3 4
5 6 7 8
? 13 9 ?
? ? ? .

复杂度 \(O(n^7)\)。肯定有更优的做法。

但这样构造,我只在测试点 \(9\) 得到了 \(3\) 分,其余均为 \(2\) 分。

【2022 克罗地亚国家队选拔】Mapa

直觉上特别违背信息论,因为只能传递 \(3000\) bits,而传递 \(y_i\) 已经需要 \(3000\) bits。

假设解密时知道了所有 \(x_i\),那么只需要在加密的时候按 \(x_i\) 排序后传递 \(y_i\),正好符合要求。

怎么不传递 \(x_i\) 呢?或者将给定的键值对转化成一些固定的 \(x_i\),比如将所有 \(x_i\) 变成 \([1,n]\) 的排列。

拉格朗日插值!

由于 \(n\) 个键值对 \((x,y)\),唯一确定了一个 \(n-1\) 次多项式,将这个多项式求出来,传递 \([1,n]\) 的点值(传系数也行),解码的时候插值一下,由于插值有除法,可以在模 \(10^9+7\) 下算,需要的位数不变。

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 N,Q,X[105],Y[105];
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 F(int x)
{
	int res=0;
	for(int i=1;i<=N;i++) 
	{
		int coef=1;
		for(int j=1;j<=N;j++) if(j!=i) coef=coef*((x-X[j]+mod)%mod)%mod*fpow((X[i]-X[j]+mod)%mod,mod-2)%mod;
		res=(res+coef*Y[i])%mod;
	}
	return res;
}
string obuf="";
int read(int len)
{
	int res=0;
	for(int i=0;i<len;i++) 
	{
		char x;
		cin>>x;
		if(x=='1') res+=(1<<i);
	}
	return res;
}
void print(int x,int len)
{
	for(int i=0;i<len;i++)
	{
		if(x&(1<<i)) obuf+='1';
		else obuf+='0';
	}
}
void enc()
{
	cin>>N;
	for(int i=1;i<=N;i++) cin>>X[i]>>Y[i];
	for(int i=1;i<=N;i++) print(F(i),30);
	cout<<obuf.size()<<"\n"<<obuf<<"\n";
}

void dec()
{
	string del="";
	int dirt;
	string en;
	cin>>N>>Q>>dirt;
	for(int i=1;i<=N;i++) X[i]=i,Y[i]=read(30);
	while(Q--)
	{
		int x;
		cin>>x;
		cout<<F(x)<<"\n";
	}
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
	cin>>_;
	if(_==1) enc();
	else dec(); 
	return 0;
}

【KOI 2023】出租车

一个非常直观的结论:换乘一定是从 \(B_i\) 大的换到 \(B_i\) 小的,也就是说经过的所有点(除了终点),\(B_i\) 是递减的。

将所有 \(i\)\(B_i\) 从大到小排序,令 \(dp(u)\) 表示到达点 \(u\) 所需的最小代价,转移:\(dp(u)=\min_{v|B_v \gt B_u}\{ dp(v)+A_v+B_v \cdot dist(u,v)\}\)

\(dp(v)+A_v+B_v\cdot dist(u,v)\) 中,\(B_v\) 看成斜率,\(A_v+dp(v)\) 看成截距,\(dist(u,v)\) 看成自变量 \(x\)。考虑点分树,在点分树上的所有点上维护一个凸包,计算完 \(dp(v)\) 之后,在点分树上所有 \(v\) 的祖先 \(fa\) 上的凸包,加入直线 \(y=B_vx+(B_v \cdot dist(v,fa)+A_v+dp(v))\),因为 \(B\) 是递减的,用单调栈维护凸包,查询时二分查询 \(x=dist(u,fa)\) 处的 \(y\),复杂度 \(O(n \log^2 n)\)

有个小细节,路径上经过的所有点中,终点不保证是 \(B_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,A[100005],B[100005];
int ord[100005];
bool cmp(int x,int y)
{
	return B[x]>B[y];
}
vector <pii > g[100005];
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[100005],maxsz[100005],rt=0,tot;
vector <int> h[100005];
int vis[100005],par[100005];
void dfs0(int u,int fa)
{
	sz[u]=1;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i].fi;
		if(vis[v]||v==fa) continue;
		dfs0(v,u),sz[u]+=sz[v];
	}
}
void dfs1(int u,int fa)
{
	maxsz[u]=0;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i].fi;
		if(vis[v]||v==fa) continue;
		maxsz[u]=max(maxsz[u],sz[v]),dfs1(v,u);
	}
	maxsz[u]=max(maxsz[u],tot-sz[u]);
	if(maxsz[u]<maxsz[rt]) rt=u;
}
void calc(int u)
{
	vis[u]=1;
	vector <pii > ss;
	ss.clear();
	dfs0(u,-1);
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i].fi;
		if(vis[v]) continue;
		ss.pb(mp(v,sz[v]));
	}
	for(int i=0;i<ss.size();i++)
	{
		rt=0,tot=ss[i].se;
		dfs1(ss[i].fi,-1);
		par[rt]=u;
		calc(rt);
	}
}
int dp[100005];
vector <pii > stk[100005];
vector <double > pnt[100005];
double get(pii x,pii y)
{
	if(x.fi==y.fi) return (x.se>y.se?-1e18:1e18);
	return 1.0*(y.se-x.se)/(x.fi-y.fi);
}
void ins(int id,pii li)
{
	li.fi*=-1,li.se*=-1;
	if(!stk[id].size()) stk[id].pb(li),pnt[id].pb(-1e18);
	else
	{
		while(stk[id].size()>1&&get(li,stk[id].back())<pnt[id].back()) stk[id].pop_back(),pnt[id].pop_back();
		pnt[id].pb(get(li,stk[id].back())),stk[id].pb(li);
	}
}
int query(int x,int y)
{
	if(!stk[x].size()) return INF;
	int pos=upper_bound(pnt[x].begin(),pnt[x].end(),(double)(y))-pnt[x].begin()-1;
	int res=y*stk[x][pos].fi+stk[x][pos].se;
	return -res;
}
void trans(int id)
{
	dp[id]=min(dp[id],A[1]+Lca.getdis(1,id)*B[1]);
	int u=id;
	while(u) dp[id]=min(dp[id],query(u,Lca.getdis(id,u))),u=par[u];
	u=id;
	while(u) ins(u,mp(B[id],dp[id]+Lca.getdis(id,u)*B[id]+A[id])),u=par[u];
}
void trans2(int id)
{
	dp[id]=min(dp[id],A[1]+Lca.getdis(1,id)*B[1]);
	int u=id;
	while(u) dp[id]=min(dp[id],query(u,Lca.getdis(id,u))),u=par[u];
}
vector <int> travel(vector <int> _A,vector <signed> _B,vector <signed> _U,vector <signed> _V,vector <signed> _W)
{
	n=_A.size();
	for(int i=1;i<=n;i++) A[i]=_A[i-1],B[i]=_B[i-1];
	for(int i=0;i<_U.size();i++)
	{
		int u=_U[i],v=_V[i],w=_W[i];
		u++,v++;
		g[u].pb(mp(v,w)),g[v].pb(mp(u,w));
	}
	Lca.build();
	maxsz[0]=tot=n;
	dfs0(1,-1),dfs1(1,-1),calc(rt);
	memset(dp,0x3f,sizeof(dp));
	dp[1]=0;
	for(int i=1;i<=n;i++) ord[i]=i;
	sort(ord+1,ord+1+n,cmp);
	for(int i=1;i<=n;i++) if(ord[i]!=1) trans(ord[i]);
	for(int i=1;i<=n;i++) if(ord[i]!=1) trans2(ord[i]);
	vector <int> ret;
	for(int i=2;i<=n;i++) ret.pb(dp[i]);
	return ret;
}

【UOI 2023】乌克兰

非常直观的感觉,操作次数不会特别的多,考虑证明上界为 \(3\)

\(a_i\) 为给定的数组,\(s_i\)\(a_i\) 的前缀和,令 \(s_x,s_y\) 分别为 \(s_i\) 的最小值和最大值。

  • \(x \gt y\),则用操作 \(1\)\(x \lt y\)
  • 不妨令 \(s_x \lt 0,s_y \gt 0\),先用操作 \(2\) 操作 \([x+1,n]\),被操作的所有数 \(u\) 会变成 \(s_u-s_x\),是非负的,因为 \(s_x \lt 0\),故最大值的位置仍然在 \([x+1,n]\) 中,令 \(y\) 为新的最大值出现位置,用操作 \(3\) 操作 \([1,y]\),被操作的所有数 \(v\) 会变成 \(s_y-s_{v-1}\),同样非负。

分情况讨论:

若答案为 \(0\),当且仅当所有 \(a_i \ge 0\)

若答案为 \(1\),不妨要么进行一次 \(1\) 操作,要么存在一个区间满足前/后缀和 \(\ge 0\),且区间外的所有数 \(\ge 0\)。而在区间左右两端加上非负数是不劣的,故只要判断前/后缀和数组是否都 \(\ge 0\)

若答案为 \(3\),直接构造。

若答案为 \(2\),分两种操作类型相同/不同讨论。

若相同,令两次都为 \(2\) 操作,我们需要找到一个区间,满足将这个区间替换成前缀数组后,整个序列的前缀数组均 \(\ge 0\)。令第一步操作的区间为 \([l,r]\),则 \([1,l-1]\) 的前缀数组需要 \(\ge 0\),令 \(l=1\),是不劣的,故第一次操作一定是操作一个前缀。枚举这个前缀,需要维护:单点修改,求前缀和数组最小值。用线段树维护前缀和数组,单点修改对应了区间加减,查询对应了全局最小值。

若不同,不妨令第一次为 \(3\) 操作,第二次为 \(2\) 操作,考虑分治,令当前区间为 \([l,r]\),中点为 \(mid\),令 \(a_i\) 为原数组,\(s_i\) 为前缀和。

对于所有 \(i \in [mid+1,r]\) 计算:

  • \(A_i\):若第一次操作的右端点为 \(i\),则操作完后 \(a_{mid+1}=A_i\)。计算是容易的。
  • \(B_i\):若右端点为 \(i\),第一次操作完之后,\([1,mid]\) 的和至少是多少才能保证对于所有 \(j \in [mid+1,n]\),有 \(s_j \ge 0\)\(B_i\) 由两部分:\([mid+1,r],[r+1,n]\) 确定,\([r+1,n]\) 是容易计算的,预处理原数组中 \(s_i\) 的后缀最小值,计算 \([mid+1,r]\) 中后缀和的和即可。\([mid+1,r]\) 有点麻烦,考虑一个位置 \(j\),第一次操作完之后,\([mid+1,j]\) 中的和 \(t_j\) 会随着 \(r\) 的增加发生什么变化:令新加入的数为 \(a_r\),则对于所有 \(j \in [mid+1,r-1]\)\(t_j\) 会增加 \((j-mid)\cdot t_j\)。将所有 \(j\) 看成一条直线 \(y=(j-mid) \cdot x+b\),计算 \(B_i\) 可以看做查询凸包上 \(x=s_i-s_{mid}\) 处的 \(y\) 值。可以用李超树或单调栈维护。

对于所有 \(i \in [l,mid]\) 计算:

  • \(D_i\):原序列中 \([i,m]\) 后缀和之和。
  • \(E_i\):第一次操作完之后,若左端点为 \(i\),则 \(a_{mid+1}\) 至少为多少才能保证 \([1,mid]\) 中所有前缀和非负。同样的考虑将 \(l\) 减少 \(1\) 之后,所有位置 \(j\) 的前缀和 \(t_j\) 会发生什么变化,这个也可以写成一条直线 \(y=(j-i+1)\cdot x+b\),我们需要找到一个最小的 \(x\) 使得对于所有直线均有 \(y \ge 0\),即 \((j+1)x+b \ge i \cdot x\),可以看做求直线 \(y=i \cdot x\) 和凸包的交点,用单调栈+二分维护,如果不介意多个 \(\log\) 的话也可以用李超树维护+二分,也能擦着时限过。
  • \(F_i\):原序列中 \([1,i-1]\) 的和。

一个合法的第一次操作区间 \([L,R]\) 需要满足:

  • \([1,L-1]\) 的前缀和数组均 \(\ge 0\),很好判断。
  • \(A_R \ge E_L\)
  • \(D_L+F_L+A_R(mid-L+1) \ge B_R\)

三个限制条件分别令 \([1,L-1],[L,mid],[mid+1,n]\) 三段满足条件。将所有满足第一条限制的 \(L\),和所有 \(R\) 搞出来,按 \(A_R\)\(E_L\) 排序。第三个限制,对于所有 \(L\) 可以看成直线 \(y=(mid-L+1) \cdot x+(D_L+F_L)\),对于所有 \(R\) 可以看做查询 \(x=A_R\)

复杂度 \(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,dirt;
struct Line
{
	int k,b;
};
struct SegTree
{
	int t[800015],tag[800015];
void pushdown(int id)
{
	if(tag[id])
	{
		t[id<<1]+=tag[id],t[id<<1|1]+=tag[id];
		tag[id<<1]+=tag[id],tag[id<<1|1]+=tag[id];
		tag[id]=0;
	}
}
void update_add(int id,int l,int r,int x,int y,int d)
{
	if(x<=l&&r<=y)
	{
		tag[id]+=d,t[id]+=d;
		return;
	}
	int mid=(l+r)>>1;
	pushdown(id);
	if(x<=mid) update_add(id<<1,l,mid,x,y,d);
	if(y>mid) update_add(id<<1|1,mid+1,r,x,y,d);
	t[id]=min(t[id<<1],t[id<<1|1]);
}
void update(int i,int x,int y,int tp)
{
	if(tp==0)
	{
		update_add(1,1,n,i,n,-x);
		update_add(1,1,n,i,n,y);
	}
	else
	{
		update_add(1,1,n,1,i,-x);
		update_add(1,1,n,1,i,y);
	}
}
}st;
struct Lichao
{
Line p[2000005];
int t[5000005];
int ls[5000005],rs[5000005];
int idx,tid;
void init()
{
	idx=0,tid=1;
	ls[1]=0,rs[1]=0,t[1]=0;
	p[0].k=1,p[0].b=-INF;
}
int add_line(int k,int b)
{
	p[++idx].k=k,p[idx].b=b;
	return idx;
}
int get_y(int id,int x)
{
	return p[id].k*x+p[id].b;
}
void update(int &id,int l,int r,int lid)
{
	if(!id) id=++tid,t[id]=0,ls[id]=rs[id]=0;
	int mid=(l+1000000+r+1000000)/2-1000000;
	if(get_y(lid,mid)>get_y(t[id],mid)) swap(lid,t[id]);
	if(get_y(lid,l)>get_y(t[id],l)) update(ls[id],l,mid,lid);
	if(get_y(lid,r)>get_y(t[id],r)) update(rs[id],mid+1,r,lid);	
}
void insert(int &id,int l,int r,int x,int y,int lid)
{
	if(!id) id=++tid,t[id]=0,ls[id]=rs[id]=0;
	if(x<=l&&r<=y)
	{
		update(id,l,r,lid);
		return;
	}
	int mid=(l+1000000+r+1000000)/2-1000000;
	if(x<=mid) insert(ls[id],l,mid,x,y,lid);
	if(y>mid) insert(rs[id],mid+1,r,x,y,lid);
}
int query(int id,int l,int r,int x)
{
	assert(l<=x&&x<=r);
	if(!id) return -INF;
	int mid=(l+r)/2;
	int res=get_y(t[id],x);
	if(l==r) return res;
	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;
}
}lt;
int A[200005];
int a[200005],b[200005],c[200005],d[200005],e[200005],f[200005],ps[200005],ss[200005],pspm[200005],pssm[200005];

vector <pii > stk;
vector <double > pnt;
double get(pii x,pii y)
{
	if(x.fi==y.fi) return (x.se>y.se?-1e18:1e18);
	return 1.0*(y.se-x.se)/(x.fi-y.fi);
}
void ins(pii li)
{
	if(!stk.size()) stk.pb(li),pnt.pb(-1e18);
	else
	{
		while(stk.size()>1&&get(li,stk.back())<pnt.back()) stk.pop_back(),pnt.pop_back();
		pnt.pb(get(li,stk.back())),stk.pb(li);
	}
}
double inter(pii l1,pii l2)
{
	return 1.0*(l2.se-l1.se)/(l1.fi-l2.fi);
}
int query(pii li)
{
	if(!stk.size()) return INF;
	pnt.pb(1e18);
	int L=0,R=pnt.size()-2,res=0;
	while(L<=R)
	{
		int mid=(L+R)>>1;
		double tmp=inter(stk[mid],li);
		if(pnt[mid]<=tmp&&tmp<=pnt[mid+1]) 
		{
			res=mid;
			break;
		}
		if(tmp>pnt[mid+1]) L=mid+1;
		else R=mid-1;
	}
	pnt.pop_back();
	return ceil(inter(stk[res],li));
}


pii divide(int l,int r)
{
	if(l>=r) return mp(-1,-1);
	int mid=(l+r)>>1;
	for(int i=mid+1,s=0;i<=r;i++) s+=A[i],a[i]=s;
	lt.init();
	int rt=1;
	for(int i=mid+1,s=0,sum=0;i<=r;i++)
	{
		s+=(i-mid)*A[i];
		sum+=A[i];
		int K=i-mid,B=s;
		B-=K*sum;
		int lid=lt.add_line(-K,-B);
		lt.insert(rt,-n,n,-n,n,lid);
		b[i]=lt.query(rt,-n,n,sum);
		b[i]=max(b[i],-(pssm[i+1]-ps[i]+s));
	}
	for(int i=mid,s=0,sum=0;i>=l;i--)
	{
		sum+=A[i],s+=sum;
		d[i]=s;
	}
	lt.init();
	
	stk.clear(),pnt.clear();
	for(int i=mid,sum=0,tag=0;i>=l;i--)
	{
		sum+=A[i],tag+=sum;
		int K=(i+1),B=sum-tag;
		ins(mp(-K,-B));
		e[i]=query(mp(-i,tag+ps[i-1]));
	}
	for(int i=mid;i>=l;i--) f[i]=ps[i-1];
	vector <array<int,4> > vec;
	vec.clear();
	for(int i=l;i<=mid;i++) if(pspm[i-1]>=0) vec.pb({e[i],0,i});
	for(int i=mid+1;i<=r;i++) vec.pb({a[i],1,i});
	sort(vec.begin(),vec.end());
	lt.init();
	for(int i=0;i<vec.size();i++)
	{
		int u=vec[i][2];
		if(vec[i][1]==0) 
		{
			int K=mid-u+1,B=d[u]+f[u];
			int lid=lt.add_line(K,B);
			lt.insert(rt,-n,n,-n,n,lid);
		}
		else
		{
			int tmp=lt.query(rt,-n,n,a[u]);
			if(tmp>=b[u])
			{
				int v=-1;
				for(int j=0;j<i;j++) if(vec[j][1]==0)
				{
					v=vec[j][2];
					int K=mid-v+1,B=d[v]+f[v];
					if(K*a[u]+B==tmp) break;
				}
				assert(v!=-1);
				return mp(v,u);
			}
		}
	}
	
	pii tmp=divide(l,mid);
	if(tmp.fi!=-1) return tmp;
	tmp=divide(mid+1,r);
	if(tmp.fi!=-1) return tmp;
	return mp(-1,-1);
}
vector <array<int,3> > chk()
{
	memset(st.t,0,sizeof(st.t));
	memset(st.tag,0,sizeof(st.tag));
	for(int i=1;i<=n;i++) st.update(i,0,A[i],0);
	for(int i=1,s=0;i<=n;i++)
	{
		s+=A[i];
		st.update(i,A[i],s,0);
		if(st.t[1]>=0)
		{
			vector <array<int,3> > vec;
			vec.pb({2,1,i});
			vec.pb({2,1,n});
			return vec;
		}
	}
	
	memset(st.t,0,sizeof(st.t));
	memset(st.tag,0,sizeof(st.tag));
	for(int i=1;i<=n;i++) st.update(i,0,A[i],1);
	for(int i=n,s=0;i>=1;i--)
	{
		s+=A[i];
		st.update(i,A[i],s,1);
		if(st.t[1]>=0)
		{
			vector <array<int,3> > vec;
			vec.pb({3,i,n});
			vec.pb({3,1,n});
			return vec;
		}
	}
	
	memset(pssm,0x3f,sizeof(pssm)),memset(pspm,0x3f,sizeof(pspm));
	for(int i=1;i<=n;i++) ps[i]=ps[i-1]+A[i],pspm[i]=pssm[i]=ps[i];
	for(int i=1;i<=n;i++) ss[i]=ss[i+1]+A[i];
	for(int i=2;i<=n;i++) pspm[i]=min(pspm[i-1],pspm[i]);
	for(int i=n-1;i>=1;i--) pssm[i]=min(pssm[i+1],pssm[i]);
	pii tmp=divide(1,n);
	vector <array<int,3> > vec;
	for(int i=0;i<100;i++) vec.pb({-1,-1,-1});
	if(tmp.fi==-1) return vec;
	vec.clear();
	vec.pb({3,tmp.fi,tmp.se}),vec.pb({2,1,n});
	return vec;
}
void solve()
{
	cin>>n>>dirt;
	for(int i=1;i<=n;i++) cin>>A[i];
	bool ok=1;
	for(int i=1;i<=n;i++) ok&=(A[i]>=0);
	if(ok) 
	{
		cout<<"0\n";
		return;
	}
	ok=1;
	for(int i=1,s=0;i<=n;i++) s+=A[i],ok&=(s>=0);
	if(ok)
	{
		cout<<"1\n";
		cout<<2<<" "<<1<<" "<<n<<"\n";
		return;
	}
	ok=1;
	for(int i=n,s=0;i>=1;i--) s+=A[i],ok&=(s>=0);
	if(ok)
	{
		cout<<"1\n";
		cout<<3<<" "<<1<<" "<<n<<"\n";
		return;
	}
	
	for(int i=1;i<=n;i++) A[i]*=-1;
	ok=1;
	for(int i=1;i<=n;i++) ok&=(A[i]>=0);
	if(ok) 
	{
		cout<<"1\n1\n";
		return;
	}
	ok=1;
	for(int i=1,s=0;i<=n;i++) s+=A[i],ok&=(s>=0);
	if(ok)
	{
		cout<<"2\n1\n";
		cout<<2<<" "<<1<<" "<<n<<"\n";
		return;
	}
	ok=1;
	for(int i=n,s=0;i>=1;i--) s+=A[i],ok&=(s>=0);
	if(ok)
	{
		cout<<"2\n1\n";
		cout<<3<<" "<<1<<" "<<n<<"\n";
		return;
	}
	for(int i=1;i<=n;i++) A[i]*=-1;
	vector <array<int,3> > v1=chk();
	reverse(A+1,A+1+n);
	vector <array<int,3> > v2=chk();
	for(int i=0;i<v2.size();i++) 
	{
		if(v2[i][0]==2) v2[i][0]=3;
		else if(v2[i][0]==3) v2[i][0]=2;
		v2[i][1]=n-v2[i][1]+1,v2[i][2]=n-v2[i][2]+1;
		swap(v2[i][1],v2[i][2]);
	}
	reverse(A+1,A+1+n);
	for(int i=1;i<=n;i++) A[i]*=-1;
	vector <array<int,3> > v3=chk();
	v3.insert(v3.begin(),{1,-1,-1});
	reverse(A+1,A+1+n);
	vector <array<int,3> > v4=chk();
	v4.insert(v4.begin(),{1,-1,-1});
	for(int i=0;i<v4.size();i++) 
	{
		if(v4[i][0]==2) v4[i][0]=3;
		else if(v4[i][0]==3) v4[i][0]=2;
		v4[i][1]=n-v4[i][1]+1,v4[i][2]=n-v4[i][2]+1;
		swap(v4[i][1],v4[i][2]);
	}
	vector <array<int,3> > ans;
	for(int i=0;i<100;i++) ans.pb({-1,-1,-1});
	if(v1.size()<ans.size()) ans=v1;
	if(v2.size()<ans.size()) ans=v2;
	if(v3.size()<ans.size()) ans=v3;
	if(v4.size()<ans.size()) ans=v4;
	cout<<ans.size()<<"\n";
	for(int i=0;i<ans.size();i++) 
	{
		cout<<ans[i][0];
		if(ans[i][0]!=1) cout<<" "<<ans[i][1]<<" "<<ans[i][2];
		cout<<"\n";
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【2022 克罗地亚国家队选拔】喝喝粥

将每个所有人帽子颜色的状态 \(u\) 拆成两个节点 \(u_0,u_1\)。若两个状态 \(x,y\) 仅有一位 \(d\) 不同,不妨令 \(x\) 的第 \(d\) 位为 \(0\),则在 \(x_0,y_1\) 之间连一条无向边,含义是第 \(d\) 个人无法区分 \(x,y\) 这两个状态,需要给这条边定向:指向 \(x_0\) 表示猜 \(0\),指向 \(y_1\) 表示猜 \(1\)

定完向后,对于一个度数为 \(deg\) 的节点,至少有 \(\lfloor deg/2 \rfloor\) 条边指向它。我们可以想到欧拉回路:建立一个虚点,先对于所有度数为奇数的点(一定有偶数个)向这个虚点连边,跑出欧拉路,按照欧拉路定向,每个点入度和出度相等,均为 \(\ge \lfloor deg/2 \rfloor\),符合题意。

复杂度 \(O(2^NN)\)

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 ans[18][300005];
int del_bt(int x,int d)
{
	int res=x&((1<<d)-1);
	int t=x-res;
	t>>=(d+1);
	t<<=d;
	return res+t;
}
vector <pii > g[1000005]; 
vector <int> pa;
int u0[300005],u1[300005],uidx,eidx,tmp[1000005],cur[1000005];
bool vis[1000005];
bool used[5000005]; 
void dfs(int u)
{
	vis[u]=1;
	while(1)
	{
		if(cur[u]>=g[u].size()) return;
		int v=g[u][cur[u]].fi;
		cur[u]++;
		if(used[g[u][cur[u]-1].se]) continue;
		used[g[u][cur[u]-1].se]=1;
		dfs(v);
		pa.pb(v); 
	}
}
void solve()
{
	cin>>n;
	for(int i=0;i<(1<<n);i++) u0[i]=++uidx,u1[i]=++uidx,tmp[u0[i]]=tmp[u1[i]]=i;
	for(int i=0;i<(1<<n);i++) for(int j=0;j<n;j++)
	{
		int u=i,v=i^(1<<j);
		if(u>v) continue;
		int x=u0[u],y=u1[v];
		if(u&(1<<j)) x=u1[u],y=u0[v];
//		cout<<"addedge: "<<x<<" "<<y<<" "<<j<<"\n";
		int id=++eidx;
		g[x].pb(mp(y,id)),g[y].pb(mp(x,id));
	}
	
	for(int i=1;i<=uidx;i++) if(g[i].size()%2==1)
	{
		int id=++eidx;
		g[0].pb(mp(i,id)),g[i].pb(mp(0,id));
	}
	memset(ans,-1,sizeof(ans));
	for(int i=0;i<=uidx;i++) if(!vis[i]) 
	{
		pa.clear();
		dfs(i);
//		for(int j=0;j<pa.size();j++) cout<<pa[j]<<" ";
//		cout<<"\n";
		for(int j=0;j<pa.size();j++) 
		{
			int u=pa[j],v=pa[(j+1)%pa.size()];
//			cout<<u<<" --> "<<v<<"\n";
			if(!u||!v) continue;
			u=tmp[u],v=tmp[v];
//			cout<<u<<" "<<v<<" ";
			int d=-1,op=0;
			for(int l=0;l<n;l++) 
			{
				int x=0,y=0;
				if(u&(1<<l)) x=1;
				if(v&(1<<l)) y=1;
				if(x!=y) 
				{
					d=l,op=y;
					break;
				}
			}
//			cout<<d<<" "<<del_bt(u,d)<<" "<<op<<"\n";
			ans[d][del_bt(u,d)]=op;
		}
	}
	for(int i=n-1;i>=0;i--) 
	{
		for(int j=0;j<((1<<(n-1)));j++) cout<<(char)(ans[i][j]+'B');
		cout<<"\n";
	}
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【JOI 2018 Final】毒蛇越狱

先考虑只有 0? 的情况,可以通过预处理高维前缀和解决,只有 1? 也是容易的,预处理后缀和即可。

如果 0 1 ? 都存在,可以考虑对某种字符容斥:以 0 为例,先把所有 0 替换成 ?,再减去这些位置上至少有一个位置为 1 的所有情况,枚举一个子集用容斥算算就行。

取三种字符出现次数最少的算,0 1 用容斥,? 直接暴力,复杂度 \(O(2^nn+Q2^{\lfloor n/3 \rfloor})\)

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 w[1100005];
char s[1100005];
int pre[1100005],suf[1100005],ppc[1100005],n,q;
int ans=0;
vector <int> vec;
void dfs(int idx,int mask,int coef,int tp)
{
	if(idx==vec.size())
	{
		if(tp==0) ans+=coef*suf[mask];
		if(tp==1) ans+=coef*pre[mask];
		if(tp==2) ans+=w[mask];
		return;
	}
	dfs(idx+1,mask,coef*(tp==0?1:-1),tp);
	dfs(idx+1,mask+(1<<vec[idx]),coef*(tp==1?1:-1),tp);
}
void calc0()
{
	int mask=0;
	for(int i=0;i<n;i++) 
	{
		if(s[i]=='0') vec.pb(i);
		if(s[i]=='1') mask+=(1<<i);
	}
	ans=0;
	dfs(0,mask,1,0);
}
void calc1()
{
	int mask=0;
	for(int i=0;i<n;i++) 
	{
		if(s[i]=='1') vec.pb(i);
		if(s[i]=='?') mask+=(1<<i);
	}
	ans=0;
	dfs(0,mask,1,1);
}
void calc2()
{
	int mask=0;
	for(int i=0;i<n;i++) 
	{
		if(s[i]=='?') vec.pb(i);
		if(s[i]=='1') mask+=(1<<i);
	}
	ans=0;
	dfs(0,mask,1,2);
}
void solve()
{
	cin>>n>>q;
	cin>>s;
	for(int i=0;i<(1<<n);i++) w[i]=pre[i]=suf[i]=s[i]-'0',ppc[i]=__builtin_popcount(i);
	for(int i=0;i<n;i++) for(int j=0;j<(1<<n);j++)
	{
		if(j&(1<<i)) pre[j]+=pre[j^(1<<i)];
		else suf[j]+=suf[j^(1<<i)];
	 } 
	 while(q--)
	 {
	 	cin>>s;
	 	reverse(s,s+n);
	 	int c0=0,c1=0,c2=0;
	 	for(int i=0;i<n;i++)
	 	{
	 		if(s[i]=='0') c0++;
	 		if(s[i]=='1') c1++;
	 		if(s[i]=='?') c2++;
		}
		vec.clear();
//		calc2();
		if(c0<=c1&&c0<=c2) calc0();
		else if(c1<=c0&&c1<=c2) calc1();
		else calc2(); 
		cout<<ans<<"\n";
	 }
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【IOI 2021】喷泉公园

把喷泉放到网格上,连一条边就相当于画上一个小正方形的一条边,然后需要给每条边选一个格子。

先给每个格子钦定一个方向(只被横向的边选或者只被纵向的边选),可以黑白染色,这样每条边都被恰好“预分配”了一个格子。

将点以行为第一关键字,列为第二关键字排序,每次去判断往右或往下是否可以加入这条边,加完之后判断连通性。

正确性蛮玄学的,大概就是如果一条边加入不了就一定形成环,画出来就大概是往右下方向递归寻找合法的边,一定可以找到。

Code
#include "parks.h"
#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

int N,fa[200005];
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;
}
map <pii,int> m1,m2;
int construct_roads(vector <int> _x, vector <int> _y)
{
	N=_x.size();
	for(int i=0;i<N;i++) fa[i]=i;
	vector <array<int,3> > vec;
	vec.clear();
	for(int i=0;i<N;i++) vec.pb({_x[i],_y[i],i}),m1[mp(_x[i],_y[i])]=i;
	sort(vec.begin(),vec.end());
	for(int i=0;i<N;i++)
	{
		if(m1.find(mp(_x[i],_y[i]+2))!=m1.end()) merge(i,m1[mp(_x[i],_y[i]+2)]);
		if(m1.find(mp(_x[i]+2,_y[i]))!=m1.end()) merge(i,m1[mp(_x[i]+2,_y[i])]);
	}
	for(int i=1;i<N;i++) if(find(i)!=find(1)) return 0;
	for(int i=0;i<N;i++) fa[i]=i;
	vector <int> U,V,A,B;
	for(int i=0;i<vec.size();i++)
	{
		int x=vec[i][0],y=vec[i][1],u=vec[i][2];
		if(m1.find(mp(x,y+2))!=m1.end())
		{
			int v=m1[mp(x,y+2)];
			pii pos=mp(x-1,y+1);
			if((pos.fi/2+pos.se/2)%2==0) pos.fi+=2;
			if(find(u)!=find(v)&&!m2[pos]) merge(u,v),U.pb(u),V.pb(v),A.pb(pos.fi),B.pb(pos.se),m2[pos]=1;
		}
		if(m1.find(mp(x+2,y))!=m1.end())
		{
			int v=m1[mp(x+2,y)];
			pii pos=mp(x+1,y-1);
			if((pos.fi/2+pos.se/2)%2==1) pos.se+=2;
			if(find(u)!=find(v)&&!m2[pos]) merge(u,v),U.pb(u),V.pb(v),A.pb(pos.fi),B.pb(pos.se),m2[pos]=1;
		}
	}
	if(U.size()<N-1) return 0;
	build(U,V,A,B);
	return 1;
}

【PA 2021】Od deski do deski

如何判断一个序列合法:只需要判断序列是否能被划分为若干连续段,每一段首位相同。令 \(f(i)\) 表示前 \(i\) 个数是否可行,转移从 \(f(i)\) 转移到 \(f(j)\) 的条件是 \(a_{i+1}=a_j\)

回到原问题,令 \(dp(i,j,0/1)\) 表示,长度为 \(i\),有 \(j\) 个数 \(x\) 满足能找到一个位置 \(pos\),满足 \(a_{pos}=x\)\(f(pos-1)=1\),当前整个序列是否合法,转移:

  • \(dp(i,j,0) \times j \rightarrow dp(i+1,j,1)\)
  • \(dp(i,j,0) \times (m-j) \rightarrow dp(i+1,j,0)\)
  • \(dp(i,j,1) \times j \rightarrow dp(i+1,j,1)\)
  • \(dp(i,j,1) \times (m-j) \rightarrow dp(i+1,j+1,0)\)

含义很显然。

复杂度 \(O(n^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=1e9+7;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n,m;
int dp[3005][3005][2];
void solve()
{
	cin>>n>>m;
	dp[0][0][1]=1;
	for(int i=0;i<n;i++) 
	{
		for(int j=0;j<=min(i,m);j++) 
		{
//			cout<<i<<" "<<j<<" "<<dp[i][j][0]<<" "<<dp[i][j][1]<<"...\n";
			if(dp[i][j][0]) dp[i+1][j][1]=(dp[i+1][j][1]+dp[i][j][0]*j)%mod,dp[i+1][j][0]=(dp[i+1][j][0]+dp[i][j][0]*(m-j))%mod;
			if(dp[i][j][1]) dp[i+1][j][1]=(dp[i+1][j][1]+dp[i][j][1]*j)%mod,dp[i+1][j+1][0]=(dp[i+1][j+1][0]+dp[i][j][1]*(m-j))%mod;
		}
//		for(int j=0;j<=n;j++) cout<<dp[i+1][j][0]<<" "<<dp[i+1][j][1]<<"\n";
//		system("pause");
	}
	int ans=0;
	for(int j=0;j<=min(n,m);j++) ans=(ans+dp[n][j][1])%mod;
	cout<<ans<<"\n"; 
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【CTT 2021】经典游戏

在节点 \(u\) 处棋子的 \(sg\) 值是:令 \(v\)\(u\) 子树内深度最大的一个叶子,则 \(sg(u)=dep_v-dep_u\)

后手可以获胜当且仅当目前整体的异或和 \(\le sg(rt)\)

维护处每个点的 \(sg\) 值是容易的。以 \(u\) 为根,令 \(f_u,g_u\) 分别表示在所有 \(u\) 的儿子 \(v\) 中,\(sg(v)\) 的最大值 \(+1\) 和次大值 \(+1\),修改时的形式大概为:任意定一个根后,将一个子树异或上 \(f_u\)\(g_u\),子树外异或上另一个数,可以在 dfs 序上维护。

查询的话,暴力判断 \(u\) 和它的父亲,对于所有儿子,我们只需要对于每个节点维护关于儿子的 Trie,由于大多数修改都是整体 XOR,可以打懒标记,令当前的懒标记为 \(x\),查询的形式大概为:求 \(i\) 的个数满足 \(a_i \space \text{xor} \space x \gt b_i\)。一次修改整体只有 \(O(1)\) 个儿子的 \(a_i\) 会改变,这个可以暴力。

复杂度 \(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;
vector <int> g[1000005];
struct Trie
{
	int rt[1000005],sum[35000005],uidx;
	int son[2][35000005];
	void init()
	{
		for(int i=1;i<=n;i++) rt[i]=++uidx;
	}
//	vector <array<int,3> > vec[1000005];
	void update(int Rt,int a,int b,int d)
	{
//		vec[Rt].pb({a,b,d});
		int u=Rt;
		for(int i=19;i>=0;i--)
		{
			int ba=0,bb=0;
			if(a&(1<<i)) ba=1;
			if(b&(1<<i)) bb=1;
			if(bb==0)
			{
				if(!son[ba^1][u]) son[ba^1][u]=++uidx;
				sum[son[ba^1][u]]+=d;
				if(!son[ba][u]) son[ba][u]=++uidx;
				u=son[ba][u];
			}
			else 
			{
				if(!son[ba^1][u]) son[ba^1][u]=++uidx;
				u=son[ba^1][u];
			}
		}
	}
	int query(int Rt,int tag)
	{
		int u=Rt,res=0;
//		for(int i=0;i<vec[Rt].size();i++) if((vec[Rt][i][0]^tag)>vec[Rt][i][1]) res+=vec[Rt][i][2];
//		return res;
		for(int i=19;i>=0;i--)
		{
			if(!u) break;
			res+=sum[u];
			int b=0;
			if(tag&(1<<i)) b=1;
			u=son[b][u];
		}
		if(u) res+=sum[u];
		return res;
	}
}tr;

struct BIT
{
	int t[1000005];
	int lowbit(int x)
	{
		return x&(-x);
	} 
	void update(int x,int y,int d)
	{
		for(int i=x;i<=n;i+=lowbit(i)) t[i]^=d;
		for(int i=y+1;i<=n;i+=lowbit(i)) t[i]^=d;
	} 
	int query(int x)
	{
		int res=0;
		for(int i=x;i>=1;i-=lowbit(i)) res^=t[i];
		return res;
	}
}bt;
int dfn[1000005],dfnR[1000005],son[1000005],par[1000005],clk;
pii m1[1000005],m2[1000005];
int a0[1000005];

void upd_dfs(int u,pii x)
{
	if(x.fi>m1[u].fi) m2[u]=m1[u],m1[u]=x;
	else if(x.fi>m2[u].fi) m2[u]=x;
}
void dfs1(int u,int fa)
{
	dfn[u]=dfnR[u]=++clk;
	for(int i=0;i<g[u].size();i++) 
	{
		int v=g[u][i];
		if(v==fa) continue;
		dfs1(v,u),upd_dfs(u,mp(m1[v].fi+1,v));
		dfnR[u]=dfnR[v];
	}
	par[u]=fa,son[u]=m1[u].se;
}
pii in[10000005],out[1000005];
void dfs2(int u,int fa)
{
	int max1=0,max2=0;
	max1=out[u].fi+1;
	for(int i=0;i<g[u].size();i++) 
	{
		int v=g[u][i];
		if(v==fa) continue;
		int t=in[v].fi+2;
		if(t>max1) max2=max1,max1=t;
		else if(t>max2) max2=t;
	} 
	int cnt1=0,cnt2=0;
	if(out[u].fi+1==max1) cnt1=u;
	if(out[u].fi+1==max2) cnt2=u;
	for(int i=0;i<g[u].size();i++) 
	{
		int v=g[u][i];
		if(v==fa) continue;
		int t=in[v].fi+2;
		if(t==max1) cnt1=v;
		if(t==max2) cnt2=v;
	} 
//	cout<<u<<" "<<" "<<max1<<" "<<max2<<" "<<cnt1<<" "<<cnt2<<"\n";
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(v==fa) continue;
		if(v!=cnt1) upd_dfs(v,mp(max1,u)),out[v]=mp(max1,u);
		else upd_dfs(v,mp(max2,u)),out[v]=mp(max2,u);
		dfs2(v,u);
	}
}


void solve()
{
	int Q;
	cin>>n>>Q;
	if(n==1)
	{
		while(Q--) cout<<0<<"\n";
		return;
	}
	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<=n;i++) cin>>a0[i];
	tr.init();
	for(int i=1;i<=n;i++) m1[i].se=m2[i].se=i;
	dfs1(1,-1);
	for(int i=1;i<=n;i++) in[i]=m1[i];
	dfs2(1,-1);
//	for(int i=1;i<=n;i++) cout<<dfn[i]<<" "<<dfnR[i]<<"\n";
//	system("pause");
	for(int i=1;i<=n;i++)
	{
		a0[i]%=2;
		if(a0[i])
		{
			if(m1[i].se==par[i])
			{
//				cout<<"in: "<<i<<" "<<m1[i].fi<<"\n";
//				cout<<"out: "<<i<<" "<<m2[i].fi<<"\n";
				bt.update(dfn[i],dfnR[i],m1[i].fi);
				if(dfn[i]>1) bt.update(1,dfn[i]-1,m2[i].fi);
				if(dfnR[i]<n) bt.update(dfnR[i]+1,n,m2[i].fi);
			}
			else
			{
				int u=son[i];
//				cout<<"in: "<<u<<" "<<m2[i].fi<<"\n";
//				cout<<"out: "<<u<<" "<<m1[i].fi<<"\n";
				bt.update(dfn[u],dfnR[u],m2[i].fi);
				if(dfn[u]>1) bt.update(1,dfn[u]-1,m1[i].fi);
				if(dfnR[u]<n) bt.update(dfnR[u]+1,n,m1[i].fi);
			}
		}
	}
//		for(int i=1;i<=n;i++) cout<<m1[i].fi<<" "<<m1[i].se<<" "<<m2[i].fi<<" "<<m2[i].se<<"\n";
//	for(int i=1;i<=n;i++) cout<<bt.query(dfn[i])<<" "<<m1[i].fi<<"\n";
//	system("pause");
	for(int i=1;i<=n;i++) 
	{
		a0[i]=bt.query(dfn[i])^bt.query(dfn[par[i]]);
		if(par[i]) tr.update(tr.rt[par[i]],a0[i],m1[i].fi,1);
	}
	while(Q--)
	{
		int x,y;
		cin>>x>>y;
		if(m1[x].se==par[x])
		{
			tr.update(tr.rt[par[x]],a0[x],m1[x].fi,-1);
			bt.update(dfn[x],dfnR[x],m1[x].fi);
			if(dfn[x]>1) bt.update(1,dfn[x]-1,m2[x].fi);
			if(dfnR[x]<n) bt.update(dfnR[x]+1,n,m2[x].fi);
			a0[x]=bt.query(dfn[x])^bt.query(dfn[par[x]]);
			tr.update(tr.rt[par[x]],a0[x],m1[x].fi,1);
		}
		else
		{
			int u=son[x];
			tr.update(tr.rt[x],a0[u],m1[u].fi,-1);
			bt.update(dfn[u],dfnR[u],m2[x].fi);
			if(dfn[u]>1) bt.update(1,dfn[u]-1,m1[x].fi);
			if(dfnR[u]<n) bt.update(dfnR[u]+1,n,m1[x].fi);
			a0[u]=bt.query(dfn[u])^bt.query(dfn[x]);
			tr.update(tr.rt[x],a0[u],m1[u].fi,1);
		}
		
		int res=tr.query(tr.rt[y],bt.query(dfn[y]));
		if(bt.query(dfn[y])>m1[y].fi) res++;
		if(y!=1&&bt.query(dfn[par[y]])>m1[par[y]].fi) res++;
//		res=0;
//		for(int i=1;i<=n;i++) if((i==y||par[i]==y||par[y]==i)&&bt.query(dfn[i])>m1[i].fi) res++;
//		for(int i=1;i<=n;i++) cout<<bt.query(dfn[i])<<" "<<m1[i].fi<<"\n";
		cout<<res<<"\n"; 
	}

}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
	int dirt;
	cin>>dirt;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【USACO 2022 Open】Up Down Subsequence

一个很直接的贪心想法,只需要尽可能最大化长度即可。

\(dp(i)\) 表示前 \(i\) 个数能选出的最大长度,转移用两个 BIT 维护。

也不知道这个状态为什么对,感性理解就是牺牲一个长度不会更优,越往后面增加长度越困难。

复杂度 \(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;
int a[300005];
char b[300005];
struct BIT
{
	int t[300005];
	int lowbit(int x)
	{
		return x&(-x);
	}
	void update(int x,int d)
	{
		for(int i=x;i<=n;i+=lowbit(i)) t[i]=max(t[i],d);
	}
	int query(int x)
	{
		int res=0;
		for(int i=x;i>=1;i-=lowbit(i)) res=max(res,t[i]);
		return res;
	}
}bt1,bt2;
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<n;i++) cin>>b[i];
	int ans=1;
	for(int i=1;i<=n;i++)
	{
		int tmp=max(bt1.query(a[i]),bt2.query(n-a[i]+1))+1;
		ans=max(ans,tmp);
		if(b[tmp]=='U') bt1.update(a[i],tmp);
		else bt2.update(n-a[i]+1,tmp);
	}
	cout<<ans-1<<"\n";
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【ICPC 2022 Jinan】Skills

不能掉到 \(0\) 以下,这个限制特别烦。如果某一天掉到 \(0\) 以下,那么这一天之前所做的练习都是无效的。也就是说一定存在一个最优方案,在开始练习一个技能之后,一定不会掉到 \(0\) 以下。

\(dp(i,0/1/2,d_1,d_2)\) 表示,考虑前 \(i\) 天,第 \(i\) 天练习了什么技能,另外两个技能分别有多少天没有练习。一个非常直观的感觉,\(d_1,d_2\) 不会太大,是 \(O(\sqrt {A})\) 级别的,\(A\) 是值域。感性理解就是,超出这个级别会导致掉下来太多,而在这一段没有练习的时间的正中间强制练习这个技能,会取得 \(\ge A\) 的收益,复杂度 \(O(nA)\),注意一下要处理一些技能没开始练习的情况。

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 dp[2][205][205][3];
const int LIM=200;
int n,a[1005][3];
void trans(int &x,int y)
{
	if(y>x) x=y;
}
void solve()
{
	memset(dp,-0x3f,sizeof(dp));
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i][0]>>a[i][1]>>a[i][2];
	for(int i=0;i<3;i++) dp[0][0][0][i]=a[1][i];
	int nw=0;
	for(int i=2;i<=n;i++)
	{
		nw^=1;
		memset(dp[nw],-0x3f,sizeof(dp[nw]));
		for(int j=0;j<=LIM;j++) for(int k=0;k<=LIM;k++) for(int lst=0;lst<3;lst++) if(dp[nw^1][j][k][lst]>=0)
		{
			array<int,3> cost;
			if(lst==0) cost={1,j,k};
			if(lst==1) cost={j,1,k};
			if(lst==2) cost={j,k,1};
			trans(dp[nw][(cost[0]==0?0:cost[0]+1)][(cost[1]==0?0:cost[1]+1)][2],dp[nw^1][j][k][lst]+a[i][2]-cost[0]-cost[1]);
			trans(dp[nw][(cost[0]==0?0:cost[0]+1)][(cost[2]==0?0:cost[2]+1)][1],dp[nw^1][j][k][lst]+a[i][1]-cost[0]-cost[2]);
			trans(dp[nw][(cost[1]==0?0:cost[1]+1)][(cost[2]==0?0:cost[2]+1)][0],dp[nw^1][j][k][lst]+a[i][0]-cost[1]-cost[2]);
		}
		
	}
	int ans=0;
	for(int i=0;i<=LIM;i++) for(int j=0;j<=LIM;j++) for(int k=0;k<3;k++) ans=max(ans,dp[nw][i][j][k]);
	cout<<ans<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
	cin>>_;
	while(_--) solve();
	return 0;
}

【Ptz2021 Winter】Designing a PCB

考虑 2-sat,有 \(2n\)\(0/1\) 变量 \(x_0,x_1,\cdots,x_{2n-1}\),每个变量表示 \(x\) 轴上的一个点所在连线的方向,是向上还是向下的。想到这个,剩下的通过手玩都可以解决。

\(l_i,r_i\) 表示颜色 \(i\) 靠左,靠右的两个点的位置。

  • \(l_i \lt l_j \lt r_j \lt r_i\),即区间包含,可以得到 \(x_{l_j}=x_{r_j}\)
  • \(l_i \lt l_j \lt r_i \lt r_j\),即区间相交,可以得到 \(x_{l_j} \neq x_{r_i}\)

手玩 \(2^4\) 种情况会发现是必要且充分的,构造方案可以对每个 \(l_i\) 上的竖线钦定长度,\(l_i\) 越小越长。用 2-sat 或并查集求解,复杂度 \(O(n^2)\)。可以主席树优化建图,复杂度 \(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,L[1005],R[1005];
int U[2005],D[2005],uidx;
vector <int> g[4005];
int dfn[1000005],low[1000005],times,scccnt,bl[1000005],vis[1000005],stk[1000005],top;
void tarjan(int u)
{
	dfn[u]=low[u]=++times;
	vis[u]=1,stk[++top]=u;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		scccnt++;
		while(1)
		{
			int x=stk[top];
			top--;
			bl[x]=scccnt,vis[x]=0;
			if(x==u) break;
		}
	}
}
void solve()
{
	cin>>n;
	for(int i=1;i<=2*n;i++)
	{
		int x;
		cin>>x;
		if(!L[x]) L[x]=i;
		else R[x]=i;
	}
	for(int i=1;i<=2*n;i++) U[i]=++uidx,D[i]=++uidx;
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i!=j&&L[i]<L[j]&&L[j]<R[i]&&R[i]<R[j]) 
	{
		int x=L[j],y=R[i];
//		cout<<x<<" "<<y<<" "<<"different\n";
		g[U[x]].pb(D[y]),g[D[x]].pb(U[y]);
		g[U[y]].pb(D[x]),g[D[y]].pb(U[x]);
	}
	
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i!=j&&L[i]<L[j]&&L[j]<R[j]&&R[j]<R[i]) 
	{
		int x=L[j],y=R[j];
//		cout<<x<<" "<<y<<" "<<"same\n";
		g[U[x]].pb(U[y]),g[D[x]].pb(D[y]);
		g[U[y]].pb(U[x]),g[D[y]].pb(D[x]);
	}
	for(int i=1;i<=uidx;i++) if(!dfn[i]) tarjan(i);
	for(int i=1;i<=2*n;i++) if(bl[U[i]]==bl[D[i]])
	{
		cout<<"NO\n";
		return;
	}
	cout<<"YES\n";
	for(int i=1;i<=n;i++)
	{
		int len=2*n-L[i];
		char dl=(bl[U[L[i]]]<bl[D[L[i]]]?'U':'D');
		char dr=(bl[U[R[i]]]<bl[D[R[i]]]?'U':'D');
		if(dl==dr) cout<<3<<" "<<dl<<" "<<len<<" "<<'R'<<" "<<R[i]-L[i]<<" "<<(dr=='U'?'D':'U')<<" "<<len<<"\n";
		else if(dl=='U') cout<<5<<" "<<'U'<<" "<<len<<" "<<'R'<<" "<<4*n-L[i]-L[i]+1<<" "<<'D'<<" "<<2*len<<" "<<'L'<<" "<<4*n-L[i]-R[i]+1<<" "<<'U'<<" "<<len<<"\n";
		else cout<<5<<" "<<'D'<<" "<<len<<" "<<'R'<<" "<<4*n-L[i]-L[i]+1<<" "<<'U'<<" "<<2*len<<" "<<'L'<<" "<<4*n-L[i]-R[i]+1<<" "<<'D'<<" "<<len<<"\n";
	}
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UNR #4】序列妙妙值

做前缀和,代价可以写成 \(\sum_{i=1}^{n} s_{p_i-1} \space \text{xor} \space s_{p_i}\)

\(dp(k,i)\) 表示前缀 \([1,i]\)\(k\) 段的答案,转移考虑一种类似于 meet-in-middle 的思想,从小到大枚举 \(k\),在每一轮中,从小到大枚举 \(i\),固定 \(s_{i-1}\) 的前 \(8\) 位,枚举所有后 \(8\) 位的情况,开桶记录。然后固定 \(s_i\) 的后 \(8\) 位,枚举前 \(8\) 位的所有情况,到桶里查询并更新 dp 值,复杂度 \(O(kn\sqrt{A})\)\(A\) 是值域,

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,k;
int a[60005];
int dp[9][60005];
int g[1005][1005];
void solve()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i],a[i]^=a[i-1];
	memset(dp,0x3f,sizeof(dp)),memset(g,0x3f,sizeof(g));
	dp[0][0]=0;
	for(int i=1;i<=k;i++)
	{
		memset(g,0x3f,sizeof(g));
		for(int j=0;j<=n;j++)
		{
			for(int l=0;l<(1<<8);l++) dp[i][j]=min(dp[i][j],g[(a[j]>>8)^l][(a[j]&255)]+(l<<8));
			for(int l=0;l<(1<<8);l++) g[a[j]>>8][(a[j]&255)^l]=min(g[a[j]>>8][(a[j]&255)^l],dp[i-1][j]+l);
		}
	}
	for(int i=k;i<=n;i++) cout<<dp[k][i]<<" ";
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UNR #4】网络恢复

将所有边随机分成大小相等的 \(50\) 份,对每一份进行一次询问,\(a_i\) 随机生成,得到的 \(b_i\) 可以看做是对 \(i\) 的邻居集合进行 XOR-Hashing 的结果。

如果是一棵树的话,可以逐步删叶子。这个想法可以拓展到图上,先逐步删度数为 \(1\) 的点,如果没有了,就随便取一个点,有很大概率其度数为 \(2\),枚举一个点,用哈希表等数据结构查询另一个点,然后继续删度数为 \(1\) 的点。

复杂度玄学,正确性玄学,反正过了。

Code
#include "explore.h"
#include<bits/stdc++.h>
#define ull unsigned long long
#define pb push_back
using namespace std;
mt19937_64 rnd(114514);
int n,m;
unordered_map <ull,int> ma;
unordered_map <int,bool> g[50005];
void report(int u,int v)
{
	if(g[u].find(v)!=g[u].end()||g[v].find(u)!=g[v].end()) return;
	Report(u,v);
	g[u][v]=g[v][u]=1;
}
int get(vector <ull> &A,vector <ull> &B,vector <int> vis)
{
	for(int i=1;i<=n;i++) if(!vis[i-1]&&B[i-1])
	{
		for(int j=1;j<=n;j++) if(!vis[j-1]&&B[j-1]&&i!=j&&ma.find(B[i-1]^A[j-1])!=ma.end()) 
		{
//			cout<<"find: "<<i<<" "<<j<<"\n";
			report(i,j);
			B[i-1]^=A[j-1],B[j-1]^=A[i-1];
			return i;
		}
	}
	return -1;
}
void work(vector <int> S)
{
	if(!S.size()) return;
	vector <ull > A;
	vector <int> vis;
	for(int i=0;i<n;i++) vis.pb(0);
	ma.clear();
	for(int i=0;i<n;i++) A.pb(rnd()),ma[A[i]]=i+1;
	vector <ull> B=Query(A,S);
	while(1)
	{
		queue <int> q;
		for(int i=1;i<=n;i++) if(ma.find(B[i-1])!=ma.end()) q.push(i);
		while(q.size())
		{
			int u=q.front();
			q.pop();
			vis[u-1]=1;
//			cout<<u<<" visited\n"; 
			int v=ma[B[u-1]];
			if(!v) continue;
			report(u,v);
			B[v-1]^=A[u-1],B[u-1]^=A[v-1];
			if(ma.find(B[v-1])!=ma.end()) q.push(v);
		}
		int tmp=get(A,B,vis);
		if(tmp==-1) return;
		q.push(tmp);
	}
}
void Solve(int _n,int _m)
{
	n=_n,m=_m;
	vector <int> es;
	for(int i=1;i<=m;i++) es.pb(i);
	for(int i=m-1;i>=0;i--) swap(es[i],es[rnd()%(i+1)]);
	vector <int> que[51];
	for(int i=0;i<m;i++) que[i%50].pb(es[i]);
	for(int i=0;i<50;i++) work(que[i]);
}

【UNR #4】己酸集合

\((x_i,y_i)\)\((0,z_j)\) 的距离是 \(\sqrt{x_i^2+y_i^2+z_j^2+2y_iz_j}\),对于一个询问,求上式 \(\le R\)\(i\) 就行,条件还可以写成:\(2y_iz_j+x_i^2+y_i^2 \le R^2-z_j^2\),将左边看成一条斜率为 \(2y_i\),截距为 \(x_i^2+y_i^2\) 的直线,查询就是求 \(x=z_j\)\(y \le R^2-z_j^2\) 的直线个数。

将询问离线,从小到大枚举 \(x\),维护直线按 \(y\) 排序的结果,由于两条直线只有一个交点,故只会发生 \(O(n^2)\) 次相邻交换操作,维护复杂度 \(O(n^2 \log)\),查询复杂度 \(O(Q \log)\)

考虑分块,将 \(B\) 个直线一起处理,做 \(n/B\) 次,复杂度 \(O(nB^2\log/B+nQ\log/B)\),令 \(B=\sqrt{Q}\),复杂度 \(O(n \sqrt Q \log)\)

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 ll INF=2e18;
const int B=1000;
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 ans[1000005];
int X[1005],Y[1005],n;
ll k[1005],b[1005];
void add(int x,int y)
{
	n++;
	X[n]=x,Y[n]=y,k[n]=-2LL*y,b[n]=1LL*x*x+1LL*y*y;
}
ll getX(int i,int j)
{
	if(k[i]==k[j]) return INF;
	ll p=b[j]-b[i],q=k[i]-k[j];
	if(abs(p)%abs(q)==0) return p/q+1;
	if(p<0) p*=-1,q*=-1;
	if(p>0&&q>0) return (p+q-1)/q;
	return p/q; 
}
ll pos[1000005];
int U[1000005],V[1000005];
int a[1005],P[1005];
bool cmp(int x,int y)
{
	return pos[x]<pos[y];
}
ll calc(int i,int x)
{
	return 1LL*k[i]*x+b[i];
}
int nowx;
bool cmp2(int x,int y)
{
	return calc(x,nowx)<calc(y,nowx);
}
vector <int> vec;
void init()
{
	int idx=0;
	for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) idx++,vec.pb(idx),pos[idx]=getX(i,j),U[idx]=i,V[idx]=j;
	sort(vec.begin(),vec.end(),cmp);
//	for(int j=0;j<vec.size();j++) cout<<pos[vec[j]]<<" "<<U[vec[j]]<<" "<<V[vec[j]]<<"\n";
}
void query(vector <array<ll,3> > que) // x,y,id
{
	nowx=-1000000000;
	for(int i=1;i<=n;i++) a[i]=i;
	sort(a+1,a+1+n,cmp2);
//	for(int l=1;l<=n;l++) cout<<a[l]<<" "<<k[a[l]]<<" "<<b[a[l]]<<"\n"; 
//		system("pause");
	for(int i=1;i<=n;i++) P[a[i]]=i;
	int j=0;
	while(j<vec.size()&&pos[vec[j]]<nowx) j++;
	for(int i=0;i<que.size();i++)
	{
		ll px=que[i][0],py=que[i][1];
		nowx=px;
		vector <int> re;
		re.clear();
		while(j<vec.size()&&pos[vec[j]]<=nowx) re.pb(U[vec[j]]),re.pb(V[vec[j]]),j++;
		sort(re.begin(),re.end()),re.resize(unique(re.begin(),re.end())-re.begin());
		sort(re.begin(),re.end(),cmp2);
		vector <int> po;
		po.clear();
		for(int l=0;l<re.size();l++) po.pb(P[re[l]]);
		sort(po.begin(),po.end());
//		for(int l=0;l<re.size();l++) cout<<re[l]<<" ";
//		cout<<"\n";
//		for(int l=0;l<po.size();l++) cout<<po[l]<<" ";
//		cout<<"\n";
		for(int l=0;l<re.size();l++) a[po[l]]=re[l],P[re[l]]=po[l];
//		cout<<que[i][2]<<" "<<que[i][0]<<" "<<que[i][1]<<" ...:\n";
//		for(int l=1;l<=n;l++) cout<<a[l]<<" "<<k[a[l]]<<" "<<b[a[l]]<<"\n"; 
//		system("pause");
		int L=1,R=n,res=0;
		while(L<=R)
		{
			int mid=(L+R)>>1;
			if(calc(a[mid],px)<=py) res=mid,L=mid+1;
			else R=mid-1;
		}
		ans[que[i][2]]+=res;
	}
}
int XX[12005],YY[12005];
void solve()
{
	int NN=read(),QQ=read();
	for(int i=1;i<=NN;i++) XX[i]=read(),YY[i]=read();
	vector <array<ll,3> > que;
	que.clear();
	for(int i=1;i<=QQ;i++)
	{
		int z=read(),R=read();
		que.pb({z,1LL*R*R-1LL*z*z,i});
	}
	sort(que.begin(),que.end());
	for(int i=1;i<=NN;i++)
	{
		add(XX[i],YY[i]);
		if(n==B||i==NN)
		{
			init(),query(que);
			memset(X,0,sizeof(X));
			memset(Y,0,sizeof(Y));
			memset(k,0,sizeof(k));
			memset(b,0,sizeof(b));
			memset(pos,0,sizeof(pos));
			memset(U,0,sizeof(U));
			memset(V,0,sizeof(V));
			memset(a,0,sizeof(a));
			memset(P,0,sizeof(P));
			vec.clear();
			n=0;
		}
	}
	for(int i=1;i<=QQ;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;
}

【UNR #5】提问系统

可以把操作序列写成一棵树,限制可以看成对于每个点 \(u\),根到 \(u\) 的路径上 R 的个数在区间 \([L_u,R_u]\) 中。

先拆一下组合意义,\(p_r,p_b^2\) 可以看做,选择一个 R 和两个 B 构成有序三元组 \((r,b_1,b_2)\) 的方案数。

\(dp(u,L,R,0/1,0/1,0/1)\) 表示,考虑填 \(u\) 的子树,未来 \(1\)\(u\) 的路径上可以填 \([L,R]\)R,三元组的三个位置是否都被填的方案数。而我们可以将这个状态加到区间 \([L,R]\) 上,故我们可以更改状态,\(dp(u,cnt,0/1,0/1,0/1)\) 表示,\(1\)\(u\) 的路径上恰好填 \(cnt\)R,复杂度 \(O(n^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 par[2505],dep[2505];
vector <int> g[2505];
int dp[2505][2505][8],f[2][8],tmp[2505][8];
int limR,limB,n;
void dfs(int u)
{
	if(!g[u].size())
	{
		for(int i=max(0LL,dep[u]-limB);i<=limR;i++) 
		{
			if(i) dp[u][i-1][0]++,dp[u][i-1][1]++;
			dp[u][i][0]++,dp[u][i][2]++,dp[u][i][4]++,dp[u][i][6]++;
		}
		return;
	}
	for(int i=0;i<g[u].size();i++) dfs(g[u][i]);
	int L=max(0LL,dep[u]-limB),R=limR;
	for(int cr=L;cr<=R;cr++)
	{
		memset(f,0,sizeof(f));
		int nw=0;
		for(int i=0;i<8;i++) f[nw][i]=dp[g[u][0]][cr][i];
		for(int i=1;i<g[u].size();i++) 
		{
			nw^=1;
			memset(f[nw],0,sizeof(f[nw]));
			for(int j=0;j<8;j++) for(int l=7-j;;l=(l-1)&(7-j))
			{
				f[nw][j|l]=(f[nw][j|l]+f[nw^1][j]*dp[g[u][i]][cr][l])%mod;
				if(!l) break;
			}
		}
		for(int i=0;i<8;i++) dp[u][cr][i]=f[nw][i];
	}
	if(!u) return;
	memset(tmp,0,sizeof(tmp));
	for(int cr=L;cr<=R;cr++) for(int i=0;i<8;i++)
	{
		if(cr) 
		{
			if(i%2==0) tmp[cr-1][i+1]=(tmp[cr-1][i+1]+dp[u][cr][i])%mod;
			tmp[cr-1][i]=(tmp[cr-1][i]+dp[u][cr][i])%mod;
		}
		for(int l=0;l<8;l+=2) if((l&i)==0) 
			tmp[cr][i+l]=(tmp[cr][i+l]+dp[u][cr][i])%mod;
	}
	memset(dp[u],0,sizeof(dp[u]));
	for(int cr=L-1;cr<=R;cr++) for(int i=0;i<8;i++) 
		dp[u][cr][i]=tmp[cr][i];
}
void solve()
{
	cin>>n>>limR>>limB;
	int u=0;
	for(int i=1,j=0;i<=2*n;i++)
	{
		string s;
		cin>>s;
		if(s=="push") j++,par[j]=u,dep[j]=dep[u]+1,g[u].pb(j),u=j;
		else u=par[u];
	}
	dfs(0);
	cout<<dp[0][0][7];
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UNR #6】神隐

考虑二进制分组,对于每一位,把所有这一位为 \(0\) 和为 \(1\) 的编号拿出来做一次询问,总共问 \(2\lceil \log n \rceil\) 次,一条边 \((u,v)\) 存在当且仅当恰好在一半的询问中 \(u,v\) 连通。证明:

  • \((u,v)\) 有边,正确性显然。
  • \((u,v)\) 没有边,则 \((u,v)\) 路径上有 \(\ge 2\) 条边,任意取两条边 \(x,y\),由于所有边编号不同,故一定存在一次包含 \(x\) 的询问不包含 \(y\),即同时询问 \(x,y\) 的次数严格小于一半,即 \(u,v\) 连通的询问次数严格小于一半。

再考虑进行一个映射:将所有边重标号为两两不同的,\(0,1\) 出现次数相等的二进制数,故只需要对所有二进制位,把这一位为 \(1\) 的所有边拿出来询问。

寻找答案可以不断删叶子,叶子的判定条件是在恰好一半的询问中是孤点,父亲就是另一半询问中这个叶子所在连通块的交,这个交只有一个点,因为在这些询问中,连向父亲的其它边都至少被断了一次。

这个还是不好做,我们额外维护删去所有叶子的连通情况,一定可以找到一个叶子,比如最深的叶子,其对应连通块只有一个点,就是父亲。

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

Code
#include "tree.h"
#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> S1[21][140005],S2[21][140005];
int sz1[21][140005],sz2[21][140005];
bool del1[140005],del2[140005];
int lg;
vector <pii > bl[140005];
int chk(int x)
{
	int cnt=0;
	for(int i=0;i<bl[x].size();i++) if(sz1[bl[x][i].fi][bl[x][i].se]==1) cnt++;
	if(cnt!=lg/2) return 0;
	return 1;
}
int get_fa(int x)
{
	for(int i=0;i<bl[x].size();i++) if(sz2[bl[x][i].fi][bl[x][i].se]==1)
	{
		int u=bl[x][i].fi,v=bl[x][i].se;
		while(del2[S2[u][v].back()]) S2[u][v].pop_back();
		return S2[u][v].back();
	}
	return -1;
}
vector <pii > solve(int n)
{
	lg=(n<=2000?14:20);
	vector <int> ma;
	for(int i=0;i<(1<<lg);i++) if(__builtin_popcount(i)==lg/2&&ma.size()<n-1) ma.pb(i);
	for(int i=0;i<lg;i++)
	{
		vector <int> vec;
		vec.clear();
		for(int j=0;j<n-1;j++) 
		{
			if(ma[j]&(1<<i)) vec.pb(1);
			else vec.pb(0);
		}
		vector <vector<int> > tmp=query(vec);
		for(int j=0;j<tmp.size();j++) for(int l=0;l<tmp[j].size();l++)
		{
			S1[i+1][j+1].pb(tmp[j][l]+1),S2[i+1][j+1].pb(tmp[j][l]+1);
			sz1[i+1][j+1]++,sz2[i+1][j+1]++;
			bl[tmp[j][l]+1].pb(mp(i+1,j+1));
		}
	}
	vector <pii > ans;
	if(n==1) return ans;
	for(int i=1;i<=n;i++) if(chk(i))
	{
		del2[i]=1;
		for(int j=0;j<bl[i].size();j++) sz2[bl[i][j].fi][bl[i][j].se]--;
	}
	
	queue <pii > q;
	for(int i=1;i<=n;i++) if(chk(i)) 
	{
		int fa=get_fa(i);
		if(fa!=-1) q.push(mp(i,fa)); 
	}
	while(ans.size()<n-2)
	{
//		cerr<<ans.size()<<" "<<q.size()<<"\n";
		int u=q.front().fi,fa=q.front().se;
		q.pop();
		if(del1[u]) continue;
		ans.pb(mp(u,fa));
		del1[u]=1;
		for(int i=0;i<bl[u].size();i++) sz1[bl[u][i].fi][bl[u][i].se]--;
		if(chk(fa))
		{
			u=fa,del2[u]=1;
			for(int i=0;i<bl[u].size();i++) 
			{
				int x=bl[u][i].fi,y=bl[u][i].se;
				sz2[x][y]--;
				if(sz2[x][y]==1)
				{
					while(del2[S2[x][y].back()]) S2[x][y].pop_back();
					for(int j=0;j<S1[x][y].size();j++)
					{
						int v=S1[x][y][j];
						if(!del1[v]&&v!=S2[x][y].back()) q.push(mp(v,S2[x][y].back()));
					}
				}
			}
		}
	}
	pii t=mp(-1,-1);
	for(int i=1;i<=n;i++) if(!del1[i])
	{
		if(t.fi==-1) t.fi=i;
		else t.se=i;
	}
	ans.pb(t);
	for(int i=0;i<ans.size();i++) ans[i].fi--,ans[i].se--;
	return ans;
}

【UNR #3】To Do Tree

神秘贪心,一个最直接想法是按儿子个数贪心,这个显然不对,比如一个菊花,上面再加一个节点,就可以欺骗这个算法不去解锁这个菊花。

但是按子树大小贪心就是对的!也不知道为什么,用 pq 维护,复杂度 \(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;
vector <int> g[100005],ansv[100005];
int n,m,ans,sz[100005],fa[100005];
priority_queue <pii > pq;
void solve()
{
	cin>>n>>m;
	for(int i=2;i<=n;i++) cin>>fa[i],g[fa[i]].pb(i);
	for(int i=n;i>=1;i--) sz[i]++,sz[fa[i]]+=sz[i];
	pq.push(mp(sz[1],1));
	while(pq.size())
	{
		ans++;
		vector <int> vec;
		for(int i=1;i<=m&&pq.size();i++) vec.pb(pq.top().se),pq.pop();
		ansv[ans]=vec;
		for(int i=0;i<vec.size();i++) 
		{
			int u=vec[i];;
			for(int j=0;j<g[u].size();j++) pq.push(mp(sz[g[u][j]],g[u][j]));
		}
	}
	cout<<ans<<"\n";
	for(int i=1;i<=ans;i++)
	{
		cout<<ansv[i].size()<<" ";
		for(int j=0;j<ansv[i].size();j++) cout<<ansv[i][j]<<" ";
		cout<<"\n";
	}
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【Ptz2021 Summer】Minimal Cyclic Shift

先考虑循环节,等概率选取一个长度为 \(n\) 循环节为 \(d\) 的字符串,考察最小表示在 \(f\) 处的概率,若 \(f \le d\),则概率为 \(d^{-1}\),否则为 \(0\)

先计算循环节为 \(d\) 的字符串个数 \(cnt(d)\),这个可以用莫反求,\(cnt(d)=\sum_{c|d} \mu(c)26^{d/c}\)

然后问题就变成了,每次询问两个数 \(m,n\),求等概率选择一个长度为 \(n\) 的字符串和一个长度为 \(m\) 的字符串,最小表示位置相同的概率。枚举位置 \(f\),概率是 \((\sum_{d|n, f \le d}cnt(d)d^{-1}26^{-n}) \cdot (\sum_{d|m, f \le d}cnt(d)d^{-1}26^{-m})\)

观察到两部分都可以被分为至多 \(\sqrt{A}\) 段相等的连续段,\(A\) 是值域,复杂度 \(O(n \sqrt{A})\)

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;
}
const int N=100000;
int n,a[100005],pw[100005],inv[100005],invn[100005];
int miu[100005],g[100005],flg[100005],p[100005],tot;
void sieve()
{
	miu[1]=1;
	for(int i=2;i<=N;i++)
	{
		if(!flg[i]) p[++tot]=i,miu[i]=-1;
		for(int j=1;j<=tot&&i*p[j]<=N;j++) 
		{
			flg[i*p[j]]=1;
			if(i%p[j]==0)
			{
				miu[i*p[j]]=0;
				break;
			}
			miu[i*p[j]]=-miu[i];
		}
	}
} 
int calc(int l1,int l2)
{
	vector <pii > d1,d2;
	vector <int> rg;
	rg.pb(min(l1,l2));
	for(int i=1;i*i<=l1;i++) if(l1%i==0)
	{
		d1.pb(mp(i,invn[i]*g[i]%mod*inv[l1]%mod));
		if(l1/i!=i) d1.pb(mp(l1/i,invn[l1/i]*g[l1/i]%mod*inv[l1]%mod));
		rg.pb(i),rg.pb(l1/i);
	}
	for(int i=1;i*i<=l2;i++) if(l2%i==0)
	{
		d2.pb(mp(i,invn[i]*g[i]%mod*inv[l2]%mod));
		if(l2/i!=i) d2.pb(mp(l2/i,invn[l2/i]*g[l2/i]%mod*inv[l2]%mod));
		rg.pb(i),rg.pb(l2/i);
	}
	rg.pb(0);
	sort(d1.begin(),d1.end()),sort(d2.begin(),d2.end());
	sort(rg.begin(),rg.end()),rg.resize(unique(rg.begin(),rg.end())-rg.begin());
	int res=0;
	int sum1=0,sum2=0;
	for(int i=rg.size()-1,x1=d1.size()-1,x2=d2.size()-1,s1=0,s2=0;i>=1;i--)
	{
		int L=rg[i-1]+1,R=rg[i];
//		cout<<L<<" "<<R<<"\n";
		while(x1>=0&&d1[x1].fi>=L) s1=(s1+d1[x1].se)%mod,x1--;
		while(x2>=0&&d2[x2].fi>=L) s2=(s2+d2[x2].se)%mod,x2--;
		res=(res+(R-L+1)*s1%mod*s2%mod)%mod;
		sum1=(sum1+(R-L+1)*s1)%mod;
		sum2=(sum2+(R-L+1)*s2)%mod;
	}
//	cout<<res<<" "<<sum1<<" "<<sum2<<"\n";
	return res;
}
void solve()
{
//	cout<<351LL*fpow(676,mod-2)%mod<<"\n";
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	pw[0]=1;
	for(int i=1;i<=N;i++) pw[i]=26LL*pw[i-1]%mod;
	for(int i=0;i<=N;i++) inv[i]=fpow(pw[i],mod-2),invn[i]=fpow(i,mod-2);
	sieve();
	for(int i=1;i<=N;i++) for(int d=1;d*d<=i;d++) if(i%d==0)
	{
		g[i]=(g[i]+miu[d]*pw[i/d]%mod+mod)%mod;
		if(i/d!=d) g[i]=(g[i]+miu[i/d]*pw[d]%mod+mod)%mod;
	}
//	for(int i=1;i<=10;i++) cout<<miu[i]<<" "<<g[i]<<"\n";
	int ans=0;
	if(n==1)
	{
		cout<<1<<"\n";
		return;
	}
	for(int i=1;i<=n;i++) ans=(ans+calc(a[i],a[(i==1?n:i-1)]));
	cout<<ans%mod<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UNR #7】那些你不要的

一次操作会删去原序列中一个奇数位置的数和偶数位置的数,原序列偶数位置的数不可能留下来,所以最优策略是选择奇数位置最大/最小的数删去,答案就是奇数位置的中位数。

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,a[1000005],b[1000005];
void solve()
{
	cin>>n;
	int m=0;
	for(int i=1;i<=n;i++) 
	{
		cin>>a[i];
		if(i%2==1) b[++m]=a[i];
	}
	sort(b+1,b+1+m);
	if(m%2==1) cout<<b[(m+1)/2]<<"\n";
	else cout<<b[m/2+1]<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
	cin>>_;
	while(_--) solve();
	return 0;
}

【UNR #7】比特迷宫

一次操作是,选择一个区间,将其异或上模 \(2\) 意义下杨辉三角的第区间长度行。

赛时的想法是,考虑一个阈值 \(B\),对于所有长度 \(\le B\) 的二进制串暴力求得最优解,然后将给出的序列划分成若干长度 \(\le B\) 的段,每一段用最优的方式操作,令 \(B=20\) 可以在大约 \(168000\) 次通过前三个子任务,稍微把 \(B\) 调大,操作次数大概在 \(164000\) 左右,特别难受。

还有一个想法是,同样预处理 \(\le B\) 的所有情况,然后 \(dp(i,mask)\) 表示考虑给定序列前 \(i\) 位,最后几位是 \(mask\) 的最优步数,没写,不知道表现如何。

赛后写的做法是,从左往右扫,碰到一个 \(1\) 的时候,考虑以它为区间左端点进行一次操作,枚举所有长度 \(\le B\) 的情况,取剩余 \(1\) 最少的操作,令 \(B=550\),随机数据下表现很优秀,大约 \(157000\) 次。

如果数据不随机的话,可以在操作开始前随机进行若干次操作把序列打乱,实测操作 \(1000\) 次会变得比较随机。

Code
#pragma GCC optimize("Ofast")
#pragma GCC target("avx")
#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;
const int LEN=550;
bitset <LEN+5> C[LEN+5]; 
vector <pii > ans;
int n,K,T,a[1100005],tmpa[1100005];
mt19937 rnd(time(0));
void solve()
{
	cin>>n>>K>>T;
	for(int i=0;i<n;i++) cin>>a[i],tmpa[i]=a[i];
	while(1)
	{
		ans.clear();
		for(int i=0;i<n;i++) a[i]=tmpa[i];
		for(int _=0;_<1000;_++)
		{
			int i=rnd()%n,j=rnd()%(n-i);
			for(int l=j;;l=(l-1)&j)
			{
				a[i+l]^=1;
				if(!l) break;
			}
			ans.pb(mp(i,j));
		}
//		cerr<<"...\n"; 
		for(int i=0;i<n;i++) if(a[i])
		{
			bitset <LEN+5> nw;
			nw.reset();
			for(int j=i;j<min(n,i+LEN);j++) if(a[j]) nw[j-i]=1;
			int minn=inf,b=-1;
			for(int j=0;j<min(LEN,n-i);j++)
			{
				int cst=(nw^C[j]).count();
				if(cst<minn) minn=cst,b=j;
			}
			ans.pb(mp(i,b));
			for(int j=0;j<LEN;j++) if(C[b][j]) a[i+j]^=1;
		}
		cerr<<ans.size()<<"\n";
		if(ans.size()>T) continue;
		cout<<ans.size()<<"\n";
		for(int i=0;i<ans.size();i++) cout<<ans[i].fi<<" "<<ans[i].se<<"\n"; 
		return;
	}
	
}
signed main()
{
	C[0][0]=1;
	for(int i=1;i<LEN;i++)
	{
		C[i][0]=C[i][i]=1;
		for(int j=1;j<i;j++) C[i][j]=C[i-1][j-1]^C[i-1][j];
	}
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UNR #7】火星式选拔

先考虑 \(k=1\),目前的数 \((a_i,b_i)\) 会在之后第一个满足 \(a_j \ge b_i\) 时候被淘汰,每次询问可以倍增求解。

对于一般情况,观察到区间内的前 \(k-1\) 大的 \(b_i\) 一定会留下,找出区间内前 \(k\) 大中下标的最大值 \(R\),从 \(R+1\) 开始,第 \(k\) 大的 \(b_i\) 可能会被淘汰,按照 \(k=1\) 的方法倍增找就行,复杂度 \(O(n \log n)\)

Code
#pragma GCC optimize("Ofast")
#pragma GCC target("avx")
#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 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 Rt2[500005];
struct Segtree2
{
	int uidx;
	int ls[11000005],rs[11000005],maxx[11000005];
	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);
		return u;
	}
	int update(int id,int l,int r,int x)
	{
		int u=++uidx;
		ls[u]=ls[id],rs[u]=rs[id],maxx[u]=max(maxx[id],x);
		if(l==r) return u;
		int mid=(l+r)>>1;
		if(x<=mid) ls[u]=update(ls[id],l,mid,x);
		else rs[u]=update(rs[id],mid+1,r,x);
		return u;
	}
	int querymax(int id,int l,int r,int x,int y)
	{
		if(x<=l&&r<=y) return maxx[id];
		int mid=(l+r)>>1;
		int res=0;
		if(x<=mid) res=max(res,querymax(ls[id],l,mid,x,y));
		if(y>mid) res=max(res,querymax(rs[id],mid+1,r,x,y));
		return res;
	}
}st2;
struct Segtree
{
	int uidx;
	int ls[11000005],rs[11000005],cnt[11000005];
	ll val[11000005];
	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);
		return u;
	}
	int update(int id,int l,int r,int x,int d)
	{
		int u=++uidx;
		ls[u]=ls[id],rs[u]=rs[id],cnt[u]=cnt[id]+1,val[u]=val[id]+1LL*d;
		if(l==r) 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);
		return u;
	}
	ll queryval(int id,int l,int r,int x,int y)
	{
		if(x<=l&&r<=y) return val[id];
		int mid=(l+r)>>1;
		ll res=0;
		if(x<=mid) res+=queryval(ls[id],l,mid,x,y);
		if(y>mid) res+=queryval(rs[id],mid+1,r,x,y);
		return res;
	}
	int querykth(int p,int q,int l,int r,int k)
	{
//		cout<<p<<" "<<q<<" "<<cnt[p]<<" "<<cnt[q]<<" "<<" "<<l<<" "<<r<<" "<<k<<"\n";
		if(l==r) return l;
		int mid=(l+r)>>1;
		if(cnt[rs[p]]-cnt[rs[q]]>=k) return querykth(rs[p],rs[q],mid+1,r,k);
		return querykth(ls[p],ls[q],l,mid,k-cnt[rs[p]]+cnt[rs[q]]);
	}
}st;
int n,Q;
int a[500005],b[500005],c[500005],Rt[500005],posb[500005];
int fa[20][500005];
struct ST_table
{
	int st[20][500005],Lg[500005];
	void init()
	{
		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<20;k++) for(int i=1;i+(1<<k)-1<=n;i++) st[k][i]=max(st[k-1][i],st[k-1][i+(1<<(k-1))]);
	}
	int query(int l,int r)
	{
		int s=Lg[r-l+1];
		return max(st[s][l],st[s][r-(1<<s)+1]);
	}
	int get_next(int x,int lim)
	{
		int res=-1,L=x+1,R=n;
		while(L<=R)
		{
			int mid=(L+R)>>1;
			if(query(x+1,mid)>=lim) res=mid,R=mid-1;
			else L=mid+1;
		}
		return res;
	}
}tb;
void solve()
{
	n=read(),Q=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++) b[i]=read(),posb[b[i]]=i;
	for(int i=1;i<=n;i++) c[i]=read();
//	Rt[n+1]=st.build(1,n);
//	for(int i=n;i>=1;i--) Rt[i]=st.update(Rt[i+1],1,n,posb[i],c[posb[i]]);
	Rt[0]=st.build(1,n);
	for(int i=1;i<=n;i++) Rt[i]=st.update(Rt[i-1],1,n,b[i],c[i]);
	Rt2[n+1]=st2.build(1,n);
	for(int i=n;i>=1;i--) Rt2[i]=st2.update(Rt2[i+1],1,n,posb[i]);
	memset(fa,-1,sizeof(fa));
	tb.init();
	for(int i=1;i<=n;i++) fa[0][i]=tb.get_next(i,b[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]];
	while(Q--)
	{
		int l=read(),r=read(),k=read();
		ll ans=0;
		int kth=st.querykth(Rt[r],Rt[l-1],1,n,k);
//		cout<<kth<<"\n";
		if(kth+1<=n) ans+=st.queryval(Rt[r],1,n,kth+1,n)-st.queryval(Rt[l-1],1,n,kth+1,n);
//		cout<<ans<<"\n";
		int mx=st2.querymax(Rt2[kth],1,n,l,r);
//		cout<<b[mx]<<" "<<mx<<"\n";
		kth=posb[kth];
		int u=tb.get_next(mx,b[kth]);
//		cout<<kth<<" "<<mx<<" "<<" "<<u<<"\n";
		if(u==-1||u>r)
		{
			ans+=1LL*c[kth];
			print(ans),*O++=10;
			continue;
		}
		for(int i=19;i>=0;i--) if(fa[i][u]!=-1&&fa[i][u]<=r) u=fa[i][u];
		ans+=1LL*c[u];
		print(ans),*O++=10;
	}
	fwrite(obuf,O-obuf,1,stdout);
}
signed main()
{
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
//	ios::sync_with_stdio(0);
//	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UER #11】科考工作

奶一口一定有解,而且解的密度很大。考虑一个随机化做法,每一轮将 \(A_i\) 随机打乱,检查是否有一个区间满足条件,这个是可以 \(O(n)\) 判断一次的。

观察到 \(0\) 比较特殊,将所有 \(0\) 拿出来,令 \(0\) 的个数为 \(cnt\),则只需要找到一个长度 \(\in [N-cnt,N]\) 且和是 \(N\) 的倍数的区间,就能构造出答案。

显然,我们需要让 \(0\) 尽可能多,先找出众数,把每个数减去众数,就可以得到最多的 \(0\)。正确性玄学,感性理解就是,如果只有很少的数出现,则 \(0\) 会很多,如果有很多的数出现,则大致可以看做序列随机,而序列随机只需要尝试 \(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;
pii a[600005];
vector <pii > v1,v2;

int cnt[600005];
vector <int> sum[600005];
mt19937 rnd(114514);
void solve()
{
	cin>>n;
	for(int i=1;i<=2*n-1;i++) cin>>a[i].fi,a[i].se=i,cnt[a[i].fi]++;
	int maxx=0;
	for(int i=1;i<n;i++) if(cnt[i]>cnt[maxx]) maxx=i;
	for(int i=1;i<=2*n-1;i++) 
	{
		a[i].fi=(a[i].fi-maxx+n)%n;
		if(a[i].fi) v1.pb(a[i]);
		else v2.pb(a[i]);
	}
	if(v2.size()>=n)
	{
		for(int i=0;i<n;i++) cout<<v2[i].se<<" ";
		return;
	}
	while(1)
	{
		for(int i=0;i<n;i++) sum[i].clear();
		for(int i=v1.size()-1;i>=0;i--) swap(v1[i],v1[rnd()%(i+1)]);
		sum[0].pb(-1);
		for(int i=0,s=0;i<v1.size();i++)
		{
			s+=v1[i].fi,s%=n;
			int pos=lower_bound(sum[s].begin(),sum[s].end(),i-n)-sum[s].begin();
			if(pos<sum[s].size())
			{
				pos=sum[s][pos];
				if(n-i+pos<=v2.size())
				{
					for(int j=pos+1;j<=i;j++) cout<<v1[j].se<<" ";
					for(int j=0;j<n-i+pos;j++) cout<<v2[j].se<<" ";
					return;
				}
			}
			sum[s].pb(i);
			
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UER #11】企鹅游戏

\(s_i\) 的长度只有 \(O(\sqrt L)\) 种取值,而由于 \(s_i\) 互不相同,每一个 \(t\) 的前缀都只会带来 \(O(\sqrt L)\) 个合法的匹配,建出 \(s_i\) 的 AC 自动机,对于每个状态 \(u\) 求出最近的 fail 树上的祖先 \(nxt_u\),满足这个状态是一个给定的 \(s_i\),将 \(t\) 在 AC 自动机上跑一遍,从每个经过的节点向上跳 \(nxt\),使用光速幂计算答案,复杂度 \(O(L \sqrt L)\)

一个特别直观的感受是,在所有询问中,出现次数 \(\ge 1\) 次的 \(s_i\) 的个数之和是严重不足 \(O(L\sqrt L)\) 的。所以上述做法有一个很大的优化空间,如果避免重复经过同一个 AC 自动机上的节点,效率会快很多。大致过程是把这些点的虚树拿出来(也不是要真正的建虚树,每个点往上跳,直到跳到根或者跳到一个之前跳到过的点,把所有经过的点拿出来,就是虚树),在虚树上 dfs 一下。但是 uoj 的递归速度很慢,要改成拓扑排序才可以过。

复杂度的话,对于所有长度 \(\le B\) 的串,最多出现 \(L \cdot B\) 次,对于一个长度 \(\gt B\) 的串,总共只有 \(\sqrt{L}-B\) 个,想让其产生复杂度的贡献至少需要长度 \(\gt B\) 的询问串,故最多出现 \(L(\sqrt{L}-B)/B\),令 \(B=L^{1/4}\),复杂度 \(O(L^{5/4})\)

Code
#pragma GCC optimize("Ofast")
#pragma GCC target("avx")
#include<bits/stdc++.h>
using namespace std;
#define uint unsigned int
#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 ACAM
{
	int ch[2000005][26],id[2000005],nxt[2000005],idx;
	int fail[2000005];
	int add(string s,int x)
	{
		int nw=0;
		for(int i=0;i<s.size();i++)
		{
			int u=s[i]-'a';
			if(!ch[nw][u]) ch[nw][u]=++idx;
			nw=ch[nw][u];
		}
		id[nw]=x;
		return nw;
	}
	queue <int> q;
	void build()
	{
		for(int i=0;i<26;i++) if(ch[0][i]) q.push(ch[0][i]);
		while(q.size())
		{
			int u=q.front();
			q.pop();
			if(id[u]) nxt[u]=u;
			else nxt[u]=nxt[fail[u]];
			for(int i=0;i<26;i++)
			{
				if(ch[u][i]) fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
				else ch[u][i]=ch[fail[u]][i];
			}
		}
//		for(int i=0;i<=idx;i++) 
//		{
//			cout<<i<<":\n";
//			for(int j=0;j<26;j++) if(ch[i][j]) cout<<j<<" "<<ch[i][j]<<"\n";
//			cout<<id[i]<<"\n";
//			cout<<nxt[i]<<"\n";
//		}
	}
}acam;
struct LightPower
{
	uint pw1[2000005],pw2[2000005];
	int B=(1<<20);
	void init()
	{
		pw1[0]=pw2[0]=1;
		for(int i=1;i<=B;i++) pw2[i]=pw2[i-1]*3;
		pw1[1]=pw2[B];
		for(int i=2;i<=B;i++) pw1[i]=pw1[i-1]*pw1[1];
	}
	uint query(long long x)
	{
		return pw1[x>>20]*pw2[x&((1<<20)-1)];
	}
}lp;
int n,Q;
int p[2000005],nxt[2000005],to[2000005],eid;
vector <int> vs;
int sum[2000005],tag[2000005],par[2000005],deg[2000005];
bool flg[2000005];
uint ans;
//void dfs(int u)
//{
////	cout<<"dfs: "<<u<<"\n";
//	sum[u]=tag[u];
//	for(int i=p[u];i;i=nxt[i])
//	{
//		int v=to[i];
//		dfs(v),sum[u]+=sum[v];
//	}
//	if(acam.id[u]) ans--,ans+=lp.query(1LL*sum[u]*acam.id[u]);
//	flg[u]=0,tag[u]=0,p[u]=0;
//}
void solve()
{
	lp.init();
	cin>>n;
	cin>>n>>Q;
	for(int i=1;i<=n;i++)
	{
		string s;
		cin>>s;
		acam.add(s,i);
	}
	acam.build();
//	cout<<"...\n"; 
	while(Q--)
	{
		ans=n;
		string s;
		cin>>s;
		int u=0;
		queue <int> q;
		for(int i=0;i<s.size();i++)
		{
			int tu=acam.ch[u][s[i]-'a'];
			u=tu;
			u=acam.nxt[u];
//			cout<<"pass: "<<u<<"\n";
			sum[u]++;
			while(u&&!flg[u])
			{
				flg[u]=1;
				int v=acam.nxt[acam.fail[u]];
//				cout<<deg[v]<<"\n";
				par[u]=v,deg[v]++;
//				cout<<u<<" --- "<<v<<"\n";
//				cout<<deg[v]<<"\n";
//				eid++,to[eid]=u,nxt[eid]=p[v],p[v]=eid;
//				vs.pb(v);
				u=v;
			}
			u=tu;
		}
		u=0;
		for(int i=0;i<s.size();i++)
		{
			int tu=acam.ch[u][s[i]-'a'];
			u=tu;
			u=acam.nxt[u];
			if(!deg[u]&&u&&flg[u]) flg[u]=0,q.push(u);//,cout<<"init: "<<u<<"\n";
			u=tu;
		}
//		for(int i=0;i<=10;i++) cout<<deg[i]<<" "<<par[i]<<"\n";
//		cout<<"\n";
		while(q.size())
		{
			u=q.front();
			q.pop();
//			if(deg[u]) continue;
			if(!u) continue;
			if(acam.id[u]) ans--,ans+=lp.query(1LL*sum[u]*acam.id[u]);
			sum[par[u]]+=sum[u],deg[par[u]]--;
			if(deg[par[u]]==0) q.push(par[u]);
//			deg[par[u]]=max(deg[par[u]],0);
			sum[u]=flg[u]=0;
//			cout<<u<<" "<<par[u]<<" "<<deg[par[u]]<<"\n";
		}
//		for(int i=1;i<=10;i++) cout<<sum[i]<<" "<<deg[i]<<"\n";
//		cout<<"...\n";  
//		dfs(0),eid=0;
//		for(int i=0;i<vs.size();i++) sum[vs[i]]=0,p[vs[i]]=0,flg[vs[i]]=0;
//		vs.clear();
		cout<<ans<<"\n";
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【候选队互测2022】毛估估就行

一开始的想法是,随机一些点开始 BFS 得到若干 BFS 树,每次询问枚举所有 BFS 树算树上距离取 min,这个表现并不是很好。

不妨抛弃树的想法,BFS 完之后记录每个点到起点的距离 \(d_i\),询问用 \(d_u+d_v\) 估计,输出时将答案 \(-1\) 后输出,观察到如果起点或者起点的邻居在两点的最短路上,答案就是正确的,也就是说一轮 BFS 可以选择度数最大的点,做完之后可以把起点和起点的邻居删除。

BFS 不能进行太多轮,因为询问很多。观察到进行若干轮之后剩下的点和边很少,直接在这些点中暴力 BFS 就行,复杂度玄学。

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[8005]; 
bitset <8015> mask[8015],vis,nw;
int n;
int dist[105][8005];
void bfs(int tid,int u)
{
	for(int i=1;i<=n;i++) g[i].clear(),vis[i]=1,dist[tid][i]=inf;
	queue <int> q;
	q.push(u),vis[u]=0,dist[tid][u]=0;
	while(q.size())
	{
		u=q.front();
		q.pop();
		nw=vis&mask[u];
		for(int i=nw._Find_first();i<=n;i=nw._Find_next(i))
		{
			vis[i]=0,dist[tid][i]=dist[tid][u]+1;
			q.push(i);
		}
	}
}
int dist2[8005][8005];
void bfs2(int u)
{
	int st=u;
	for(int i=1;i<=n;i++) vis[i]=1,dist2[u][i]=inf;
	queue <int> q;
	q.push(u),vis[u]=0,dist2[u][u]=0;
	while(q.size())
	{
		u=q.front();
		q.pop();
		for(int i=0;i<g[u].size();i++)
		{
			int v=g[u][i];
			if(vis[v]==0) continue;
			vis[v]=0,dist2[st][v]=dist2[st][u]+1;
			q.push(v);
		}
	}
}
int q;
const int K=80;
int deg[8005];
void delnode(int x)
{
	for(int i=1;i<=n;i++) if(mask[x][i]) 
		deg[x]--,deg[i]--,mask[x][i]=mask[i][x]=0;
	deg[x]=-1;
}

void solve()
{
	cin>>n>>q;
	for(int i=2;i<=n;i++)
	{
		string s;
		cin>>s;
		for(int j=0,l=1;j<s.size();j++)
		{
			int x=('0'<=s[j]&&s[j]<='9'?s[j]-'0':10+s[j]-'A');
			for(int _=0;_<4;_++)
			{
				if(x&(1<<_)) mask[i][l]=mask[l][i]=1,deg[i]++,deg[l]++;//,cout<<i<<" --- "<<l<<"\n";
				l++;
			}
		}
	}
	for(int _=1;_<=K;_++) 
	{
		int mx=1;
		for(int i=1;i<=n;i++) if(deg[i]>deg[mx]) mx=i;
		bfs(_,mx);
		for(int i=1;i<=n;i++) if(mask[i][mx]) delnode(i);
		delnode(mx);
	}
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(mask[i][j]) g[i].pb(j);
	for(int i=1;i<=n;i++) bfs2(i);
	
	while(q--)
	{
		int u,v;
		cin>>u>>v;
		int ans=dist2[u][v];
		for(int i=1;i<=K;i++) ans=min(ans,dist[i][u]+dist[i][v]);
		cout<<ans-1<<"\n"; 
	}
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UER #10】随机薅羊毛

\(dp(i)\) 表示从 \(i\) 开始抽奖,直到成功的期望次数,可以对于每个 \(dp(i)\) 写出一个方程,\(n\) 个方程 \(n\) 个未知数,可以解了。

但是我们只关注 \(\sum dp(i)\),令 \(x= \sum dp(i)\),在 \(n\) 个方程中用 \(x\)\(p_i\) 去表示 \(dp(i)\),把 \(n\) 个方程加起来,就可以解出 \(x\),复杂度 \(O(n)\)

Extra test 卡精度,可以用 float128。

Code
#include<bits/stdc++.h>
using namespace std;
#define d128 __float128
#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;
}
int n;
d128 p[1000005]; 

void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++) 
	{
		double tmp;
		cin>>tmp;
		p[i]=(d128)(tmp);
		p[i]/=1000000.0;
	}
	d128 coef_k=-1.0,coef_b=0;
	for(int i=1;i<=n;i++)
	{
		d128 k=(1.0-p[i])/((d128)(n)-p[i]);
		d128 b=((d128)(n)-1.0)/((d128)(n)-p[i]);
		coef_k+=k,coef_b+=b;
//		cout<<k<<" "<<b<<"\n";
	}
	double ans=(double)((-coef_b)/(coef_k*(d128)(n)));
	cout<<fixed<<setprecision(12)<<ans<<"\n";
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int _=1;
//	cin>>_;
	while(_--) solve();
	return 0;
}

【UER #10】重构元宇宙

不妨令第一个点的坐标为 \((0,0,\cdots,0)\),第二个点的坐标为 \((d(1,2),0,\cdots,0)\)

每次询问 \(d(i,j)\) 可以知道 \(\sum x_i^2+x_j^2-2x_ix_j\),依次加入点,令目前加入点 \(u\),先询问 \(d(u,0)\),得到 \(\sum x_u^2\),然后任意取 \(k\) 个点询问,得到 \(k\) 个方程,可以解出 \(k\) 个坐标(自由元就令其为 \(0\))。观察到排序后就是下三角矩阵,不需要消元,直接解就行。

复杂度 \(O(n^3)\)

Code
#include "distance.h"

#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;
double ans[505][505];
double a[505][505];
int id[505],nowd;
vector <vector<double> > calcans(int n,int k)
{
	vector <vector<double> > res;
	for(int i=1;i<=n;i++)
	{
		vector <double> nw;
		for(int j=1;j<=k;j++) nw.pb(ans[i][j]);
		res.pb(nw);
	}
	return res;
}
int _query(int x,int y)
{
	return query(x-1,y-1);
}
vector <vector<double> > solve(int n,int k,int lim)
{
	if(n==1) return calcans(n,k);
	for(int i=2;i<=n;i++)
	{
		if(!nowd)
		{
			ans[i][1]=(double)(_query(1,i));
			ans[i][1]=sqrt(ans[i][1]);
		} 
		else
		{
			double sq=(double)(_query(1,i));
			for(int j=1;j<=nowd;j++)
			{
				a[j][nowd+1]=(double)(_query(i,id[j]));
				a[j][nowd+1]-=sq;
				for(int l=1;l<=nowd;l++) a[j][nowd+1]-=ans[id[j]][l]*ans[id[j]][l];
				for(int l=1;l<=nowd;l++) a[j][l]=-2.0*ans[id[j]][l];
			}
			for(int j=1;j<=nowd;j++)
			{
				double b=a[j][nowd+1];
				for(int l=1;l<j;l++) b-=a[j][l]*ans[i][l];
				ans[i][j]=b/a[j][j];
			}
			for(int j=1;j<=nowd;j++) sq-=ans[i][j]*ans[i][j];
			if(nowd<k) ans[i][nowd+1]=sqrt(sq);
		}
//		cout<<nowd<<"\n";
//		for(int j=1;j<=k;j++) cout<<ans[i][j]<<" ";
//		cout<<"\n";
		if(ans[i][nowd+1]) id[++nowd]=i;
	}
	return calcans(n,k);
}
posted @ 2023-07-03 20:19  znstz  阅读(17)  评论(0编辑  收藏  举报