zoukankan      html  css  js  c++  java
  • 【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;
    }
    
  • 相关阅读:
    Mysql配置文件模板
    shell命令记录
    SuSE Linux Enterprise Server
    安装jdk1.8
    云南-第一个应用节点-ssh登录-卡顿的问题
    Python重新安装pip
    Centos6.5修改镜像为国内的阿里云源
    supervisord.conf
    Pandas连接Mysql数据库
    Vim速查命令简版
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC025.html
Copyright © 2011-2022 走看看