2022.11.(07/08/09) 题解大汇总

xdm 记住了,Gmt丶FFF 从不咕题解(

D1:

T1:P2571 [SCOI2010]传送带(蓝)

T2:P2485 [SDOI2011]计算器(蓝)

T3:P2495 [SDOI2011]消耗战(紫)

T4:P2489 [SDOI2011]迷宫探险(紫)


D2:

T1:P3247 [HNOI2016]最小公倍数(紫)

T2:P3250 [HNOI2016] 网络(紫)

T3:P3246 [HNOI2016]序列(紫)

T4:预估蓝(2000)


D3:

T1:[ABC077D] Small Multiple(紫)

T2:Enlarge GCD(蓝)

T3:[ARC085E] MUL(紫)

T4:[AGC006D] Median Pyramid Hard(紫)


DAY1:

T1:

题意简述:

这个就不简述题意了,题意够简单了。

解:

这,就正常三分就行了,实在不行可以像我枚举一条线上的点,对另一条线三分。

至于证明,很明显有最优决策点两边都不比它优,至于单峰的证明,这就感性理解就好了。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<ctime>
#define int long long
#define double long double
using namespace std;
const double eps=1e-9;
int ax,ay,bx,by,cx,cy,dx,dy,p,q,r;
double ans,len1,len2,k1,k2;
inline double min(double x,double y)
{
	return x-eps>y?y:x;
}
inline double dis(double x1,double y1,double x2,double y2)
{
	return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
inline double slope(int x1,int y1,int x2,int y2)
{
	if(x1==x2)return 1e9;
	return (y1-y2)*1.0/(x1-x2);
}
inline double getans(double l1,double l2)
{
	double x1=((bx>ax)?1.0:-1.0)*sqrt(l1*l1/(k1*k1+1))+ax,y1=((by>ay)?1.0:-1.0)*sqrt(l1*l1/(k1*k1+1)*k1*k1)+ay;
	double x2=((dx>cx)?1.0:-1.0)*sqrt(l2*l2/(k2*k2+1))+cx,y2=((dy>cy)?1.0:-1.0)*sqrt(l2*l2/(k2*k2+1)*k2*k2)+cy;
	return dis(x1,y1,ax,ay)/p+dis(x1,y1,x2,y2)/r+dis(x2,y2,dx,dy)/q;
}
double check(double len)
{
	double l=0,r=len2;
	double res=1e12;
	while(l<r-eps)
	{
		double mid1=(l+l+r)/3.0,mid2=(l+r+r)/3.0;
		double num1=getans(len,mid1),num2=getans(len,mid2);
		if(num1<num2)r=mid2-eps;
		else l=mid1+eps;
		res=min(res,min(num1,num2));
	}
	return res;
}
void sanfen()
{
	for(double i=0.0;i<=len1+eps;i+=0.001)ans=min(ans,check(i));
}
signed main()
{
	//freopen("tran.in","r",stdin);
	//freopen("tran.out","w",stdout);
	srand('C'+'Z'+'B');
	scanf("%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld%lld",&ax,&ay,&bx,&by,&cx,&cy,&dx,&dy,&p,&q,&r);
	len1=dis(ax,ay,bx,by),len2=dis(cx,cy,dx,dy);
	k1=slope(ax,ay,bx,by),k2=slope(cx,cy,dx,dy);
	ans=getans(0.0,0.0);
	sanfen();
	printf("%.2Lf",ans);
	return 0;
}
/*
0 0 100 100
100 200 0 300
10 10 1
*/

T2:

题意简述:

好像也不用。

解:

操作 \(1\),快速幂。

操作 \(2\),逆元。

操作 \(3\),BSGS。

上述三个都是模板,就自己复习吧。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<map>
#define int long long
using namespace std;
const int N=1e5+5;
int T,K,q[N];
inline int quick_pow(int x,int y,int mod)
{
	int sum=1,num=x;
	while(y)
	{
		if(y&1)sum*=num,sum%=mod;
		num*=num;
		num%=mod;
		y>>=1;
	}
	return sum;
}
map<int,int>mp;
signed main()
{
	//freopen("clac.in","r",stdin);
	//freopen("clac.out","w",stdout);
	scanf("%lld%lld",&T,&K);
	while(T--)
	{
		int y,z,p;
		scanf("%lld%lld%lld",&y,&z,&p);
		if(K==1)
		{
			int ans=quick_pow(y,z,p);
			printf("%lld\n",ans);
		}
		if(K==2)
		{
			int ans=quick_pow(y*quick_pow(z,p-2,p)%p,p-2,p);
			if(ans*y%p!=z%p)printf("Orz, I cannot find x!\n");
			else printf("%lld\n",ans);
		}
		if(K==3)
		{
			if(y%p==0&&z%p==0)
			{
				printf("1\n");
				continue;
			}
			int num=1,sp=sqrt(p);
			q[0]=1;
			mp[1]=0;
			for(int i=1;i<=sp;i++)
			{
				num*=y;
				num%=p;
				q[i]=num;
				mp[num]=i;
			}
			int sum=1,tot=quick_pow(z,p-2,p),res=quick_pow(y,sp,p);
			int ans=0;
			for(int i=1;i*sp<=p;i++)
			{
				sum*=res;
				sum%=p;
				if(mp.find(sum*tot%p)!=mp.end()&&mp[sum*tot%p]!=-1)
				{
					ans=i*sp-mp[sum*tot%p];
					break;
				}
			}
			for(int i=1;i<=sp;i++)mp[q[i]]=-1;
			if(quick_pow(y,ans,p)!=z%p)printf("Orz, I cannot find x!\n");
			else printf("%lld\n",ans);
		}
	}
	return 0;
}

T3:

题意简述:

给出一棵大小为 \(n\) 带边权的有根树与 \(m\) 次询问,每次询问要使给定的 \(k\) 个点与根节点不连通,求最小花费。

对于每次操作,建一次虚树,然后跑树形 dp。

\(f_{i}\) 代表以 \(i\) 为子树的所有点全被割掉的最小花费。

如果 \(i\) 要被割,那么 \(f_i=dis(i,fa_i)\)

否则 \(f_i=\min(dis(i,fa_i),\sum f_{son})\)

如果只跑树形 dp 肯定会寄,所以建虚树就行了,这里给出一个四种情况的建虚树方式。

1、当前加入点为栈顶点的儿子节点,那么直接把这个点入栈即可。

2、当前点与栈顶点的 \(\text{lca}\) 为栈中第二个元素,那么删除栈顶元素,加入这个点。

3、当前点与栈顶点的 \(\text{lca}\) 在栈顶点与栈中第二个元素之间,直接删除栈顶元素,加入 \(\text{lca}\) 与当前点。

4、当前点与栈顶点的 \(\text{lca}\) 在栈中第二个元素的上方,一直删除栈顶元素直到满足 \(1,2,3\) 中的一个形态为止。

剩下的就看代码吧。

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N=5e5+5;
const int M=N/2;
int n,m,h[M],cnt,f[M][25],vis[M],dp[M],h2[M],cnt2,g[M],dep[M],st[N],topp,q[N],dfn[M],tim;
struct node
{
	int to,data,next;
}a[N];
int cmp(int x,int y)
{
	return dfn[x]<dfn[y];
}
inline void addedge(int u,int v,int w)
{
	a[++cnt]={v,w,h[u]};
	h[u]=cnt;
	a[++cnt]={u,w,h[v]};
	h[v]=cnt;
}
struct node2
{
	int to,next;
}b[N];
inline void addedge2(int u,int v)
{
	b[++cnt2]={v,h2[u]};
	h2[u]=cnt2;
	b[++cnt2]={u,h2[v]};
	h2[v]=cnt2;
}
void dfs(int x,int fa)
{
	dfn[x]=++tim;
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	for(int i=1;i<=20;i++)f[x][i]=f[f[x][i-1]][i-1];
	for(int i=h[x];i!=0;i=a[i].next)
	{
		int v=a[i].to;
		if(v==fa)continue;
		g[v]=min(g[x],a[i].data);
		dfs(v,x);
	}
}
int lca(int x,int y)
{
	if(dep[x]<dep[y])swap(x,y);
	for(int i=20;i>=0;i--)if(dep[f[x][i]]>=dep[y])x=f[x][i];
	if(x==y)return x;
	for(int i=20;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
void getans(int x,int fa)
{
	//cout<<x<<" "<<g[x]<<endl;
	int num=0;
	bool flag=0;
	for(int i=h2[x];i!=0;i=b[i].next)
	{
		int v=b[i].to;
		if(v==fa)continue;
		getans(v,x);
		num+=dp[v];
		flag=1;
	}
	if(!vis[x]&&flag)dp[x]=min(g[x],num);
	else if(vis[x])dp[x]=g[x];
	vis[x]=0;
	h2[x]=0;
}
signed main()
{
	//freopen("war.in","r",stdin);
	//freopen("war.out","w",stdout);
	scanf("%lld",&n);
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%lld%lld%lld",&u,&v,&w);
		addedge(u,v,w);
	}
	g[1]=1e18;
	dfs(1,0);
	scanf("%lld",&m);
	for(int i=1;i<=m;i++)
	{
		int k;
		scanf("%lld",&k);
		for(int j=1;j<=k;j++)
		{
			int x;
			scanf("%lld",&x);
			q[j]=x;
			vis[x]=1;
		}
		sort(q+1,q+1+k,cmp);
		topp=1;
		cnt2=0;
		st[1]=1;
		for(int j=1;j<=k;j++)
		{
			while(1)
			{
				int lc=lca(st[topp],q[j]);
				if(dep[lc]>=dep[st[topp-1]])
				{
					if(dep[lc]<dep[st[topp]])
					{
						addedge2(st[topp],lc);
						topp--;
						if(lc!=st[topp])st[++topp]=lc;
					}
					break;
				}
				else
				{
					addedge2(st[topp],st[topp-1]);
					topp--;
				}
			}
			st[++topp]=q[j];
		}
		while(topp>1)addedge2(st[topp],st[topp-1]),topp--;
		getans(1,0);
		printf("%lld\n",dp[1]);
	}
	return 0;
}
/*
10
1 5 13
1 9 6
2 1 19
2 4 8
2 3 91
5 6 8
7 5 4
7 8 31
10 7 9
3
2 10 6
4 5 7 8 3
3 9 4 6
*/

T4:

题意简述:

一个 \(n\times m\) 的带墙体单入口多出口迷宫中有 \(k\) 个陷阱,陷阱分为有害或无害,有害会使人掉血,给出所有垃圾的有害与无害的所有排列组成的概率,给定人的血量,求掉最少血走出迷宫的概率。

解:

提到迷宫问题,考虑搜索。

首先将垃圾状态状压,\(0\) 为未知,\(1\) 为无害,\(2\) 为有害,由于一来所有陷阱的情况都是未知的,所以状态为 \(0\),而人需要去试探陷阱,每次试探后分有害无害搜索两种垃圾状态搜下去即可。

很明显会 \(T\),考虑优化,在迷宫中有限考虑记忆化。

\(f_{i,j,s,h}\) 代表人在 \((i,j)\),陷阱状态为 \(s\),生命值为 \(h\) 的概率。

那么我们需要提前预处理出来每一个三进制状态中一个未知的陷阱是有害的概率 \(g_{i,j}\)

那么我们找到每一个合法的陷阱的组成方式,那么对于一个未知状态,它的概率即为所有组成方式的概率和除以组成方式个数。

那么记忆化也能转移了,对于踩到未知陷阱,\(f_{i,j,s,h}=f_{i,j,new1,h-1}\times g_{s,k}+f_{i,j,new2,h}\times (1-g{s,k})\),其中 \(new1,new2\) 代表踩到有害、无害陷阱的新状态,而 \(k\) 代表踩的是哪一种陷阱。

如果对于已知陷阱,踩到未知的不扣,踩到已知的扣血即可。

但是这样会有问题,因为有情况可以绕一圈然后再走出去,那么就导致了状态会未算完就转移了。

那么我们用拓扑序就能解决这个问题,对于每一种陷阱组成状态预处理出来自己下一步可以走到的未知、有害陷阱和出口,这样就处理出了这个问题。

预处理只需要枚举状态,枚举起始点坐标,然后对于每一个状态与初始坐标进行广搜记录能走到的点即可。

复杂度:\(O(n\times m\times 3^k\times h)\)

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int N=35;
const int M=750;
const int K=6;
const double eps=1e-10;
int n,m,k,hp,xb,yb;
char s[N][N];
int p[M],power[6]={1,3,9,27,81,243},b[4][2]={{1,0},{0,1},{-1,0},{0,-1}},vis[N][N],cnt,r[M];
double g[M][K],f[N][N][M][K];
vector<pair<int,int>>v[N][N][1<<K];
void dfs(int x,int y,int q,int xx,int yy,int id)
{
	if(vis[x][y]==id)return;
	vis[x][y]=id;
	if((x!=xx||y!=yy)&&(s[x][y]=='@'||(s[x][y]>='A'&&s[x][y]<='Z'&&((1<<(s[x][y]-'A'))&q)==0)))
	{
		//cout<<xx<<" "<<yy<<" "<<q<<" "<<x<<" "<<y<<endl;
		v[xx][yy][q].push_back({x,y});
		return;
	}
	for(int i=0;i<4;i++)
	{
		int xt=x+b[i][0],yt=y+b[i][1];
		if(xt<1||yt<1||xt>n||yt>m||s[xt][yt]=='#'||(xx==xt&&yy==yt))continue;
		dfs(xt,yt,q,xx,yy,id);
	}
}
double dp(int x,int y,int q,int h)
{
	//cout<<x<<" "<<y<<" "<<q<<" "<<h<<endl;
	if(f[x][y][q][h]>=-eps)return f[x][y][q][h];
	if(h==0)return f[x][y][q][h]=0;
	if(s[x][y]=='@')return f[x][y][q][h]=1;
	int len=v[x][y][r[q]].size();
	double res=0;
	for(int i=0;i<len;i++)
	{
		int xx=v[x][y][r[q]][i].first,yy=v[x][y][r[q]][i].second;
		if(s[xx][yy]=='@')
		{
			res=max(res,dp(xx,yy,q,h));
			continue;
		}
		if(q/power[s[xx][yy]-'A']%3==2)res=max(res,dp(xx,yy,q,h-1));
		if(q/power[s[xx][yy]-'A']%3==0)
		{
			int xzt1=q+power[s[xx][yy]-'A']*2,xzt2=q+power[s[xx][yy]-'A'];
			res=max(res,dp(xx,yy,xzt1,h-1)*g[q][s[xx][yy]-'A']+dp(xx,yy,xzt2,h)*(1-g[q][s[xx][yy]-'A']));
		}
	}
	return f[x][y][q][h]=res;
}
int main()
{
	//freopen("maze.in","r",stdin);
	//freopen("maze.out","w",stdout);
	scanf("%d%d%d%d",&n,&m,&k,&hp);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)for(int q=0;q<power[k];q++)for(int t=0;t<=hp;t++)f[i][j][q][t]=-1;
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='$')xb=i,yb=j;
	for(int i=0;i<(1<<k);i++)scanf("%d",&p[i]);
	for(int i=0;i<power[k];i++)
	{
		int num=0;
		for(int j=0;j<(1<<k);j++)
		{
			bool flag=0;
			for(int q=0;q<k;q++)
			{
				if(i/power[q]%3==0)continue;
				if(i/power[q]%3==1&&(j&(1<<q))==0)continue;
				if(i/power[q]%3==2&&(j&(1<<q)))continue;
				flag=1;
				break;
			}
			if(flag)continue;
			num+=p[j];
			for(int q=0;q<k;q++)
			{
				if(i/power[q]%3)continue;
				if((j&(1<<q)))g[i][q]+=p[j];
			}
		}
		for(int j=0;j<k;j++)
		{
			if(i/power[j]%3==1)g[i][j]=1,r[i]|=(1<<j);
			if(i/power[j]%3==2)g[i][j]=0;
			if(i/power[j]%3==0)g[i][j]/=num;
			//cout<<i<<" "<<j<<" "<<g[i][j]<<endl;
		}
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int q=0;q<1<<k;q++)if(s[i][j]!='#')dfs(i,j,q,i,j,++cnt);
	double ans=dp(xb,yb,0,hp);
	printf("%.3lf",ans);
	return 0;
}

DAY2:

T1:

题意简述:

给定一个无向图,边权带两个值 \((a,b)\),给定 \(q\) 次询问,每次询问给定两个点,求两个点直接是否有 \(\max(a)=x\)\(\max(b)=y\) 的简单或非简单路径。

解:

如果是单次询问,可以想到我们把所有 \(a\le x\)\(b\le y\) 的边加入,判断 \((u,v)\) 是否联通且带有 \(a=x\) 和带有 \(b=y\) 的边是否能走到,这可以用并查集解决就行了。

现在问题在处理多次询问。

我们可以把每条边按 \(a\) 不下降排序,询问按 \(y\) 不下降排序。

我们再把边按数量分块,再从小到大枚举块,每次找出所有 \(x\le max_a\)\(x\ge min_a\)\(max_a\)\(min_a\) 是指块内的值,也就是找到所有在块内的询问。

那么对于在这个块前面的边 \(a\) 一定小于等于 \(x\),那么可以对前面的块中所有边按 \(b\) 排序,那么只需要找到 \(b\le y\) 的所有边即可,由于询问的 \(y\) 单调递增,那么枚举前面块的边时可以从上次继承过来。

然后处理块内元素,暴力枚举找合适的边加入即可。

为下一次询问,我们需要撤销所有块内操作,这个直接在修改时用栈维护一下修改的值,然后处理完询问复原即可。

那么块内操作不能路径压缩,否则撤回的复杂度会高达 \(O(n)\),每一次询问撤一次复杂度就为 \(O(n\times q)\)

最后一步,我们需要判断是否能走到带有 \(a=x\)\(b=y\) 的边,这个好处理,在并查集时记录一下当前集合的最大 \(a\) 与最大 \(b\) 即可。

时间复杂度:\(O(q\times\sqrt n\times\log(\sqrt n))\)

#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=2e5+5;
int n,m,k,cnt,siz[N],p[N],ans[N],f[N],fa[N],fb[N];
struct node4
{
	int u,v,data,maxa,maxb;
}qp[N];
struct node
{
	int from,to,dat1,dat2;
}a[N];
int cmp(node fi,node se)
{
	return fi.dat1<se.dat1;
}
int cmp3(node fi,node se)
{
	return fi.dat2<se.dat2;
}
struct node2
{
	int name,u,v,dat1,dat2;
}b[N];
int cmp2(node2 fi,node2 se)
{
	return fi.dat2<se.dat2;
}
int bfind(int x)
{
	if(x==f[x])return x;
	return bfind(f[x]);
}
int afind(int x)
{
	if(x==f[x])return x;
	return f[x]=afind(f[x]);
}
int main()
{
	//freopen("eegcd.in","r",stdin);
	//freopen("eegcd.out","w",stdout);
	scanf("%d%d",&n,&m);
	k=sqrt(m);
	for(int i=1;i<=m;i++)scanf("%d%d%d%d",&a[i].from,&a[i].to,&a[i].dat1,&a[i].dat2);
	int q;
	scanf("%d",&q);
	for(int i=1;i<=q;i++)scanf("%d%d%d%d",&b[i].u,&b[i].v,&b[i].dat1,&b[i].dat2),b[i].name=i;
	sort(a+1,a+1+m,cmp);
	sort(b+1,b+1+q,cmp2);
	for(int i=1;i<=m;i++)p[i]=(i-1)/k+1;
	a[m+1].dat1=-1;
	for(int i=1;(i-1)*k+1<=m;i++)
	{
		int l=(i-1)*k+1,r=min(i*k,m),bef=0;
		for(int j=1;j<=n;j++)f[j]=j,fa[j]=fb[j]=-1;
		for(int j=1;j<=q;j++)
		{
			if(b[j].dat1>=a[l].dat1&&b[j].dat1<=a[r].dat1)
			{
				if(b[j].dat1==a[r].dat1&&a[r+1].dat1==b[j].dat1)continue;
				int cnt=0;
				for(int t=bef+1;t<=l-1&&a[t].dat2<=b[j].dat2;t++)
				{
					fa[afind(a[t].to)]=max(fa[afind(a[t].to)],max(fa[afind(a[t].from)],a[t].dat1));
					fb[afind(a[t].to)]=max(fb[afind(a[t].to)],max(fb[afind(a[t].from)],a[t].dat2));
					f[afind(a[t].from)]=afind(a[t].to);
					bef=t;
				}
				for(int t=l;t<=r&&a[t].dat1<=b[j].dat1;t++)
				{
					if(a[t].dat2<=b[j].dat2)
					{
						qp[++cnt]={bfind(a[t].from),bfind(a[t].to),f[bfind(a[t].from)],fa[bfind(a[t].to)],fb[bfind(a[t].to)]};
						fa[bfind(a[t].to)]=max(fa[bfind(a[t].to)],max(fa[bfind(a[t].from)],a[t].dat1));
						fb[bfind(a[t].to)]=max(fb[bfind(a[t].to)],max(fb[bfind(a[t].from)],a[t].dat2));
						f[bfind(a[t].from)]=f[bfind(a[t].to)];
					}
				}
				if(bfind(b[j].u)==bfind(b[j].v)&&fa[bfind(b[j].u)]==b[j].dat1&&fb[bfind(b[j].u)]==b[j].dat2)ans[b[j].name]=1;
				for(int t=cnt;t>=1;t--)
				{
					fa[qp[t].v]=qp[t].maxa;
					fb[qp[t].v]=qp[t].maxb;
					f[qp[t].u]=qp[t].data;
				}
			}
			
		}
		sort(a+1,a+1+r,cmp3);
	}
	for(int i=1;i<=q;i++)
	{
		if(ans[i])puts("Yes");
		else puts("No");
	}
	return 0; 
}

T2:

题意简述:

给定一棵大小为 \(n\) 的树,\(q\) 次操作,每次操作给定一条带权路径,或是删除一条给定路径,或是给定一个点,询问所有不与这个点相交的给定路径的权值最大值。

解:

维护一棵时间树,加操作就把路径外的所有点加入,删除操作只需要标记一下,询问时向下查找,时间树的每个点维护一个堆,用于查找最大的边权,利用树剖套取时间树即可。

时间复杂度:\(O(\log(n)^3_2)\)

#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=2e5+5;
int n,m,k,cnt,siz[N],p[N],ans[N],f[N],fa[N],fb[N];
struct node4
{
	int u,v,data,maxa,maxb;
}qp[N];
struct node
{
	int from,to,dat1,dat2;
}a[N];
int cmp(node fi,node se)
{
	return fi.dat1<se.dat1;
}
int cmp3(node fi,node se)
{
	return fi.dat2<se.dat2;
}
struct node2
{
	int name,u,v,dat1,dat2;
}b[N];
int cmp2(node2 fi,node2 se)
{
	return fi.dat2<se.dat2;
}
int bfind(int x)
{
	if(x==f[x])return x;
	return bfind(f[x]);
}
int afind(int x)
{
	if(x==f[x])return x;
	return f[x]=afind(f[x]);
}
int main()
{
	//freopen("eegcd.in","r",stdin);
	//freopen("eegcd.out","w",stdout);
	scanf("%d%d",&n,&m);
	k=sqrt(m);
	for(int i=1;i<=m;i++)scanf("%d%d%d%d",&a[i].from,&a[i].to,&a[i].dat1,&a[i].dat2);
	int q;
	scanf("%d",&q);
	for(int i=1;i<=q;i++)scanf("%d%d%d%d",&b[i].u,&b[i].v,&b[i].dat1,&b[i].dat2),b[i].name=i;
	sort(a+1,a+1+m,cmp);
	sort(b+1,b+1+q,cmp2);
	for(int i=1;i<=m;i++)p[i]=(i-1)/k+1;
	a[m+1].dat1=-1;
	for(int i=1;(i-1)*k+1<=m;i++)
	{
		int l=(i-1)*k+1,r=min(i*k,m),bef=0;
		for(int j=1;j<=n;j++)f[j]=j,fa[j]=fb[j]=-1;
		for(int j=1;j<=q;j++)
		{
			if(b[j].dat1>=a[l].dat1&&b[j].dat1<=a[r].dat1)
			{
				if(b[j].dat1==a[r].dat1&&a[r+1].dat1==b[j].dat1)continue;
				int cnt=0;
				for(int t=bef+1;t<=l-1&&a[t].dat2<=b[j].dat2;t++)
				{
					fa[afind(a[t].to)]=max(fa[afind(a[t].to)],max(fa[afind(a[t].from)],a[t].dat1));
					fb[afind(a[t].to)]=max(fb[afind(a[t].to)],max(fb[afind(a[t].from)],a[t].dat2));
					f[afind(a[t].from)]=afind(a[t].to);
					bef=t;
				}
				for(int t=l;t<=r&&a[t].dat1<=b[j].dat1;t++)
				{
					if(a[t].dat2<=b[j].dat2)
					{
						qp[++cnt]={bfind(a[t].from),bfind(a[t].to),f[bfind(a[t].from)],fa[bfind(a[t].to)],fb[bfind(a[t].to)]};
						fa[bfind(a[t].to)]=max(fa[bfind(a[t].to)],max(fa[bfind(a[t].from)],a[t].dat1));
						fb[bfind(a[t].to)]=max(fb[bfind(a[t].to)],max(fb[bfind(a[t].from)],a[t].dat2));
						f[bfind(a[t].from)]=f[bfind(a[t].to)];
					}
				}
				if(bfind(b[j].u)==bfind(b[j].v)&&fa[bfind(b[j].u)]==b[j].dat1&&fb[bfind(b[j].u)]==b[j].dat2)ans[b[j].name]=1;
				for(int t=cnt;t>=1;t--)
				{
					fa[qp[t].v]=qp[t].maxa;
					fb[qp[t].v]=qp[t].maxb;
					f[qp[t].u]=qp[t].data;
				}
			}
			
		}
		sort(a+1,a+1+r,cmp3);
	}
	for(int i=1;i<=q;i++)
	{
		if(ans[i])puts("Yes");
		else puts("No");
	}
	return 0; 
}

T3:

题意简述:

貌似题意已经够简单了。

解:

明显莫队。

那么对于加入一个点,相当于到区间内离它最近小于它的点之前,构成的答案最小值都是它自己,之后就是那个点,再依次递归。

那么我们可以预处理出来这个值和这个点。

但递归的复杂度承受不住,所以我们可以把递归的过程绑在一起看成一个前(后)缀和,除了这个序列中最小的数,其他的数的前(后)缀和都是跑满了的,所以用 st 表维护最小数,计算最小数的贡献,剩下的用前(后)缀和跑即可。

预处理可以单调队列加二分处理。

时间复杂度:\(O(n\sqrt n)\)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#define int long long
using namespace std;
const int N=1e5+5;
int n,m,a[N],k,p[N],pre[N],suf[N],fpre[N],fsuf[N],ans[N],res,f[N][25],lg[N],q[N];
struct node
{
	int name,l,r;
}t[N];
int cmp(node fi,node se)
{
	if(p[fi.l]==p[se.l])return fi.r<se.r;
	return p[fi.l]<p[se.l];
}
void prepare()
{
	int r=0;
	for(int i=1;i<=n;i++)
	{
		int lc=0,rc=r;
		while(lc<rc)
		{
			int mid=(lc+rc+1)>>1;
			if(a[q[mid]]<a[i])lc=mid;
			else rc=mid-1;
		}
		pre[i]=q[lc];
		while(a[i]<a[q[r]])r--;
		q[++r]=i;
	
	}
	for(int i=1;i<=n;i++)fpre[i]=fpre[pre[i]]+(i-pre[i])*a[i];
	r=0;
	q[0]=n+1;
	for(int i=n;i>=1;i--)
	{
		int lc=0,rc=r;
		while(lc<rc)
		{
			int mid=(lc+rc+1)>>1;
			if(a[q[mid]]<a[i])lc=mid;
			else rc=mid-1;
		}
		suf[i]=q[lc];
		while(a[i]<a[q[r]])r--;
		q[++r]=i;
	}
	for(int i=n;i>=1;i--)fsuf[i]=fsuf[suf[i]]+(suf[i]-i)*a[i];
}
inline int getl(int l,int r)
{
	int num=a[f[l][lg[r-l+1]]]<a[f[r-(1<<lg[r-l+1])+1][lg[r-l+1]]]?f[l][lg[r-l+1]]:f[r-(1<<lg[r-l+1])+1][lg[r-l+1]];
	return fsuf[l]-fsuf[num]+a[num]*(r-num+1);
}
inline int getr(int l,int r)
{
	int num=a[f[l][lg[r-l+1]]]<a[f[r-(1<<lg[r-l+1])+1][lg[r-l+1]]]?f[l][lg[r-l+1]]:f[r-(1<<lg[r-l+1])+1][lg[r-l+1]];
	return fpre[r]-fpre[num]+a[num]*(num-l+1);
}
signed main()
{
	//freopen("aaseq.in","r",stdin);
	//freopen("aaseq.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	k=sqrt(n);
	lg[0]=-1;
	for(int i=1;i<=n;i++)p[i]=(i-1)/k+1,lg[i]=lg[i/2]+1;
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]),f[i][0]=i;
	a[0]=a[n+1]=-2e9;
	for(int j=1;j<=20;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			f[i][j]=a[f[i][j-1]]<a[f[i+(1<<(j-1))][j-1]]?f[i][j-1]:f[i+(1<<(j-1))][j-1];
	prepare();
	for(int i=1;i<=m;i++)scanf("%lld%lld",&t[i].l,&t[i].r),t[i].name=i;
	sort(t+1,t+1+m,cmp);
	int l=1,r=0;
	for(int i=1;i<=m;i++)
	{
		while(r<t[i].r)res+=getr(l,++r);
		while(l>t[i].l)res+=getl(--l,r);
		while(r>t[i].r)res-=getr(l,r--);
		while(l<t[i].l)res-=getl(l++,r);
		ans[t[i].name]=res;
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	return 0;
}

T4:

题意简述:

给定一个有 \(n\) 个点,其中有 \(m\) 个特殊点的带权无向图,给定可行走距离上限,走到特殊点可以恢复初始可行走距离,每次询问给定 \((u,v)\) 与行走距离上限,求是否能从 \(u\) 走到 \(v\)

解:

对于每一个非特殊点找到最近的特殊点 \(dis_i\),跑多源最短路即可,然后对于边权重新赋值为 \(dis_u+dis_v+dis(u,v)\)。最后跑重构树即可。

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

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int N=2e5+5;
const int M=1000000;
int n,s,m,vis[N],f[2*N],t[2*N],p[2*N][20],co[N],lim,dis[N],dep[N],num,bel[N];
struct node
{
	int from,to,data;
}b[10*N];
struct node2
{
	int to,data;
};
struct node3
{
	int name,data;
};
priority_queue<node3>q;
bool operator <(node3 fi,node3 se)
{
	return fi.data>se.data;
}
vector<node2>c[N];
vector<int>a[N];
int cmp(node fi,node se)
{
	return dis[fi.from]+dis[fi.to]+fi.data<dis[se.from]+dis[se.to]+se.data;
}
int afind(int x)
{
	if(f[x]==x)return x;
	return f[x]=afind(f[x]);
}
void krus()
{
	sort(b+1,b+1+m,cmp);
	for(int i=1;i<=2*n;i++)f[i]=i;
	num=n+1;
	for(int i=1;i<=m;i++)
	{
		int u=bel[b[i].from],v=bel[b[i].to];
		if(afind(u)==afind(v))continue;
		t[++num]=b[i].data+dis[b[i].from]+dis[b[i].to];
		a[num].push_back(afind(u));
		a[num].push_back(afind(v));
		f[afind(u)]=f[afind(v)]=num;
	}
}
void bfs()
{
	for(int i=1;i<=n;i++)if(vis[i])q.push({i,0}),bel[i]=i;
	while(!q.empty())
	{
		int x=q.top().name;
		q.pop();
		int len=c[x].size();
		for(int i=0;i<len;i++)
		{
			if(dis[c[x][i].to]>dis[x]+c[x][i].data)
			{
				dis[c[x][i].to]=dis[x]+c[x][i].data;
				bel[c[x][i].to]=bel[x];
				q.push({c[x][i].to,c[x][i].data+dis[x]});
			}
		}
	}
}
int lca(int x,int y)
{
	if(dep[x]<dep[y])swap(x,y);
	for(int i=19;i>=0;i--)if(dep[p[x][i]]>=dep[y])x=p[x][i];
	if(x==y)return x;
	for(int i=19;i>=0;i--)if(p[x][i]!=p[y][i])x=p[x][i],y=p[y][i];
	return p[x][0];
}
void dfs(int x,int deep)
{
	dep[x]=deep;
	int len=a[x].size();
	for(int i=0;i<len;i++)p[a[x][i]][0]=x,dfs(a[x][i],deep+1);
}
int read()
{
	char ch=getchar();
	int sum=0;
	while(ch>'9'||ch<'0')ch=getchar();
	while(ch>='0'&&ch<='9')sum=(sum<<1)+(sum<<3)+(ch^48),ch=getchar();
	return sum;
}
int main()
{
	freopen("pertol.in","r",stdin);
	freopen("d.out","w",stdout);
	n=read(),s=read(),m=read();
	for(int i=1;i<=s;i++)
	{
		int x;
		x=read();
		vis[x]=1;
	}
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		u=read(),v=read(),w=read();
		b[i]={u,v,w};
		c[u].push_back({v,w});
		c[v].push_back({u,w});
	}
	
	for(int i=1;i<=n;i++)dis[i]=(vis[i]^1)*2e9;
	bfs();
	krus();
	dfs(num,1);
	for(int j=1;j<=19;j++)for(int i=1;i<=2*n;i++)p[i][j]=p[p[i][j-1]][j-1];
	int q;
	q=read();
	for(int i=1;i<=q;i++)
	{
		int u,v,s;
		u=read(),v=read(),s=read();
		int ans=t[lca(u,v)];
		if(ans<=s)putchar('T'),putchar('A'),putchar('K');
		else putchar('N'),putchar('I'),putchar('E');
		putchar('\n');
	}
	return 0;
}
/*
5 5 5
1 2 3 4 5
1 2 1
2 3 2
3 4 3
4 5 4
5 1 5
1
3 1 1
*/

