AtCoder Regular Contest 158

Preface

这场比赛刚好周日晚上没事就打了,堪堪混过三道题,也算是小上了一波分吧

但是由于A题脑抽了一波卡了30min,导致排名不高,也没时间看DE了,属实有点可惜


A - +3 +5 +7

显然\(+3,+5,+7\)的操作可以看作\(0,+2,+4\),那么此时我们发现每次加数不会改变每个数的奇偶性,因此如果初始时三个数的奇偶性不同则直接输出无解

然后由于我们只要考虑数之间的相对大小,因此不妨把每个数都减去其中最小的一个数,此时就得到了三个数\(0,x,y\),其中\(2|x\and 2|y\)

然后如果我们可以令\(x'=\frac{x}{2},y'=\frac{y}{2}\),然后这样加数就变成了\(0,+1,+2\),因此我们不难发现若\(3\nmid x'+y'\)则无解

否则我们考虑如何构造最小的答案,一个显然的想法是最大的数不动,一直加\(0\),然后我们设最小的数加了\(a\)\(1\),第二个数加了\(b\)\(1\),则

\[a+2b=y'\\ 2a+b=y'-x' \]

因此我们很容易推出\(a+b=\frac{2y'-x'}{3}\),但是这种方法在遇到最大的数也要加的时候就GG了

但是我们可以换一手思路,考虑原问题的对偶问题,把所有数减去最大数,不难发现此时有\(a+b=\frac{y'+x'}{3}\)

然后我们稍加分析就发现此时可以用一个式子来概括答案\(\frac{y'+\max(y'-x',x')}{3}\),然后才堪堪做完这个A题

上面是比赛时的做法,然后今天写博客的时候突然想到把\(0,+1,+2\)变成\(-1,0,+1\)的话就很trival了,我真是个SB

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
int t,a[3],sum;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		bool flag=1; RI i; for (i=0;i<3;++i) scanf("%d",&a[i]);
		for (sort(a,a+3),i=1;i<3;++i) if ((a[i]&1)!=(a[0]&1)) flag=0;
		for (i=1;i<3;++i) a[i]-=a[0],a[i]/=2; a[0]=0;
		for (sum=0,i=1;i<3;++i) sum+=a[i]; if (sum%3) flag=0;
		if (!flag) { puts("-1"); continue; }
		printf("%d\n",(a[2]+max(a[2]-a[1],a[1]))/3);
	}
	return 0;
}

B - Sum-Product Ratio

很小清新的一题啊,随便写写就过了

对于\(\frac{x_i+x_j+x_k}{x_i x_j x_k}\),我们考虑对其中一个变量求偏导,比如关于\(x_k\)的偏导为\(\frac{-x_i^2x_j-x_ix_j^2}{(x_i x_j x_k)^2}\),不难发现它只和\(x_i,x_j\)的取值有关,且其符号是确定的

那么我们不难发现\(x_k\)的取值一定是取在靠近定义域的端点处时最优,然后同样的分析方法对于\(x_i,x_j\)也是同理

然后我比赛时写了个找出最小的两个数,最大的两个数,最接近\(0\)的正负各两个数,然后在这\(8\)个数里枚举两个作为\(x_i,x_j\),然后枚举\(x_k\)的做法

现在一想直接每种情况找\(3\)个,然后在\(12\)个数里面枚举\(3\)个即可,不过比赛时求稳了一手也无伤大雅

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
struct Data
{
	int x,id;
	inline Data(CI X=0,CI Id=0)
	{
		x=X; id=Id;
	}
	friend inline bool operator < (const Data& A,const Data& B)
	{
		return A.x<B.x;
	}
}A[N],B[N],C[10]; int n,a[N],c1,c2,cnt; double mi=1e18,mx=-1e18;
inline void calc(CI A,CI B,CI p,CI q)
{
	for (RI i=1;i<=n;++i) if (i!=p&&i!=q)
	{
		double tmp=1.0*(A+B+a[i])/(1LL*A*B*a[i]);
		mi=min(mi,tmp); mx=max(mx,tmp);
	}
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (scanf("%d",&n),i=1;i<=n;++i)
	if (scanf("%d",&a[i]),a[i]>0) A[++c1]=Data(a[i],i); else B[++c2]=Data(a[i],i);
	sort(A+1,A+c1+1); sort(B+1,B+c2+1);
	if (c1<4) for (i=1;i<=c1;++i) C[++cnt]=A[i]; else
	{
		C[++cnt]=A[1]; C[++cnt]=A[2]; C[++cnt]=A[c1-1]; C[++cnt]=A[c1];
	}
	if (c2<4) for (i=1;i<=c2;++i) C[++cnt]=B[i]; else
	{
		C[++cnt]=B[1]; C[++cnt]=B[2]; C[++cnt]=B[c2-1]; C[++cnt]=B[c2];
	}
	for (i=1;i<=cnt;++i) for (j=i+1;j<=cnt;++j)
	calc(C[i].x,C[j].x,C[i].id,C[j].id);
	return printf("%.12lf\n%.12lf",mi,mx),0;
}

