Atcoder ARC 107赛后总结

文章目录

前言

比赛链接:https://atcoder.jp/contests/arc107

止步 E E E题,完全不会。。。

上飞分啦!!!!

一下子从0变到900+,一场就绿啦,哈哈哈(雾

只可惜今天的CF比赛和ATcoder的比赛重时间了,还是先以codeforces为主吧。

A

题意: 给你 A , B , C A,B,C A,B,C,让你计算 ∑ a = 1 A ∑ b = 1 B ∑ c = 1 C a b c m o d    998244353 \sum\limits_{a=1}^{A}\sum\limits_{b=1}^{B}\sum\limits_{c=1}^{C}abc\mod{998244353} a=1Ab=1Bc=1Cabcmod998244353

做法:首先根据乘法分配律,不难化成这个式子: A ( A + 1 ) 2 B ( B + 1 ) 2 B ( B + 1 ) 2 \frac{A(A+1)}{2}\frac{B(B+1)}{2}\frac{B(B+1)}{2} 2A(A+1)2B(B+1)2B(B+1),然后直接计算即可。

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  LL;
const  LL  mod=998244353;
LL  a,b,c;
int  main()
{
	scanf("%lld%lld%lld",&a,&b,&c);
	a=(a+1)*a/2%mod;
	b=(b+1)*b/2%mod;
	c=(c+1)*c/2%mod;
	printf("%lld\n",a*b%mod*c%mod);
	return  0;
}

B

题意:给你 N , K N,K N,K,让你算满足要求的四元组个数。
对于四元组 ( a , b , c , d ) (a,b,c,d) (a,b,c,d),其需要满足: 1 ≤ a , b , c , d ≤ N , a + b − c − d = K 1≤a,b,c,d≤N,a+b-c-d=K 1a,b,c,dN,a+bcd=K

做法: O ( n ) O(n) O(n)可以过,不妨考虑 O ( n ) O(n) O(n)的做法。
首先,我们可以再 O ( 1 ) O(1) O(1)的时间在算出满足 1 ≤ a , b ≤ N , a + b = t 1≤a,b≤N,a+b=t 1a,bN,a+b=t的二元组 ( a , b ) (a,b) (a,b)的个数,考虑化化式子: ( a + b ) − ( c + d ) = K (a+b)-(c+d)=K (a+b)(c+d)=K,那么只需要枚举 a + b = t a+b=t a+b=t中的 t t t,然后两边计算方案相乘即可,而且通过计算过程不难看出,答案是在 n 3 n^3 n3级别的,所以不会爆 l o n g long long l o n g long long

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  LL;
inline  LL  mymin(LL  x,LL  y){return  x<y?x:y;}
inline  LL  mymax(LL  x,LL  y){return  x>y?x:y;}
LL  ans,n,k;
inline  LL  fangan(LL  x)
{
	LL  l=mymax(1,x-n),r=mymin(n,x-1);
	return  r-l+1;
}
int  main()
{
	scanf("%lld%lld",&n,&k);
	if(k<0)k=-k;
	LL  ed=n<<1;
	for(LL  i=k+2;i<=ed;i++)ans+=fangan(i)*fangan(i-k);
	printf("%lld\n",ans);
	return  0;
}

C

题意:对于给定的 n ∗ n n*n nn的矩阵,满足 a i , j a_{i,j} ai,j各不相同且在 [ 1 , n 2 ] [1,n^2] [1,n2]范围内。
现在给定一个 K K K,你可以进行无限次操作,每次操作有两个选择:

  1. 选定 x , y ( x ≠ y ) x,y(x≠y) x,y(x=y)行,如果 a x , i + a y , i ≤ K ( 1 ≤ i ≤ n ) a_{x,i}+a_{y,i}≤K(1≤i≤n) ax,i+ay,iK(1in),那么交换这两行。
  2. 选定 x , y ( x ≠ y ) x,y(x≠y) x,y(x=y)列,如果 a i , x + a i , y ≤ K ( 1 ≤ i ≤ n ) a_{i,x}+a_{i,y}≤K(1≤i≤n) ai,x+ai,yK(1in),那么交换这两列。
    问你通过操作最多可以得到多少个不同的矩阵,对 998244353 998244353 998244353取模?

做法:不难发现,如果把每一行单独看成一个集合,那么不管进行哪个操作,这些集合都不会有任何改变,改变的只会是这些集合到底在哪一行,对列也是如此。

我们把行的编号列出一个数组 a a a a a a数组初始就为 1 , 2 , 3 , . . . , n 1,2,3,...,n 1,2,3,...,n,每交换一次行,比如交换行 x , y x,y x,y,那么交换 a x , a y a_{x},a_{y} ax,ay,设 b a i = i b_{a_{i}}=i bai=i b b b数组表示每个行在 a a a数组中的位置),根据上面的发现,不难推敲出:对于初始的 a a a数组, i i i行和 j j j行能交换,那么不管任何时候, a b i a_{b_{i}} abi a b j a_{b_{j}} abj都能进行交换(相应地, b i , b j b_{i},b_{j} bi,bj也会进行交换)。

