AtCoder Grand Contest 018

Preface

昨天太颓废了今天趁着不比赛的空余多打点吧


A - Getting Difference

容易发现如果我们给集合中所有数排序那么能变化的最小差量就是\(d=\gcd(a_2-a_1,a_3-a_2,\cdots,a_n-a_{n-1})\)

显然此时若某个数\(a_i\)要变化成\(k\)需要满足\(d|a_i-k(a_i\ge k)\)

注意要给数去重并且特判去重之后\(n=1\)的情况

#include<cstdio>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,k,a[N],d;
int main()
{
	RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]);
	sort(a+1,a+n+1); n=unique(a+1,a+n+1)-a-1;
	if (n==1) return puts(a[1]==k?"POSSIBLE":"IMPOSSIBLE"),0;
	for (i=1;i<n;++i) d=__gcd(d,a[i+1]-a[i]);
	for (i=1;i<=n;++i) if (a[i]>=k&&(a[i]-k)%d==0)
	return puts("POSSIBLE"),0; return puts("IMPOSSIBLE"),0;
}

B - Sports Festival

刚开始naive了想成网络流了,后来发现是个SB题

首先显然可以二分答案,考虑此时如何check

假设我们所有活动均选,那么此时显然所有选择人数超过二分的值时这些活动显然都不能选,因为若减少其它活动显然这些活动还是会超过上界,因此必须删去这些活动

暴力维护上述过程即可,复杂度上界\(O(n^3\log n)\),用脚想想都知道跑不满

#include<cstdio>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=305;
int n,m,a[N][N],ans,c[N],pos[N]; bool cs[N]; 
inline bool check(CI x)
{
	RI i,j,k; for (i=1;i<=m;++i) c[i]=0,cs[i]=1;
	for (i=1;i<=n;++i) ++c[pos[i]=a[i][1]];
	while (*max_element(c+1,c+m+1)>x)
	{
		for (i=1;i<=m;++i) if (c[i]>x)
		{
			for (c[i]=cs[i]=0,j=1;j<=n;++j) if (pos[j]==i)
			{
				for (pos[j]=-1,k=1;k<=m;++k) if (cs[a[j][k]]) { pos[j]=a[j][k]; break; }
				if (!~pos[j]) return 0; ++c[pos[j]];
			} 
		}
	}
	return 1;
}
int main()
{
	RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=n;++i)
	for (j=1;j<=m;++j) scanf("%d",&a[i][j]);
	int l=1,r=n,mid; while (l<=r)
	if (check(mid=l+r>>1)) ans=mid,r=mid-1; else l=mid+1;
	return printf("%d",ans),0; 
}

C - Coins

刚开始naive了想成模拟费用流了,后来发现是个SB题

首先若只有两种币的情况显然随便做,假设其中\(b_i\)全选然后挑\(X\)\(a_i-b_i\)最大的即可

当有三种币时,我们考虑若存在两个人\(p,q\),若\(p,q\)都不选\(c\),满足\(a_p+b_q>a_q+b_p\),此时显然\(p\)\(a\)\(q\)\(b\)是最优的

移下项就会发现此时满足\(a_p-b_p>a_q-b_q\),因此我们按\(a_i-b_i\)排序之后可以枚举一个分界点,显然分界点之前都是选\(\{a,c\}\)的,分界点之后都是选\(\{b,c\}\)

前后的答案直接开个堆和两种币的时候同样维护即可

