【比赛记录】国庆集训合集

\(\text{T1}\) GirlFriend区间 3

好题。

先把质数筛了。

考虑将所有区间按照左右端点离散化。将询问离线下来,然后对于每个右端点统计左端点上的贡献。即从小到大扫描 \(r\),维护每一个后缀的答案。

考虑使用 set 维护区间的并。考虑已处理前 \(r-1\) 的询问,处理位于 \(r\) 上的所有右端点的询问产生的影响。那么对于 \([i,r-1]\) 的区间并,\(r\) 的区间产生的贡献覆盖的是 \([i,r]\) 的并减去 \([i,r-1]\) 的区间。也就是空出来的部分。然后使用树状数组维护。

注意到对于一个之前的区间 \(i\),它最多被 update 覆盖两次(左端点被覆盖时一次,右端点一次)。所以颜色段均摊 \(O((n+m)\log n)\)

代码还没写完。

upd:你妈的不写了。

\(\text{T2}\) ABC小精灵

考虑 AB 通过 ABAA->ABABBA->BA 可以变成 BA。同理 BC 可以变成 CB

那么 ABCB 可以相互交换。那么 B 就可以随意放置。将 B 扔到最前面,然后 A,C 相对位置无法交换。所以最终序列必然形如 B...BACAACCAA...,对于相邻两个相同的字符再删去,比较最终序列即可判断。

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

const int N=5e5+6;

int T,n,m,B1,B2,top1,top2;
char s1[N],s2[N],t1[N],t2[N];

inline void solve(){
	B1=B2=top1=top2=0;
	scanf("%s%s",s1+1,s2+1),n=strlen(s1+1),m=strlen(s2+1);
	for(int i=1;i<=n;i++){
		if(s1[i]=='B')B1++;
		else{
			if(s1[i]==t1[top1])top1--;
			else t1[++top1]=s1[i];
		}
	}
	for(int i=1;i<=m;i++){
		if(s2[i]=='B')B2++;
		else{
			if(s2[i]==t2[top2])top2--;
			else t2[++top2]=s2[i];
		}
	}
	if(top1!=top2||(B1-B2)&1){
		puts("NO");
		return;
	}
	for(int i=1;i<=top1;i++)if(t1[i]!=t2[i]){
		puts("NO");
		return;
	}
	puts("YES");
	return;
}

int main(){
	scanf("%d",&T);
	while(T--)solve(); 
}

\(\text{T3}\) 树的计数

显然是个笛卡尔树相关。然后对于多个相同的 min 必定形如一棵二叉树。所以对于一段区间其贡献为相同的 min 的个数。使用卡特兰数计算。然后多个 min 会将一个区间分割为多个区间,那么递归求解。对于区间 min 与 min 的位置使用线段树实现。当然你也可以用 ST表+set 一只 \(\log\)

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

#define int long long
#define ls p<<1
#define rs p<<1|1
#define pb push_back

const int N=1e6+7;
const int mod=1e9+7;

int n,a[N],fac[N<<1],ifac[N<<1],tr[N<<2],mn;
vector<int>pos[N<<2],mnpos;

inline int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

inline void init(int n){
	fac[0]=ifac[0]=1;
	for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
	ifac[n]=qpow(fac[n],mod-2);
	for(int i=n-1;i;i--)ifac[i]=ifac[i+1]*(i+1)%mod;
	return;
}

inline int C(int n,int m){
	return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}

inline int Cat(int n){
	return (C(2*n,n)-C(2*n,n-1)+mod)%mod;

}

inline void merge(vector<int>&x,int y){
	for(auto i:pos[y])x.pb(i);
	return;
}

inline void pushup(int p){
	tr[p]=min(tr[ls],tr[rs]);
	if(tr[p]==tr[ls])merge(pos[p],ls);
	if(tr[p]==tr[rs])merge(pos[p],rs);
	return;
}

inline void build(int p,int l,int r){
	if(l==r){
		tr[p]=a[l],pos[p].pb(l);
		return;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid),build(rs,mid+1,r);
	pushup(p);
	return;
}

inline void query(int p,int l,int r,int s,int t){
	if(s>t)return;
	if(s<=l&&r<=t){
		if(mn>tr[p])mn=tr[p],mnpos=pos[p];
		else if(mn==tr[p])merge(mnpos,p);
		return;
	}
	int mid=(l+r)>>1;
	if(s<=mid)query(ls,l,mid,s,t);
	if(t>mid)query(rs,mid+1,r,s,t);
	return;
}

inline int solve(int l,int r){
	if(l>=r)return 1;
	mnpos.clear(),mn=LLONG_MAX;
	query(1,1,n,l,r);
	int ans=1,num=0,lst=l;
	vector<pair<int,int> >vec;
	vec.clear();
	for(auto i:mnpos){
		num++;
		vec.push_back(make_pair(lst,i-1));
		lst=i+1;
	}
	for(auto i:vec)ans=ans*solve(i.first,i.second)%mod;
	ans=ans*solve(lst,r)%mod;
	ans=ans*Cat(num)%mod;
	return ans;
}

signed main(){
	scanf("%lld",&n);init(N-1);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	build(1,1,n);
	printf("%lld",solve(1,n));
	return 0;
}

\(\text{T4}\) AND=OR

考虑将两个区间独立开然后依次与。那么显然 popcount 是单调不升的。同理对于或的情况单调不减。

所以对于一段区间,按照 popcount 排序,选出的必然是选择一段前缀与后缀。

枚举两边相等数的 popcount \(k\),查询区间内 popcount 为 \([0,k-1]\) 的或和与 \([k+1,30]\) 的数的与和。注意对于 popcount 为 \(k\) 的数必然全部相同。否则无解。对于每个 \(k\) 使用线段树维护。

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

#define ls p<<1
#define rs p<<1|1

const int N=1e5+6;
const int M=32;

