【AtCoder】AtCoder Grand Contest 025 解题报告(A~E)

点此进入比赛

(A):Digits Sum(点此看题面

  • 定义(s(x))为十进制数(x)的各位数码和。
  • 给出一个数(n),求(min_{i=1}^{n-1}(s(i)+s(n-i)))
  • (nle10^5)

签到题

暴枚即可。

代码:(O(nlg n))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
using namespace std;
int n;I int S(RI x) {RI t=0;W(x) t+=x%10,x/=10;return t;}//求数码和
int main()
{
	RI i,t=1e9;for(scanf("%d",&n),i=1;i^n;++i) t=min(t,S(i)+S(n-i));//暴枚
	return printf("%d
",t),0;
}

(B):RGB Coloring(点此看题面

  • 有一个长度为(n)的序列,给定两个数(A,B)
  • 你可以在序列每个位置上填入(A,B,A+B,0)中的一个。
  • 求使得序列总和为(k)的方案数。
  • (n,A,Ble3 imes10^5,Kle18 imes{10^{10}})

组合数学

考虑我们枚举填了(i)(A),然后判一下(j=frac{k-iA}B)是否是一个在([0,n])范围内的整数,就可以得到所有使得(iA+jB=k)的方案。

对于一种方案,发现放(A)和放(B)是独立的,因此对答案的贡献就是(C_n^i imes C_n^j)

代码:(O(n))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define LL long long
#define N 300000
#define X 998244353
#define C(x,y) (1LL*Fac[x]*IFac[y]%X*IFac[(x)-(y)]%X)
using namespace std;
int n,A,B,pw[N+5],Fac[N+5],IFac[N+5];LL k;
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
int main()
{
	RI i,j,t=0;scanf("%d%d%d%lld",&n,&A,&B,&k);
	for(Fac[0]=i=1;i<=n;++i) Fac[i]=1LL*Fac[i-1]*i%X;//预处理阶乘
	for(IFac[n]=QP(Fac[n],X-2),i=n-1;~i;--i) IFac[i]=1LL*IFac[i+1]*(i+1)%X;//预处理阶乘逆元
	for(i=0;i<=n&&1LL*i*A<=k;++i)//枚举放i个A
	{
		if((k-1LL*i*A)%B||(k-1LL*i*A)/B>n) continue;j=(k-1LL*i*A)/B;//找到一组方案
		t=(1LL*C(n,i)*C(n,j)+t)%X;//求出对答案的贡献
	}return printf("%d
",t),0;
}

(C):Interval Game(点此看题面

  • 有一个数轴,给定(n)个区间。
  • 现在甲、乙两人要进行一个(n)轮的游戏,初始时乙在原点。对于第(i)轮:
    • 甲选择(n)个区间中一个尚未选过的区间。
    • 乙走到该区间中任意一个点。
  • 甲想要最大化乙的路程,乙想要最小化乙的路程,求乙最终的路程。
  • (nle10^5)

贪心

首先对于乙,容易发现局部最优解就是全局最优解,因此每次直接贪心走到当前区间离他最近的点就好了。

然后对于甲,肯定尽可能让乙左右横跳,因此轮流选择剩余区间中左端点最大、右端点最小的区间(或轮流选择右端点最小、左端点最大)即可。

代码:(O(nlogn))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LL long long
using namespace std;
int n,l[N+5],r[N+5],a[N+5],b[N+5],vis[N+5];
I bool cmp1(CI x,CI y) {return l[x]>l[y];}I bool cmp2(CI x,CI y) {return r[x]<r[y];}
I int Dis(int& x,CI p)//求出点x到区间p的距离,同时移动x
{
	RI t;if(l[p]<=x&&x<=r[p]) return 0;//若x在区间中,不动
	if(x>r[p]) return t=x-r[p],x=r[p],t;return t=l[p]-x,x=l[p],t;//移到较近的端点
}
int main()
{
	RI i,x,p,q;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d",l+i,r+i);
	for(i=1;i<=n;++i) a[i]=b[i]=i;sort(a+1,a+n+1,cmp1),sort(b+1,b+n+1,cmp2);
	LL t1=0;for(x=0,p=q=i=1;i<=n;++i)//轮流选择左端点较大、右端点较小
	{
		W(p<=n&&vis[a[p]]) ++p;if(p>n) break;t1+=Dis(x,a[p]),vis[a[p]]=1;
		W(q<=n&&vis[b[q]]) ++q;if(q>n) break;t1+=Dis(x,b[q]),vis[b[q]]=1;
	}t1+=abs(x);
	LL t2=0;for(x=0,p=q=i=1;i<=n;++i)//轮流选择右端点较小、左端点较大
	{
		W(q<=n&&!vis[b[q]]) ++q;if(q>n) break;t2+=Dis(x,b[q]),vis[b[q]]=0;
		W(p<=n&&!vis[a[p]]) ++p;if(p>n) break;t2+=Dis(x,a[p]),vis[a[p]]=0;
	}t2+=abs(x);
	return printf("%lld
",max(t1,t2)),0;//两种情况取较大值
}

(D):Choosing Points(点此看题面

  • 有一张(2n imes 2n)的网格图,给定(D_1,D_2)
  • 要求构造一种方案,从中选出(n^2)个点,满足任意两点距离既不是(sqrt{D_1})也不是(sqrt{D_2})
  • (nle300)

平方和模(4)的余数

众所周知,一个数的平方模(4)意义下有一些性质。

对于一个奇数,((2k+1)^2=4(k^2+k)+1equiv1(mod 4))

对于一个偶数,((2k)^2=4k^2equiv0(mod 4))

所以对于某一个(D),若它是两个数的平方和,可以根据其模(4)的余数讨论。

注意,两数平方和模(4)显然不可能余(3),因此这种情况直接忽略。

(Dequiv1(mod 4))

(D)必然为一个奇数和一个偶数的平方和。

则我们可以采取国际棋盘的选数方式,显然任意两点横纵距离奇偶性相同。

例如:

[egin{matrix} 0 & 1 & 0 & 1 & 0 & 1\ 1 & 0 & 1 & 0 & 1 & 0\ 0 & 1 & 0 & 1 & 0 & 1\ 1 & 0 & 1 & 0 & 1 & 0\ 0 & 1 & 0 & 1 & 0 & 1\ 1 & 0 & 1 & 0 & 1 & 0 end{matrix} ]

(Dequiv2(mod 4))

(D)必然为两个奇数的平方和。

则我们可以采取隔行的选数方式,显然两点竖直距离必然为偶数。

例如:

[egin{matrix} 0 & 0 & 0 & 0 & 0 & 0\ 1 & 1 & 1 & 1 & 1 & 1\ 0 & 0 & 0 & 0 & 0 & 0\ 1 & 1 & 1 & 1 & 1 & 1\ 0 & 0 & 0 & 0 & 0 & 0\ 1 & 1 & 1 &1 & 1 & 1 end{matrix} ]

(Dequiv0(mod 4))

(D)必然为两个偶数的平方和。

(D=x^2+y^2),则(frac{D}4=(frac x2)^2+(frac y2)^2)

所以我们不断将(D)除以(4)直至它不再是(4)的倍数,与此同时我们将每个单位的长宽分别变为原来的(2)倍,然后按上面两种情况做。

说起来可能有点抽象,例如(D=8):(此时是以每个(2 imes2)的正方形为一个单位)

[egin{matrix} 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1\ 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1\ 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ end{matrix} ]

代码:(O(n^2))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 300
using namespace std;
int n,nn,d1,d2,a[2*N+5][2*N+5];struct Data {int x,y;}s[N*N+5];
I void Draw(RI x)
{
	RI i,j,t=0;W(!(x%4)) x/=4,++t;//若是4的倍数,不断除以4,同时扩大单位长度
	if(x%4==1) for(i=0;i^nn;++i) for(j=0;j^nn;++j) a[i][j]|=((i>>t)^(j>>t))&1;//棋盘
	if(x%4==2) for(i=0;i^nn;++i) for(j=0;j^nn;++j) a[i][j]|=(i>>t)&1;//隔行
}
int main()
{
	RI i,j,t=0;scanf("%d%d%d",&n,&d1,&d2),nn=n<<1;Draw(d1),Draw(d2);
	for(i=0;i^nn;++i) for(j=0;j^nn;++j) !a[i][j]&&t^(n*n)&&(printf("%d %d
",i,j),++t);return 0;//输出前n^2个没打过标记的点即可
}

(E):Walking on a Tree(点此看题面

  • 给定一棵(n)个点的树以及(m)条树上路径。
  • 你需要给这(m)条树上路径定向,并定义每条树边的权值是经过它的路径的方向数。
  • 求最大权值总和,并给出任意一种定向方案。
  • (n,mle2000)

题解

考虑我们从叶节点开始往上搞,并分类讨论:

  • 如果一个叶节点没有经过它的路径,直接忽略。

  • 如果一个叶节点有一条路径,那么无论这条路径如何定向,这个叶节点到其父节点的路径贡献都是(1)。那么可以直接把这条路径送给父节点,对答案没有影响。

  • 如果一个叶节点有大于一条路径,我们可以从中任选两条,让它们方向不同,贡献就可以取到(2)

    • 假设叶节点为(k),两条路径另一端点分别为(a,b),则(k)(a,b)路径交集中的所有边贡献都成了(2)
    • 而对于其他边,只要保留下一条端点为(a,b)的新的路径,容易发现并不会对能取到的最大贡献造成影响。且(k,a)(k,b)两条路径的方向完全取决于(a,b)这条路径的方向。

    而这个叶节点其他边可以和上种情况一样直接送给父节点。

具体实现需要对每个点开个(set),然后就是模拟了。

代码:(O(n(n+m)logm))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,m,fa[N+5],dep[N+5],deg[N+5],tg[N+5],X[N+5],Y[N+5];
int ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
struct Edge {int x,y,f,t;}s[N+5];set<int> S[N+5];set<int>::iterator it;
I void Init(CI x=1)//初始化,求出每个点的深度及其父节点
{
	for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^fa[x]&&
		(dep[e[i].to]=dep[fa[e[i].to]=x]+1,Init(e[i].to),++deg[x]);
}
I int Mark(CI x,CI a,CI b,CI lst=0)//给x到a,b路径交集打标记
{
	#define M(x,y) (dep[e[x].to]>dep[e[y].to]?tg[e[x].to]=1:tg[e[y].to]=1)//一条边的标记打在深度较大的端点上
	RI t,res=(x==a)<<1|(x==b);
	for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
		((t=Mark(e[i].to,a,b,x))==3&&M(i,((i-1)^1)+1),res|=t);//如果得到3,说明这条边能到达a,b两点
	return res;
}
int Q[N+5];I int Topo()//拓扑
{
	RI i,k,H=1,T=0,res=0,p,q;for(i=1;i<=n;++i) !deg[i]&&(Q[++T]=i);W(Q[H]^1)//初始化拓扑序列
	{
		#define Swap(i) (swap(s[i].x,s[i].y),s[i].t^=1)//交换i号边的两个端点
		#define Union(x,y) (s[y].t^=s[s[y].f=x].t)//y的方向取决于x,注意先异或上x已有的变化值
		#define Trans(i,t) ((s[i].x=t)^s[i].y?(S[t].insert(i),0):(S[t].erase(i),0))//将i的左端点修改为t,注意判断当边缩成点时要删掉
		k=Q[H++],!--deg[fa[k]]&&(Q[++T]=fa[k]),//拓扑
		(tg[k]||S[k].size()>1)?res+=2:res+=S[k].size();//计算这条边的贡献
		for(it=S[k].begin();it!=S[k].end();++it) s[*it].y==k&&Swap(*it);//方便起见,将所有边左端点改为当前点
		!tg[k]&&S[k].size()>1&&//如果没打过标记且有超过一条边
		(
			p=*S[k].begin(),S[k].erase(S[k].begin()),q=*S[k].begin(),S[k].erase(S[k].begin()),//任取两条边
			Mark(k,s[p].y,s[q].y),Union(p,q),s[q].t^=1,S[s[q].y].erase(q),Trans(p,s[q].y)//打标记,把p变成a,b,删去q且其方向取决于p
		);
		for(it=S[k].begin();it!=S[k].end();++it) Trans(*it,fa[k]);//剩下的边送给父节点
	}return res;
}
int vis[N+5];I void F5(CI x) {s[x].f&&!vis[x]&&(F5(s[x].f),vis[x]=1,s[x].t^=s[s[x].f].t);}//求出每条边的最终方向
int main()
{
	RI i,x,y;for(scanf("%d%d",&n,&m),i=1;i^n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);Init();
	for(i=1;i<=m;++i) scanf("%d%d",X+i,Y+i),S[s[i].x=X[i]].insert(i),S[s[i].y=Y[i]].insert(i);
	for(printf("%d
",Topo()),i=1;i<=m;++i) F5(i),//输出方案之前先更新信息
		s[i].t&&swap(X[i],Y[i]),printf("%d %d
",X[i],Y[i]);return 0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC025.html