#include<cstdio>
#include<queue>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
struct element
{
	int a,b,c;
	friend inline bool operator < (const element& A,const element& B)
	{
		return A.a-A.b>B.a-B.b;
	}
}p[N]; int n,x,y,z; long long ans,sbz,sgd,ssv,fr[N],bk[N];
priority_queue < int,vector <int>,greater <int> > gd,sv;
int main()
{
	RI i; scanf("%d%d%d",&x,&y,&z); n=x+y+z;
	for (i=1;i<=n;++i) scanf("%d%d%d",&p[i].a,&p[i].b,&p[i].c),sbz+=p[i].c;
	for (sort(p+1,p+n+1),i=1;i<=n;fr[i]=sgd,++i) if (gd.size()<x) gd.push(p[i].a-p[i].c),sgd+=p[i].a-p[i].c;
	else if (p[i].a-p[i].c>gd.top()) sgd-=gd.top(),gd.pop(),gd.push(p[i].a-p[i].c),sgd+=p[i].a-p[i].c;
	for (i=n;i;bk[i]=ssv,--i) if (sv.size()<y) sv.push(p[i].b-p[i].c),ssv+=p[i].b-p[i].c;
	else if (p[i].b-p[i].c>sv.top()) ssv-=sv.top(),sv.pop(),sv.push(p[i].b-p[i].c),ssv+=p[i].b-p[i].c;
	for (i=x;n-i>=y;++i) ans=max(ans,sbz+fr[i]+bk[i+1]); return printf("%lld",ans),0;
}

D - Tree and Hamilton Path

XJB猜了两个结论然后陈指导交了一发就过了?不可思议的说

首先根据AGC025E的套路先想到把最优化问题变成构造题,容易发现对于每条边\((x,y,z)\)它的贡献的上界就是\(2\times z\times \min(size_x,size_y)\),因为我们最多在这条边两边进行左右横跳\(\min(size_x,size_y)\)

然后我们手玩一下样例发现这个有些问题,因为这时哈密顿路径而不是回路,因此会存在某条边经过次数减少\(1\)

此时若存在一条边\(p\)把整棵树分成了两个\(size\)均为\(\frac{n}{2}\),那么我们选择这条边让其贡献减少\(1\)来使得其它每条边都达到上界显然不会更劣:

若选择令一条边,它显然无法把树分成两个\(size\)均为\(\frac{n}{2}\)的子树。那么在这条边达不到上界的同时原来能达到上界\(\frac{n}{2}-1\)的边\(p\)也不会得到比\(\frac{n}{2}-1\)更好的结果。

然后我们考虑找不到这样的边的情况怎么半,我们发现此时必然存在一些点使得以这些点为根时它们的每个子树大小都不超过$\lfloor \frac{n}{2}\rfloor $,满足这个条件时显然这个点的子树内的点可以刷到上界

此时我们发现会存在一条与该点相连的边达不到上界使贡献\(-1\),我们直接贪心地找出最短边即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
struct edge
{
	int to,nxt,v;
}e[N<<1]; int n,head[N],cnt,x,y,z,sz[N],anc[N],cur; long long ans,sum;
inline void addedge(CI x,CI y,CI z)
{
	e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
	e[++cnt]=(edge){x,head[y],z}; head[y]=cnt;
}
#define to e[i].to
inline void DFS(CI now=1,CI fa=0)
{
	sz[now]=1; anc[now]=fa; for (RI i=head[now];i;i=e[i].nxt) if (to!=fa)
	{
		DFS(to,now); sz[now]+=sz[to]; sum+=1LL*min(sz[to],n-sz[to])*e[i].v;
		if (sz[to]*2==n) cur=e[i].v;
	}
}
#undef to
int main()
{
	RI i,j; for (scanf("%d",&n),i=1;i<n;++i)
	scanf("%d%d%d",&x,&y,&z),addedge(x,y,z);
	DFS(); if (cur) return printf("%lld",2LL*sum-cur),0;
	for (i=1;i<=n;++i)
	{
		int mx=0,mi=1e9; for (j=head[i];j;j=e[j].nxt)
		mx=max(mx,e[j].to!=anc[i]?sz[e[j].to]:n-sz[i]),mi=min(mi,e[j].v);
		if (mx*2<=n) ans=max(ans,2LL*sum-mi);
	}
	return printf("%lld",ans),0;
}

E - Sightseeing Plan

坐标系上走路观止题,思路还是比较巧妙的

从点到点