int n,Q,mxpc,And_[M],Or_[M],Cnt_[M],a[N];

struct SegmentTree{
	int And[N<<2],Or[N<<2],tr[N<<2];
	inline void pushup(int p){
		And[p]=And[ls]&And[rs];
		Or[p]=Or[ls]|Or[rs];
		tr[p]=tr[ls]+tr[rs];
		return;
	}
	inline void build(int p,int l,int r){
		if(l==r){
			And[p]=-1,Or[p]=tr[p]=0;
			return;
		}
		int mid=(l+r)>>1;
		build(ls,l,mid),build(rs,mid+1,r);
		pushup(p);
	}
	inline void update(int p,int l,int r,int ps,int val){
		if(l==r){
			And[p]=Or[p]=val,tr[p]=1;
			return;
		}
		int mid=(l+r)>>1;
		if(ps<=mid)update(ls,l,mid,ps,val);
		else update(rs,mid+1,r,ps,val);
		pushup(p);
	}
	inline int queryand(int p,int l,int r,int s,int t){
		if(s<=l&&r<=t)return And[p];
		int mid=(l+r)>>1,res=-1;
		if(s<=mid)res&=queryand(ls,l,mid,s,t);
		if(t>mid)res&=queryand(rs,mid+1,r,s,t);
		return res;
	}
	inline int queryor(int p,int l,int r,int s,int t){
		if(s<=l&&r<=t)return Or[p];
		int mid=(l+r)>>1,res=0;
		if(s<=mid)res|=queryor(ls,l,mid,s,t);
		if(t>mid)res|=queryor(rs,mid+1,r,s,t);
		return res;
	}
	inline int query(int p,int l,int r,int s,int t){
		if(s<=l&&r<=t)return tr[p];
		int mid=(l+r)>>1,res=0;
		if(s<=mid)res+=query(ls,l,mid,s,t);
		if(t>mid)res+=query(rs,mid+1,r,s,t);
		return res;
	} 
}ch[M];

inline int pc(int x){
	if(x<0)return 47497;
	int res=0;
	while(x){
		res+=x&1;
		x>>=1;
	}
	return res;
}

int main(){
	scanf("%d%d",&n,&Q);
	for(int i=0;i<=30;i++)ch[i].build(1,1,n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		int x=pc(a[i]);
		mxpc=max(mxpc,x);
		ch[x].update(1,1,n,i,a[i]);
	}
	while(Q--){
		int l,r,flag=0;
		scanf("%d%d",&l,&r);
		for(int i=0;i<=mxpc;i++){
			And_[i]=ch[i].queryand(1,1,n,l,r);
			Or_[i]=ch[i].queryor(1,1,n,l,r);
			Cnt_[i]=ch[i].query(1,1,n,l,r);
		}
		for(int i=0;i<=mxpc;i++){
			int Sor=0,Sand=-1,Cntor=0,Cntand=0;
			for(int j=0;j<i;j++)Sor|=Or_[j],Cntor+=Cnt_[j];
			for(int j=i;j<=mxpc;j++)Sand&=And_[j],Cntand+=Cnt_[j];
			if(Sor==Sand&&Cntor>0&&Cntand>0){
				puts("YES");flag=1;
				break;
			}
			if(pc(And_[i])!=i)continue;
			if(Cnt_[i]>1&&(Sor|And_[i])==(Sand)){
				puts("YES");flag=1;
				break;
			}
		}
		if(!flag)puts("NO");
	}
	return 0;
}

\(\text{T5}\) 数学题

如 FWT 按照 \(2\) 的次幂为合并长度合并答案。然后直接子集 dp。

具体实现请参考:https://codeforces.com/blog/entry/45223

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

#define int long long

const int N=6e5+6;
const int mod=1e9+7;
const int lg=20;

int n,a[N],f[N],g[N],sf[N][lg+1],sg[N][lg+1],ans;

signed main(){
	scanf("%lld",&n);
	for(int i=0;i<n;i++)scanf("%lld",&a[i]);
	for(int i=0;i<n;i++){
		sf[i][lg]=sg[i][lg]=0;
		for(int j=lg-1;~j;j--){
			sf[i][j]=sf[i][j+1],sg[i][j]=sg[i][j+1];
			int s=1<<j;
			if(i&s){
				sf[i][j]=(sf[i][j]+sf[i-s][j])%mod;
				sg[i][j]=(sg[i][j]+sg[i-s][j])%mod;
			}
		}
		f[i]=(a[i]*a[i]%mod+sg[i][0]*sg[i][0]%mod)%mod;
		g[i]=(f[i]*f[i]%mod+sf[i][0])%mod;
		for(int j=lg;~j;j--){
			sf[i][j]=(sf[i][j]+f[i]*f[i]%mod)%mod;
			sg[i][j]=(sg[i][j]+g[i])%mod;
		}
		ans=(ans+f[i]*g[i]%mod*i%mod)%mod;
	}
	printf("%lld",ans);
	return 0;
}

\(\text{T6}\) 考试

先把理论考试全部选择,然后就变为从 \(n\)\(b_i-a_i\) 中至少选择 \(B\) 个的最大代价。显然通过贪心容易。

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

const int N=2e5+6;

int n,A,B,a[N],b[N],ans;

inline bool cmp(int x,int y){return x>y;}

int main(){
	scanf("%d%d%d",&n,&A,&B);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),ans+=a[i];
	for(int i=1;i<=n;i++)scanf("%d",&b[i]),b[i]-=a[i];
	sort(b+1,b+n+1,cmp);
	for(int i=1;i<=B;i++)ans+=b[i];
	for(int i=B+1;i<=n&&n-i>=A;i++)if(b[i]>0)ans+=b[i];
	printf("%d",ans);
	return 0;
}

\(\text{T7}\) 背包