C - All Pair Digit Sums

一个直观的想法就是考虑到每一次进位会使得\(f(A_i+A_j)\)比起\(f(A_i)+f(A_j)\)少了\(9\)的贡献

因此我们可以这样统计答案,设\(g(x,y)\)表示\(x,y\)在做加法时产生进位的位数,则有

\[\sum_{i=1}^N\sum_{j=1}^N f(A_i+A_j)=2n\times \sum_{i=1}^n f(A_i)-9\times \sum_{i=1}^N\sum_{i=1}^N g(A_i,A_j) \]

然后后面那个部分显然我们可以按位考虑,分别考虑所有数对的加法中,在每一位上产生进位的次数

粗暴地考虑把每一位独立开来看的话会很麻烦,因为涉及到进位之后再进位的问题,很难下手

但如果我们假设现在考虑从低到高的第\(i\)位,对于某个数它后\(i\)位的值若记为\(t\),则所有能和\(t\)在第\(i\)产生进位的数就是那些\(i\)位的值大于\(10^i-t\)的那些数

那么我们直接对于每个二进制位,把所有数的后\(i\)位排序后二分找一下个数即可

总复杂度\(O(n\log n\log |A_i|)\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,x,ans,cur,c[15][N],pw[20];
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (pw[0]=i=1;i<=15;++i) pw[i]=pw[i-1]*10LL;
	for (scanf("%lld",&n),i=1;i<=n;++i)
	{
		for (scanf("%lld",&x),j=0;j<15;++j) c[j][i]=x%pw[j+1];
		for (j=0;j<15;++j) ans+=x%10,x/=10;
	}
	for (ans*=2LL*n,i=0;i<15;++i) sort(c[i]+1,c[i]+n+1);
	for (j=0;j<15;++j) for (i=1;i<=n;++i)
	cur+=c[j]+n+1-lower_bound(c[j]+1,c[j]+n+1,pw[j+1]-c[j][i]);
	return printf("%lld",ans-cur*9LL),0;
}

D - Equation

woc竟然是随机乱艹题,我还以为是高难数论题,比赛时看了题面不超过5s就弃了

首先忽略顺序问题,然后不妨把左边记为\(F(x,y,z)\),右边记为\(G(x,y,z)\),然后以下的所有等式都默认为同余等式

然后我们利用这两个函数都是齐次的性质,发现\(F(tx,ty,tz)=t^{3n+1}\times F(x,y,z),G(tx,ty,tz)=t^{3n}\times G(x,y,z)\)

这意味着什么呢,如果我们找到一组\(x,y,z\)使得\(F(x,y,z)\ne 0\and G(x,y,z)\ne 0\),则我们可以直接由上面的关系解出\(t=\frac{G(x,y,z)}{F(x,y,z)}\)

那么一个很naive的想法,就是直接随机在\([1,p)\)里取一组\(x,y,z\),满足:

  • \(x\ne y\and x\ne z\and y\ne z\)
  • \(x^i+y^i+z^i\ne 0\ (i\in\{1,n,2n,3n\})\)

粗略地估计下,在所有数都随机任取的时候,\(x^i+y^i+z^i=0\)的概率大约就是\(\frac{1}{p}\),这是完全可以接受的,而关于详细的复杂度证明可以看官方题解

因此直接随机做没几次就能猜出一组可行解,复杂度\(O(\alpha\times T\log n)\)

#include<cstdio>
#include<iostream>
#include<random>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
int T,n,mod;
inline int sum(CI x,CI y)
{
	return x+y>=mod?x+y-mod:x+y;
}
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;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&T);T;--T)
	{
		RI i; scanf("%d%d",&n,&mod);
		mt19937 rnd(random_device{}()); uniform_int_distribution <> dist(1,mod-1);
		for (int x,y,z;;)
		{
			for (x=dist(rnd),y=dist(rnd),z=dist(rnd);x==y||x==z||y==z;)
			x=dist(rnd),y=dist(rnd),z=dist(rnd);
			int pw[4]; bool flag=1; for (i=0;i<=3&&flag;++i)
			{
				pw[i]=sum(quick_pow(x,i?1LL*n*i%(mod-1):1),sum(quick_pow(y,i?1LL*n*i%(mod-1):1),quick_pow(z,i?1LL*n*i%(mod-1):1)));
				if (!pw[i]) flag=0;
			}
			if (flag)
			{
				int t=1LL*pw[3]*quick_pow(1LL*pw[0]*pw[1]%mod*pw[2]%mod)%mod;
				int ans[3]; ans[0]=1LL*x*t%mod; ans[1]=1LL*y*t%mod; ans[2]=1LL*z*t%mod;
				for (sort(ans,ans+3),i=0;i<3;++i) printf("%d%c",ans[i]," \n"[i==2]);
				break;
			}
		}
	}
	return 0;
}