\(F(x,y)\)表示从\((0,0)\)\((x,y)\)的方案数,显然\(F(x,y)=C_{x+y}^x\)

从点到矩形

这是这道题的核心部分,我们首先发现\(F(x,y)=\sum_{i=0}^ y F(x-1,i)\),它的意义就是在\(x-1\)的不同位置选择向上走最后一步,然后再一直向右

然后我们把式子展开就可以得到\(F(x,y)=\sum_{i=0}^{x-1} \sum_{j=0}^{y-1} F(i,j)\),它的意义就是在枚举之前走到的任意位置然后直接向上向右走到\((x,y)\)

因此我们发现其实\(F(x,y)\)是一个类似二维前缀和的东西,因此我们可以类似地容斥了:

\[F(x_2+1,y_2+1)-F(x_2+1,y1)-F(x_1,y_2+1)+F(x_1,y_1) \]

这就是点\((0,0)\)到的\((x_1,y_1)-(x_2,y_2)\)的方案数,我们就把一个矩形变成了\(4\)个关键点

从矩形到矩形

我们枚举第一个矩形中的每个点\((x_0,y_0)\),此时问题变为从\((x_0,y_0)\)到矩形\((x_5,y_5)-(x_5,y_5)\)的方案数

把后面的矩形拆成关键点之后我们再交换一下求和可以发现现在就是对于第二个矩形的\(4\)个关键点各自求出到第一个矩形的方案数

把第一个矩形也拆成关键点,此时我们可以\(O(16)\)计算这部分

从一个矩形经过另一个矩形再到另一个矩形

我们考虑对于第二个矩形显然只要经过它的上边界\((x,y_3)\)左边界\((x_3,y)\)即可

这里的点我们显然可以直接枚举进入点和走出点,并且强制它在向矩形方向走一步才碰到边界(或离开矩形)才能产生贡献

然后我们发现这样算出的是合法路径条数,但题目要求只要选择的点不同就是不同的方案,因此我们还要考虑在第二个矩形中走的点数,这些点的选择不同会导致方案的不同但路径是相同的

我们考虑乘上这个系数,设进入点\((x_1',y_1')\),离开点\((x_2',y_2')\),显然在矩形中走过的点数为\(x_2'-x_1'+y_2'-y_1'+1\),这个显然可以拆开来单独算