注意到 \(m\) 很小,考虑状压 dp。\(f_{i,S}\) 表示第 \(i\) 个背包,\(S\) 表示选择的物品集合。这样是 \(O(2^nn^2)\)。考虑优化。令 \(f_{S},g_{S}\),其中 \(f\) 表示 \(S\) 状态下最少选择的背包数量,\(g\) 表示当前最后加入的这个背包最大还可以留下多少空间。此时 \(O(2^nn)\),可以通过。

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

const int N=2e6+7;
const int M=100;

int n,m,a[M],w[M],f[N],g[N],S;

inline bool cmp(int x,int y){return x>y;}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=m;i++)scanf("%d",&w[i]);
	sort(w+1,w+m+1,cmp);
	f[0]=1,g[0]=w[1];S=(1<<n)-1;
	for(int i=0;i<=S;i++){
		if(!f[i])continue;
		for(int j=1;j<=n;j++){
			int I=i|(1<<j-1);
			if(i==I)continue;
			if(g[i]>=a[j]){
				if(!f[I]||f[i]<f[I]){
					f[I]=f[i];
					g[I]=g[i]-a[j];
				}
				else if(f[I]==f[i])g[I]=max(g[I],(g[i]-a[j]));
			}
			else if(f[i]!=m){
				int k=f[i]+1;
				if(w[k]>=a[j]){
					if(f[I]>f[i]+1||!f[I]){
						f[I]=f[i]+1;
						g[I]=w[k]-a[j];
					}
					else if(f[I]==f[i]+1)g[I]=max(g[I],w[k]-a[j]);
				}
			}
		}
	}
	if(!f[S])puts("-1");
	else printf("%d",f[S]);
	return 0;
}

\(\text{T8}\) 移动细胞

\(f_{i,j}\) 表示第 \(i\) 列,第 \(i\) 列黑色细胞的上端放在第 \(j\) 行。

\[f_{i,j}=\min_{k=j-len_{i-1}+1}^{j-len_i+1} f_{i-1,k}+|l_i-j| \]

直接 dp \(O(n^2)\),考虑单调队列优化即可。

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

const int N=1e5+6;

int n,m,l[N],r[N],len[N],ans=INT_MAX,dp[2][N],q[N];

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&l[i],&r[i]);
		len[i]=r[i]-l[i]+1;
	}
	for(int i=1;i<=m;i++){
		int I=(i&1),head=1,tail=0;
		for(int j=1;j<len[i];j++){
			while(head<=tail&&dp[I^1][q[tail]]>dp[I^1][j])tail--;
			q[++tail]=j;
		}
		for(int j=1;j<=n-len[i]+1;j++){
			while(head<=tail&&q[head]<j-len[i-1]+1)head++;
			while(head<=tail&&dp[I^1][q[tail]]>dp[I^1][j+len[i]-1])tail--;
			q[++tail]=j+len[i]-1;
			dp[I][j]=min(dp[I^1][q[head]],dp[I^1][j+len[i]-1])+abs(l[i]-j); 
		}
		for(int j=n-len[i]+2;j<=n;j++)dp[I][j]=INT_MAX;
	}
	for(int i=1;i<=n-len[m]+1;i++)ans=min(ans,dp[m&1][i]);
	printf("%d",ans);
	return 0;
}

\(\text{T9}\)

\(\text{T1}\) 经典原题,不讲。

\(\text{T2}\) 雪猫头鹰

直接 dp。记录上个断点与前缀 min 的 pos 集合。使用 st表 \(O(n^2)\)。使用线段树 \(O(n\log n)\)。啥ds都不用 \(O(n)\)。然而我是大怨种。

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

#define ls p<<1
#define rs p<<1|1

const int N=5e5+4;
const int mod=1e9+7;

int n,a[N],tr[N<<2],f[N],mx,mxpos,pos[N<<2],g[N];

inline void pushup(int p){
	tr[p]=max(tr[ls],tr[rs]);
	if(tr[p]==tr[rs])pos[p]=pos[rs];
	if(tr[p]==tr[ls])pos[p]=pos[ls];
	return;
}

inline void build(int p,int l,int r){
	if(l==r){
		tr[p]=a[l],pos[p]=l;
		return;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid),build(rs,mid+1,r);
	pushup(p);
	return;
}

inline void query(int p,int l,int r,int s,int t){
	if(s>t)return;
	if(s<=l&&r<=t){
		if(mx<tr[p])mx=tr[p],mxpos=pos[p];
		return;
	}
	int mid=(l+r)>>1;
	if(s<=mid)query(ls,l,mid,s,t);
	if(t>mid)query(rs,mid+1,r,s,t);
	return;
}

signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	build(1,1,n);f[0]=1,g[0]=1;
	for(int i=1;i<=n;i++){
		mx=0,mxpos=0;
		query(1,1,n,1,i-1);
		if(mx>a[i])f[i]=g[mxpos-1];
		else f[i]=g[i-1];
		g[i]=g[i-1]+f[i];
		f[i]%=mod,g[i]%=mod;
//		f[i]=g[mxpos]
	}
	printf("%lld",f[n]);
	return 0;
}

\(\text{T3}\) 白头鹰

什么诈骗题。赛时就我过了就离谱。

使用打表容易发现当 \(k\)\(a+1\)\(f\) 恒为 \(1\)。然后 \(a\) 很小。于是做完了。

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

#define int long long

int b,a,p;

inline int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=res*a%p;
		a=a*a%p;
		b>>=1;
	}
	return res;
}

inline int f(int x,int y){
	int v=a*b+x*y;
	if(v%p==0)return 1;
	else return (a*x+b*y)%p*qpow(v%p,p-2)%p;
}

inline int F(int a,int b,int p,int k){
	int lst=k;
	for(int i=k-1;i;i--){
		int x=f(lst,i);
		lst=x;
	}
	return lst;
}

signed main(){
	int k;
	cin>>a>>b>>p>>k;int mx=max(a,b);
	if(k>a)cout<<F(a,b,p,a+1);
	else cout<<F(a,b,p,k);
//	for(int i=1;i<=n;i++)
}