DAY3:

T1:

题意简述:

给定一个数 \(k\),求 \(n\times k(n>0)\) 使得 \(n\times k\) 的各个位子上的数之和最小。

解:

对于一个数操作,如果对它乘 \(10\),该数的各个位子上数之和不变,如果对它加 \(1\),那么位子上的数会 \(+1\)(不考虑进位,因为如果进位实际上也可以用先乘后加表示出来)。

那么我们可以从各个位子上之和最小的正整数 \(1\) 开始,进行广搜,广搜维护现在这个数,那么对于乘 \(10\),我们把它放到队首,\(+1\) 则放在队尾。

然后当找到第一个数取模 \(k\)\(0\) 时,那这个就为答案,因为我们满足了先搜的为位数和最小的。

但是这样没限制深度会一直乘 \(10\) 下去,我们可以记录一个 \(0\)\(k-1\) 的桶,判断这个桶是否被遍历过,对于每一个数,我们对 \(k\) 取模,这样如果这个桶之前被遍历过,之前的解比现在优,且搜下去状态实际上取模 \(k\) 的余数状态相同,那么就没必要搜了。

所以我们维护一个现在的数取模 \(k\) 的值,在记录一个现在这个数的所有位数之和,乘 \(10\) 时位数和不变,而 \(+1\) 时位数和 \(+1\) 即可。

