zoukankan      html  css  js  c++  java
  • 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

    有个数,一开始是 (0),要把它变成 (n)
    将它乘 (2)(a) 的代价。
    将它乘 (3)(b) 的代价。
    将它乘 (5)(c) 的代价。
    将它加或减 (1)(d) 的代价。
    (T) 组数据,每次给定 (n,a,b,c,d),求最小代价。
    (1le Tle 10,1le nle 10^{18},1le a,b,c,dle 10^9)

    这真的是 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

    (n imes n) 的矩阵。初始全是 (1)
    接下来 (n imes n) 个操作。每次给定一个还是 (1) 的位置,把它变成 (0)
    然后选择一条从它开始,到边界(任意一边均可)的一条路径。代价是这条路径上 (1) 的个数。
    问全过程的代价和的最小值。
    (2le nle 500)

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

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

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

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

    代码不想写了。


    C

    有一个 (0)(3^n-1) 的排列,初始是 (0)(3^n-1) 递增。
    接下来给定一个操作序列 (T)。有两种操作。
    第一种:在每个数的三进制表示中,把 (1) 变成 (2),把 (2) 变成 (1)
    第二种:每个数加 (1) 后模 (3^n)
    问最后的排列。
    (1le nle 12,1le |T|le 2 imes 10^5)

    正好在 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]);
    }
    
  • 相关阅读:
    测试工具文件4. 数据分析——定义analyseXML
    关于sprintf的"_CRT_SECURE_NO_WWARNINGS"问题的解决
    测试工具文件3. 输出文件——定义TestLog
    测试工具文件2. 支持代码——定义TestUtility
    测试工具文件1. 平台问题——定义Platform.h
    python之正则表达式
    python之字典总结
    python之global关键字的用法
    python + selenium 常用方法验证页面上的UI元素
    python + selenium 元素定位方法 (索引)By属性
  • 原文地址:https://www.cnblogs.com/1000Suns/p/12944940.html
Copyright © 2011-2022 走看看