//signed main(){
//	srand(time(0));
//	
//	a=2,b=4,p=5;
//	int T;cin>>T;
//	for(int k=T;k;--k) cout<<F(a,b,p,k)<<" "<<k<<endl;
//	cout<<a<<" "<<b<<" "<<p<<"?\n";
////	solved();
//}

\(\text{T4}\) 鸵鸟

发现我们对于每个加号,可以使用后面的 \(i\) 来匹配这里的加号。\(f_{i,j}\) 表示前 \(i\) 个还有 \(j\) 个加号没有匹配。当前位若是减号则匹配之前的加号。

我的评价是:如果想到加号匹配,那么一眼丁真。

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

#define int long long

const int N=5e3+4;
const int mod=1e9+7;

int n,dp[N][N],a[N],cnt;
char s[N];

signed main(){
	scanf("%s",s+1),n=strlen(s+1);
	for(int i=1;i<=n;i++)a[i]=s[i]=='+';
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		cnt+=a[i];
		for(int j=0;j<=cnt;j++){
			if(a[i])dp[i][j]=(dp[i][j]+dp[i-1][j-1]+j*dp[i-1][j]%mod)%mod;
			else dp[i][j]=(dp[i][j]+dp[i-1][j]*j%mod+dp[i-1][j+1]*(j+1)%mod*(j+1)%mod)%mod;
		}
	}
	printf("%lld",dp[n][0]);
	return 0;
}

\(\text{T5}\) 帝企

注意到最后肯定是每个极长的只含有两种字符的连续段,然后这个连续段的字符不可能移动到前一个或后一个连续段。如 ..PSS|SR..,那么每个极长段由于只包含两种字符(若三种则必然可以拆分或合并),直接冒泡排序。

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

const int N=1e6+7;

int n,k,q[N],a[N],top,res,lst=1,m;
map<char,int>mps;
char s[N],ans[N];

signed main(){
	scanf("%d%d%s",&n,&k,s+1);
	for(int i=1;i<=n;i++){
		if(s[i]=='R')a[i]=1;
		else if(s[i]=='S')a[i]=2;
		else a[i]=3;
	}
	for(int i=1;i<=n+1;i++){
		if(!mps[a[i]])res++;
		mps[a[i]]++;
		if(res==3){
			mps[a[i]]--,res--;top=0;int o=0;
			if(mps[1]&&mps[2])o=1;
			if(mps[2]&&mps[3])o=2;
			if(mps[3]&&mps[1])o=3;
			for(int j=lst;j<i;j++){
				if(top<k&&a[j]==o)q[++top]=j;
				else ans[++m]=s[j];
			}
			for(int j=1;j<=top;j++)ans[++m]=s[q[j]];
			for(int j=lst;j<i;j++)if(!(--mps[a[j]]))res--;
			if(!mps[a[i]])res++;
			mps[a[i]]++;lst=i;
		}
	}
	for(int i=lst;i<=n;i++)ans[i]=s[i];
	printf("%s",ans+1);
	return 0;
}

\(\text{T1}\) 糖果

发现肯定是见到一对可以消除的就消除。显然消除顺序对于答案无关。所以顺着跑一遍,将能删除的直接删除,或者与栈顶的数配对,若配对失败则也扔到栈中。最后在栈中元素首尾配对来计算反向配对的答案。

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

const int N=1e5+6;

int n,m,a[N],ans,top,stk[N];

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++){
		if(top&&(stk[top]==a[i]||stk[top]+a[i]==m))top--,ans++;
		else stk[++top]=a[i];
	}
	for(int i=1;i<=top;i++){
		if(stk[i]==stk[top-i+1]||stk[i]+stk[top-i+1]==m)ans++;
		else break;
	}
	printf("%d",ans);
	return 0;
}

\(\text{T2}\) 铜川

考虑给定序列怎么求有多少子区间和 \(≡0\pmod k\)

记录前缀和为 \([0,k-1]\) 的每种前缀出现次数 \(c\),那么答案是 \(\binom{c}{2}\)。即对于任意两个同余的前缀,其区间必然整除 \(k\)

\(f_{i,j,t}\) 表示当前计算前缀和为 \(i\),放了 \(j\) 个位置,好序列个数为 \(t\)的方案数。

\[f_{i,j,t}\times \binom{n-j}{x}\to f_{i+1,j+x,t+\binom{x}2} \]

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

#define int long long

const int N=66;
const int mod=998244353;

int n,k,tt,fac[N],ifac[N],f[N][N][N*N];

inline int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=1ll*res*a%mod;
		a=1ll*a*a%mod;
		b>>=1;
	}
	return res;
}

inline void init(int n){
	fac[0]=ifac[0]=1;
	for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
	ifac[n]=qpow(fac[n],mod-2);
	for(int i=n-1;i;i--)ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
	return;
}

inline int C(int n,int m){
	return 1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}

signed main(){
	init(N-1);
	scanf("%lld%lld%lld",&n,&k,&tt);
	for(int i=0;i<=n;i++)f[0][i][i*(i+1)>>1]=C(n,i);
	for(int i=1;i<k;i++){
		for(int j=0;j<=n;j++){
			for(int t=0;t<=((j+1)*j>>1);t++){
				for(int jj=0;jj<=j;jj++){
					int o=((j-jj)*(j-jj-1)>>1);
					if(t<o)continue;
					f[i][j][t]=(f[i][j][t]+f[i-1][jj][t-o]*C(n-jj,j-jj)%mod)%mod;
				}
			}
		}
	}
	printf("%lld",f[k-1][n][tt]);
	return 0;
}

\(\text{T3}\) 山峰

发现考虑确定最大值的位置不好做。发现最小值的位置唯一,一定在最左或最右。然后考虑将当前的 min 移到一方端点,然后删除 min,因为对答案不可能再产生影响。如此做 \(n\) 次,使用线段树维护当前第 \(i\) 位到第 \(1\) 位与第 \(n\) 位之间还剩下多少数。

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