时间复杂度:\(O(k)\)

#include<iostream>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<deque>
#define int long long
using namespace std;
int k,ans=1e18;
const int N=1e5+5;
int a[N];
struct node
{
	int name,data;
};
deque<node>q;
int bfs()
{
	q.push_back({1,1});
	while(!q.empty())
	{
		int x=q.front().name,y=q.front().data;
		a[x]=1;
		q.pop_front();
		if(!x)return y;
		if(!a[x*10%k])q.push_front({x*10%k,y});
		if(!a[(x+1)%k])q.push_back({(x+1)%k,y+1});
	}
	return 0;
}
signed main()
{
	//freopen("small.in","r",stdin);
	//freopen("small.out","w",stdout);
	scanf("%lld",&k);
	int ans=bfs();
	printf("%lld",ans);
	return 0;
}

T2:

题意简述:

删去最少的数使所有的数的 \(\text{gcd}\) 增加。

解:

先对每个数除以所有数的 \(\text{gcd}\),然后剩下的需要找到所有数的质因子,找到一个最多的序列中数拥有的质因子,那么答案就是总数减去拥有这个质因子的数的个数。

用质数筛先预处理,再进行质因子分解,最后取最值即可。

\(-1\) 就判断所有数是否都为 \(1\) 就行。

时间复杂度:\(O(\max(a_i))\)

