「GDKOI2021普及组Day2」简要题解

T1:初中生数学题

题目大意:给出 a 1 , a 2 , a 3 , … , a 10 a_1,a_2,a_3,\dots,a_{10} a1,a2,a3,,a10,求 1 a 1 × 2 a 2 × 3 a 3 × ⋯ × 1 0 a 10 1^{a_1}\times2^{a_2}\times3^{a_3}\times\dots\times10^{a_{10}} 1a1×2a2×3a3××10a10的结果的从低到高位的第一个非0数。

题解:

首先知道1是无用的,然后我们可以将2~10分解到2、3、5、7这4个质数,然后根据小学知识知道,0是由2和5产生的,那么2和5的指数减去两者中最小值就不会产生0了。然后题目转换为求几个数相乘后的最后一位,显然可以乘起来模10,龟速乘和快速幂都可以

当然也可以用周期, O ( 1 ) O(1) O(1)解决,快人一等……

当然你用高精度+快速幂说不定也可以……

Code

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll ans,ten,a[15];
bool flag;
int main()
{
	for (int i=1;i<=10;++i)
		scanf("%d",&a[i]);
	a[2]+=a[6];
	a[3]+=a[6];
	a[6]=0;
	a[2]+=a[4]*2;
	a[2]+=a[8]*3;
	a[4]=a[8]=0;
	a[3]+=a[9]*2;
	a[9]=0;
	a[2]+=a[10];
	a[5]+=a[10];
	a[10]=0;
	ten=min(a[2],a[5]);
	a[2]-=ten;
	a[5]-=ten;
	ans=1;
	flag=false;
	for (int i=1;i<=10;++i)
	{
		for (int j=1;j<=a[i];++j)
		{
			flag=true;
			ans=ans*i%10;
		}
	}
	printf("%d\n",ans); 
	return 0;
} 

T2:二叉树

题目大意:给出一颗二叉搜索树的 BFS \text{BFS} BFS序,判断这棵树是否是正则二叉树

题解:

我们可以预处理这棵二叉搜索数的大小关系,然后用两个指针 i , j i,j i,j,分别指向序列和树,然后把序列上的数放到树上来,记录一下每个节点的儿子节点的个数。最后看能不能找到一个点的儿子节点的数量不为2或0即可

Code

