AtCoder Grand Contest 044 题解

Posted 1000suns

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AtCoder Grand Contest 044 题解相关的知识,希望对你有一定的参考价值。

为了不误人子弟,以后我在开头写出目前我做了哪些题,以防一些搜到这篇博客的人没找到想要的内容而产生负面情绪。

目前进度:A, B, C

(你问我为什么不放在标题里面?因为看着难看啊


开场看 A,不会。看 B,不会。

不打算了吧,反正之前打了 A 然后就自闭,这次即使把 A 做出来了也挂惨了……

没事,rating 乃身外之物,还是把 A 做出来算了,不然太耻辱了。

30min 后……自闭。

60min 后……自闭。

75min 后……这好像是个很蠢的 dp 啊……

90min 后……溢出好烦啊……诶我怎么忘了有个东西叫 __int128(

100min 后……点名被卡?啊我个傻子为什么活生生把三个 log 写成了六个 log……

110min 后……这 B 还是不会啊,看 C 算了。

115min 后……这真不是 sb 题?

然后就这样,对我来说 C<A<B,卡到恰好 rk100,终于上黄了 /kel

终于能 AGC 上分了……

所以说,以后抱着 rating 身外之物的观念,就能升分了


A

这真的是 AGC 的 A?我惊了……

可能有更好写的做法,但我就是写成了 dp。

先考虑一个暴力。暴搜出依次用了哪些乘法,然后在里面插入加减号。加减号的最小代价是个 dp,下面还要用到,这里不再赘述。

虽然这个搜看起来应该跑挺快,但它不孚众望(注意是“孚”,不是“负”)。

注意到乘法的具体顺序不是很重要,我们只关心每个加减号后面所有乘法的乘积。

如果从前到后的乘积满足三维的偏序,那么一定可以还原出乘法序列。代价也很好算,就是所有的乘积的三个位弄一弄。

我们从后往前做(也就是加减号贡献的乘积是递减的)。我们把没有被乘号隔开的加减看成一组。

(d_{i,j,k,0/1}) 表示目前考虑到 (2^i3^j5^k)。最后一维是 (0) 表示 (n) 与最大的小于等于 (n)(2^i3^j5^k) 的倍数的差,是 (1) 则表示最小的大于等于 (n) 的。

(f_{i,j,k,0/1}) 表示目前考虑到 (2^i3^j5^k),表示取到上文提到的那个倍数的最小代价。

为什么要记录 (0/1) 呢?因为我们可能在前面加着加着加过头了,然后在后面减回去,可能是更优的。同时注意到我们应该枚举到 (2^i3^j5^kle 2n)

转移有两种。第一种,这个是第一组加减号。那么直接算 (0)(d_{i,j,k,0/1}) 的代价。

第二种,不是第一组。你可以枚举上一组是 ((x,y,z)),然后计算 (d_{x,y,z,0/1})(d_{i,j,k,0/1}) 的代价。这样是六个 log,虽然跑不满但是仍然会 T。

注意到枚举上一组选什么没有用,我们直接从 (f_{i+1,j,k,0/1},f_{i,j+1,k,0/1},f_{i,j,k+1,0/1}) 转移就够了。

时间复杂度 (O(Tlog^3n)),实际上比这个还要快好多好多。

#include<bits/stdc++.h>
using namespace std;
typedef __int128 ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
const int maxn=100010;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<‘0‘ || ch>‘9‘) f|=ch==‘-‘,ch=getchar();
	while(ch>=‘0‘ && ch<=‘9‘) x=x*10+ch-‘0‘,ch=getchar();
	return f?-x:x;
}
int t;
ll n,dp[66][44][33][2],dif[66][44][33][2],ans,a,b,c,d;
bool ok[66][44][33],vld[66][44][33][2];
int main(){
	t=read();
	while(t--){
		n=read();a=read();b=read();c=read();d=read();
		ans=9e18;
		int cnt=0;
		ROF(i,60,0) ROF(j,40,0) ROF(k,30,0){
			ull prr=1;
			FOR(l,1,i){
				prr*=2;
				if(prr>2*n) break;
			}
			FOR(l,1,j){
				prr*=3;
				if(prr>2*n) break;
			}
			FOR(l,1,k){
				prr*=5;
				if(prr>2*n) break;
			}
			ok[i][j][k]=false;
			if(prr>2*n) continue;
			cnt++;
			ll pr=prr;
			ok[i][j][k]=true;
			dif[i][j][k][0]=n-n/pr*pr;
			dif[i][j][k][1]=(n/pr+1)*pr-n;
			dp[i][j][k][0]=n/pr*d+i*a+j*b+k*c;
			dp[i][j][k][1]=(n/pr+1)*d+i*a+j*b+k*c;
			if(n%pr==0) dif[i][j][k][1]-=pr,dp[i][j][k][1]-=d;
			FOR(x,i,min(60,i+1)) FOR(y,j,min(40,j+1)) FOR(z,k,min(k+1,30)) if(ok[x][y][z]){
				if(x==i && y==j && z==k) continue;
				dp[i][j][k][0]=min(dp[i][j][k][0],dp[x][y][z][0]+(dif[x][y][z][0]-dif[i][j][k][0])/pr*d);
				dp[i][j][k][0]=min(dp[i][j][k][0],dp[x][y][z][1]+(dif[x][y][z][1]+dif[i][j][k][0])/pr*d);
				dp[i][j][k][1]=min(dp[i][j][k][1],dp[x][y][z][0]+(dif[x][y][z][0]+dif[i][j][k][1])/pr*d);
				dp[i][j][k][1]=min(dp[i][j][k][1],dp[x][y][z][1]+(dif[x][y][z][1]-dif[i][j][k][1])/pr*d);
			}
			else break;
			if(!dif[i][j][k][0]) ans=min(ans,dp[i][j][k][0]);
			if(!dif[i][j][k][1]) ans=min(ans,dp[i][j][k][1]);
			assert(dp[i][j][k][0]>=0);
			assert(dp[i][j][k][1]>=0);
		}
		long long tmp=ans;
		printf("%lld
",tmp);
	}
}

B

考虑个 (O(n^4)) 暴力。每次 01bfs,相信大家都会。

注意到每个点的最短路是单调不升的,所以每次 01bfs 的时候可以不清空,在上一次的基础上松弛即可。

写一发,交上去,过了。(我当时居然没这么干我在想什么???)

其实这个算法真实复杂度是 (O(n^3))。因为一个点的最短路的最大值是 (O(n)) 的,而每次进入队列肯定是因为被松弛了。所以整个过程中一个点只会进入队列 (O(n)) 次。

代码不想写了。


C

正好在 ZROI 做过类似的题,于是就做出来了,还是比较幸运的)