#define ls p<<1
#define rs p<<1|1
#define int long long

const int N=2e5+6;

int n,lsh[N],a[N],ans,tr[N<<2],pos[N];

inline void pushup(int p){
	tr[p]=tr[ls]+tr[rs];
	return;
}

inline void update(int p,int l,int r,int ps,int val){
	if(l==r){
		tr[p]+=val;
		return;
	}
	int mid=(l+r)>>1;
	if(ps<=mid)update(ls,l,mid,ps,val);
	else update(rs,mid+1,r,ps,val);
	pushup(p);
	return;
}

inline int query(int p,int l,int r,int s,int t){
	if(s>t)return 0;
	if(s<=l&&r<=t)return tr[p];
	int mid=(l+r)>>1,res=0;
	if(s<=mid)res+=query(ls,l,mid,s,t);
	if(t>mid)res+=query(rs,mid+1,r,s,t);
	return res;
}

signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		lsh[i]=a[i];
	}
	sort(lsh+1,lsh+n+1);
	for(int i=1;i<=n;i++)a[i]=lower_bound(lsh+1,lsh+n+1,a[i])-lsh;
	for(int i=1;i<=n;i++)update(1,1,n,i,1),pos[a[i]]=i;
	for(int i=1;i<=n;i++){
		int j=pos[i];
		ans+=min(query(1,1,n,1,j-1),query(1,1,n,j+1,n));
		update(1,1,n,j,-1);
	}
	printf("%lld",ans);
	return 0;
}

\(\text{T4}\) 瓦片

注意到 \(k\le 5\)于是分类讨论。没了。

事实上可以发现答案是关于 \(n,m\) 的多项式,于是暴力跑 \(n,m\) 较小的情况,搜出来后插值。

我的评价:厂。

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

#define int long long

const int mod=998244353;

int n,m,k;

inline int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

inline int C(int n,int m){
	int res=1;
	for(int i=1;i<=m;i++)res=res*(n-i+1)%mod;
	for(int i=1;i<=m;i++)res=res*qpow(i,mod-2)%mod;
	return res;
}

inline int sum(vector<int>vec){
	int ans=0;
	for(auto i:vec)ans=(ans+i)%mod;
	return ans;
}

inline void sub1(){
	puts("1");
	return;
}

inline void sub2(){
	printf("%lld",(n+m-2)%mod);
	return;
}

inline void sub3(){
	int ans=0;
	ans=(C(n-1,2)+C(m-1,2))%mod;
	ans=(ans+(n-1)*(m-1)%mod*4%mod)%mod;
	printf("%lld",ans);
	return;
}

inline void sub4(){
	int ans=0,x=0,y=0;
	ans=(n-1)*(m-1)%mod+(C(n-1,3)+C(m-1,3))%mod;
	x=(C(n-1,2)*(m-1)%mod*3%mod+C(m-1,2)*(n-1)%mod*3%mod);
	y=(n-1)*C(m-1,2)%mod*4%mod+(m-1)*C(n-1,2)%mod*4%mod;y=y*2%mod;y=(y+ans)%mod;
	printf("%lld",(x+y)%mod);
	return;
}

inline void sub5(){
	vector<int>ly={
		C(n-1,2)*C(m-1,2)%mod*2%mod,C(n-1,4),C(m-1,4),
		C(n-1,3)*(m-1)%mod*4%mod,C(m-1,3)*(n-1)%mod*4%mod,
		C(n-1,3)*(m-1)%mod*6%mod,C(m-1,3)*(n-1)%mod*6%mod,
		C(n-1,2)*C(m-1,2)%mod*3%mod*2%mod,C(m-1,2)*C(n-1,2)%mod*3%mod*2%mod,
		C(n-1,2)*(m-1)%mod*3%mod,C(m-1,2)*(n-1)%mod*3%mod,
		C(n-1,2)*C(m-1,2)%mod*3%mod,C(m-1,2)*C(n-1,2)%mod*3%mod,
		C(n-1,2)*C(m-1,2)%mod*8%mod,C(m-1,2)*C(n-1,2)%mod*8%mod,
		C(n-1,3)*(m-1)%mod*4%mod,C(m-1,3)*(n-1)%mod*4%mod,
		C(n-1,3)*(m-1)%mod*4%mod,C(m-1,3)*(n-1)%mod*4%mod,
		C(n-1,2)*C(m-1,2)%mod*6%mod,C(m-1,2)*C(n-1,2)%mod*6%mod,
		C(n-1,3)*(m-1)%mod*2%mod,C(m-1,3)*(n-1)%mod*2%mod,
		C(n-1,3)*(m-1)%mod*6%mod,C(m-1,3)*(n-1)%mod*6%mod,
		C(n-1,2)*(m-1)%mod*4%mod,C(m-1,2)*(n-1)%mod*4%mod,
		C(n-1,2)*C(m-1,2)%mod*8%mod,C(m-1,2)*C(n-1,2)%mod*8%mod
	};
	printf("%lld",sum(ly));
	return;
}

signed main(){
	scanf("%lld%lld%lld",&n,&m,&k);
	if(k==1)sub1();
	else if(k==2)sub2();
	else if(k==3)sub3();
	else if(k==4)sub4();
	else sub5();
	return 0;
}

\(\text{T5}\) 聊天