#include<iostream>
#include<cstdio>
#include<map>
#include<ctime>
using namespace std;
const int N=3e5+5;
const int M=15e6+5;
map<int,int>mp;
int n,ans=1e9,a[N],vis[M],num[M],cnt,pri[M];
int gcd(int x,int y)
{
	if(y==0)return x;
	return gcd(y,x%y);
}
int main()
{
	//freopen("gcd.in","r",stdin);
	//freopen("gcd.out","w",stdout);
	scanf("%d",&n);
	for(int i=2;i<=M-5;i++)
	{
		if(!pri[i])num[++cnt]=i;
		for(int j=1;j<=cnt&&num[j]*i<=M-5;j++)
		{
			pri[num[j]*i]=1;
			if(i%num[j]==0)break; 
		}
	}
	int res=0;
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),res=gcd(res,a[i]);
	for(int i=1;i<=n;i++)
	{
		int x=a[i]/res;
		for(int j=1;num[j]*num[j]<=x;j++)
		{
			if(x%num[j]==0)
			{
				vis[num[j]]++;
				while(x%num[j]==0)x/=num[j];
			}
		}
		if(x!=1)vis[x]++;
	}
	for(int i=1;i<=M-5;i++)if(vis[i]&&vis[i]!=n)ans=min(ans,n-vis[i]);
	if(ans>n)printf("-1");
	else printf("%d",ans);
	return 0;
}