综上这道题总算是做完了

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=2e6+10,mod=1e9+7;
struct point
{
	int x,y,tp;
	inline point(CI X=0,CI Y=0,CI Tp=0) { x=X; y=Y; tp=Tp; }
}p[8]; int x[6],y[6],ans,fact[N],inv[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
	RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
	for (inv[n]=quick_pow(fact[n]),i=n-1;~i;--i) inv[i]=1LL*inv[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
	return 1LL*fact[n]*inv[m]%mod*inv[n-m]%mod;
}
inline int F(const point& A,const point& B)
{
	return C(abs(A.x-B.x)+abs(A.y-B.y),abs(A.x-B.x));
}
inline int calc(const point& A,const point& B,int ret=0)
{
	RI i; for (i=x[2];i<=x[3];++i)
	ret=(ret-1LL*F(A,point(i,y[2]-1))*F(point(i,y[2]),B)%mod*(i+y[2])%mod+mod)%mod,
	ret=(ret+1LL*F(A,point(i,y[3]))*F(point(i,y[3]+1),B)%mod*(i+y[3]+1)%mod)%mod;
	for (i=y[2];i<=y[3];++i)
	ret=(ret-1LL*F(A,point(x[2]-1,i))*F(point(x[2],i),B)%mod*(i+x[2])%mod+mod)%mod,
	ret=(ret+1LL*F(A,point(x[3],i))*F(point(x[3]+1,i),B)%mod*(i+x[3]+1)%mod)%mod;
	return (ret*A.tp*B.tp+mod)%mod;
}
int main()
{
	RI i,j; for (i=0;i<6;++i) scanf("%d",&x[i]); for (i=0;i<6;++i) scanf("%d",&y[i]);
	p[0]=point(x[1],y[1],1); p[1]=point(x[0]-1,y[0]-1,1);
	p[2]=point(x[1],y[0]-1,-1); p[3]=point(x[0]-1,y[1],-1);
	p[4]=point(x[5]+1,y[5]+1,1); p[5]=point(x[4],y[4],1);
	p[6]=point(x[5]+1,y[4],-1); p[7]=point(x[4],y[5]+1,-1);
	for (init(x[5]-x[0]+y[5]-y[0]+5),i=0;i<4;++i)
	for (j=4;j<8;++j) ans=(ans+calc(p[i],p[j]))%mod;
	return printf("%d",ans),0;
}

F - Two Trees

太神了根本想不到的说,这道题有两种构造方法,我只看了较简单好理解的黑白染色的做法

首先我们可以根据每个点的儿子的个数定下该点的权值的奇偶性:

  • 奇数点:指该点权值为奇数,说明其有偶数个儿子
  • 偶数点:指该点权值为偶数,说明其有奇数个儿子

可以发现若有解,可以只用到\(\{0,1,-1\}\),具体构造如下:

  • 若一个点为奇数点,那么这个点考虑给自己的儿子在新图上连边,表示这一对儿子产生了一对冲突(即一个选\(1\)一个选\(-1\)),此时子树权值和取决于该点的权值

  • 若一个点为偶数点,那么假设该点权值为\(0\)。考虑该点的子树权值和是什么,显然应该是它所有儿子两两匹配之后剩下的哪个的权值,因此我们发现此时子树权值和取决与那个剩下点

我们发现在两两连边时,相当于在拿决定这个点子树权值的那个奇数点在连边,可以简单记录一下每个点的权值由哪个点支配来完成

易证新图一定能进行黑白染色(因为没有奇环),因此我们可以把奇数点的权值都推出来

#include<cstdio>
#include<vector>
#define RI register int
#define CI const int&
#define pb push_back
using namespace std;
const int N=100005;
vector <int> G[N]; int n,x,c[N];
class Tree
{
	private:
		int id[N];
		inline void addedge(CI x,CI y)
		{
			G[x].pb(y); G[y].pb(x);
		}
	public: 
		vector <int> v[N]; int rt;
		inline void init(CI n)
		{
			for (RI i=1;i<=n;++i) id[i]=i;
		}
		inline void DFS(CI now)
		{
			int len=v[now].size(); for (RI i=0;i<len;++i)
			if (DFS(v[now][i]),i&1) addedge(id[v[now][i-1]],id[v[now][i]]);
			if (len&1) id[now]=id[v[now][len-1]];
		}
}T1,T2;
inline void paint(CI now)
{
	int len=G[now].size(); for (RI i=0;i<len;++i)
	if (!c[G[now][i]]) c[G[now][i]]=-c[now],paint(G[now][i]);
}
int main()
{
	RI i; for (scanf("%d",&n),T1.init(n),i=1;i<=n;++i)
	if (scanf("%d",&x),~x) T1.v[x].pb(i); else T1.rt=i;
	for (T2.init(n),i=1;i<=n;++i)
	if (scanf("%d",&x),~x) T2.v[x].pb(i); else T2.rt=i;
	for (i=1;i<=n;++i) if ((T1.v[i].size()&1)!=(T2.v[i].size()&1))
	return puts("IMPOSSIBLE"),0; T1.DFS(T1.rt); T2.DFS(T2.rt);
	for (i=1;i<=n;++i) if (!(T1.v[i].size()&1)&&!c[i]) c[i]=1,paint(i);
	for (puts("POSSIBLE"),i=1;i<=n;++i) printf("%d ",c[i]);
	return 0;
}

Postscript

计划还剩两场,冲冲冲

posted @ 2020-11-04 09:47  空気力学の詩  阅读(113)  评论(0编辑  收藏  举报