首先将区间分为若干个连通块, 对于每个联通块内部必然有 \(r_i\ge l_{i+1}\) 成立。然后考虑让相邻两个连通块之间损失的代价最小,令当前连通块右端点 \(R\) 与下一个连通块左端点 \(L'\) 尽量接近。

按照右端点排序,对于当前的 \(r_{i-1}\ge l_{i}\) 则将区间加入当前连通块;若 \(r_{i-1}<l_{i}\) 则将区间 \(i\) 当作新的连通块。注意到有情况 \(l_j<l_i,j>i\) 时,区间 \(j\) 完全覆盖 \(i\),那么区间 \(i\) 没有任何作用。

那么先按照 \(l\) 排序后记录每个区间是否大于之前的 \(\max r\),若不是则必然被之前的某个区间所包含,于是打个标记。

那么上述过程可以通过 deque 实现。加入当前连通块时或者此连通块作为新的连通块的起点则扔到后面,否则扔到最前(即被覆盖的区间)。

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

#define int long long

const int N=5e5+6;

int n,lst,mx,ans[N],sum;
struct node{
	int l,r,id,mx;
}q[N];
deque<node>dq;

inline bool cmp1(node a,node b){
	return a.l!=b.l?a.l<b.l:a.r>b.r;
}
inline bool cmp2(node a,node b){
	return a.r!=b.r?a.r<b.r:a.l<b.l;
}

signed main(){
	scanf("%lld%lld",&n,&lst);
	for(int i=1;i<=n;i++){
		scanf("%lld%lld",&q[i].l,&q[i].r);
		q[i].id=i;
	}
	sort(q+1,q+n+1,cmp1);
	for(int i=1;i<=n;i++)if(q[i].r>mx)mx=q[i].r,q[i].mx=1;
	sort(q+1,q+n+1,cmp2);dq.push_back(q[1]);
	for(int i=2;i<=n;i++){
		if(dq.back().r<q[i].l&&!q[i].mx)dq.push_front(q[i]);
		else dq.push_back(q[i]);
	}
	for(int i=1;i<=n;i++){
		node tmp=dq.front();dq.pop_front();
		ans[i]=tmp.id;
		sum+=max(lst-tmp.l,0ll),lst=tmp.r;
	}
	printf("%lld\n",sum);
	for(int i=1;i<=n;i++)printf("%lld ",ans[i]);
	return 0;
}

10.3省选组训练

\(\text{T1}\) Z

\(n\) 为偶数构造 \(2\times n,n\),当 \(n\) 为奇数,枚举质数 \(\min p\) 满足 \(p\) 不是 \(n\) 的因子则构造 \(pn,(p-1)n\)

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

#define int long long

int p[30]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97},T,n;

signed main(){
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&n);
		if(n&1){int i;
			for(i=1;i<25;i++)if(n%p[i])break;
			printf("%lld %lld\n",n*p[i],(p[i]-1)*n);
		}
		else printf("%lld %lld\n",n*2,n);
	}
	return 0;
}

国庆联赛模拟赛DAY4

\(\text{T1}\) 区间

同 CF1285E Delete a Segment。

所有区间按照端点离散化。

考虑对于一段新的区间 \([l,r]\),而此区间属于原来的区间 \([L,R]\)。若 \([l,r]\) 上没有其他的区间覆盖则会贡献答案至区间 \([L,R]\)。注意若 \(l=L\)\(r=R\) 时无贡献。所以用 set 扫描线即可。

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

const int N=4e5+6;

int T,n,m,ans,cnt[N],mx;
pair<int,int>q[N];
set<int>st;

inline void solve(){
	scanf("%d",&n);ans=m=0;st.clear();mx=-1;
	for(int i=1;i<=n;i++){
		int l,r;
		scanf("%d%d",&l,&r);
		q[++m]=make_pair(l,-i);
		q[++m]=make_pair(r,i);
	}
	sort(q+1,q+m+1);
	for(int i=1;i<=m;i++){
		int j=q[i].second;
		if(j>0){
			if(st.size()==1)cnt[j]--;
			st.erase(j);
		}
		else{
			if(st.empty())cnt[-j]--;
			st.insert(-j);
		}
		if(st.empty())ans++;
		else if(st.size()==1)cnt[*st.begin()]++;
	}
	for(int i=1;i<=n;i++)mx=max(mx,cnt[i]),cnt[i]=0;
	printf("%d\n",mx+ans);
	return;
}

int main(){
	scanf("%d",&T);
	while(T--)solve();
	return 0;
}

\(\text{T2}\) 打印机

栈是循环的。否则必定不优。那么可能的栈的类型共 \(6\) 种(ABC,ABC,BAC,BCA,CAB,CBA)。设 \(f_{i,j,t}\) 表示当前第 \(i\) 位,\(j\) 为栈的大小,\(t\) 表示栈的类型。

dp 时根据栈内元素个数分类转移。

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

const int mps[8][5]={{0,0,0,0},{0,1,2,3},{0,1,3,2},{0,2,1,3},{0,2,3,1},{0,3,1,2},{0,3,2,1}};
const int N=5e3+4;
const int inf=1e9;

int n,dp[2][N][8],a[N],ans=inf;
char s[N];

inline void Min(int &x,int y){
	x=min(x,y);
	return;
} 

int main(){
	scanf("%s",s+1);int len=strlen(s+1);
	for(int i=1;i<=len;i++)if(s[i]!=s[i-1])a[++n]=s[i]-'A'+1;
	memset(dp,0x3f,sizeof(dp));
	dp[1][1][a[1]*2-1]=1;
	for(int i=2;i<=n;i++){
		int p=i&1,q=(i-1)&1;
		for(int j=0;j<=i;j++)for(int k=1;k<=6;k++)dp[p][j][k]=inf;
		for(int j=0;j<=i;j++){
			for(int k=1;k<=6;k++){
//				cout<<k<<" "<<a[i]<<" "<<i<<" "<<(j-1)%3+1<<"?\n";
				if(a[i]!=mps[k][(j-1)%3+1])continue;
				if(j==1){
					if(!(k&1))continue;
					Min(dp[p][j][k],dp[q][j-1][1]+1);
					Min(dp[p][j][k],min(dp[q][j+1][k],dp[q][j+2][k]));
					Min(dp[p][j][k],min(dp[q][j+1][k+1],dp[q][j+2][k+1]));
				}
				else if(j==2){
					Min(dp[p][j][k],min(dp[q][j+1][k],dp[q][j+2][k]));
					if(k&1)Min(dp[p][j][k],dp[q][j-1][k]+1);
					else Min(dp[p][j][k],dp[q][j-1][k-1]+1);
				}
				else{
					Min(dp[p][j][k],min(dp[q][j+1][k],dp[q][j+2][k]));
					Min(dp[p][j][k],dp[q][j-1][k]+1);
				}
//				cout<<dp[p][j][k]<<" ";
			}
		}
	}
	for(int i=0;i<=n;i++)for(int k=1;k<=6;k++)ans=min(ans,dp[n&1][i][k]);
	printf("%d",ans*2+len);
	return 0;
}