T3:

题意简述:

给定一个长度为 \(n\) 的序列,进行任意次操作,每次可以删去序列中下标为 \(x\) 的倍数的所有数,求剩下的数的和的最大值。

解:

明显最大权闭环子图。

要删去一个数,就连带要删去很多数,就将一个数对连带要删的数,即它的倍数连边,流量无限,然后犹豫我们要尽量多删去负数,所以我们可以用源点对负权点建边,正权点对汇点建边,然后跑最小割,答案即为原来所有的数和加上所有负数的绝对值再减去最小割。

然后就没有了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define int long long
using namespace std;
const int N=105;
int n,p[N],h[2*N],cnt,s,t,ans,vis[2*N],dis[2*N],sum;
struct node
{
	int to,data,next;
}a[4*N*N];
inline void addedge(int u,int v,int w)
{
	a[cnt]={v,w,h[u]};
	h[u]=cnt++;
	a[cnt]={u,0,h[v]};
	h[v]=cnt++;
}
queue<int>q;
bool bfs()
{
	for(int i=1;i<=2*n+3;i++)dis[i]=0;
	dis[s]=1;
	q.push(s);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=h[x];i!=-1;i=a[i].next)
		{
			int v=a[i].to;
			if(dis[v]||a[i].data==0)continue;
			dis[v]=dis[x]+1;	
			//cout<<v<<" "<<dis[v]<<endl;
			q.push(v);
		}
	}
	//cout<<"\n\n\n\n";
	return dis[t];
}
int dfs(int x,int num)
{
	if(x==t)return num;
	vis[x]=1;
	for(int i=h[x];i!=-1;i=a[i].next)
	{
		int v=a[i].to;
		if(vis[v]||a[i].data==0||dis[v]!=dis[x]+1)continue;
		int res=dfs(v,min(num,a[i].data));
		if(res)
		{
			//cout<<x<<" "<<v<<" "<<res<<" "<<a[i].data<<endl;
			a[i].data-=res;
			a[i^1].data+=res;
			return res;
		}
	}
	return 0;
}
signed main()
{
	//freopen("mul.in","r",stdin);
	//freopen("mul.out","w",stdout);
	memset(h,-1,sizeof(h));
	scanf("%lld",&n);
	s=2*n+1,t=2*n+2;
	for(int i=1;i<=n;i++)scanf("%lld",&p[i]),ans+=p[i];
	for(int i=1;i<=n;i++)
	{
		if(p[i]>0)addedge(i,t,p[i]);
		else addedge(s,i,-p[i]),sum-=p[i];
		for(int j=i+i;j<=n;j+=i)addedge(i,j,1e18);
	}
	while(bfs())
	{
		for(int i=1;i<=2*n+3;i++)vis[i]=0;
		int num=dfs(s,1e18);
		while(num)
		{		
			for(int i=1;i<=2*n+3;i++)vis[i]=0;
			sum-=num;
			num=dfs(s,1e18);
		}
	}
	printf("%lld",ans+sum);
	return 0;
}

