zoukankan      html  css  js  c++  java
  • AtCoder Grand Contest 044

    Preface

    这场真的炒鸡难的说,一晚上只做出了A,B(5.26)

    6/2: C,D solved 竟然都是自己rush出来的,其中D题被一个奇怪的语法错误坑了(AT评测机真神奇)

    6/4:E solved,真的是妙不可言


    A - Pay to Win

    题意很简单,但是一眼看去根本不知道怎么做的说

    DP显然不行(可能是可以的),那只能请出DFS大法了

    但我们显然不能纯搜索,不然就GG了,考虑怎样减少状态

    经过一些简单的证明(或猜测),我们发现每次只要倒序过来每次直接在(2,3,5)的倍数时做除法即可,经过一些不太严谨的分析状态数不是很多

    然后可能就有人说了,万一直接减比除法来的划算怎么办,于是再特判一下减法即可

    #include<cstdio>
    #include<map>
    #include<iostream>
    #define RI register int
    #define CL const long long&
    using namespace std;
    map <long long,long long> f; long long n,t,a,b,c,d;
    inline long long cof(CL a,CL x,CL y)
    {
    	CL dlt=x-y; if (dlt<=a) return min(a,1LL*dlt*d); return a;
    }
    inline long long DFS(CL n)
    {
    	if (!n) return 0; if (n==1) return d;
    	if (f.count(n)) return f[n]; long long ret=1e18;
    	if (n%2==0) ret=min(ret,cof(a,n,n/2)+DFS(n/2));
    	else ret=min(ret,d+min(cof(a,n,n+1>>1)+DFS(n+1>>1),cof(a,n,n-1>>1)+DFS(n-1>>1)));
    	if (n%3==0) ret=min(ret,cof(b,n,n/3)+DFS(n/3)); else
    	if (n%3==1) ret=min(ret,min(cof(b,n,(n-1)/3)+d+DFS((n-1)/3),cof(b,n,(n+2)/3)+2*d+DFS((n+2)/3)));
    	else ret=min(ret,min(cof(b,n,(n+1)/3)+d+DFS((n+1)/3),cof(b,n,(n-2)/3)+2*d+DFS((n-2)/3)));
    	if (n%5==0) ret=min(ret,cof(c,n,n/5)+DFS(n/5)); else
    	if (n%5==1) ret=min(ret,min(cof(c,n,(n-1)/5)+d+DFS((n-1)/5),cof(c,n,(n+4)/5)+4*d+DFS((n+4)/5))); else
    	if (n%5==2) ret=min(ret,min(cof(c,n,(n-2)/5)+2*d+DFS((n-2)/5),cof(c,n,(n+3)/5)+3*d+DFS((n+3)/5))); else
    	if (n%5==3) ret=min(ret,min(cof(c,n,(n-3)/5)+3*d+DFS((n-3)/5),cof(c,n,(n+2)/5)+2*d+DFS((n+2)/5)));
    	else ret=min(ret,min(cof(c,n,(n-4)/5)+4*d+DFS((n-4)/5),cof(c,n,(n+1)/5)+d+DFS((n+1)/5)));
    	if (n<=(long long)1e9) ret=min(ret,n*d); return f[n]=ret;
    }
    int main()
    {
    	for (scanf("%lld",&t);t;--t)
    	{
    		scanf("%lld%lld%lld%lld%lld",&n,&a,&b,&c,&d);
    		f.clear(); printf("%lld
    ",DFS(n));
    	}
    	return 0;
    }
    
    

    B - Joker

    一开始想到正解了然后石乐志觉得过不了就写了个假算法

    WA了之后才意识到原来的想法是正确的,浪费30min的说

    首先我们看完题目马上可以出一个暴力的(O(n^4))做法,即枚举每一个人然后开一个数组(dis_{i,j})表示每个位置出去的最小步数,每次暴力更新

    然后乍一看这个复杂度是单次(O(n^2))的,其实不然

    我们考虑每个点被松弛的时候答案必然会(-1),然而初始时(dis_{i,j})(O(n))的,也就意味着每个位置最多被松弛(O(n))

    因此总复杂度(O(n^3)),足以通过此题

    #include<cstdio>
    #include<utility>
    #include<iostream>
    #define RI register int
    #define CI const int&
    #define mp make_pair
    #define fi first
    #define se second
    using namespace std;
    typedef pair <int,int> pi;
    const int N=505,dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
    int n,p[N*N],a[N][N],dis[N][N]; long long ans; pi nw;
    inline pi trs(CI x)
    {
    	if (x%n) return mp(x/n+1,x%n); return mp(x/n,n);
    }
    inline void DFS(pi nw)
    {
    	for (RI i=0;i<4;++i)
    	{
    		pi to=mp(nw.fi+dx[i],nw.se+dy[i]);
    		if (to.fi<1||to.fi>n||to.se<1||to.se>n) continue;
    		if (dis[to.fi][to.se]>dis[nw.fi][nw.se]+a[nw.fi][nw.se])
    		dis[to.fi][to.se]=dis[nw.fi][nw.se]+a[nw.fi][nw.se],DFS(to);
    	}
    }
    int main()
    {
    	RI i,j; for (scanf("%d",&n),i=1;i<=n*n;++i) scanf("%d",&p[i]);
    	for (i=1;i<=n;++i) for (j=1;j<=n;++j) a[i][j]=1,dis[i][j]=min(min(i-1,j-1),min(n-i,n-j));
    	for (i=1;i<=n*n;++i) nw=trs(p[i]),ans+=dis[nw.fi][nw.se],a[nw.fi][nw.se]=0,DFS(nw);
    	return printf("%lld",ans),0;
    }
    
    

    C - Strange Dance

    个人感觉这题比A题简单的说,非常顺利地在自习课出了做法

    我们考虑第一个操作特别奇怪,就从它入手

    一想到题目中给出了三进制,我们容易想到按进制建立一棵三叉树,其中从根到叶代表原来的低位到高位

    此时我们发现这个操作一就很简单了,直接交换(1)儿子和(2)儿子即可,然后子树里的可以打标记

    然后考虑操作二,考虑我们在做最低位,加(1)相当于将原来的((0,1,2))儿子变成((1,2,0)),然后在现在的(0)儿子进行进位操作

    然后我们发现进位就和原来的操作一样了,直接递归处理即可

    最后DFS一遍就可以得出答案,复杂度(O(3^n+T imes n))

    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<iostream>
    #define RI register int
    #define CI const int&
    using namespace std;
    const int N=1594324;
    int n,m,rt,tot,ch[N][3],ans[N],id[N]; char s[N]; bool rev[N];
    inline void build(int& now,CI dep=0,CI st=0)
    {
    	now=++tot; if (dep==n) return (void)(id[now]=st);
    	int len=pow(3,dep); build(ch[now][0],dep+1,st);
    	build(ch[now][1],dep+1,st+len); build(ch[now][2],dep+1,st+(len<<1));
    }
    inline void rever(CI now)
    {
    	swap(ch[now][1],ch[now][2]); rev[now]^=1;
    }
    inline void pushdown(CI now)
    {
    	if (!rev[now]) return; rev[now]=0;
    	for (RI i=0;i<3;++i) rever(ch[now][i]);
    }
    inline void swaping(CI now,CI dep=0)
    {
    	pushdown(now); if (dep==n) return; swap(ch[now][1],ch[now][2]);
    	swap(ch[now][0],ch[now][1]); swaping(ch[now][0],dep+1);
    }
    inline void DFS(CI now,CI dep=0,CI st=0)
    {
    	pushdown(now); if (dep==n) return (void)(ans[id[now]]=st); int len=pow(3,dep);
    	DFS(ch[now][0],dep+1,st); DFS(ch[now][1],dep+1,st+len); DFS(ch[now][2],dep+1,st+(len<<1));
    }
    int main()
    {
    	RI i; scanf("%d%s",&n,s+1); m=strlen(s+1);
    	for (build(rt),i=1;i<=m;++i) if (s[i]=='S') rever(rt); else swaping(rt);
    	for (DFS(rt),n=pow(3,n),i=0;i<n;++i) printf("%d ",ans[i]); return 0;
    }
    
    

    D - Guess the Password

    AT竟然有交互题,太好玩了的说,感谢伟大领袖陈指导及时帮我的做法拉了回来,不然我可能就弃了合并的想法的

    首先我们发现长度很重要,因此我们可以用(62)次询问每个字符(一个)来判断长度(L)

    • 若所有回答答案都一样,说明所有数都出现过,那么长度就是回答的答案(+1)
    • 若有不同的回答(显然值只有两种),那么较小的那个肯定是没出现过的,因此长度就是较小的回答的答案(+1)

    然后考虑知道了长度,我们再问(62)次询问每个字符((L)个),来得出每个字符出现的次数

    考虑知道了这个可以干嘛,考虑我们现在对于两种字符,我们可以通过枚举其中一个字符在令一个中的插入位置来确定它们的相对位置

    容易发现假设每次合并的字符是(A,B),那么显然我们会花费(|A|+|B|)次询问来确定顺序,同时得到了一个长度为(|A|+|B|)的新子序列

    很显然我们应该每次挑长度最短的两个子序列合并,这样总体复杂度乘上(log)(大概)是在要求步数内的

    #include<cstdio>
    #include<iostream>
    #include<string>
    #include<queue>
    #define RI register int
    #define CI const int&
    const int N=64;
    struct data
    {
    	int c,lst; std::string s;
    	friend inline bool operator < (const data& A,const data& B)
    	{
    		return A.c>B.c;
    	}
    }a[N];
    int n; std::string s; bool flag; std::priority_queue <data> q;
    inline char dgt_ch(CI x)
    {
    	if (x<=26) return 'a'+x-1; else if (x<=52) return 'A'+x-27; else return '0'+x-53;
    }
    inline int query(std::string s)
    {
    	std::cout<<"? "<<s<<'
    '; fflush(stdout);
    	int res; std::cin>>res; return res;
    }
    int main()
    {
    	RI i,j; for (i=1;i<=62;++i)
    	{
    		s=dgt_ch(i); int res=query(s); if (!n) n=res;
    		else if (n!=res) n=std::max(n,res),flag=1;
    	}
    	if (!flag) ++n; for (i=1;i<=62;++i)
    	{
    		for (s="",j=1;j<=n;++j) s+=dgt_ch(i); a[i].c=n-query(s);
    		for (j=1;j<=a[i].c;++j) a[i].s+=dgt_ch(i); a[i].lst=n-a[i].c; if (a[i].c) q.push(a[i]);
    	}
    	while (q.size()>1)
    	{
    		data A=q.top(); q.pop(); data B=q.top(); q.pop();
    		//cout<<A.s<<' '<<B.s<<'
    '; fflush(stdout);
    		for (i=j=0;i<=A.s.length();++i) if (j<B.c)
    		{
    			A.s.insert(i,1,B.s[j]); int res=query(A.s);
    			if (res<A.lst) A.lst=res,++A.c,++j; else A.s.erase(i,1);
    		}
    		q.push(A);
    	}
    	std::cout<<"! "<<q.top().s<<'
    '; return 0;
    }
    
    

    E - Random Pawn

    这题两个转化是真的妙啊,又学到了好有用的技巧的说

    首先我们考虑破环为链,因为这个过程在最大值处一定会停止,因此我们把环变成以最大值开头和结尾的长(n+1)的序列

    我们很容易想到DP出每个点的贡献(g_i=max(a_i,frac{g_{i-1}+g_{i+1}}{2}-b_i))

    考虑这个式子里有一个(-b_i)不好处理,我们想办法把它消掉,考虑设一个数组(c_i),然后两边同减去(c_i)

    [g_i-c_i=max(a_i-c_i,frac{g_{i-1}-c_{i-1}+g_{i+1}-c_{i+1}}{2}-b_i-c_i+frac{c_{i-1}+c_{i+1}}{2}) ]

    然后令(f=g-c,v=a-c),考虑让转移的式子每次移动没有代价,我们得到:

    [f_i=max(v_i,frac{f_{i-1}+f_{i+1}}{2})\ s.t.-b_i-c_i+frac{c_{i-1}+c_{i+1}}{2}=0Rightarrow 2 imes (b_i+c_i)=c_{i-1}+c_{i+1} ]

    然后我们钦定(c_1=0)就可以求出(c),成功消除了常数项,然后考虑怎么算贡献

    考虑我们随机走的过程肯定会有一些点是终止点,我们假设两个相邻的终止点(l,r),考虑(sum_{i=l}^r f_i)怎么求

    (f_l=v_l,f_r=v_r)显然,发现在((l,r))中,由于走路的过程不会停止,因此满足(f_i-f_{i-1}=f_{i+1}-f_i),换句话说,(f_i)([l,r])中为等差数列

    考虑现在我们知道了它的首尾项和项数,这个数列的每一项都是可以求出来的:

    [f_i=frac{(i-l) imes f_r+(r-i) imes f_l}{r-l} ]

    然后求和就很容易得到:

    [sum_{i=l+1}^{r-1} f_i=frac{1}{2} imes (f_l+f_r) imes(r-l+1) ]

    然后我们考虑先算出答案的两倍,加上首尾的贡献即有一段区间([l,r])的贡献为((f_l+f_r) imes (r-l))

    考虑我们把点((i,v_i))在坐标系上画出来,这个贡献是选取的相邻点向(x)轴做垂线形成的梯形面积的两倍

    因此当({(i,v_i)})形成一个上凸壳时,梯形面积和取得最大值(因为显然每个(f_i)取得最大值)

    直接做即可,复杂度(O(n))

    #include<cstdio>
    #define RI register int
    #define CI const int&
    #define CL const LL&
    using namespace std;
    typedef long long LL;
    const int N=200005;
    struct point
    {
    	LL x,y;
    	inline point(CL X=0,CL Y=0) { x=X; y=Y; }
    	friend inline point operator - (const point& A,const point& B)
    	{
    		return point(A.x-B.x,A.y-B.y);
    	}
    }p[N],stk[N]; LL n,m,a[N],b[N],c[N],pos,ans;
    inline LL Cross(const point& A,const point& B)
    {
    	return A.x*B.y-A.y*B.x;
    }
    inline int Convex_Hull(point *p,CI n)
    {
    	RI i,top=0; for (i=1;i<=n;++i)
    	{
    		while (top>1&&Cross(stk[top]-stk[top-1],p[i]-stk[top])>0) --top;
    		stk[++top]=p[i];
    	}
    	for (i=1;i<=top;++i) p[i]=stk[i]; return top;
    }
    int main()
    {
    	RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&a[i]);
    	for (i=1;i<=n;++i) scanf("%lld",&b[i]),a[i]>a[pos]&&(pos=i);
    	for (i=pos;i<=n;++i) c[++m]=a[i]; for (i=1;i<=pos;++i) c[++m]=a[i];
    	for (i=1;i<=m;++i) a[i]=c[i]; m=0;
    	for (i=pos;i<=n;++i) c[++m]=b[i]; for (i=1;i<=pos;++i) c[++m]=b[i];
    	for (i=1;i<=m;++i) b[i]=c[i]; c[1]=0; ++n;
    	for (i=2;i<=n;++i) c[i]=2LL*(b[i-1]+c[i-1])-c[i-2];
    	for (i=1;i<=n;++i) p[i]=point(i,a[i]-c[i]);
    	for (m=n,n=Convex_Hull(p,n),i=2;i<=n;++i)
    	ans+=(p[i].x-p[i-1].x)*(p[i].y+p[i-1].y);
    	for (ans+=p[1].y+p[n].y,i=1;i<=m;++i) ans+=2LL*c[i];
    	return ans-=2LL*a[1],printf("%.12lf",0.5*ans/(m-1)),0;
    }
    
    

    Postscript

    下场AGC打完就满10场了,这么久我才写了10场真的是太屑了的说

  • 相关阅读:
    C语言高速入口系列(七)
    数据结构:最小生成树--Prim算法
    poj2387-Til the Cows Come Home dijkstra获得水的问题
    iOS开展UI一片—简单的浏览器观看节目
    spark安装mysql与hive
    键入强力推进并解决强转
    华为u8800怎样root?
    用友ERP-U8最新破解(再次更新版本,附安装过程中的解决办法)
    Delphi 自带的那个 Hand 光标很难看?没关系,一行代码解决问题:
    阿里余额宝的来龙与去脉
  • 原文地址:https://www.cnblogs.com/cjjsb/p/12968857.html
Copyright © 2011-2022 走看看