那么发现了上述规律,如何统计方案呢?

我们把列的编号也列出一个数组 c c c,对应的便有 d d d数组,不难发现,初始的矩阵以及 a , c a,c a,c数组就可以确定一种不同的矩阵,特别的,初始的 a , c a,c a,c数组其实就对应了初始的矩阵,这样,我们只需要分别的统计 a a a数组能有多少种可能乘以 c c c数组的便是答案(事实上, b , d b,d b,d数组更加的直观,后面直接讲 b , d b,d b,d数组)。

那么如何求 b b b数组有多少种呢?我们不妨建一个 n n n个点的图, i , j i,j i,j连边表示在初始矩阵中第 i , j i,j i,j行能够交换,现在证明,对于一个联通块内的两个点,其可以自由♂的交换。

考虑联通块中的一条链: x − t 1 − t 2 − . . . − t k − y x-t_1-t_2-...-t_k-y xt1t2...tky,只要我们能证明能够在不改变 b t i b_{t_{i}} bti的情况下,让 b x b_{x} bx b y b_{y} by交换即可,考虑数学归纳法,当 k = 1 k=1 k=1时, x , t 1 x,t_1 x,t1交换,然后然后 t 1 t_1 t1 y y y交换, x x x再和 t 1 t_{1} t1交换便可以了,对于 k > 1 k>1 k>1的情况,我们只需要交换 x , t k x,t_{k} x,tk,然后再交换 t k , y t_{k},y tk,y,最后再交换 x , t k x,t_{k} x,tk即可,证毕,因此,一个联通块中的点可以互相交换,所以一个联通块最多会有 联 通 块 大 小 ! 联通块大小! !个不同的方案, d d d数组也是同理。

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)(因为用了并查集)

#include<cstdio>
#include<cstring>
#define  N  60
using  namespace  std;
typedef  long  long  LL;
const  LL  mod=998244353;
int  a[N][N],n,k;
inline  bool  check1(int  x,int  y)//行比较 
{
	for(int  i=1;i<=n;i++)
	{
		if(a[x][i]+a[y][i]>k)return  0;
	}
	return  1;
}
inline  bool  check2(int  x,int  y)//行比较 
{
	for(int  i=1;i<=n;i++)
	{
		if(a[i][x]+a[i][y]>k)return  0;
	}
	return  1;
}
LL  fc[N];
int  fa[N],siz[N];
int  findfa(int  x)
{
	if(fa[x]!=x)fa[x]=findfa(fa[x]);
	return  fa[x];
}
inline  void  mer(int  x,int  y)
{
	int  tx=findfa(x),ty=findfa(y);
	if(tx!=ty)
	{
		fa[tx]=ty;
		siz[ty]+=siz[tx];
	}
}
int  main()
{
	scanf("%d%d",&n,&k);
	fc[1]=1;for(int  i=2;i<=n;i++)fc[i]=(fc[i-1]*i)%mod;
	for(int  i=1;i<=n;i++)
	{
		for(int  j=1;j<=n;j++)scanf("%d",&a[i][j]);
		fa[i]=i;
		siz[i]=1;
	}
	for(int  i=1;i<=n;i++)
	{
		for(int  j=i+1;j<=n;j++)
		{
			if(findfa(i)!=findfa(j)  &&  check1(i,j)==1)mer(i,j);
		}
	}
	LL  ans=1;
	for(int  i=1;i<=n;i++)
	{
		if(findfa(i)==i)ans=ans*fc[siz[i]]%mod;
	}
	for(int  i=1;i<=n;i++)fa[i]=i,siz[i]=1;
	for(int  i=1;i<=n;i++)
	{
		for(int  j=i+1;j<=n;j++)
		{
			if(findfa(i)!=findfa(j)  &&  check2(i,j)==1)mer(i,j);
		}
	}
	for(int  i=1;i<=n;i++)
	{
		if(findfa(i)==i)ans=ans*fc[siz[i]]%mod;
	}
	printf("%lld\n",ans);
	return  0;
}