\(\text{T3}\) 餐厅

同 CF1369E DeadLee。

考虑每个人都吃了两道菜。然后如果当前对于一个人还有剩下的那他肯定放哪里都没问题。所以扔到后面。

对于剩下的在放到最前面构成一个新的问题。注意此时要将之前扔到后面去的人吃掉的菜吐出来(bushi。如此做子问题即可。

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

const int N=2e5+6;

int n,m,a[N],head[N],tot,top,st[N];
bool vis[N];
queue<int>q;

struct edge{
	int to,nxt,w;
}e[N<<1];

inline void addedge(int u,int v,int w){
	e[++tot].to=v;
	e[tot].nxt=head[u];
	e[tot].w=w;
	head[u]=tot;
	return;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		addedge(x,y,i),addedge(y,x,i);
		a[x]--,a[y]--;
	}
	for(int i=1;i<=n;i++)if(a[i]>=0)q.push(i);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(vis[e[i].w])continue;
			vis[e[i].w]=true;
			st[++top]=e[i].w;
			a[v]++;
			if(!a[v])q.push(v);
		}
	}
	if(top<m)puts("DEAD");
	else{
		puts("ALIVE");
		while(top)printf("%d ",st[top--]);
	}
	return 0;
}

\(\text{T4}\) 战略游戏

同 P5332 [JSOI2019]精准预测。

一眼发现是 2-sat,发现暴力建图不可接受,考虑对于每个人只保留判定时间。然后图本身就是个 DAG,套路地使用 bitset 优化。

对于空间的限制,使用分块来计算结果。块长取 \(10^4\) 时优秀。

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

const int N=1e5+6;
const int inf=1e9;
const int block=1e4;
const int M=N*3;

int nowt,n,k,ed[N],vis[M],head[M],ans[N],tot;
bitset<block>S,bs[M];
struct node{int op,t,x,y;}q[N];
map<int,int>mps[N];
struct edge{
	int to,nxt;
}e[M<<1];

inline void addedge(int u,int v){
	e[++tot].to=v;
	e[tot].nxt=head[u];
	head[u]=tot;
	return;
}

inline void add(int u,int v,int op){
	addedge(u+op,v),addedge(v+1,u+!op);
	return;
}

inline void dfs(int u){
	if(vis[u])return;
	vis[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		dfs(v);
		bs[u]|=bs[v];
	}
	return;
}

int main(){
	scanf("%d%d%d",&nowt,&n,&k);
	for(int i=1;i<=k;i++){
		scanf("%d%d%d%d",&q[i].op,&q[i].t,&q[i].x,&q[i].y);
		mps[q[i].x][q[i].t]=i*2;
	}
	for(int i=1;i<=n;i++)ed[i]=mps[i][nowt+1]=(k+i)*2;
	for(int i=1;i<=k;i++){
		int u=mps[q[i].x][q[i].t],v=(*mps[q[i].y].lower_bound(q[i].t+!q[i].op)).second;
		add(u,v,q[i].op);
	}
	for(int i=1;i<=n;i++){
		int lst=0;
		for(auto tmp:mps[i]){
			int v=tmp.second;
			if(lst)add(lst,v,0);
			lst=v;
		}
	}
	for(int l=1,r;l<=n;l+=block){
		r=min(l+block-1,n);S.reset();
		for(int i=2;i<=(n+k)*2+1;i++)vis[i]=0,bs[i].reset();
		for(int i=l;i<=r;i++)bs[ed[i]][i-l]=1;
		for(int i=2;i<=(n+k)*2+1;i++)dfs(i);
		for(int i=l;i<=r;i++)if(bs[ed[i]+1][i-l])ans[i]=-inf,S[i-l]=1;
		for(int i=1;i<=n;i++)ans[i]+=(r-l+1)-(S|bs[ed[i]+1]).count();
	}
	for(int i=1;i<=n;i++)printf("%d ",max(ans[i]-1,0));
	return 0;
}

国庆联赛模拟赛DAY5

\(\text{T1}\) Box 不讲。

\(\text{T2}\) Replace

\(f_i\) 为考虑前 \(i\) 个数,第 \(i\) 个数不变的最小操作次数。

\(j(j>i)\) 能转移到 \(i\) 的充要条件是 \(a_i,a_j\) 之间的 \(b\)
数量 \(\ge j-i-1\)

使用线段树维护。

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

#define ls p<<1
#define rs p<<1|1

const int N=5e5+6;
const int inf=0x3f3f3f3f;

int n,m,a[N],b[N],pos[N],tr[N<<3],f[N],g[N];

inline void pushup(int p){
	tr[p]=min(tr[ls],tr[rs]);
	return;
}

inline void update(int p,int l,int r,int ps,int val){
	if(l==r){
		tr[p]=min(tr[p],val);
		return;
	}
	int mid=(l+r)>>1;
	if(ps<=mid)update(ls,l,mid,ps,val);
	else update(rs,mid+1,r,ps,val);
	pushup(p);
	return;
}