T4

题意简述:

给定一个 \(n\times(2\times n-1)\) 的金字塔,给出第一行与转移式:

\(f_{i,j}=k\hspace{0.1cm}\text{其中}\hspace{0.1cm}k\hspace{0.1cm}\text{为}\hspace{0.1cm}f_{i-1,j-1},f_{i-1,j},f_{i-1,j+1}\hspace{0.1cm}\text{的中位数}\)

\(f_{n,n}\)

解:

离谱的一道题。

假设我们知道中位数,那我们可以把金字塔里的所有数用 \(0,1\) 表示为是否大于中位数。

那么反过来,假设我们有一个这个 \(0,1\) 序列,我们看是否能推出金字塔顶端的数。

很明显数越大,金字塔最顶端就最有可能是 \(0\),所以可以二分答案。

现在问题在于如何知道金字塔最顶端的两个数是什么。

如果序列中有两个相邻的数相同,那么向上转移时,除非达到边界,否则上面两个数也会是这两个数。(这里都是在 \(0,1\) 序列中)

那么如果到达边界了呢,那就只会有靠近中间的会往上转移。

我们可以先找到左边离中间最近的一对和右边离中间最近的一对,那么这两对数中间一定是 \(0,1\) 交错。

所以离的最近的一队在到达边界时,会达到类似于这样的状态:\(0100\),实际上转移上去就是 \(?00\)

那么可以知道,离的最近的会最终更新到顶部,所以找到离自己的最近一对相邻的数的值就是金字塔顶部的值。

不可能会出现 \(0,1\) 同时最近,如果有,那么中间 \(0,1\) 交错是奇数个,明显会有更近的,所以答案唯一。

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

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2e5+5;
int a[N],t[N],p[N];
int n;
bool check(int x)
{
	for(int i=1;i<=2*n-1;i++)p[i]=(a[i]>x);
	for(int i=1;i<n;i++)
	{
		if(p[n-i+1]==p[n-i])return p[n-i+1];
		if(p[n+i-1]==p[n+i])return p[n+i-1];
	}
	return p[1];
}
int main()
{
	//freopen("Hard.in","r",stdin);
	//freopen("Hard.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=2*n-1;i++)scanf("%d",&a[i]),t[i]=a[i];
	sort(t+1,t+2*n);
	int l=1,r=2*n-1;
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(!check(t[mid]))r=mid;
		else l=mid+1;
	}
	printf("%d",l);
	return 0;
}
posted @ 2023-02-24 13:58  Gmt丶Fu9ture  阅读(26)  评论(0)    收藏  举报