E - All Pair Shortest Paths

神仙题,一步神之转换真是妙手,瞬间盘活全局

首先为了方便记我们把第一行记为\(x_i\),第二行记为\(y_i\),然后设一个集合\(S_i\)表示所有到第\(i\)列的点对的最优答案

具体的,对于\(S_i\)中的每个元素\((a,b)\),分别表示从之前的某个点到\(x_i\)的最短距离为\(a\),到\(y_i\)的最短距离为\(b\)

粗略地考虑转移,则有:

\[S_i=\{(\min(a+x_i,b+x_i+y_i),\min(a+x_i+y_i,b+y_i))|(a,b)\in S_{i-1}\}\cup\{(x_i,x_i+y_i),(x_i+y_i,y_i)\} \]

然后我们稍作观察就会发现这两个\(\min\)可以通过分类讨论来去掉,即:

  • \(a-b<-x_i\),则\((\min(a+x_i,b+x_i+y_i),\min(a+x_i+y_i,b+y_i))=(a+x_i,a+x_i+y_i)\)
  • \(-x_i\le a-b\le y_i\),则\((\min(a+x_i,b+x_i+y_i),\min(a+x_i+y_i,b+y_i))=(a+x_i,b+y_i)\)
  • \(y_i<a-b\),则\((\min(a+x_i,b+x_i+y_i),\min(a+x_i+y_i,b+y_i))=(b+x_i+y_i,b+y_i)\)

然后乍一看感觉进入死局了,但是我们分析一下,这个分类讨论的条件只和\(a-b\)有关,并且我们其实只关心\(a+b\)的值

那么不妨设\(T_i=\{(a-b,a+b)|(a,b)\in S_i\}=\{(a',b')\}\),则我们可以改写上面的式子为:

  • \(a'<-x_i\),则转移到\((-y_i,b'+a'+2x_i+y_i)\)
  • \(-x_i\le a'\le y_i\),则转移到\((a'+x_i-y_i,b'+x_i+y_i)\)
  • \(y_i<a'\),则转移到\((x_i,b'-a'+x_i+2y_i)\)

乍一看着还是一次增加集合中两个元素,复杂度还是平方级别的,但我们仔细一看发现,中间那种情况所有数\(a',b'\)的偏移量都是相同的

那么我们可以通过打标记来一次性完成对这部分元素的修改,每次只处理第一种和第三种情况即可

直接用map的话复杂度是带上\(\log\)的,但如果我们用deque来时刻动态维护第一维有序,就可以在\(O(n)\)的复杂度下完成整个问题

#include<cstdio>
#include<iostream>
#include<queue>
#include<utility>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=998244353;
typedef pair <long long,int> pi;
int n,x[N],y[N],ans,ret; long long tag; deque <pi> q;
inline int sum(CI x,CI y)
{
	return x+y>=mod?x+y-mod:x+y;
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&x[i]);
	for (i=1;i<=n;++i) scanf("%lld",&y[i]); for (i=1;i<=n;++i)
	{
		ret=sum(ret,2LL*(i-1)*sum(x[i],y[i])%mod);
		int cnt_fr=0,cnt_bk=0;
		while (!q.empty())
		{
			auto [key,cnt]=q.front(); key+=tag;
			if (key>=-x[i]) break; q.pop_front();
			ret=sum(ret,1LL*(key%mod+x[i])*cnt%mod); cnt_fr+=cnt;
		}
		while (!q.empty())
		{
			auto [key,cnt]=q.back(); key+=tag;
			if (key<=y[i]) break; q.pop_back();
			ret=sum(ret,1LL*(mod-key%mod+y[i])*cnt%mod); cnt_bk+=cnt;
		}
		ret=sum(ret,3LL*(x[i]+y[i])%mod); ++cnt_fr; ++cnt_bk;
		tag+=x[i]-y[i]; ans=sum(ans,ret);
		q.push_front(pi(-y[i]-tag,cnt_fr)); q.push_back(pi(x[i]-tag,cnt_bk));
	}
	for (ans=sum(ans,ans),i=1;i<=n;++i) ans=sum(ans,mod-3LL*(x[i]+y[i])%mod);
	return printf("%lld",(ans%mod+mod)%mod),0;
}

Postscript

ARC还是劲啊,读题体验还极佳,真是不容错过

posted @ 2023-03-16 20:48  空気力学の詩  阅读(49)  评论(0编辑  收藏  举报