inline int query(int p,int l,int r,int s,int t){
	if(s<=l&&r<=t)return tr[p];
	int mid=(l+r)>>1,res=inf;
	if(s<=mid)res=min(res,query(ls,l,mid,s,t));
	if(t>mid)res=min(res,query(rs,mid+1,r,s,t));
	return res;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=m;i++)scanf("%d",&b[i]);
	sort(b+1,b+m+1);
	a[n+1]=2*n+m-1,b[m+1]=inf;
	for(int i=0;i<=n;i++)pos[i]=lower_bound(b+1,b+m+2,a[i])-b-1;pos[n+1]=m;
	memset(f,0x3f,sizeof(f)),memset(g,0x3f,sizeof(g)),memset(tr,0x3f,sizeof(tr));
	f[0]=0;
	update(1,-n,m,1,0);
	for(int i=1;i<=n;i++){
		f[i]=min(f[i],g[i-1]);
		g[i]=query(1,-n,m,-n,pos[i+1]-i+1)+i;
		if(a[i]>a[i-1])f[i]=min(f[i],f[i-1]);
		update(1,-n,m,1+pos[i]-i,f[i]-i);
	}
	if(min(f[n],g[n])==inf)puts("-1");
	else printf("%d",min(f[n],g[n]));
	return 0;
}

\(\text{T3}\) 苦难交易

同 CF1713E Cross Swapping。

从小到大枚举,对于 \(a_{x,y}\)\(a_{y,x}\) 之间的关系容易推出,使用带权并查集维护。

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

const int N=1e3+4;

int T,n,a[N][N],fa[N<<1];

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

inline void merge(int x,int y){
	x=find(x),y=find(y);
	if(abs(x-y)%n)fa[x]=y;
	return;
}

inline void solve(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);	
	for(int i=1;i<=2*n;i++)fa[i]=i;
	for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++){
		if(a[i][j]<a[j][i])merge(i,j),merge(i+n,j+n);
		else if(a[i][j]>a[j][i])merge(i,j+n),merge(i+n,j);
	}
	for(int i=1;i<=n;i++){
		if(find(i)>n)continue;
		for(int j=1;j<=n;j++)swap(a[i][j],a[j][i]);	
	}
	for(int i=1;i<=n;i++,puts(""))for(int j=1;j<=n;j++)printf("%d ",a[i][j]);
	return;
}

int main(){
	scanf("%d",&T);
	while(T--)solve();
	return 0;
}

\(\text{T4}\) 线性代树

同 P3340 [ZJOI2014]星系调查。

考虑直接大力推式子。式子很容易推,最终结果是 \(S=\dfrac{C+A+\sqrt{(A-C)^2+B^2}}{2}\),那么维护 \(\sum x,\sum y,\sum x^2,\sum y^2,\sum xy\) 即可。

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

const int N=2e5+6;

int n,m,Q,S,T,X[N],Y[N],dep[N],sx[N],sy[N],sxx[N],syy[N],xy[N],top[N],tot,head[N],sz[N],son[N],fa[N];
double X_,Y_,XX,YY,XY,Dis;

struct edge{
	int to,nxt;
}e[N<<1];

inline void addedge(int u,int v){
	e[++tot].to=v;
	e[tot].nxt=head[u];
	head[u]=tot;
	return;
}

inline void dfs1(int u,int faa){
//	cout<<u<<" "<<faa<<"?\n";
	sz[u]=1,fa[u]=faa,dep[u]=dep[faa]+1;
	sx[u]=sx[faa]+X[u],sxx[u]=sxx[faa]+X[u]*X[u];
	sy[u]=sy[faa]+Y[u],syy[u]=syy[faa]+Y[u]*Y[u];
	xy[u]=xy[faa]+X[u]*Y[u];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==faa||sz[v]){
			if(v&&v!=faa)S=u,T=v;
			continue;
		}
		dfs1(v,u);
		sz[u]+=sz[v];
		if(sz[son[u]]<sz[v])son[u]=v;
	}
	return;
}

inline void dfs2(int u,int tp){
	top[u]=tp;
	if(son[u])dfs2(son[u],tp);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(top[v]||v==fa[u]||v==son[u]||(u==S&&v==T)||(u==T&&v==S))continue;
		dfs2(v,v);
	}
	return;
}

inline int lca(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
}

inline double dis(int u,int v){
	int o=lca(u,v);
	return dep[u]+dep[v]-dep[o]*2.0+1;
}

inline void query(int u,int v){
	int o=lca(u,v);
	X_+=sx[u]+sx[v]-sx[o]*2+X[o],XX+=sxx[u]+sxx[v]-2*sxx[o]+X[o]*X[o];
	Y_+=sy[u]+sy[v]-sy[o]*2+Y[o],YY+=syy[u]+syy[v]-2*syy[o]+Y[o]*Y[o];
	XY+=xy[u]+xy[v]-xy[o]*2+X[o]*Y[o];
	Dis+=dis(u,v);
	return;
}

inline double F(){
	double D=1.0/Dis,u=XX-D*X_*X_,v=YY-D*Y_*Y_,w=XY-D*X_*Y_;
	double res=(u+v)/2.0-sqrt(w*w+(u-v)/4.0*(u-v));
	X_=XX=Y_=YY=XY=Dis=0;
	return res;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d%d",&X[i],&Y[i]);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		addedge(u,v),addedge(v,u);
	}
	dfs1(1,0);
//	puts("!");
	dfs2(1,1);
	scanf("%d",&Q);
	while(Q--){
		int u,v,o;double ans;
		scanf("%d%d",&u,&v),o=lca(u,v);
		query(u,v);
		ans=F();
		if(n==m){
			query(u,S),query(v,T);
			ans=min(ans,F());
			query(u,T),query(v,S);
			ans=min(ans,F());
		}
		printf("%.6lf\n",ans);
	}
	return 0;
}

国庆联赛模拟赛DAY6

咕了。

posted @ 2024-01-17 18:44  trsins  阅读(7)  评论(0编辑  收藏  举报