D

题意:对于给定的正整数 N , K N,K N,K,求有多少个集满足一下要求:

  1. 集中的数字个数是 n n n个。
  2. 集中的每个数字都能被表示为: 1 2 t ( t ≥ 0 ) \frac{1}{2^t}(t≥0) 2t1(t0)
  3. 集中每个数字的和是 K K K
    需要注意的是,集是无序的,即 1 , 1 2 1,\frac{1}{2} 1,21 1 2 , 1 \frac{1}{2},1 21,1是等价的。

做法:第一眼:肯定不是DP。
还真是DP,甚至当时都没发现自己已经想到做法了
首先,不难想的是,默认集中的数字从大到小排序,这样比较好处理,但是怎么枚举方案呢?
不妨认为一开始有 K K K 1 1 1,然后枚举有多少个 1 1 1分成了 1 2 \frac{1}{2} 21,设有 i i i a 1 a_1 a1分裂了吧,这样我们就有 2 a 1 2a_1 2a1 1 2 \frac{1}{2} 21,然后我们可以继续枚举分裂多少个 1 2 \frac{1}{2} 21变成 1 4 \frac{1}{4} 41,记为 a 2 a_{2} a2。(事实上,不用考虑 1 1 1直接分裂成四个 1 4 \frac{1}{4} 41的情况,因为其必须先分裂成 1 2 \frac{1}{2} 21

不断的枚举下去,我们可以得到 a a a序列: a 1 , a 2 , a 3 , a 4 , . . . , a k a_1,a_2,a_3,a_4,...,a_k a1,a2,a3,a4,...,ak,不难发现: a 1 ≤ K , a i ≤ 2 a i − 1 ( i > 1 ) a_{1}≤K,a_{i}≤2a_{i-1}(i>1) a1Kai2ai1(i>1),同时因为每分裂一次多一个数字,所以 a 1 + a 2 + a 3 + . . . + a k = n − K a_1+a_2+a_3+...+a_k=n-K a1+a2+a3+...+ak=nK,所以只需要统计不同的 a a a序列即可, D P DP DP完全可以 O ( n 2 ) O(n^2) O(n2)转移。

#include<cstdio>
#include<cstring>
#define  N  3100
using  namespace  std;
const  int  mod=998244353;
int  f[N][N];//统计方案 
int  n,m;
int  main()
{
	scanf("%d%d",&n,&m);
	if(n==m)
	{
		printf("1\n");
		return  0;
	}
	int  limit=n-m;//背包数量 
	for(int  i=1;i<=limit;i++)
	{
		if(i<=m)f[i][i]=1;//a1
		for(int  j=1;j<i;j++)f[i][j]=(f[i][j]+f[i-j][(j+1)/2])%mod;
		for(int  j=i;j>=1;j--)f[i][j]=(f[i][j]+f[i][j+1])%mod;
	}
	printf("%d\n",f[limit][1]);
	return  0;
}

E

题意:对于 n ∗ n n*n nn的矩阵,给了你第一行和第一列,对于 a i , j = m e x ( a i − 1 , j , a i , j − 1 ) ( i > 1 , j > 1 ) a_{i,j}=mex(a_{i-1,j},a_{i,j-1})(i>1,j>1) ai,j=mex(ai1,j,ai,j1)(i>1,j>1),矩阵满足: 0 ≤ a i , j < 3 ( ∀ i ∈ [ 1 , n ] , j ∈ [ 1 , n ] ) 0≤a_{i,j}<3(∀i∈[1,n],j∈[1,n]) 0ai,j<3(i[1,n],j[1,n]),其中 m e x mex mex是其自己定义的运算。
在这里插入图片描述
做法:神TM找规律???我一个常年走路想题的,你跟我说要暴力找规律???

给一个官方给出的 n = 20 n=20 n=20的随机矩阵:
在这里插入图片描述不难发现,有很多位置 a i , j = a i − 1 , j − 1 a_{i,j}=a_{i-1,j-1} ai,j=ai1,j1,事实上,你的感觉没错,只要 i > 4 , j > 4 i>4,j>4 i>4,j>4就满足这个性质,然后只要暴力枚举前四行四列即可。

至于证明,据说是暴力跑 n = 5 n=5 n=5的矩阵,枚举所有的情况,可以发现 a 4 , 4 = a 5 , 5 a_{4,4}=a_{5,5} a4,4=a5,5,然后只要对于每一个 i > 4 , j > 4 i>4,j>4 i>4,j>4的位置,就可以通过这个性质判定 a i − 1 , j − 1 = a i , j a_{i-1,j-1}=a_{i,j} ai1,j1=ai,j了。

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

#include<cstdio>
#include<cstring>
#define  N  510000
using  namespace  std;
typedef  long  long  LL;
int  a[6][N],b[N][6];
int  n;
LL  cnt[5];
inline  int  mex(int  x,int  y)
{
	if(x>y)x^=y^=x^=y;//x<=y
	return  (x>0)?0:((y&1)?2:1);
}
int  main()
{
	scanf("%d",&n);
	for(int  i=1;i<=n;i++)scanf("%d",&a[1][i]);
	b[1][1]=a[1][1];
	for(int  i=2;i<=n;i++)scanf("%d",&b[i][1]);
	if(n<=4)
	{
		for(int  i=2;i<=n;i++)a[i][1]=b[i][1];
		for(int  i=2;i<=n;i++)
		{
			for(int  j=2;j<=n;j++)a[i][j]=mex(a[i][j-1],a[i-1][j]);
		}
		for(int  i=1;i<=n;i++)
		{
			for(int  j=1;j<=n;j++)cnt[a[i][j]]++;
		}
		printf("%lld %lld %lld\n",cnt[0],cnt[1],cnt[2]);
		return  0;
	}
	a[2][1]=b[2][1];a[3][1]=b[3][1];a[4][1]=b[4][1];
	b[1][2]=a[1][2];b[1][3]=a[1][3];b[1][4]=a[1][4];
	for(int  i=2;i<=4;i++)
	{
		for(int  j=2;j<=n;j++)a[i][j]=mex(a[i][j-1],a[i-1][j]);
	}
	for(int  i=2;i<=4;i++)
	{
		for(int  j=2;j<=n;j++)b[j][i]=mex(b[j-1][i],b[j][i-1]);
	}
	for(int  i=1;i<=4;i++)
	{
		for(int  j=1;j<=n;j++)cnt[a[i][j]]++;
	}
	for(int  i=1;i<=4;i++)
	{
		for(int  j=5;j<=n;j++)cnt[b[j][i]]++;
	}
	for(int  i=4;i<=n;i++)cnt[a[4][i]]+=n-i;
	for(int  i=5;i<=n;i++)cnt[b[i][4]]+=n-i;
	printf("%lld %lld %lld\n",cnt[0],cnt[1],cnt[2]);
	return  0;
}

F

下文有借鉴或者直接搬运https://www.cnblogs.com/gmh77/p/13908571.html博客的内容。

题意:给出一个无向图,可以删掉若干点,删 i i i的代价是 a i a_i ai,一个联通块的价值为其中每个点的 b i b_i bi之和的绝对值,最大化 Σ 新 图 中 每 个 连 通 块 的 价 值 − 删 点 代 价 Σ新图中每个连通块的价值-删点代价 Σ
1 ≤ n , m < = 300 1≤n,m<=300 1n,m<=300

题解:先删掉一些点,对剩下的一个块里的贡献同为 + 1 +1 +1 − 1 -1 1,则可以转化为对每个点赋+1/-1/删掉,最终贡献为bi*点权之和,且有边相连的点的权相同

先加上 Σ ∣ b i ∣ Σ|bi| Σbi,接下来用最小割减去使其合法的最小代价即可。

连S->i1->i2->T,对应+1/删/-1,若bi>=0则为0/bi+ai/2bi,<0则为2|bi|/|bi|+ai/0

对于一条边,其两端的点编号不能不同,所以对于(u,v)直接连v2->u1和u2->v1的inf边即可。

不难发现,这样子构造边权都是非负的,直接最小割走起。

时间复杂度: O ( n 2 ( n + m ) ) O(n^2(n+m)) O(n2(n+m))

#include<cstdio>
#include<cstring>
#define  N  610
#define  M  3100
using  namespace  std;
typedef  long  long  LL;
template<class  T>
inline  T  mymin(T  x,T  y){return  x<y?x:y;}
struct  node
{
	int  y,next;
	LL  c;
}a[M];int  len=1,last[N];
inline  void  insnode(int  x,int  y,LL  c){len++;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;}
inline  void  ins(int  x,int  y,LL  c){insnode(x,y,c);insnode(y,x,0);}
int  h[N],list[N],head,tail,st,ed;
bool  bfs()
{
	memset(h,0,sizeof(h));h[ed]=1;
	list[head=tail=1]=ed;
	while(head<=tail)
	{
		int  x=list[head++];
		for(int  k=last[x];k;k=a[k].next)
		{
			int  y=a[k].y;
			if(!h[y]  &&  a[k^1].c)h[y]=h[x]+1,list[++tail]=y;
		}
	}
	return  h[st];
}
LL  findflow(int  x,LL  f)
{
	if(x==ed)return  f;
	LL  s=0,t;
	for(int  k=last[x];k;k=a[k].next)
	{
		int  y=a[k].y;
		if(h[y]+1==h[x]  &&  a[k].c)
		{
			s+=t=findflow(y,mymin(f-s,a[k].c));
			a[k].c-=t;a[k^1].c+=t;
			if(s==f)return  s;
		}
	}
	if(!s)h[x]=0; 
	return  s;
}
int  n,m;
LL  ans=0,aa[N],bb[N];
int  main()
{
	scanf("%d%d",&n,&m);st=(n<<1)+1;ed=(n<<1)+2;
	for(int  i=1;i<=n;i++)scanf("%lld",&aa[i]);
	for(int  i=1;i<=n;i++)scanf("%lld",&bb[i]);
	for(int  i=1;i<=n;i++)
	{
		if(bb[i]>=0)
		{
			ans+=bb[i];
			ins(i,i+n,aa[i]+bb[i]);
			ins(i+n,ed,bb[i]<<1);
		}
		else
		{
			ans+=-bb[i];
			ins(st,i,(-bb[i])<<1);
			ins(i,i+n,aa[i]+(-bb[i]));
		}
	}
	for(int  i=1;i<=m;i++)
	{
		int  x,y;scanf("%d%d",&x,&y);
		ins(x+n,y,(LL)999999999999999);
		ins(y+n,x,(LL)999999999999999);
	}
	while(bfs()==1)
	{
		ans-=findflow(st,(LL)999999999999999);
	}
	printf("%lld\n",ans);
	return  0;
}

小结

还不够强,后面两道题还做不出来。

找规律太草了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值