boboniu nb! boboniu nb!

维护一个 012 Trie,从浅到深是从低位到高位(与平时的不一样)。

每个叶子记录它代表的人的编号。

如果是 12 反转,就在根上打标记,pushdown 的时候交换两棵子树。

如果是整体加一,那么从根开始,把子树 1 变成子树 0,把子树 2 变成子树 1,把子树 0 变成子树 2,然后递归到新的子树 0 继续进行这个操作。

最后 dfs 一遍还原。

时间复杂度 (O(3^nn+|T|n))

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=888888;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<‘0‘ || ch>‘9‘) f|=ch==‘-‘,ch=getchar();
	while(ch>=‘0‘ && ch<=‘9‘) x=x*10+ch-‘0‘,ch=getchar();
	return f?-x:x;
}
int n,m,q,rt,cnt,ch[maxn][3],id[maxn],ans[maxn];
bool rev[maxn];
char s[maxn];
inline void setrev(int x){
	rev[x]^=1;
	swap(ch[x][1],ch[x][2]);
}
inline void pushdown(int x){
	if(rev[x]){
		if(ch[x][0]) setrev(ch[x][0]);
		if(ch[x][1]) setrev(ch[x][1]);
		if(ch[x][2]) setrev(ch[x][2]);
		rev[x]=false;
	}
}
void build(int &x,int dep,int cur,int pr){
	x=++cnt;
	if(dep==n){
		id[x]=cur;
		return;
	}
	build(ch[x][0],dep+1,cur,pr*3);
	build(ch[x][1],dep+1,cur+pr,pr*3);
	build(ch[x][2],dep+1,cur+pr*2,pr*3);
}
void add(int x,int dep){
	pushdown(x);
	if(dep==n) return;
	swap(ch[x][1],ch[x][2]);
	swap(ch[x][0],ch[x][1]);
	add(ch[x][0],dep+1);
}
void dfs(int x,int dep,int cur,int pr){
	pushdown(x);
	if(dep==n){
		ans[id[x]]=cur;
		return;
	}
	dfs(ch[x][0],dep+1,cur,pr*3);
	dfs(ch[x][1],dep+1,cur+pr,pr*3);
	dfs(ch[x][2],dep+1,cur+pr*2,pr*3);
}
int main(){
	n=read();
	build(rt,0,0,1);
	scanf("%s",s+1);
	q=strlen(s+1);
	FOR(i,1,q) if(s[i]==‘S‘) setrev(rt);
	else add(rt,0);
	dfs(rt,0,0,1);
	m=1;
	FOR(i,1,n) m*=3;
	FOR(i,0,m-1) printf("%d ",ans[i]);
}

以上是关于AtCoder Grand Contest 044 题解的主要内容,如果未能解决你的问题,请参考以下文章

AtCoder Grand Contest 044 题解

markdown AtCoder Grand Contest 016

AtCoder Grand Contest 005

AtCoder Grand Contest 006

AtCoder Grand Contest 008 题解

AtCoder Grand Contest 025 Problem D