#include<cstdio>
#include<cstring>
#include<cmath>
#define N 100005
#define inf 1e9
using namespace std;
struct node
{
	int mn,mx;
}tree[200005];
int t,n,a[N],v[N];
bool flag,bj[N],bz[N];
int read()
{
	int res=0;char ch=getchar();
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar();
	return res;
}
void build(int now,int l,int r)
{
	if (now>N) return;
	tree[now].mn=l;
	tree[now].mx=r;
	build(now<<1,l,now);
	build(now<<1|1,now,r);
}
int main()
{
	build(1,0,N);
	t=read();
	while (t--)
	{
		memset(bj,true,sizeof(bj));
		memset(bz,false,sizeof(bz));
		memset(v,0,sizeof(v));
		n=read();
		for (int i=1;i<=n;++i)
			a[i]=read();
		v[N]=inf;
		v[0]=-1;
		int i=1,j=1;
		flag=true;
		while (j<=n&&i<=N)
		{
			while (!v[i>>1]) ++i;
			if (a[j]>v[tree[i].mn]&&a[j]<=v[tree[i].mx]) 
			{
				v[i]=a[j];
				bz[i>>1]=true;
				++j;
			}
			else bj[i>>1]=false;
			++i;
		}
		for (i=1;i<=n;++i)
			if (bz[i]&&!bj[i])
			{
				flag=false;
				break;
			}		
		if (flag) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

为什么讲的不清楚是因为我也没搞清楚我在打什么

T3:我的世界

题目大意:有两个世界:主世界和异界,其中若一条边在异界的权值为 e e e,那么在主世界也会有对应的边,权值为 8 e 8e 8e。任意一个点 i i i都可以花费 a i a_i ai进入异界或主世界。求从主世界的 x x x走到 y y y的经过最少点的最短路径。

题解:

容易得出只会下去上来一次的结论

那么就可以分类讨论( d i s [ x ] dis[x] dis[x]表示从 x x x到根的在异界的最短路, u u u为路径上最优去异界的点, v v v为路径上最优回主世界的点)

  1. 不去异界: a n s = ( d i s [ x ] − d i s [ l c a ] + d i s [ y ] − d i s [ l c a ] ) × 8 ans=(dis[x]-dis[lca]+dis[y]-dis[lca])\times8 ans=(dis[x]dis[lca]+dis[y]dis[lca])×8
  2. x x x l c a lca lca的路径上去异界,在 l c a lca lca y y y的路径上回主世界: a n s = ( d i s [ x ] − d i s [ u ] ) × 8 + ( d i s [ u ] − d i s [ l c a ] ) + ( d i s [ v ] − d i s [ l c a ] ) + ( d i s [ y ] − d i s [ v ] ) × 8 ans=(dis[x]-dis[u])\times8+(dis[u]-dis[lca])+(dis[v]-dis[lca])+(dis[y]-dis[v])\times8 ans=(dis[x]dis[u])×8+(dis[u]dis[lca])+(dis[v]dis[lca])+(dis[y]dis[v])×8
  3. x x x l c a lca lca的路径上去异界,在 x x x l c a lca lca的路径上回主世界:注意到这种情况需要满足 u u u v v v下面,那么就可以先求 u u u再求 u u u l c a lca lca上的 v v v,也可以先求 v v v,再求 x x x v v v上的 u u u,两种情况比较就可以了。 a n s = ( d i s [ x ] − d i s [ u ] ) × 8 + ( d i s [ u ] − d i s [ v ] ) + ( d i s [ v ] − d i s [ l c a ] ) × 8 + ( d i s [ y ] − d i s [ l c a ] ) × 8 ans=(dis[x]-dis[u])\times8+(dis[u]-dis[v])+(dis[v]-dis[lca])\times8+(dis[y]-dis[lca])\times8 ans=(dis[x]dis[u])×8+(dis[u]dis[v])+(dis[v]dis[lca])×8+(dis[y]dis[lca])×8
  4. l c a lca lca y y y的路径上去异界,在 l c a lca lca y y y的路径上回主世界:这种情况要满足 u u u v v v上面,求法跟第3中情况类似。 a n s = ( d i s [ x ] − d i s [ l c a ] ) × 8 + ( d i s [ u ] − d i s [ l c a ] ) × 8 + ( d i s [ v ] − d i s [ u ] ) + ( d i s [ y ] − d i s [ v ] ) × 8 ans=(dis[x]-dis[lca])\times8+(dis[u]-dis[lca])\times8+(dis[v]-dis[u])+(dis[y]-dis[v])\times8 ans=(dis[x]dis[lca])×8+(dis[u]dis[lca])×8+(dis[v]dis[u])+(dis[y]dis[v])×8

那么现在问题就难在求 u u u v v v

可以考虑倍增,设 g 1 [ i ] [ j ] g1[i][j] g1[i][j]表示从 i i i往上 2 j − 1 2^j-1 2j1的区间内某个最优点, g 2 [ i ] [ j ] g2[i][j] g2[i][j]表示从 i i i往上 2 j − 1 2^j-1 2j1的区间另一个最优的点(好像说了没说,其实这两个的不同只有比较的方式)

直接放出如何比较:

g 1 g1 g1:如果 u u u v v v优( u u u v v v下面),那么就有 a [ u ] + ( d i s [ u ] − d i s [ v ] ) < a [ v ] + ( d i s [ u ] − d i s [ v ] ) × 8 a[u]+(dis[u]-dis[v])<a[v]+(dis[u]-dis[v])\times8 a[u]+(dis[u]dis[v])<a[v]+(dis[u]dis[v])×8

反之则是 v v v u u u

g 2 g2 g2:如果 u u u v v v优( u u u v v v下面),那么就有 a [ u ] + ( d i s [ u ] − d i s [ v ] ) × 8 < a [ v ] + ( d i s [ u ] − d i s [ v ] ) a[u]+(dis[u]-dis[v])\times8<a[v]+(dis[u]-dis[v]) a[u]+(dis[u]dis[v])×8<a[v]+(dis[u]dis[v])

反之则是 v v v u u u

然后在查询的时候,我们从 x x x y y y不断向上跳 2 j 2^j 2j,然后比较一下就好

这个比较也需要分类讨论……

  1. x x x l c a lca lca的路上找去异界的点:比较 g 1 g1 g1
  2. x x x l c a lca lca的路上找回主世界的点:比较 g 2 g2 g2
  3. y y y l c a lca lca的路上找去异界的点:比较 g 2 g2 g2
  4. y y y l c a lca lca的路上找回主世界的点:比较 g 1 g1 g1

自己画画图理解就好了

Code

我的代码有点奇怪是因为学校的出题人卡栈而且卡常……

#pragma GCC optimize(2)
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
#define N2 500005
#define ll long long
using namespace std;
struct node
{
	ll val;
	int to,next,head;
}a[N<<1];
int n,q,x,y,tot,lca,deep[N],f[N2][20],g1[N2][20],g2[N2][20],d[N][3];
ll z,ans1,ans2,ans3,ans4,c[N],dis[N];;
void add(int x,int y,ll z)
{
	a[++tot].to=y;
	a[tot].val=z;
	a[tot].next=a[x].head;
	a[x].head=tot;
}
void bfs()
{
	int h=0,t=1;
	d[1][1]=1;
	d[1][2]=0;
	d[1][3]=0;
	while (h<t)
	{
		int now=d[++h][1];
		f[now][0]=d[h][2];
		g1[now][0]=g2[now][0]=now;
		deep[now]=deep[f[now][0]]+1;
		dis[now]=dis[f[now][0]]+d[h][3];
		for (int i=1;i<=18;++i)
		{
			f[now][i]=f[f[now][i-1]][i-1];
			int u=g1[now][i-1],v=g1[f[now][i-1]][i-1];
			if (c[u]<c[v]+7*(dis[u]-dis[v])) g1[now][i]=u;
			else g1[now][i]=v;
			u=g2[now][i-1],v=g2[f[now][i-1]][i-1];
			if (c[u]<c[v]-7*(dis[u]-dis[v])) g2[now][i]=u;
			else g2[now][i]=v;
		}
		for (int i=a[now].head;i;i=a[i].next)
		{
			int v=a[i].to;
			if (v==f[now][0]) continue;
			d[++t][1]=v;d[t][2]=now;d[t][3]=a[i].val;
		}
	}
}
int LCA(int x,int y)
{
	if (deep[x]!=deep[y])
	{
		if (deep[x]<deep[y]) swap(x,y);
		for (int i=18;i>=0;--i)
			if (deep[f[x][i]]>deep[y]) x=f[x][i];
		x=f[x][0];
	}
	if (x==y) return x;
	for (int i=18;i>=0;--i)
		if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
}
int get_up_down(int x,int y)
{
	if (x==y) return x;
	int u=0;
	for (int i=18;i>=0;--i)
	{
		if(deep[f[x][i]]>deep[y])
		{
			int v=g1[x][i];
			if (!u) u=v;
			else if (c[u]>c[v]+7*(dis[u]-dis[v])) u=v;
			x=f[x][i];
		}
	}
	int v=g1[f[x][0]][0];
	if (!u) u=v;
	else if (c[u]>c[v]+7*(dis[u]-dis[v])) u=v;
	v=g1[x][0];
	if (!u) u=v;
	else if (c[u]>c[v]+7*(dis[u]-dis[v])) u=v;
	return u;
}
int get_up_up(int x,int y)
{
	if (x==y) return x;
	int u=0;
	for (int i=18;i>=0;--i)
	{
		if(deep[f[x][i]]>deep[y])
		{
			int v=g2[x][i];
			if (!u) u=v;
			else if (c[u]>c[v]-7*(dis[u]-dis[v])) u=v;
			x=f[x][i];
		}
	}
	int v=g2[f[x][0]][0];
	if (!u) u=v;
	else if (c[u]>c[v]-7*(dis[u]-dis[v])) u=v;
	v=g2[x][0];
	if (!u) u=v;
	else if (c[u]>c[v]-7*(dis[u]-dis[v])) u=v;
	return u;
} 
int get_down_up(int x,int y)
{
	if (x==y) return x;
	int u=0;
	for (int i=18;i>=0;--i)
	{
		if(deep[f[x][i]]>deep[y])
		{
			int v=g1[x][i];
			if (!u) u=v;
			else if (c[u]>c[v]+7*(dis[u]-dis[v])) u=v;
			x=f[x][i];
		}
	}
	int v=g1[f[x][0]][0];
	if (!u) u=v;
	else if (c[u]>c[v]+7*(dis[u]-dis[v])) u=v;
	v=g1[x][0];
	if (!u) u=v;
	else if (c[u]>c[v]+7*(dis[u]-dis[v])) u=v;
	return u;
}
int get_down_down(int x,int y)
{
	if (x==y) return x;
	int u=0;
	for (int i=18;i>=0;--i)
	{
		if(deep[f[x][i]]>deep[y])
		{
			int v=g2[x][i];
			if (!u) u=v;
			else if (c[u]>c[v]-7*(dis[u]-dis[v])) u=v;
			x=f[x][i];
		}
	}
	int v=g2[f[x][0]][0];
	if (!u) u=v;
	else if (c[u]>c[v]-7*(dis[u]-dis[v])) u=v;
	v=g2[x][0];
	if (!u) u=v;
	else if (c[u]>c[v]-7*(dis[u]-dis[v])) u=v;
	return u;
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
		scanf("%lld",&c[i]);
	for (int i=1;i<n;++i)
	{
		scanf("%d%d%lld",&x,&y,&z);
		add(x,y,z);add(y,x,z);
	}
	bfs();
	scanf("%d",&q);
	while (q--)
	{
		int up,down;
		scanf("%d%d",&x,&y);
		lca=LCA(x,y);
		ans1=(dis[x]-dis[lca]+dis[y]-dis[lca])*8;
		down=get_up_down(x,lca);up=get_down_up(y,lca);
		ans2=(dis[x]-dis[down])*8+(dis[down]-dis[lca])+(dis[up]-dis[lca])+(dis[y]-dis[up])*8+c[down]+c[up]; 
		down=get_up_down(x,lca);up=get_up_up(down,lca);
		ans3=(dis[x]-dis[down])*8+(dis[down]-dis[up])+(dis[up]-dis[lca])*8+(dis[y]-dis[lca])*8+c[down]+c[up];
		up=get_up_up(x,lca);down=get_up_down(x,up);
		ans3=min(ans3,(dis[x]-dis[down])*8+(dis[down]-dis[up])+(dis[up]-dis[lca])*8+(dis[y]-dis[lca])*8+c[down]+c[up]);
		down=get_down_down(y,lca);up=get_down_up(y,down);
		ans4=(dis[x]-dis[lca])*8+(dis[down]-dis[lca])*8+(dis[up]-dis[down])+(dis[y]-dis[up])*8+c[down]+c[up];
		up=get_down_up(y,lca);down=get_down_down(up,lca);
		ans4=min(ans4,(dis[x]-dis[lca])*8+(dis[down]-dis[lca])*8+(dis[up]-dis[down])+(dis[y]-dis[up])*8+c[down]+c[up]);
		printf("%lld\n",min(min(ans1,ans2),min(ans3,ans4)));
	}
	return 0;	
} 

T4:矩阵

题目大意:给出一个 n × n n\times n n×n的矩阵 A A A,设 r i r_i ri A A A里第 i i i行的元素和, c j c_j cj A A A里第 j j j列的元素和,所有的 r i r_i ri c j c_j cj都是偶数。现在要矩阵 A A A拆成两个矩阵 B , C B,C B,C,满足 B + C = A B+C=A B+C=A,且对于任意 1 ≤ i ≤ n 1\le i\le n 1in,都有 ∑ j = 1 n B i , j = ∑ j = 1 n C i , j , ∑ j = 1 n B j , i = ∑ j = 1 n C j , i \sum\limits_{j=1}^nB_{i,j}=\sum\limits_{j=1}^nC_{i,j},\sum\limits_{j=1}^nB_{j,i}=\sum\limits_{j=1}^nC_{j,i} j=1nBi,j=j=1nCi,j,j=1nBj,i=j=1nCj,i 求任意一种方案

题解:

首先对于 A A A里的所有偶数都可以两两分开,奇数留下1后也可以两两分开,那么 A A A就变成了一个01矩阵,每行每列选取1的个数的一半给 B B B,剩下的给 C C C

那么就可以玩二分图了!!!

以下的 A A A都是拆分后的01矩阵

我们把行和列分开来

如果 A i , j = 1 A_{i,j}=1 Ai,j=1,那么就从 i i i j + n j+n j+n连一条流量为1的边( i i i表示第 i i i行, j + n j+n j+n表示第 j j j列)

然后设一个源点 S S S,汇点 T T T,然后 S S S连向所有的 i i i,流量为那一行的1的个数的一半。所有的 j + n j+n j+n连向 T T T,流量为那一列的1的个数的一半,然后跑一下最大流

最后看一下哪条边流完了,就说明对应的那个点在 B B B(或者 C C C)中是1。如果没有流完,那么就是另一个矩阵里是1

Code

#include<cstdio>
#include<cstring> 
#include<algorithm>
#define N 3005
#define ll long long
#define inf 2147483647
using namespace std;
struct node
{
	int val,to,next;
}net[N*N];
int n,tot,s,t,a[N][N],b[N][N],c[N][N],hang[N],lie[N],deep[N],q[N],head[N<<2],cur[N<<2];
int read()
{
	int res=0;char ch=getchar();
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar();
	return res;
}
void add(int x,int y,int z)
{
	net[tot].to=y;
	net[tot].val=z;
	net[tot].next=head[x];
	head[x]=tot++;	
} 
bool bfs(int st,int en)
{
	int hd=0,tl=1;
	for (int i=1;i<=2*n+2;++i)
		cur[i]=head[i];
	memset(deep,0,sizeof(deep));
	q[1]=st;
	deep[st]=1;
	while (hd<tl)
	{
		for (int i=head[q[++hd]];i!=-1;i=net[i].next)
		{
			int x=net[i].to;
			if (!deep[x]&&net[i].val>0)
			{
				q[++tl]=x;
				deep[x]=deep[q[hd]]+1;
			}
		}
	}
	if (deep[en]==0) return false;
	else return true;
}
int dfs(int now,int t,int sum)
{
	if (now==t||sum==0) return sum;
	int delat=0,del;
	for (int i=cur[now];i!=-1;i=net[i].next)
	{
		cur[now]=i;
		if (deep[net[i].to]==deep[now]+1&&net[i].val>0)
		{
			int x=net[i].to,del=dfs(x,t,min(sum,net[i].val));
			if (del<=0) deep[x]=0;
			sum-=del;
			delat+=del;
			net[i].val-=del;
			net[i^1].val+=del;
			if (sum==0) break;
		}
	}
	return delat;
}
void dinic(int s,int t)
{
	while (bfs(s,t)==true) 
		dfs(s,t,inf);
}
int main()
{
	memset(head,-1,sizeof(head));
	n=read();
	for (int i=1;i<=n;++i)
		for (int j=1;j<=n;++j)
		{
			a[i][j]=read();
			b[i][j]=c[i][j]=a[i][j]/2;
			a[i][j]%=2;
			hang[i]+=a[i][j];
			lie[j]+=a[i][j];
		}
	s=2*n+1;t=2*n+2;
	for (int i=1;i<=n;++i)
		for (int j=1;j<=n;++j)
			if (a[i][j])
			{
				add(i,j+n,1);
				add(j+n,i,0);
			}
	for (int i=1;i<=n;++i)
	{
		if (hang[i]) add(s,i,hang[i]/2),add(i,s,0);
		if (lie[i]) add(i+n,t,lie[i]/2),add(t,i+n,0);
	}
	dinic(s,t);
	for (int i=1;i<=n;++i)
	{
		for (int j=head[i];j!=-1;j=net[j].next)
		{
			if (net[j].to==s) continue;
			if (net[j].val==0) ++b[i][net[j].to-n];
			else ++c[i][net[j].to-n];
		}
	}
	for (int i=1;i<=n;++i)
	{
		for (int j=1;j<=n;++j)
			printf("%d ",b[i][j]);
		printf("\n");
	}
	for (int i=1;i<=n;++i)
	{
		for (int j=1;j<=n;++j)
			printf("%d ",c[i][j]);
		printf("\n");
	}
	return 0;
}
微信扫码订阅
UP更新不错过~
关注
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thunder_S

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值